W najbardziej ogólnym przypadku oto jak to zrobić:
SELECT name
FROM random AS r1 JOIN(SELECT CEIL(RAND()*(SELECT MAX(id)FROM random))AS id)AS r2
WHERE r1.id >= r2.id
ORDERBY r1.id ASC
LIMIT 1
Zakłada to, że rozkład identyfikatorów jest równy i że na liście identyfikatorów mogą występować przerwy. Zobacz artykuł, aby uzyskać bardziej zaawansowane przykłady
Tak, jeśli masz potencjalnie duże luki w identyfikatorach, szansa na losowe wybranie najniższego identyfikatora jest znacznie mniejsza niż twoich wysokich identyfikatorów. W rzeczywistości szansa, że pierwszy identyfikator po wybraniu największej luki jest w rzeczywistości najwyższy. Dlatego z definicji nie jest to przypadek.
lukeocodes
6
Jak uzyskać 10 różnych losowych wierszy? Czy musisz ustawić limit na 10, a następnie iterować 10 razy mysqli_fetch_assoc($result)? A może te 10 wyników niekoniecznie da się rozróżnić?
Adam
12
Moim zdaniem Random wymaga jednakowej szansy na jakikolwiek wynik. ;)
lukeocodes
4
Pełny artykuł dotyczy problemów takich jak nierówne rozkłady i powtarzające się wyniki.
Bradd Szonye
1
w szczególności, jeśli masz luki na początku swoich identyfikatorów, pierwszy zostanie wybrany (min / maks-min) danego czasu. W takim przypadku prostą modyfikacją jest MAX () - MIN () * RAND + MIN (), co nie jest zbyt wolne.
Mateusz - dowód pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10zajmuje 0,0010, bez LIMITU 10 zabrał 0,0012 (w tej tabeli 3500 słów).
Arthur Kushman
26
@zeusakm 3500 słów to niewiele; problem polega na tym, że eksploduje po pewnym punkcie, ponieważ MySQL musi właściwie posortować WSZYSTKIE rekordy po przeczytaniu każdego z nich; gdy ta operacja uderzy w twardy dysk, poczujesz różnicę.
Ja͢ck
16
Nie chcę się powtarzać, ale znowu, to pełny skan tabeli. Na dużym stole zajmuje to dużo czasu i pamięci i może powodować tworzenie i działanie tymczasowego stołu na dysku, co jest bardzo wolne.
mat
10
Kiedy przeprowadzałem wywiad z Facebookiem w 2010 r., Zapytali mnie, jak wybrać losowy zapis z ogromnego pliku o nieznanej wielkości w jednym czytaniu. Gdy wpadniesz na pomysł, łatwo go uogólnić, wybierając wiele rekordów. Tak, sortowanie całego pliku jest absurdalne. Jednocześnie jest bardzo przydatny. Właśnie zastosowałem to podejście, aby wybrać 10 losowych wierszy ze stołu z ponad 1 000 000 wierszy. Jasne, musiałem trochę poczekać; ale chciałem tylko dowiedzieć się, jak wyglądają typowe wiersze w tej tabeli ...
osa
27
Proste zapytanie, które ma doskonałą wydajność i działa z lukami :
SELECT*FROM tbl AS t1 JOIN(SELECT id FROM tbl ORDERBY RAND() LIMIT 10)as t2 ON t1.id=t2.id
To zapytanie na stole 200K zajmuje 0.08s i normalnej wersji (select * from tbl ORDER BY RAND () LIMIT 10) wykonuje 0.35s na moim komputerze.
Jest to szybkie, ponieważ w fazie sortowania używana jest tylko indeksowana kolumna identyfikatora. Możesz zobaczyć to zachowanie w wyjaśnieniu:
WYBIERZ * Z tbl ORDER BY RAND () LIMIT 10:
WYBIERZ * Z tbl JAK DOŁĄCZ DO t1 (WYBIERZ identyfikator z tbl ORDER BY RAND () LIMIT 10) jako t2 ON t1.id = t2.id
Przepraszam, testowałem! niska wydajność na 600 000 rekordach.
Dylan B
@DylanB Zaktualizowałem odpowiedź testem.
Ali
17
Dostaję szybkie zapytania (około 0,5 sekundy) z wolnym procesorem , wybierając 10 losowych wierszy w 400 000 rejestrów bazy danych MySQL niebuforowanej wielkości 2 GB. Zobacz mój kod: Szybki wybór losowych wierszy w MySQL
<?php
$time= microtime_float();$sql='SELECT COUNT(*) FROM pages';$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
ORDER BY RAND() LIMIT 0,10";$rquery= BD_Ejecutar($sql);while(list($id)=mysql_fetch_row($rquery)){if($id_in)$id_in.=",$id";else$id_in="$id";}
mysql_free_result($rquery);$sql="SELECT id,url FROM pages WHERE id IN($id_in)";$rquery= BD_Ejecutar($sql);while(list($id,$url)=mysql_fetch_row($rquery)){
logger("$id, $url",1);}
mysql_free_result($rquery);$time= microtime_float()-$time;
logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);?>
Biorąc pod uwagę moją ponad 14 milionów tabel rekordów, jest to tak powolne jakORDER BY RAND()
Fabrizio
5
@snippetsofcode W twoim przypadku - 400 tys. wierszy możesz użyć prostego „ZAMÓWIENIA według rand ()”. Twoja sztuczka z 3 zapytaniami jest bezużyteczna. Można przerobić go jak "SELECT id, URL ze stron WHERE id IN (SELECT id FROM stron ORDER BY rand () LIMIT 10)"
Roman Podlinov
4
Twoja technika nadal wykonuje skanowanie tabeli. Użyj, FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';aby to zobaczyć.
Rick James
4
Spróbuj także uruchomić to zapytanie na stronie 200 req / s. Współbieżność cię zabije.
Marki555
@RomanPodlinov ma tę przewagę nad zwykłym ORDER BY RAND(), że sortuje tylko identyfikatory (nie pełne wiersze), więc tabela temp jest mniejsza, ale nadal musi sortować wszystkie.
Marki555,
16
Jest to bardzo proste i jedno wierszowe zapytanie.
FYI, order by rand()jest bardzo powolny, jeśli stół jest duży
evilReiko
6
Czasami SLOW jest akceptowany, jeśli chcę, aby był PROSTY
Indeksowanie powinno być stosowane na stole, jeśli jest duży.
Muhammad Azeem,
1
Indeksowanie tutaj nie pomoże. Indeksy są pomocne w przypadku bardzo konkretnych rzeczy, a to zapytanie nie jest jednym z nich.
Andrew
13
Z książki:
Wybierz losowy wiersz za pomocą przesunięcia
Jeszcze inną techniką, która pozwala uniknąć problemów znalezionych w poprzednich alternatywach, jest zliczanie wierszy w zestawie danych i zwracanie losowej liczby między 0 a liczbą. Następnie użyj tego numeru jako przesunięcia przy wyszukiwaniu zestawu danych
To pomaga niektórym MyISAM, ale nie InnoDB (zakładając, że id jest klastrowany PRIMARY KEY).
Rick James
7
Cóż, jeśli nie masz żadnych przerw w klawiszach, a wszystkie są numeryczne, możesz obliczyć losowe liczby i wybrać te linie. ale prawdopodobnie tak nie będzie.
co w zasadzie zapewni, że otrzymasz liczbę losową w zakresie swoich klawiszy, a następnie wybierzesz następny najlepszy, który jest większy. musisz to zrobić 10 razy.
jednak NIE jest to tak naprawdę losowe, ponieważ twoje klucze najprawdopodobniej nie zostaną rozłożone równomiernie.
To naprawdę duży problem i niełatwy do rozwiązania, spełniający wszystkie wymagania, rand () MySQL-a jest najlepszym, co możesz uzyskać, jeśli naprawdę chcesz 10 losowych wierszy.
Czy możesz wyjaśnić coś więcej, abym mógł dać ci dobre rozwiązanie.
Na przykład firma, z którą współpracowałem, miała rozwiązanie, w którym bardzo szybko potrzebowała absolutnej przypadkowości. Skończyło się na wstępnym zapełnieniu bazy danych losowymi wartościami, które zostały wybrane malejąco i ponownie ustawione na różne wartości losowe.
Jeśli prawie nigdy nie aktualizujesz, możesz również wypełnić rosnący identyfikator, aby nie mieć żadnych luk i po prostu obliczyć losowe klucze przed wybraniem ... To zależy od przypadku użycia!
Cześć Joe. W tym konkretnym przypadku kluczom nie powinno zabraknąć luk, ale z czasem może się to zmienić. I podczas gdy twoja odpowiedź działa, wygeneruje losowe 10 wierszy (pod warunkiem, że piszę limit 10), które są kolejne i chciałem, żeby tak rzec, więcej losowości. :) Dziękuję Ci.
Francisc
Jeśli potrzebujesz 10, użyj jakiegoś połączenia, aby wygenerować 10 unikalnych wierszy.
johno
to, co powiedziałem. musisz to wykonać 10 razy. łączenie go wition union jest jednym ze sposobów umieszczenia go w jednym zapytaniu. zobacz moje uzupełnienie 2 minuty temu.
The Surrican
1
@TheSurrican, To rozwiązanie wygląda fajnie, ale jest bardzo wadliwe . Spróbuj wstawić tylko jedno bardzo duże, Ida wszystkie losowe zapytania zwrócą ci to Id.
Pacerier,
1
FLOOR(RAND()*MAX(id))jest tendencyjny do zwracania większych identyfikatorów.
Rick James
3
Potrzebowałem zapytania, aby zwrócić dużą liczbę losowych wierszy z dość dużej tabeli. Właśnie to wymyśliłem. Najpierw uzyskaj maksymalny identyfikator rekordu:
SELECT MAX(id)FROM table_name;
Następnie zamień tę wartość na:
SELECT*FROM table_name WHERE id > FLOOR(RAND()* max) LIMIT n;
Gdzie max to maksymalny identyfikator rekordu w tabeli, a n to liczba wierszy, które chcesz w zestawie wyników. Zakłada się, że w identyfikatorze nie ma żadnych luk, chociaż wątpię, by to wpłynęło na wynik, gdyby były (choć nie próbowałem). Stworzyłem również tę procedurę składowaną, aby była bardziej ogólna; przekazać nazwę tabeli i liczbę wierszy do zwrócenia. Korzystam z MySQL 5.5.38 w systemie Windows 2008, 32 GB, podwójnym 3GHz E5450, a na stole z 17 691 264 rzędów jest dość spójny przy ~ 0,03 s / ~ 11 s, aby zwrócić 1 000 000 wierszy. (czasy pochodzą z MySQL Workbench 6.1; możesz również użyć CEIL zamiast FLOOR w 2. instrukcji select, w zależności od twoich preferencji)
Poprawiłem odpowiedź @Riedsio. Jest to najbardziej wydajne zapytanie, jakie mogę znaleźć w dużej, równomiernie rozłożonej tabeli z przerwami (testowane na uzyskaniu 1000 losowych wierszy z tabeli, która ma> 2,6B wierszy).
(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max :=(SELECT MAX(id)FROMtable))+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)
Pozwól mi rozpakować, co się dzieje.
@max := (SELECT MAX(id) FROM table)
Obliczam i zapisuję maks. W przypadku bardzo dużych tabel istnieje niewielkie obciążenie związane z obliczaniem za MAX(id)każdym razem, gdy potrzebujesz wiersza
SELECT FLOOR(rand() * @max) + 1 as rand)
Pobiera losowy identyfikator
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
To wypełnia luki. Zasadniczo, jeśli losowo wybierzesz liczbę w lukach, po prostu wybierze następny identyfikator. Zakładając, że luki są równomiernie rozmieszczone, nie powinno to stanowić problemu.
Wykonanie unii pomaga dopasować wszystko do 1 zapytania, dzięki czemu można uniknąć wykonywania wielu zapytań. Pozwala także zaoszczędzić na obliczeniachMAX(id) . W zależności od aplikacji może to mieć duże lub bardzo małe znaczenie.
Zauważ, że to pobiera tylko identyfikatory i porządkuje je w losowej kolejności. Jeśli chcesz zrobić coś bardziej zaawansowanego, zalecamy zrobienie tego:
SELECT t.id, t.name -- etc, etcFROMtable t
INNERJOIN((SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max :=(SELECT MAX(id)FROMtable))+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)) x ON x.id = t.id
ORDERBY t.id
@Hassaan nie powinieneś, że zmiana LIMIT 1na LIMIT 30dostanie 30 rekordów z rzędu od losowego punktu w tabeli. Zamiast tego powinieneś mieć 30 kopii (SELECT id FROM ....części na środku.
Hans Z
Próbowałem, ale nie wydaje się bardziej skuteczny niż Riedsioodpowiedź. Próbowałem z 500 trafieniami na sekundę na stronę przy użyciu PHP 7.0.22 i MariaDB na centos 7, z Riedsioodpowiedzią dostałem ponad 500 pozytywnych odpowiedzi, a następnie twoją odpowiedź.
Hassaan,
1
@Hassaan odpowiedź Riedsio daje 1 wiersz, ten daje ci n wierszy, a także zmniejsza obciążenie we / wy na potrzeby zapytań. Być może będziesz w stanie uzyskać wiersze szybciej, ale przy większym obciążeniu systemu.
DROP TEMPORARY TABLEIFEXISTS rands;CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt <1THEN
LEAVE loop_me;ENDIF;INSERTINTO rands
SELECT r1.id
FROM random AS r1 JOIN(SELECT(RAND()*(SELECT MAX(id)FROM random))AS id)AS r2
WHERE r1.id >= r2.id
ORDERBY r1.id ASC
LIMIT 1;SET cnt = cnt -1;END LOOP loop_me;
W artykule rozwiązuje problem luk w identyfikatorach, powodując niezbyt losowe wyniki , utrzymując tabelę (używając wyzwalaczy itp. ... zobacz artykuł); Rozwiązuję problem, dodając kolejną kolumnę do tabeli, wypełnioną ciągłymi liczbami, zaczynając od 1 ( edycja: ta kolumna jest dodawana do tabeli tymczasowej utworzonej przez podzapytanie w czasie wykonywania, nie wpływa na twoją tabelę stałą):
DROP TEMPORARY TABLEIFEXISTS rands;CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt <1THEN
LEAVE loop_me;ENDIF;SET@no_gaps_id :=0;INSERTINTO rands
SELECT r1.id
FROM(SELECT id,@no_gaps_id :=@no_gaps_id +1AS no_gaps_id FROM random)AS r1 JOIN(SELECT(RAND()*(SELECT COUNT(*)FROM random))AS id)AS r2
WHERE r1.no_gaps_id >= r2.id
ORDERBY r1.no_gaps_id ASC
LIMIT 1;SET cnt = cnt -1;END LOOP loop_me;
W artykule widzę, że dołożył wszelkich starań, aby zoptymalizować kod; nie mam pojęcia, czy moje zmiany wpływają na wydajność, ale działają bardzo dobrze dla mnie.
„nie mam pojęcia, czy / jak moje zmiany wpływają na wydajność” - całkiem sporo. Ponieważ @no_gaps_idnie można użyć indeksu, więc jeśli spojrzysz na EXPLAINswoje zapytanie, masz Using filesorti Using where(bez indeksu) dla podkwerend, w przeciwieństwie do pierwotnego zapytania.
Fabian Schmengler
2
Oto zmieniacz gier, który może być pomocny dla wielu;
Mam tabelę z 200 tys. Wierszy z sekwencyjnymi identyfikatorami , musiałem wybrać N losowych wierszy, więc wybieram generowanie losowych wartości na podstawie największego identyfikatora w tabeli, stworzyłem ten skrypt, aby dowiedzieć się, która jest najszybsza operacja:
logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();
Wyniki są następujące:
Liczba: 36.8418693542479ms
Max: 0.241041183472ms
Zamówienie: 0.216960906982ms
W oparciu o te wyniki, opis zamówienia jest najszybszą operacją, aby uzyskać maksymalny identyfikator.
Oto moja odpowiedź na pytanie:
SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM(SELECT FLOOR(RAND()*(SELECT id FROM tbl ORDERBY id DESC LIMIT 1)) n FROM tbl LIMIT 10) a
...SELECT*FROM tbl WHERE id IN($result);
FYI: Aby uzyskać 10 losowych wierszy z tabeli 200 000, zajęło mi 1,78 ms (w tym wszystkie operacje po stronie php)
Chcę wskazać inną możliwość przyspieszenia - buforowanie . Zastanów się, dlaczego potrzebujesz losowych wierszy. Prawdopodobnie chcesz wyświetlić losową wiadomość lub losową reklamę na stronie internetowej. Jeśli otrzymujesz 100 req / s, czy naprawdę potrzebne jest, aby każdy odwiedzający otrzymywał losowe wiersze? Zwykle buforowanie tych X losowych wierszy przez 1 sekundę (a nawet 10 sekund) jest całkowicie w porządku. Nie ma znaczenia, czy 100 unikalnych użytkowników w tej samej 1 sekundzie otrzyma te same losowe posty, ponieważ w następnej sekundzie kolejnych 100 odwiedzających otrzyma inny zestaw postów.
Korzystając z tego buforowania, możesz również użyć wolniejszego rozwiązania do pobierania losowych danych, ponieważ będą one pobierane z MySQL tylko raz na sekundę, niezależnie od twoich wymagań / wymagań.
Zastanawiałem się nad tym samym rozwiązaniem, powiedz mi, czy jest szybsze niż metoda innych?
G. Adnane
@ G.Adnane nie jest szybszy ani wolniejszy niż zaakceptowana odpowiedź, ale zaakceptowana odpowiedź zakłada równy rozkład identyfikatorów. Nie wyobrażam sobie żadnego scenariusza, w którym można to zagwarantować. To rozwiązanie znajduje się w O (1), gdzie SELECT column FROM table ORDER BY RAND() LIMIT 10jest w O (nlog (n)). Tak, to jest szybkie rozwiązanie i działa z każdą dystrybucją identyfikatorów.
Adam
nie, ponieważ w opublikowanym linku do zaakceptowanego rozwiązania istnieją inne metody, chcę wiedzieć, czy to rozwiązanie jest szybsze niż inne, w inny sposób możemy spróbować znaleźć inne, dlatego pytam w jakikolwiek sposób, +1 dla Twojej odpowiedzi. Korzystałem z
próbkowania
zdarza się, że chcesz uzyskać x liczby wierszy, ale przesunięcie następuje na końcu tabeli, która zwróci <x wierszy lub tylko 1 wiersz. nie widziałem twojej odpowiedzi przed wysłaniem mojej, ale wyjaśniłem tutaj stackoverflow.com/a/59981772/10387008
ZOLDIK
@ZOLDIK wydaje się, że wybierasz pierwsze 10 wierszy po przesunięciu x. Twierdziłbym, że nie jest to przypadkowa generacja 10 wierszy. W mojej odpowiedzi musisz wykonać kwerendę w kroku trzecim 10 razy, tj. Jeden dostaje tylko jeden wiersz na wykonanie i nie musisz się martwić, jeśli przesunięcie znajduje się na końcu tabeli.
Adam
1
Jeśli masz tylko jedno żądanie odczytu
Połącz odpowiedź @redsio z temp-table (600 K to niewiele):
DROP TEMPORARY TABLEIFEXISTS tmp_randorder;CREATETABLE tmp_randorder (id int(11)notnull auto_increment primarykey, data_id int(11));INSERTINTO tmp_randorder (data_id)select id from datatable;
A następnie weź wersję @redsios Odpowiedź:
SELECT dt.*FROM(SELECT(RAND()*(SELECT MAX(id)FROM tmp_randorder))AS id)AS rnd
INNERJOIN tmp_randorder rndo on rndo.id between rnd.id -10and rnd.id +10INNERJOIN datatable AS dt on dt.id = rndo.data_id
ORDERBY abs(rndo.id - rnd.id)
LIMIT 1;
Jeśli stół jest duży, możesz przesiać w pierwszej części:
INSERTINTO tmp_randorder (data_id)select id from datatable where rand()<0.01;
Jeśli masz wiele żądań odczytu
Wersja: możesz zachować tabelę jako tmp_randordertrwałą, nazwij ją datatable_idlist. Odtworz ten stół w określonych odstępach czasu (dzień, godzina), ponieważ będzie on również dziury. Jeśli twój stół naprawdę się powiększy, możesz również uzupełnić dziury
wybierz l.data_id jako całość z datatable_idlist l pozostało dołącz datatable dt na dt.id = l.data_id gdzie dt.id ma wartość null;
Wersja: Nadaj swojemu zestawowi danych losową kolumnę sortowania bezpośrednio w tabeli danych lub w dodatkowej trwałej tabeli datatable_sortorder. Indeksuj tę kolumnę. Wygeneruj wartość losową w swojej aplikacji (nazywam ją $rand).
select l.*from datatable l
orderby abs(random_sortorder -$rand)desc
limit 1;
To rozwiązanie rozróżnia „rzędy brzegowe” od najwyższego i najniższego losowego sortera, więc zmieniaj je w odstępach czasu (raz dziennie).
Innym prostym rozwiązaniem byłoby uszeregowanie wierszy i pobranie jednego z nich losowo, a dzięki temu rozwiązaniu nie będziesz musiał mieć żadnej kolumny opartej na „Id” w tabeli.
SELECT d.*FROM(SELECT t.*,@rownum :=@rownum +1AS rank
FROM mytable AS t,(SELECT@rownum :=0)AS r,(SELECT@cnt :=(SELECT RAND()*(SELECT COUNT(*)FROM mytable)))AS n
) d WHERE rank >=@cnt LIMIT 10;
Możesz zmienić wartość graniczną zgodnie z potrzebą uzyskania dostępu do tylu wierszy, ile chcesz, ale najczęściej byłyby to kolejne wartości.
Jeśli jednak nie chcesz kolejnych losowych wartości, możesz pobrać większą próbkę i wybrać losowo z niej. coś jak ...
SELECT*FROM(SELECT d.*FROM(SELECT c.*,@rownum :=@rownum +1AS rank
FROM buildbrain.`commits`AS c,(SELECT@rownum :=0)AS r,(SELECT@cnt :=(SELECT RAND()*(SELECT COUNT(*)FROM buildbrain.`commits`)))AS rnd
) d
WHERE rank >=@cnt LIMIT 10000) t ORDERBY RAND() LIMIT 10;
Jednym ze sposobów, który uważam za całkiem dobry, jeśli istnieje identyfikator generowany automatycznie, jest użycie operatora modulo „%”. Na przykład, jeśli potrzebujesz 10 000 losowych rekordów na 70 000, możesz to uprościć, mówiąc, że potrzebujesz 1 na każde 7 wierszy. Można to uprościć w tym zapytaniu:
SELECT*FROMtableWHERE
id %
FLOOR((SELECT count(1)FROMtable)/10000)=0;
Jeśli wynik dzielenia wierszy docelowych przez sumę dostępnych nie jest liczbą całkowitą, będziesz mieć dodatkowe wiersze niż to, o co prosiłeś, więc powinieneś dodać klauzulę LIMIT, aby pomóc przyciąć zestaw wyników w następujący sposób:
SELECT*FROMtableWHERE
id %
FLOOR((SELECT count(1)FROMtable)/10000)=0
LIMIT 10000;
Wymaga to pełnego skanowania, ale jest szybsze niż ORDER BY RAND i moim zdaniem łatwiejsze do zrozumienia niż inne opcje wymienione w tym wątku. Również jeśli system zapisujący do DB tworzy zestawy wierszy w partiach, możesz nie otrzymać tak losowego wyniku, jak się spodziewałeś.
Teraz, gdy tak myślę, jeśli potrzebujesz losowych wierszy za każdym razem, gdy je wywołujesz, jest to bezużyteczne. Myślałem tylko o potrzebie pobrania losowych wierszy ze zbioru w celu przeprowadzenia badań. Nadal uważam, że modulo dobrze jest pomóc w innym przypadku. Możesz użyć modulo jako filtru pierwszego przejścia, aby obniżyć koszt operacji ORDER BY RAND.
Nicolas Cohen
1
Jeśli chcesz mieć jeden losowy rekord (bez względu na to, czy między identyfikatorami występują przerwy):
Przejrzałem wszystkie odpowiedzi i nie sądzę, aby ktokolwiek w ogóle wspominał o tej możliwości i nie jestem pewien, dlaczego.
Jeśli chcesz maksymalnej prostoty i szybkości, przy niewielkich kosztach, wydaje mi się, że sensowne jest przechowywanie losowych liczb dla każdego wiersza w DB. Po prostu utwórz dodatkową kolumnę random_numberi ustaw ją domyślnie na RAND(). Utwórz indeks w tej kolumnie.
Następnie, gdy chcesz pobrać wiersz, wygeneruj losową liczbę w swoim kodzie (PHP, Perl, cokolwiek) i porównaj to z kolumną.
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
Myślę, że chociaż jest to bardzo fajne dla jednego rzędu, dla dziesięciu rzędów takich jak OP poprosił, abyś musiał nazwać go dziesięć razy (lub wymyślić sprytne ulepszenie, które natychmiast mi ucieka)
To jest naprawdę bardzo ładne i skuteczne podejście. Jedyną wadą jest to, że wymieniałeś przestrzeń na szybkość, co moim zdaniem wydaje się uczciwą transakcją.
Tochukwu Nkemdilim
Dzięki. Miałem scenariusz, w którym główny stół, z którego chciałem losowego rzędu, miał 5 milionów wierszy i całkiem sporo złączeń, a po wypróbowaniu większości podejść w tym pytaniu był to kludge, na którym się zdecydowałem. Jedna dodatkowa kolumna była dla mnie bardzo opłacalna.
Codemonkey
0
Poniższe informacje powinny być szybkie, obiektywne i niezależne od kolumny identyfikatora. Nie gwarantuje to jednak, że liczba zwróconych wierszy będzie zgodna z liczbą żądanych wierszy.
SELECT*FROM t
WHERE RAND()<(SELECT10/ COUNT(*)FROM t)
Objaśnienie: zakładając, że chcesz 10 wierszy na 100, wówczas każdy rząd ma 1/10 prawdopodobieństwa otrzymania WYBORU, co można osiągnąć WHERE RAND() < 0.1. Takie podejście nie gwarantuje 10 rzędów; ale jeśli zapytanie zostanie uruchomione wystarczającą ilość razy, średnia liczba wierszy na wykonanie wyniesie około 10, a każdy wiersz w tabeli zostanie wybrany równomiernie.
PREPARE stm from'select * from table where available=true limit 10 offset ?';SET@total =(select count(*)fromtablewhere available=true);SET@_offset = FLOOR(RAND()*@total);EXECUTE stm using@_offset;
Testowanie na 600 000 wierszy (700 MB) wykonanie zapytania do tabeli zajęło ~ 0,016 s Dysk HDD -
EDYCJA
- Przesunięcie może przyjąć wartość zbliżoną do końca tabeli, co spowoduje, że instrukcja select zwróci mniej wierszy (a może tylko 1 wiersz), aby tego uniknąć, możemy to sprawdzić offsetponownie po zadeklarowaniu
Do diabła nie, to jeden z najgorszych sposobów na uzyskanie losowych wierszy ze stołu. To pełny skan tabeli + plik + tablica tmp = zła wydajność.
mat
1
Oprócz wydajności nie jest to również całkowicie przypadkowe; zamawiasz według iloczynu id i liczby losowej, zamiast po prostu sortować według liczby losowej, co oznacza, że wiersze o niższych identyfikatorach będą tendencyjne do pojawienia się wcześniej w zestawie wyników.
Odpowiedzi:
Świetny post obsługujący kilka przypadków, od prostych, przez luki, po niejednolitą z lukami.
http://jan.kneschke.de/projects/mysql/order-by-rand/
W najbardziej ogólnym przypadku oto jak to zrobić:
Zakłada to, że rozkład identyfikatorów jest równy i że na liście identyfikatorów mogą występować przerwy. Zobacz artykuł, aby uzyskać bardziej zaawansowane przykłady
źródło
mysqli_fetch_assoc($result)
? A może te 10 wyników niekoniecznie da się rozróżnić?Nie jest to skuteczne rozwiązanie, ale działa
źródło
ORDER BY RAND()
jest stosunkowo wolnySELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10
zajmuje 0,0010, bez LIMITU 10 zabrał 0,0012 (w tej tabeli 3500 słów).Proste zapytanie, które ma doskonałą wydajność i działa z lukami :
To zapytanie na stole 200K zajmuje 0.08s i normalnej wersji (select * from tbl ORDER BY RAND () LIMIT 10) wykonuje 0.35s na moim komputerze.
Jest to szybkie, ponieważ w fazie sortowania używana jest tylko indeksowana kolumna identyfikatora. Możesz zobaczyć to zachowanie w wyjaśnieniu:
WYBIERZ * Z tbl ORDER BY RAND () LIMIT 10:
WYBIERZ * Z tbl JAK DOŁĄCZ DO t1 (WYBIERZ identyfikator z tbl ORDER BY RAND () LIMIT 10) jako t2 ON t1.id = t2.id
Wersja ważona : https://stackoverflow.com/a/41577458/893432
źródło
Dostaję szybkie zapytania (około 0,5 sekundy) z wolnym procesorem , wybierając 10 losowych wierszy w 400 000 rejestrów bazy danych MySQL niebuforowanej wielkości 2 GB. Zobacz mój kod: Szybki wybór losowych wierszy w MySQL
źródło
ORDER BY RAND()
FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';
aby to zobaczyć.ORDER BY RAND()
, że sortuje tylko identyfikatory (nie pełne wiersze), więc tabela temp jest mniejsza, ale nadal musi sortować wszystkie.Jest to bardzo proste i jedno wierszowe zapytanie.
źródło
order by rand()
jest bardzo powolny, jeśli stół jest dużyZ książki:
Wybierz losowy wiersz za pomocą przesunięcia
Jeszcze inną techniką, która pozwala uniknąć problemów znalezionych w poprzednich alternatywach, jest zliczanie wierszy w zestawie danych i zwracanie losowej liczby między 0 a liczbą. Następnie użyj tego numeru jako przesunięcia przy wyszukiwaniu zestawu danych
Skorzystaj z tego rozwiązania, gdy nie możesz założyć ciągłych wartości klucza i musisz upewnić się, że każdy wiersz ma równą szansę na wybranie.
źródło
SELECT count(*)
staje się wolny.Jak wybrać losowe wiersze z tabeli:
Stąd: wybierz losowe wiersze w MySQL
Szybkim ulepszeniem w stosunku do „skanowania tabeli” jest użycie indeksu do pobrania losowych identyfikatorów.
źródło
PRIMARY KEY
).Cóż, jeśli nie masz żadnych przerw w klawiszach, a wszystkie są numeryczne, możesz obliczyć losowe liczby i wybrać te linie. ale prawdopodobnie tak nie będzie.
Tak więc jednym rozwiązaniem byłoby:
co w zasadzie zapewni, że otrzymasz liczbę losową w zakresie swoich klawiszy, a następnie wybierzesz następny najlepszy, który jest większy. musisz to zrobić 10 razy.
jednak NIE jest to tak naprawdę losowe, ponieważ twoje klucze najprawdopodobniej nie zostaną rozłożone równomiernie.
To naprawdę duży problem i niełatwy do rozwiązania, spełniający wszystkie wymagania, rand () MySQL-a jest najlepszym, co możesz uzyskać, jeśli naprawdę chcesz 10 losowych wierszy.
Istnieje jednak inne rozwiązanie, które jest szybkie, ale ma również kompromis, jeśli chodzi o przypadkowość, ale może ci bardziej odpowiadać. Przeczytaj o tym tutaj: jak mogę zoptymalizować funkcję ORDER BY RAND () w MySQL?
Pytanie brzmi, jak losowo potrzebujesz.
Czy możesz wyjaśnić coś więcej, abym mógł dać ci dobre rozwiązanie.
Na przykład firma, z którą współpracowałem, miała rozwiązanie, w którym bardzo szybko potrzebowała absolutnej przypadkowości. Skończyło się na wstępnym zapełnieniu bazy danych losowymi wartościami, które zostały wybrane malejąco i ponownie ustawione na różne wartości losowe.
Jeśli prawie nigdy nie aktualizujesz, możesz również wypełnić rosnący identyfikator, aby nie mieć żadnych luk i po prostu obliczyć losowe klucze przed wybraniem ... To zależy od przypadku użycia!
źródło
Id
a wszystkie losowe zapytania zwrócą ci toId
.FLOOR(RAND()*MAX(id))
jest tendencyjny do zwracania większych identyfikatorów.Potrzebowałem zapytania, aby zwrócić dużą liczbę losowych wierszy z dość dużej tabeli. Właśnie to wymyśliłem. Najpierw uzyskaj maksymalny identyfikator rekordu:
Następnie zamień tę wartość na:
Gdzie max to maksymalny identyfikator rekordu w tabeli, a n to liczba wierszy, które chcesz w zestawie wyników. Zakłada się, że w identyfikatorze nie ma żadnych luk, chociaż wątpię, by to wpłynęło na wynik, gdyby były (choć nie próbowałem). Stworzyłem również tę procedurę składowaną, aby była bardziej ogólna; przekazać nazwę tabeli i liczbę wierszy do zwrócenia. Korzystam z MySQL 5.5.38 w systemie Windows 2008, 32 GB, podwójnym 3GHz E5450, a na stole z 17 691 264 rzędów jest dość spójny przy ~ 0,03 s / ~ 11 s, aby zwrócić 1 000 000 wierszy. (czasy pochodzą z MySQL Workbench 6.1; możesz również użyć CEIL zamiast FLOOR w 2. instrukcji select, w zależności od twoich preferencji)
następnie
źródło
Poprawiłem odpowiedź @Riedsio. Jest to najbardziej wydajne zapytanie, jakie mogę znaleźć w dużej, równomiernie rozłożonej tabeli z przerwami (testowane na uzyskaniu 1000 losowych wierszy z tabeli, która ma> 2,6B wierszy).
Pozwól mi rozpakować, co się dzieje.
@max := (SELECT MAX(id) FROM table)
MAX(id)
każdym razem, gdy potrzebujesz wierszaSELECT FLOOR(rand() * @max) + 1 as rand)
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
Wykonanie unii pomaga dopasować wszystko do 1 zapytania, dzięki czemu można uniknąć wykonywania wielu zapytań. Pozwala także zaoszczędzić na obliczeniach
MAX(id)
. W zależności od aplikacji może to mieć duże lub bardzo małe znaczenie.Zauważ, że to pobiera tylko identyfikatory i porządkuje je w losowej kolejności. Jeśli chcesz zrobić coś bardziej zaawansowanego, zalecamy zrobienie tego:
źródło
LIMIT 1
doLIMIT 30
zapytania wszędzieLIMIT 1
naLIMIT 30
dostanie 30 rekordów z rzędu od losowego punktu w tabeli. Zamiast tego powinieneś mieć 30 kopii(SELECT id FROM ....
części na środku.Riedsio
odpowiedź. Próbowałem z 500 trafieniami na sekundę na stronę przy użyciu PHP 7.0.22 i MariaDB na centos 7, zRiedsio
odpowiedzią dostałem ponad 500 pozytywnych odpowiedzi, a następnie twoją odpowiedź.Użyłem tego http://jan.kneschke.de/projects/mysql/order-by-rand/ opublikowanego przez Riedsio (użyłem przypadku procedury składowanej, która zwraca jedną lub więcej losowych wartości):
W artykule rozwiązuje problem luk w identyfikatorach, powodując niezbyt losowe wyniki , utrzymując tabelę (używając wyzwalaczy itp. ... zobacz artykuł); Rozwiązuję problem, dodając kolejną kolumnę do tabeli, wypełnioną ciągłymi liczbami, zaczynając od 1 ( edycja: ta kolumna jest dodawana do tabeli tymczasowej utworzonej przez podzapytanie w czasie wykonywania, nie wpływa na twoją tabelę stałą):
W artykule widzę, że dołożył wszelkich starań, aby zoptymalizować kod; nie mam pojęcia, czy moje zmiany wpływają na wydajność, ale działają bardzo dobrze dla mnie.
źródło
@no_gaps_id
nie można użyć indeksu, więc jeśli spojrzysz naEXPLAIN
swoje zapytanie, maszUsing filesort
iUsing where
(bez indeksu) dla podkwerend, w przeciwieństwie do pierwotnego zapytania.Oto zmieniacz gier, który może być pomocny dla wielu;
Mam tabelę z 200 tys. Wierszy z sekwencyjnymi identyfikatorami , musiałem wybrać N losowych wierszy, więc wybieram generowanie losowych wartości na podstawie największego identyfikatora w tabeli, stworzyłem ten skrypt, aby dowiedzieć się, która jest najszybsza operacja:
Wyniki są następujące:
36.8418693542479
ms0.241041183472
ms0.216960906982
msW oparciu o te wyniki, opis zamówienia jest najszybszą operacją, aby uzyskać maksymalny identyfikator.
Oto moja odpowiedź na pytanie:
FYI: Aby uzyskać 10 losowych wierszy z tabeli 200 000, zajęło mi 1,78 ms (w tym wszystkie operacje po stronie php)
źródło
LIMIT
nieznaczne zwiększenie - możesz uzyskać duplikaty.Wszystkie najlepsze odpowiedzi zostały już opublikowane (głównie te odnoszące się do linku http://jan.kneschke.de/projects/mysql/order-by-rand/ ).
Chcę wskazać inną możliwość przyspieszenia - buforowanie . Zastanów się, dlaczego potrzebujesz losowych wierszy. Prawdopodobnie chcesz wyświetlić losową wiadomość lub losową reklamę na stronie internetowej. Jeśli otrzymujesz 100 req / s, czy naprawdę potrzebne jest, aby każdy odwiedzający otrzymywał losowe wiersze? Zwykle buforowanie tych X losowych wierszy przez 1 sekundę (a nawet 10 sekund) jest całkowicie w porządku. Nie ma znaczenia, czy 100 unikalnych użytkowników w tej samej 1 sekundzie otrzyma te same losowe posty, ponieważ w następnej sekundzie kolejnych 100 odwiedzających otrzyma inny zestaw postów.
Korzystając z tego buforowania, możesz również użyć wolniejszego rozwiązania do pobierania losowych danych, ponieważ będą one pobierane z MySQL tylko raz na sekundę, niezależnie od twoich wymagań / wymagań.
źródło
Jest to super szybki i jest w 100% losowy, nawet jeśli masz luki.
x
dostępnych wierszySELECT COUNT(*) as rows FROM TABLE
a_1,a_2,...,a_10
od 0 dox
SELECT * FROM TABLE LIMIT 1 offset a_i
dla i = 1, ..., 10Znalazłem ten siekać w książce SQL Antipatterns z Bill Karwin .
źródło
SELECT column FROM table ORDER BY RAND() LIMIT 10
jest w O (nlog (n)). Tak, to jest szybkie rozwiązanie i działa z każdą dystrybucją identyfikatorów.x
. Twierdziłbym, że nie jest to przypadkowa generacja 10 wierszy. W mojej odpowiedzi musisz wykonać kwerendę w kroku trzecim 10 razy, tj. Jeden dostaje tylko jeden wiersz na wykonanie i nie musisz się martwić, jeśli przesunięcie znajduje się na końcu tabeli.Jeśli masz tylko jedno żądanie odczytu
Połącz odpowiedź @redsio z temp-table (600 K to niewiele):
A następnie weź wersję @redsios Odpowiedź:
Jeśli stół jest duży, możesz przesiać w pierwszej części:
Jeśli masz wiele żądań odczytu
Wersja: możesz zachować tabelę jako
tmp_randorder
trwałą, nazwij ją datatable_idlist. Odtworz ten stół w określonych odstępach czasu (dzień, godzina), ponieważ będzie on również dziury. Jeśli twój stół naprawdę się powiększy, możesz również uzupełnić dziurywybierz l.data_id jako całość z datatable_idlist l pozostało dołącz datatable dt na dt.id = l.data_id gdzie dt.id ma wartość null;
Wersja: Nadaj swojemu zestawowi danych losową kolumnę sortowania bezpośrednio w tabeli danych lub w dodatkowej trwałej tabeli
datatable_sortorder
. Indeksuj tę kolumnę. Wygeneruj wartość losową w swojej aplikacji (nazywam ją$rand
).To rozwiązanie rozróżnia „rzędy brzegowe” od najwyższego i najniższego losowego sortera, więc zmieniaj je w odstępach czasu (raz dziennie).
źródło
Innym prostym rozwiązaniem byłoby uszeregowanie wierszy i pobranie jednego z nich losowo, a dzięki temu rozwiązaniu nie będziesz musiał mieć żadnej kolumny opartej na „Id” w tabeli.
Możesz zmienić wartość graniczną zgodnie z potrzebą uzyskania dostępu do tylu wierszy, ile chcesz, ale najczęściej byłyby to kolejne wartości.
Jeśli jednak nie chcesz kolejnych losowych wartości, możesz pobrać większą próbkę i wybrać losowo z niej. coś jak ...
źródło
Jednym ze sposobów, który uważam za całkiem dobry, jeśli istnieje identyfikator generowany automatycznie, jest użycie operatora modulo „%”. Na przykład, jeśli potrzebujesz 10 000 losowych rekordów na 70 000, możesz to uprościć, mówiąc, że potrzebujesz 1 na każde 7 wierszy. Można to uprościć w tym zapytaniu:
Jeśli wynik dzielenia wierszy docelowych przez sumę dostępnych nie jest liczbą całkowitą, będziesz mieć dodatkowe wiersze niż to, o co prosiłeś, więc powinieneś dodać klauzulę LIMIT, aby pomóc przyciąć zestaw wyników w następujący sposób:
Wymaga to pełnego skanowania, ale jest szybsze niż ORDER BY RAND i moim zdaniem łatwiejsze do zrozumienia niż inne opcje wymienione w tym wątku. Również jeśli system zapisujący do DB tworzy zestawy wierszy w partiach, możesz nie otrzymać tak losowego wyniku, jak się spodziewałeś.
źródło
Jeśli chcesz mieć jeden losowy rekord (bez względu na to, czy między identyfikatorami występują przerwy):
Źródło: https://www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/#comment-1266
źródło
Przejrzałem wszystkie odpowiedzi i nie sądzę, aby ktokolwiek w ogóle wspominał o tej możliwości i nie jestem pewien, dlaczego.
Jeśli chcesz maksymalnej prostoty i szybkości, przy niewielkich kosztach, wydaje mi się, że sensowne jest przechowywanie losowych liczb dla każdego wiersza w DB. Po prostu utwórz dodatkową kolumnę
random_number
i ustaw ją domyślnie naRAND()
. Utwórz indeks w tej kolumnie.Następnie, gdy chcesz pobrać wiersz, wygeneruj losową liczbę w swoim kodzie (PHP, Perl, cokolwiek) i porównaj to z kolumną.
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
Myślę, że chociaż jest to bardzo fajne dla jednego rzędu, dla dziesięciu rzędów takich jak OP poprosił, abyś musiał nazwać go dziesięć razy (lub wymyślić sprytne ulepszenie, które natychmiast mi ucieka)
źródło
Poniższe informacje powinny być szybkie, obiektywne i niezależne od kolumny identyfikatora. Nie gwarantuje to jednak, że liczba zwróconych wierszy będzie zgodna z liczbą żądanych wierszy.
Objaśnienie: zakładając, że chcesz 10 wierszy na 100, wówczas każdy rząd ma 1/10 prawdopodobieństwa otrzymania WYBORU, co można osiągnąć
WHERE RAND() < 0.1
. Takie podejście nie gwarantuje 10 rzędów; ale jeśli zapytanie zostanie uruchomione wystarczającą ilość razy, średnia liczba wierszy na wykonanie wyniesie około 10, a każdy wiersz w tabeli zostanie wybrany równomiernie.źródło
Możesz łatwo użyć losowego przesunięcia z limitem
Możesz także zastosować taką klauzulę where
Testowanie na 600 000 wierszy (700 MB) wykonanie zapytania do tabeli zajęło ~ 0,016 s Dysk HDD -
EDYCJA
- Przesunięcie może przyjąć wartość zbliżoną do końca tabeli, co spowoduje, że instrukcja select zwróci mniej wierszy (a może tylko 1 wiersz), aby tego uniknąć, możemy to sprawdzić
offset
ponownie po zadeklarowaniuźródło
Używam tego zapytania:
czas zapytania: 0,016s
źródło
Tak to robię:
Podoba mi się, ponieważ nie wymaga innych tabel, jest łatwy do napisania i bardzo szybki do wykonania.
źródło
Użyj poniższego prostego zapytania, aby uzyskać losowe dane z tabeli.
źródło
Myślę, że to najlepszy możliwy sposób ..
źródło