Jak mogę zażądać losowego wiersza (lub tak zbliżonego do naprawdę losowego, jak to możliwe) w czystym SQL?
Jak mogę zażądać losowego wiersza (lub tak zbliżonego do naprawdę losowego, jak to możliwe) w czystym SQL?
Odpowiedzi:
Zobacz ten post: SQL, aby wybrać losowy wiersz z tabeli bazy danych . Omówiono metody wykonywania tego w MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 i Oracle (z tego łącza kopiowane są następujące elementy):
Wybierz losowy wiersz za pomocą MySQL:
SELECT column FROM table
ORDER BY RAND()
LIMIT 1
Wybierz losowy wiersz za pomocą PostgreSQL:
SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1
Wybierz losowy wiersz za pomocą Microsoft SQL Server:
SELECT TOP 1 column FROM table
ORDER BY NEWID()
Wybierz losowy wiersz za pomocą IBM DB2
SELECT column, RAND() as IDX
FROM table
ORDER BY IDX FETCH FIRST 1 ROWS ONLY
Wybierz losowy rekord z Oracle:
SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
order by rand()
lub równoważne we wszystkich dbs: | wspomniano również tutaj .
ORDER BY RAND()
jest złe ...
O(n)
z n
liczbą rekordów w tabeli. Wyobraź sobie, że masz milion rekordów, czy naprawdę chcesz wygenerować milion losowych liczb lub unikatowych identyfikatorów? Wolałbym użyć COUNT()
i włączyć to w nowym LIMIT
wyrażeniu z pojedynczą liczbą losową.
Rozwiązania takie jak Jeremies:
SELECT * FROM table ORDER BY RAND() LIMIT 1
działają, ale potrzebują sekwencyjnego skanowania całej tabeli (ponieważ należy obliczyć losową wartość związaną z każdym wierszem - aby można było ustalić najmniejszą), co może być dość powolne w przypadku tabel nawet średnich. Moja rekomendacja to użycie indeksowanej kolumny numerycznej (wiele tabel ma je jako klucze podstawowe), a następnie napisanie czegoś takiego:
SELECT * FROM table WHERE num_value >= RAND() *
( SELECT MAX (num_value ) FROM table )
ORDER BY num_value LIMIT 1
Działa to w czasie logarytmicznym, niezależnie od wielkości tabeli, jeśli num_value
jest indeksowane. Jedno zastrzeżenie: zakłada się, że num_value
jest on równomiernie rozłożony w zakresie 0..MAX(num_value)
. Jeśli zestaw danych silnie odbiega od tego założenia, otrzymasz wypaczone wyniki (niektóre wiersze pojawią się częściej niż inne).
Nie wiem, jak to jest wydajne, ale używałem go wcześniej:
SELECT TOP 1 * FROM MyTable ORDER BY newid()
Ponieważ identyfikatory GUID są dość losowe, kolejność oznacza, że otrzymujesz losowy wiersz.
ORDER BY RAND() LIMIT 1
TOP 1
i newid()
.
ORDER BY NEWID()
trwa 7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
bierze 0.0065 milliseconds
!
Zdecydowanie wybiorę tę drugą metodę.
rand()
zwraca liczbę zmiennoprzecinkową n
gdzie 0 < n < 1
. Zakładając, że num_value
jest liczbą całkowitą, zwracana wartość rand() * max(num_value)
zostanie również wymuszona na liczbę całkowitą, co spowoduje obcięcie wszystkiego po przecinku. Stąd rand() * max(num_value)
będzie zawsze być mniejsza niż max(num_value)
, dlatego nigdy nie zostanie wybrany ostatni wiersz.
Nie powiedziałeś, którego serwera używasz. W starszych wersjach SQL Server możesz użyć tego:
select top 1 * from mytable order by newid()
W SQL Server 2005 i nowszych można użyć TABLESAMPLE
losowej próbki, która jest powtarzalna:
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
Dla SQL Server
Funkcja newid () / order by będzie działać, ale będzie bardzo kosztowna dla dużych zestawów wyników, ponieważ musi wygenerować identyfikator dla każdego wiersza, a następnie je posortować.
TABLESAMPLE () jest dobry z punktu widzenia wydajności, ale dostaniesz zbijanie wyników (wszystkie wiersze na stronie zostaną zwrócone).
Aby uzyskać lepszą skuteczność prawdziwej próbki losowej, najlepszym sposobem jest losowe odfiltrowanie wierszy. Znalazłem następujący przykładowy kod w artykule SQL Server Books Online Ograniczanie zestawów wyników za pomocą TABLESAMPLE :
Jeśli naprawdę chcesz losowej próbki pojedynczych wierszy, zmodyfikuj zapytanie, aby odfiltrować wiersze losowo, zamiast używać TABLESAMPLE. Na przykład w poniższym zapytaniu użyto funkcji NEWID do zwrócenia około jednego procenta wierszy tabeli Sales.SalesOrderDetail:
SELECT * FROM Sales.SalesOrderDetail WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)
Kolumna SalesOrderID jest zawarta w wyrażeniu CHECKSUM, dzięki czemu NEWID () ocenia raz na wiersz, aby uzyskać próbkowanie na podstawie wiersza. Wyrażenie CAST (CHECKSUM (NEWID (), SalesOrderID) i 0x7fffffff AS float / CAST (0x7fffffff AS int) zwraca losową wartość od 0 do 1.
Po uruchomieniu z tabelą zawierającą 1 000 000 wierszy, oto moje wyniki:
SET STATISTICS TIME ON
SET STATISTICS IO ON
/* newid()
rows returned: 10000
logical reads: 3359
CPU time: 3312 ms
elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()
/* TABLESAMPLE
rows returned: 9269 (varies)
logical reads: 32
CPU time: 0 ms
elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)
/* Filter
rows returned: 9994 (varies)
logical reads: 3359
CPU time: 641 ms
elapsed time: 627 ms
*/
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
SET STATISTICS IO OFF
SET STATISTICS TIME OFF
Jeśli możesz uciec od używania TABLESAMPLE, zapewni to najlepszą wydajność. W przeciwnym razie użyj metody newid () / filter. newid () / order by powinno być ostatecznością, jeśli masz duży zestaw wyników.
Jeśli to możliwe, użyj przechowywanych instrukcji, aby uniknąć nieskuteczności obu indeksów w RND () i tworząc pole numeru rekordu.
PREPARE RandomRecord FROM „SELECT * FROM table LIMIT?, 1”; SET @ n = FLOOR (RAND () * (SELECT COUNT (*) FROM table)); WYKONAJ RandomRecord USING @n;
Najlepszym sposobem jest umieszczenie losowej wartości w nowej kolumnie tylko w tym celu i użycie czegoś takiego (kod pseude + SQL):
randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
Jest to rozwiązanie zastosowane w kodzie MediaWiki. Oczywiście istnieje pewne odchylenie w stosunku do mniejszych wartości, ale stwierdzono, że wystarczyło zawinąć losową wartość do zera, gdy nie zostaną pobrane żadne wiersze.
Rozwiązanie newid () może wymagać pełnego skanowania tabeli, aby do każdego wiersza można było przypisać nowy identyfikator GUID, który będzie znacznie mniej wydajny.
rozwiązanie rand () może w ogóle nie działać (tj. z MSSQL), ponieważ funkcja zostanie oceniona tylko raz, a do każdego wiersza zostanie przypisany ten sam „losowy” numer.
W przypadku SQL Server 2005 i 2008, jeśli chcemy losową próbkę pojedynczych wierszy (z Books Online ):
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
Natychmiastowe użycie RAND (), ponieważ nie jest to zalecane , możesz po prostu uzyskać max ID (= Max):
SELECT MAX(ID) FROM TABLE;
uzyskaj losowość między 1..Max (= My_Generated_Random)
My_Generated_Random = rand_in_your_programming_lang_function(1..Max);
a następnie uruchom ten SQL:
SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1
Zauważ, że sprawdzi, czy wszystkie wiersze są identyczne lub WYŻSZE niż wybrana wartość. Możliwe jest również wyszukanie wiersza w dół tabeli i uzyskanie identycznego lub niższego identyfikatora niż My_Generated_Random, a następnie zmodyfikowanie zapytania w następujący sposób:
SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1
Jak wskazano w komentarzu @ BillKarwin do odpowiedzi @ cnu ...
Kiedy kombinuję z LIMITEM, odkryłem, że działa znacznie lepiej (przynajmniej w PostgreSQL 9.1) do ŁĄCZENIA z losowym porządkowaniem, a nie bezpośrednio porządkować rzeczywiste wiersze: np.
SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
FROM tbl_post
WHERE create_time >= 1349928000
) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100
Upewnij się tylko, że „r” generuje wartość „rand” dla każdej możliwej wartości klucza w złożonym zapytaniu, które jest z nią połączone, ale nadal ogranicza liczbę wierszy „r”, jeśli to możliwe.
CAST as Integer jest szczególnie pomocny dla PostgreSQL 9.2, który ma specyficzną optymalizację sortowania dla liczb całkowitych i zmiennoprzecinkowych o pojedynczej precyzji.
Większość rozwiązań tutaj ma na celu uniknięcie sortowania, ale nadal muszą wykonać sekwencyjne skanowanie tabeli.
Istnieje również sposób na uniknięcie skanowania sekwencyjnego poprzez przełączenie na skanowanie indeksu. Jeśli znasz wartość indeksu losowego wiersza, możesz uzyskać wynik niemal natychmiast. Problem polega na tym, jak odgadnąć wartość indeksu.
Następujące rozwiązanie działa na PostgreSQL 8.4:
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
limit 1;
Powyżej rozwiązania odgadniesz 10 różnych losowych wartości indeksu z zakresu 0 .. [ostatnia wartość id].
Liczba 10 jest dowolna - możesz użyć 100 lub 1000, ponieważ (zadziwiająco) nie ma to dużego wpływu na czas reakcji.
Jest też jeden problem - jeśli masz rzadkie identyfikatory, których możesz przegapić . Rozwiązaniem jest mieć plan tworzenia kopii zapasowych :) W tym przypadku czyste stare zamówienie przez losowe () zapytanie. Po połączeniu id wygląda następująco:
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
union all (select * from cms_refs order by random() limit 1)
limit 1;
Nie klauzula unii ALL . W takim przypadku, jeśli pierwsza część zwróci jakiekolwiek dane, NIGDY nie zostanie wykonana!
Późno, ale dotarłem tutaj przez Google, więc dla potomności dodam alternatywne rozwiązanie.
Innym podejściem jest dwukrotne użycie TOP, przy naprzemiennych zamówieniach. Nie wiem, czy jest to „czysty SQL”, ponieważ wykorzystuje zmienną w TOP, ale działa w SQL Server 2008. Oto przykład, którego używam w stosunku do tabeli słów słownikowych, jeśli chcę losowe słowo.
SELECT TOP 1
word
FROM (
SELECT TOP(@idx)
word
FROM
dbo.DictionaryAbridged WITH(NOLOCK)
ORDER BY
word DESC
) AS D
ORDER BY
word ASC
Oczywiście @idx to losowo generowana liczba całkowita, która zawiera się w przedziale od 1 do COUNT (*) w tabeli docelowej, włącznie. Jeśli Twoja kolumna jest zaindeksowana, również z niej skorzystasz. Kolejną zaletą jest to, że można go używać w funkcji, ponieważ NEWID () jest niedozwolony.
Na koniec powyższe zapytanie działa w około 1/10 czasu wykonania zapytania typu NEWID () w tej samej tabeli. RRMV.
Możesz także spróbować użyć new id()
funkcji.
Wystarczy napisać zapytanie i użyć kolejności według new id()
funkcji. To dość losowe.
Aby MySQL uzyskał losowy rekord
SELECT name
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
Więcej szczegółów http://jan.kneschke.de/projects/mysql/order-by-rand/
Nie widziałem jeszcze tej zmiany w odpowiedziach. Miałem dodatkowe ograniczenie, gdzie musiałem, biorąc pod uwagę początkowe ziarno, aby wybrać ten sam zestaw wierszy za każdym razem.
W przypadku MS SQL:
Minimalny przykład:
select top 10 percent *
from table_name
order by rand(checksum(*))
Znormalizowany czas wykonania: 1,00
Przykład NewId ():
select top 10 percent *
from table_name
order by newid()
Znormalizowany czas wykonania: 1,02
NewId()
jest nieznacznie wolniejszy niż rand(checksum(*))
, więc możesz nie chcieć używać go do dużych zestawów płyt.
Wybór z początkowym nasionem:
declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */
select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */
Jeśli musisz wybrać ten sam zestaw dla danego ziarna, wydaje się, że to działa.
W MSSQL (testowany na 11.0.5569) przy użyciu
SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)
jest znacznie szybszy niż
SELECT TOP 100 * FROM employee ORDER BY NEWID()
W SQL Server możesz łączyć TABLESAMPLE z NEWID (), aby uzyskać całkiem dobrą losowość i nadal mieć szybkość. Jest to szczególnie przydatne, jeśli naprawdę chcesz tylko 1 lub niewielką liczbę wierszy.
SELECT TOP 1 * FROM [table]
TABLESAMPLE (500 ROWS)
ORDER BY NEWID()
W SQL Server 2012+ można użyć kwerendy FETCH OFFSET, aby zrobić to dla pojedynczego losowego wiersza
select * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY
gdzie id to kolumna tożsamości, a n to żądany wiersz - obliczany jako liczba losowa między 0 a count () - 1 tabeli (offset 0 to przecież pierwszy wiersz)
Działa to z otworami w danych tabeli, o ile masz indeks do pracy dla klauzuli ORDER BY. Jest to również bardzo dobre dla losowości - gdy ćwiczysz, że się poddajesz, ale w innych metodach nie ma drobiazgów. Ponadto wydajność jest całkiem dobra, na mniejszym zestawie danych dobrze się trzyma, chociaż nie próbowałem poważnych testów wydajności w stosunku do kilku milionów wierszy.
SELECT * FROM table ORDER BY RAND() LIMIT 1
ORDER BY RAND()
jest złe ...
Muszę się zgodzić z CD-MaN: użycie „ORDER BY RAND ()” będzie działało dobrze na małych stolikach lub gdy wykonasz SELECT tylko kilka razy.
Używam również techniki „num_value> = RAND () * ...”, a jeśli naprawdę chcę uzyskać losowe wyniki, mam specjalną „losową” kolumnę w tabeli, którą aktualizuję raz dziennie. To pojedyncze uruchomienie UPDATE zajmie trochę czasu (zwłaszcza, że będziesz musiał mieć indeks w tej kolumnie), ale jest znacznie szybsze niż tworzenie liczb losowych dla każdego wiersza za każdym razem, gdy uruchamiany jest wybór.
Bądź ostrożny, ponieważ TableSample nie zwraca losowej próbki wierszy. Kieruje zapytanie do losowej próbki stron o wielkości 8 KB, które składają się na wiersz. Następnie zapytanie jest wykonywane na podstawie danych zawartych na tych stronach. Ze względu na sposób grupowania danych na tych stronach (kolejność wstawiania itp.) Może to prowadzić do danych, które w rzeczywistości nie są przypadkową próbką.
Zobacz: http://www.mssqltips.com/tip.asp?tip=1308
Ta strona MSDN dla TableSample zawiera przykład generowania faktycznie losowej próbki danych.
Wygląda na to, że wiele z wymienionych pomysłów nadal korzysta z funkcji zamawiania
Jeśli jednak używasz tabeli tymczasowej, możesz przypisać losowy indeks (jak sugeruje wiele rozwiązań), a następnie pobrać pierwszy, który jest większy niż dowolna liczba z zakresu od 0 do 1.
Na przykład (dla DB2):
WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY
Prosty i wydajny sposób z http://akinas.com/pages/en/blog/mysql_random_row/
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
W przypadku programu SQL Server 2005 i nowszych rozszerzenie odpowiedzi @ GreyPanther w przypadkach, gdy num_value
nie ma ciągłych wartości. Działa to również w przypadkach, gdy nie rozprowadziliśmy równomiernie zestawów danych i gdy num_value
nie jest liczbą, ale unikalnym identyfikatorem.
WITH CTE_Table (SelRow, num_value)
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
)
SELECT * FROM table Where num_value = (
SELECT TOP 1 num_value FROM CTE_Table WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)
Pomocna może być funkcja losowa z sql. Również jeśli chcesz ograniczyć się do jednego wiersza, po prostu dodaj go na końcu.
SELECT column FROM table
ORDER BY RAND()
LIMIT 1