Losowy rekord z tabeli bazy danych (T-SQL)

85

Czy istnieje zwięzły sposób na pobranie losowego rekordu z tabeli serwera sql?

Chciałbym randomizować moje dane z testów jednostkowych, więc szukam prostego sposobu na wybranie losowego identyfikatora z tabeli. W języku angielskim opcja select to „Wybierz jeden identyfikator z tabeli, gdzie identyfikator jest liczbą losową między najniższym identyfikatorem w tabeli a najwyższym identyfikatorem w tabeli”.

Nie mogę znaleźć sposobu na zrobienie tego bez uruchamiania zapytania, testowania wartości null, a następnie ponownego uruchamiania, jeśli jest to wartość null.

Pomysły?

Jeremy
źródło
istnieje kilka metod tutaj brettb.com/SQL_Help_Random_Numbers.asp
Mesh
2
Czy na pewno chcesz zastosować to podejście? Dane testów jednostkowych nie powinny być losowe - w rzeczywistości powinieneś mieć pewność, że uzyskasz te same wyniki, niezależnie od tego, ile razy wykonasz test jednostkowy. Posiadanie danych losowych może naruszyć tę fundamentalną zasadę testowania jednostkowego.
wodnik
Powyższy link z @Mesh nie jest już aktywny.
Robert Sievers

Odpowiedzi:

145

Czy istnieje zwięzły sposób na pobranie losowego rekordu z tabeli serwera sql?

tak

SELECT TOP 1 * FROM table ORDER BY NEWID()

Wyjaśnienie

Dla NEWID()każdego wiersza jest generowany znak, a tabela jest następnie sortowana według niego. Zwracany jest pierwszy rekord (tj. Rekord z „najniższym” identyfikatorem GUID).

Uwagi

  1. Identyfikatory GUID są generowane jako liczby pseudolosowe od wersji czwartej:

    UUID w wersji 4 jest przeznaczony do generowania identyfikatorów UUID z liczb prawdziwie losowych lub pseudolosowych.

    Algorytm wygląda następująco:

    • Ustaw dwa najbardziej znaczące bity (bity 6 i 7) parametru clock_seq_hi_and_reserved odpowiednio na zero i jeden.
    • Ustaw cztery najbardziej znaczące bity (bity od 12 do 15) pola time_hi_and_version na 4-bitowy numer wersji z rozdziału 4.1.3.
    • Ustaw wszystkie pozostałe bity na losowo (lub pseudolosowo) wybrane wartości.

    - Przestrzeń nazw URN Universally Unique IDentifier (UUID) - RFC 4122

  2. Alternatywa SELECT TOP 1 * FROM table ORDER BY RAND()nie zadziała tak, jak mogłoby się wydawać. RAND()zwraca jedną wartość na zapytanie, więc wszystkie wiersze będą miały tę samą wartość.

  3. Chociaż wartości GUID są pseudolosowe, będziesz potrzebować lepszego PRNG dla bardziej wymagających aplikacji.

  4. Typowa wydajność to mniej niż 10 sekund dla około 1 000 000 rzędów - oczywiście w zależności od systemu. Pamiętaj, że nie można trafić w indeks, więc wydajność będzie stosunkowo ograniczona.

Sklivvz
źródło
Dokładnie to, czego szukałem. Miałem wrażenie, że to prostsze niż robię.
Jeremy
1
Zakładasz, że NEWID tworzy wartości pseudolosowe. Jest duża szansa, że ​​wygeneruje sekwencyjne wartości. NEWID po prostu tworzy unikalne wartości. RAND generuje jednak pseudolosowe wartości.
Skizz
Uruchamiam go na mocno zindeksowanej tabeli z 1 671 145 wierszami, a powrót zajmuje 7 sekund. Tabela jest również optymalna - jest praktycznie sercem naszej bazy danych, więc zadbano o nią.
Tom Ritter
@ ÂviewAnew. 1,6 miliona wierszy i 7 sekund na zaznaczeniu, które nie trafiło (i nie może) trafić w indeks, nie jest złe.
Sklivvz
7
@Skizz, rand tak nie działa. SINGLE losowa wartość jest generowana przed SELECT. Więc jeśli spróbujesz „SELECT TOP 10 RAND () ...”, zawsze otrzymasz tę samą wartość
Sklivvz
27

W przypadku większych tabel można również użyć TABLESAMPLEtego, aby uniknąć skanowania całej tabeli.

SELECT  TOP 1 *
FROM YourTable
TABLESAMPLE (1000 ROWS)
ORDER BY NEWID()

ORDER BY NEWIDJest nadal wymagane, aby uniknąć właśnie powracających wiersze, które pojawiają się najpierw na stronie danych.

Liczbę, której należy użyć, należy starannie dobrać pod kątem rozmiaru i definicji tabeli, a jeśli nie zostanie zwrócony żaden wiersz, można rozważyć zastosowanie logiki ponawiania. Tutaj omówiono matematykę, która za tym stoi i dlaczego ta technika nie jest odpowiednia dla małych tabel

Martin Smith
źródło
Znalazłem to na stronie Microsoftu: Możesz użyć TABLESAMPLE, aby szybko zwrócić próbkę z dużej tabeli, gdy spełniony jest jeden z następujących warunków: Próbka nie musi być prawdziwie losową próbką na poziomie pojedynczych wierszy. Wiersze na poszczególnych stronach tabeli nie są skorelowane z innymi wierszami na tej samej stronie.
Mark Entingh
1
@MarkEntingh - W tym przypadku TOP 1nie ma znaczenia, czy wiersze na tej samej stronie są skorelowane, czy nie. Wybierasz tylko jednego z nich.
Martin Smith
9

Wypróbuj również swoją metodę, aby uzyskać losowy identyfikator między MIN (Id) a MAX (Id), a następnie

SELECT TOP 1 * FROM table WHERE Id >= @yourrandomid

Zawsze dostaniesz jeden wiersz.

Sklivvz
źródło
2
-1, działa to tylko wtedy, gdy nie ma brakujących identyfikatorów między min a maks. Jeśli jeden zostanie usunięty, to ten sam identyfikator zostanie wygenerowany przez funkcję losową, a odzyskasz zero rekordów.
Neil N
6
@Neil, niezupełnie - dostaniesz pierwszy wiersz z identyfikatorem większym niż liczba losowa, jeśli brakuje identyfikatorów. Problem polega na tym, że prawdopodobieństwo wystąpienia każdego rzędu nie jest stałe. Ale w większości przypadków to wystarcza.
Sklivvz
1
+1. W przypadku testów jednostkowych, które powinny osiągać różne wartości, jest to wystarczająco dobre - jeśli potrzebujesz naprawdę losowego, to jest coś innego. Ale w kontekście PO powinno wystarczyć.
TomTom
7

Jeśli chcesz wybierać duże dane, najlepszym sposobem, jaki znam, jest:

SELECT * FROM Table1
WHERE (ABS(CAST(
    (BINARY_CHECKSUM
    (keycol1, NEWID())) as int))
    % 100) < 10

Źródło: MSDN

hmfarimani
źródło
Nie jestem pewien, ale myślę, że użycie RAND () zamiast NEWID () do generowania naprawdę losowych liczb może być lepsze ze względu na wady używania NEWID () w procesie wyboru.
QMaster
Próbuję użyć tej metody z dokładną liczbą rekordów, a raczej z podstawą procentową, zrobiłem to z rozszerzeniem zakresu wyboru i ograniczeniem TOP n, czy jest jakaś sugestia?
QMaster
Znalazłem inny problem z tym scenariuszem, jeśli używasz grupowania przez ciebie, zawsze otrzymasz tę samą kolejność losowo wybranych wierszy, więc wydaje się, że w małych tabelach podejście @skilvvz jest najbardziej odpowiednie.
QMaster
0

Chciałem ulepszyć metody, które wypróbowałem i trafiłem na ten post. Zdaję sobie sprawę, że jest stara, ale ta metoda nie jest wymieniona. Tworzę i stosuję dane testowe; to pokazuje metodę "adresu" w SP wywołanej z @st (stan dwóch znaków)

Create Table ##TmpAddress (id Int Identity(1,1), street VarChar(50), city VarChar(50), st VarChar(2), zip VarChar(5))
Insert Into ##TmpAddress(street, city, st, zip)
Select street, city, st, zip 
From tbl_Address (NOLOCK)
Where st = @st


-- unseeded RAND() will return the same number when called in rapid succession so
-- here, I seed it with a guaranteed different number each time. @@ROWCOUNT is the count from the most recent table operation.

Set @csr = Ceiling(RAND(convert(varbinary, newid())) * @@ROWCOUNT)

Select street, city, st, Right(('00000' + ltrim(zip)),5) As zip
From ##tmpAddress (NOLOCK)
Where id = @csr
user2788934
źródło
0

Jeśli naprawdę potrzebujesz losowej próbki pojedynczych wierszy, zmodyfikuj zapytanie, tak aby losowo odfiltrowywało wiersze, zamiast używać TABLESAMPLE. Na przykład poniższe zapytanie używa funkcji NEWID, aby zwrócić około jednego procentu 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 uwzględniona w wyrażeniu CHECKSUM, dzięki czemu NEWID () oblicza wartość raz na wiersz, aby uzyskać próbkowanie na podstawie każdego wiersza. Wyrażenie CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) zwraca losową wartość typu float z przedziału od 0 do 1. ”

Źródło: http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx

Jest to dokładniej wyjaśnione poniżej:

Jak to działa? Podzielmy klauzulę WHERE i wyjaśnijmy to.

Funkcja SUMA KONTROLNA oblicza sumę kontrolną pozycji na liście. Można spierać się, czy SalesOrderID jest w ogóle wymagany, ponieważ NEWID () jest funkcją, która zwraca nowy losowy identyfikator GUID, więc pomnożenie losowej liczby przez stałą powinno w każdym przypadku skutkować losowością. Rzeczywiście, wykluczenie SalesOrderID wydaje się nie robić różnicy. Jeśli jesteś zapalonym statystykiem i potrafisz uzasadnić uwzględnienie tego, skorzystaj z sekcji komentarzy poniżej i daj mi znać, dlaczego się mylę!

Funkcja CHECKSUM zwraca wartość VARBINARY. Wykonywanie bitowej operacji AND z 0x7fffffff, co jest odpowiednikiem (111111111 ...) w systemie binarnym, daje wartość dziesiętną, która jest w rzeczywistości reprezentacją losowego ciągu zer i jedynek. Dzielenie przez współczynnik 0x7fffffff skutecznie normalizuje tę liczbę dziesiętną do liczby od 0 do 1. Następnie, aby zdecydować, czy każdy wiersz zasługuje na włączenie do końcowego zestawu wyników, stosuje się próg 1 / x (w tym przypadku 0,01), gdzie x to procent danych do pobrania jako próbka.

Źródło: https://www.mssqltips.com/sqlservertip/3157/different-ways-to-get-random-data-for-sql-server-data-sampling

XpiritO
źródło