Jak najlepiej napisać zapytanie, które wybiera losowo 10 wierszy z łącznej liczby 600 000?
Jak najlepiej napisać zapytanie, które wybiera losowo 10 wierszy z łącznej liczby 600 000?
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ć:
SELECT name
FROM random AS r1 JOIN
(SELECT CEIL(RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY 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
mysqli_fetch_assoc($result)
? A może te 10 wyników niekoniecznie da się rozróżnić?
SELECT column FROM table
ORDER BY RAND()
LIMIT 10
Nie jest to skuteczne rozwiązanie, ale działa
ORDER BY RAND()
jest stosunkowo wolny
SELECT 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 :
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY 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
Wersja ważona : https://stackoverflow.com/a/41577458/893432
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);
?>
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.
SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;
order by rand()
jest bardzo powolny, jeśli stół jest duży
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
<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();
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.
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.
SELECT *
FROM random, (
SELECT id AS sid
FROM random
ORDER BY RAND( )
LIMIT 10
) tmp
WHERE random.id = tmp.sid;
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:
SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1
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!
Id
a wszystkie losowe zapytania zwrócą ci to Id
.
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:
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)
DELIMITER $$
USE [schema name] $$
DROP PROCEDURE IF EXISTS `random_rows` $$
CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN
SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @t = CONCAT(
'SELECT * FROM ',
tab_name,
' WHERE id>FLOOR(RAND()*@max) LIMIT ',
num_rows);
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$
następnie
CALL [schema name].random_rows([table name], n);
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 FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
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 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, etc
FROM table t
INNER JOIN (
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id
LIMIT 1
do LIMIT 30
zapytania wszędzie
LIMIT 1
na LIMIT 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, z Riedsio
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):
DROP TEMPORARY TABLE IF EXISTS rands;
CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt < 1 THEN
LEAVE loop_me;
END IF;
INSERT INTO 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
ORDER BY 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 TABLE IF EXISTS rands;
CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt < 1 THEN
LEAVE loop_me;
END IF;
SET @no_gaps_id := 0;
INSERT INTO rands
SELECT r1.id
FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS 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
ORDER BY 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.
@no_gaps_id
nie można użyć indeksu, więc jeśli spojrzysz na EXPLAIN
swoje zapytanie, masz Using filesort
i Using 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:
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:
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:
SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
SELECT FLOOR(RAND() * (
SELECT id FROM tbl ORDER BY 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)
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ń.
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 .
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.
Połącz odpowiedź @redsio z temp-table (600 K to niewiele):
DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO 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
INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
INNER JOIN datatable AS dt on dt.id = rndo.data_id
ORDER BY abs(rndo.id - rnd.id)
LIMIT 1;
Jeśli stół jest duży, możesz przesiać w pierwszej części:
INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;
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ć 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
order by 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 + 1 AS 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 + 1 AS 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 ORDER BY 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 * FROM
table
WHERE
id %
FLOOR(
(SELECT count(1) FROM table)
/ 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 * FROM
table
WHERE
id %
FLOOR(
(SELECT count(1) FROM table)
/ 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ś.
Jeśli chcesz mieć jeden losowy rekord (bez względu na to, czy między identyfikatorami występują przerwy):
PREPARE stmt FROM 'SELECT * FROM `table_name` LIMIT 1 OFFSET ?';
SET @count = (SELECT
FLOOR(RAND() * COUNT(*))
FROM `table_name`);
EXECUTE stmt USING @count;
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 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)
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() < (SELECT 10 / 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 limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;
Możesz także zastosować taką klauzulę where
PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where 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ć offset
ponownie po zadeklarowaniu
SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;
Używam tego zapytania:
select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10
czas zapytania: 0,016s
Tak to robię:
select *
from table_with_600k_rows
where rand() < 10/600000
limit 10
Podoba mi się, ponieważ nie wymaga innych tabel, jest łatwy do napisania i bardzo szybki do wykonania.
Użyj poniższego prostego zapytania, aby uzyskać losowe dane z tabeli.
SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails
GROUP BY usr_fk_id
ORDER BY cnt ASC
LIMIT 10
Myślę, że to najlepszy możliwy sposób ..
SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no