Wybierz n losowych wierszy z tabeli programu SQL Server


309

Mam tabelę programu SQL Server z około 50 000 wierszy. Chcę wybrać losowo około 5000 z tych wierszy. Pomyślałem o skomplikowanym sposobie, tworząc tabelę tymczasową z kolumną „liczby losowej”, kopiując do niej moją tabelę, przeglądając tabelę tymczasową i aktualizując każdy wiersz RAND(), a następnie wybierając z tej tabeli, w której kolumna liczb losowych < 0,1 Szukam prostszego sposobu, aby to zrobić, w jednym oświadczeniu, jeśli to możliwe.

W tym artykule sugeruje się użycie NEWID()funkcji. To wygląda obiecująco, ale nie widzę, jak mógłbym wiarygodnie wybrać określony procent wierszy.

Czy ktoś to kiedyś robił? Jakieś pomysły?


3
MSDN ma dobry artykuł, który omawia wiele z tych problemów:
Losowanie losowe

Odpowiedzi:


387
select top 10 percent * from [yourtable] order by newid()

W odpowiedzi na komentarz „czysty kosz” dotyczący dużych tabel: możesz to zrobić w ten sposób, aby poprawić wydajność.

select  * from [yourtable] where [yourPk] in 
(select top 10 percent [yourPk] from [yourtable] order by newid())

Kosztem tego będzie kluczowy skan wartości plus koszt złączenia, który na dużym stole z niewielkim wyborem procentowym powinien być rozsądny.


1
Podobało mi się to podejście znacznie lepiej niż przywoływany artykuł.
JoshBerke

14
Zawsze warto pamiętać, że newid () nie jest naprawdę dobrym generatorem liczb pseudolosowych, przynajmniej nie tak dobrym jak rand (). Ale jeśli potrzebujesz tylko trochę przypadkowych próbek i nie przejmujesz się matematycznymi właściwościami, to będzie wystarczająco dobre. W przeciwnym razie potrzebujesz: stackoverflow.com/questions/249301/...
user12861

1
Przepraszam, jeśli to oczywiste .. ale co to [yourPk]dotyczy? EDYCJA: Nvm, wymyśliłem to ... Klucz podstawowy. Durrr
Snailer

4
newid - guid nie jest wyjątkowy, ale nie losowy. nieprawidłowe podejście
Brans Ds

2
przy dużej liczbie wierszy, na przykład ponad 1 milion newid()Oszacowanie sortowania Koszt we / wy będzie bardzo wysoki i wpłynie na wydajność.
aadi1295

81

W zależności od potrzeb, TABLESAMPLEzapewni Ci prawie tak samo losową i lepszą wydajność. jest to dostępne na serwerze MS SQL Server 2005 i nowszych.

TABLESAMPLE zwróci dane z losowych stron zamiast losowych wierszy i dlatego deos nawet nie pobierze danych, których nie zwróci.

Testowałem na bardzo dużym stole

select top 1 percent * from [tablename] order by newid()

zajęło ponad 20 minut.

select * from [tablename] tablesample(1 percent)

zajęło 2 minuty.

Wydajność poprawi się również na mniejszych próbkach, podczas TABLESAMPLEgdy nie będzie newid().

Pamiętaj, że nie jest to tak losowe jak newid() metoda, ale zapewni przyzwoite próbkowanie.

Zobacz stronę MSDN .


7
Jak zauważył Rob Boek poniżej, próbkowanie tabel grupuje wyniki, a zatem nie jest dobrym sposobem na uzyskanie niewielkiej liczby losowych wyników
Oskar Austegard

Masz pytanie, jak to działa: wybierz 1% * z kolejności [tablename] według newid (), ponieważ newid () nie jest kolumną w [tablename]. Czy serwer SQL dołącza wewnętrznie kolumnę newid () do każdego wiersza, a następnie dokonuje sortowania?
FrenkyB,

Próbka tabel była dla mnie najlepszą odpowiedzią, ponieważ robiłem złożone zapytanie na bardzo dużej tabeli. Bez wątpienia było to niezwykle szybkie. Otrzymałem różnicę w liczbie rekordów zwróconych, gdy uruchomiłem to wiele razy, ale wszystkie zawierały się w dopuszczalnym marginesie błędu.
jessier3

38

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ład kodu 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 uda Ci się uniknąć użycia 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.


Widziałem, że wyrób zbyt i próbuje go na mojego kodu wydaje się, że NewID()jest oceniany tylko raz, zamiast na wiersz, który mi się nie podoba ...
Andrew Mao

23

Losowe wybieranie wierszy z dużej tabeli w MSDN ma proste, dobrze wyartykułowane rozwiązanie, które rozwiązuje problemy związane z wydajnością na dużą skalę.

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

Bardzo interesujące. Po przeczytaniu artykułu tak naprawdę nie rozumiem, dlaczego RAND()nie zwraca tej samej wartości dla każdego wiersza (co by pokonało BINARY_CHECKSUM()logikę). Czy dlatego, że jest wywoływany w innej funkcji, a nie jest częścią klauzuli SELECT?
John M Gant,

To zapytanie uruchomiono w tabeli z wierszami 6 MM w mniej niż sekundę.
Mark Melville

2
Uruchomiłem tę kwerendę na tabeli z 35 wpisami i wciąż bardzo często mam dwa z nich w zestawie wyników. Może to być problem z rand()kombinacją powyższych lub ich kombinacja - ale z tego powodu odwróciłem się od tego rozwiązania. Również liczba wyników wahała się od 1 do 5, więc może to być również nie do zaakceptowania w niektórych scenariuszach.
Oliver

Czy funkcja RAND () nie zwraca tej samej wartości dla każdego wiersza?
Sarsaparilla,

RAND()zwraca tę samą wartość dla każdego wiersza (dlatego to rozwiązanie jest szybkie). Jednak wiersze z binarnymi sumami kontrolnymi, które są bardzo blisko siebie, są narażone na wysokie ryzyko generowania podobnych wyników sumy kontrolnej, powodując zbrylanie, gdy RAND()jest małe. Np . (ABS(CAST((BINARY_CHECKSUM(111,null,null) * 0.1) as int))) % 100== SELECT (ABS(CAST((BINARY_CHECKSUM(113,null,null) * 0.1) as int))) % 100. Jeśli dane cierpią z powodu tego problemu, pomnóż je BINARY_CHECKSUMprzez 9923.
Brian

12

Ten link ma interesujące porównanie między Orderby (NEWID ()) i innymi metodami dla tabel z 1, 7 i 13 milionami wierszy.

Często, gdy w grupach dyskusyjnych zadawane są pytania dotyczące wyboru losowych wierszy, proponuje się zapytanie NEWID; jest prosty i działa bardzo dobrze na małych stolikach.

SELECT TOP 10 PERCENT *
  FROM Table1
  ORDER BY NEWID()

Jednak zapytanie NEWID ma dużą wadę, gdy używasz go do dużych tabel. Klauzula ORDER BY powoduje, że wszystkie wiersze w tabeli są kopiowane do bazy danych tempdb, gdzie są sortowane. Powoduje to dwa problemy:

  1. Operacja sortowania zwykle wiąże się z wysokimi kosztami. Sortowanie może wykorzystywać wiele dyskowych operacji we / wy i może działać przez długi czas.
  2. W najgorszym przypadku tempdb może zabraknąć miejsca. W najlepszym przypadku tempdb może zająć dużo miejsca na dysku, które nigdy nie zostanie odzyskane bez polecenia ręcznego zmniejszenia.

Potrzebny jest sposób losowego wybierania wierszy, które nie będą używać tempdb i nie będą spowalniały, gdy tabela będzie się powiększać. Oto nowy pomysł, jak to zrobić:

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

Podstawową ideą tego zapytania jest to, że chcemy wygenerować liczbę losową od 0 do 99 dla każdego wiersza w tabeli, a następnie wybrać wszystkie wiersze, których liczba losowa jest mniejsza niż wartość określonego procentu. W tym przykładzie chcemy losowo wybrać około 10 procent wierszy; dlatego wybieramy wszystkie wiersze, których liczba losowa jest mniejsza niż 10.

Przeczytaj cały artykuł w MSDN .


2
Cześć Deumber, fajnie znaleziono, możesz to zrobić, ponieważ odpowiedzi tylko na link prawdopodobnie zostaną usunięte.
bummi

1
@bummi Zmieniłem to, aby uniknąć odpowiedzi tylko linkiem :)
QMaster

To najlepsza odpowiedź. Funkcja „ORDER BY NEWID ()” działa w większości przypadków (mniejsze tabele), ale ponieważ testy porównawcze w odświeżonym łączu wyraźnie pokazują, że pozostaje on w tyle wraz ze wzrostem tabeli
pedram bashiri

10

Jeśli potrzebujesz (w przeciwieństwie do OP) określonej liczby rekordów (co utrudnia podejście do CHECKSUM) i pragniesz bardziej losowej próbki niż sama TABLESAMPLE, a także chcesz większej prędkości niż CHECKSUM, możesz zadowolić się połączeniem Metody TABLESAMPLE i NEWID (), takie jak to:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

SELECT TOP (@sampleCount) * 
FROM [yourtable] TABLESAMPLE(10 PERCENT)
ORDER BY NEWID()

SET STATISTICS TIME OFF

W moim przypadku jest to najprostszy kompromis między losowością (nie wiem, tak naprawdę) a szybkością. Zmieniaj odpowiednio wartość procentową TABLESAMPLE (lub wierszy) - im wyższy odsetek, tym bardziej losowa próbka, ale spodziewaj się liniowego spadku prędkości. (Pamiętaj, że TABLESAMPLE nie zaakceptuje zmiennej)


9

Po prostu uporządkuj tabelę według losowej liczby i uzyskaj pierwsze 5000 wierszy za pomocą TOP.

SELECT TOP 5000 * FROM [Table] ORDER BY newid();

AKTUALIZACJA

Po prostu spróbowałem, a newid()wezwanie jest wystarczające - nie potrzeba wszystkich obsad i całej matematyki.


10
Powodem użycia „wszystkich rzutów i wszystkich matematyki” jest lepsza wydajność.
hkf

6

Jest to połączenie początkowego pomysłu początkowego i sumy kontrolnej, która wydaje mi się dawać odpowiednio losowe wyniki bez kosztu NEWID ():

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())

3

W MySQL możesz to zrobić:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;

3
To nie zadziała. Ponieważ instrukcja select jest niepodzielna, pobiera tylko jedną liczbę losową i kopiuje ją dla każdego wiersza. Będziesz musiał ponownie ustawić go w każdym rzędzie, aby wymusić zmianę.
Tom H

4
Mmm ... uwielbiam różnice dostawców. Select jest atomowy na MySQL, ale przypuszczam, że w inny sposób. Będzie to działać w MySQL.
Jeff Ferland

2

Nie widziałem jeszcze tej zmiany w odpowiedziach. Miałem dodatkowe ograniczenie, gdzie potrzebował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.


Czy jest jakaś zaleta używania specjalnego @seed przeciwko RAND ()?
QMaster

absolutnie, użyłeś parametru początkowego i wypełniłeś go według parametru daty, funkcja RAND () robi to samo, z wyjątkiem użycia pełnej wartości czasu, chcę wiedzieć, czy jest jakaś korzyść z używania poręcznie utworzonego parametru, takiego jak ziarno powyżej RAND () czy nie?
QMaster 24.09.16

Ach! OK, to był wymóg projektu. Musiałem wygenerować listę n-losowych wierszy w sposób deterministyczny. Zasadniczo przywódcy chcieli wiedzieć, jakie „losowe” wiersze wybieramy na kilka dni przed ich wybraniem i przetworzeniem. Budując wartość początkową na podstawie roku / miesiąca, mogłem zagwarantować, że każde wywołanie zapytania w tym roku zwróci tę samą „losową” listę. Wiem, to było dziwne i prawdopodobnie były lepsze sposoby, ale działało ...
Klyd

HAHA :) Rozumiem, ale myślę, że ogólne znaczenie losowo wybranych rekordów nie jest tymi samymi rekordami dla różnych uruchomionych zapytań.
QMaster


0

Wygląda na to, że newid () nie może być użyte w klauzuli where, więc to rozwiązanie wymaga wewnętrznego zapytania:

SELECT *
FROM (
    SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
    FROM MyTable
) vw
WHERE Rnd % 100 < 10        --10%

0

Użyłem go w podzapytaniu i zwróciło mi to samo wiersze w podzapytaniu

 SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

następnie rozwiązałem z włączeniem zmiennej tabeli nadrzędnej gdzie

SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              Where Mytable.ID>0
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

Zwróć uwagę na to, gdzie warunki


0

Używany język przetwarzania po stronie serwera (np. PHP, .net itp.) Nie jest określony, ale jeśli jest to PHP, należy pobrać wymaganą liczbę (lub wszystkie rekordy) i zamiast losowo w zapytaniu użyć funkcji losowego PHP. Nie wiem, czy .net ma równoważną funkcję, ale jeśli tak, użyj jej, jeśli używasz .net

ORDER BY RAND () może mieć dość negatywny wpływ na wydajność, w zależności od liczby rekordów.


Nie pamiętam dokładnie, do czego wtedy tego używałem, ale prawdopodobnie pracowałem w C #, może na serwerze, a może w aplikacji klienckiej, nie jestem pewien. C # nie ma nic bezpośrednio porównywalnego z tasowaniem afaika PHP, ale można to zrobić, stosując funkcje z obiektu Random w operacji Select, porządkując wynik, a następnie biorąc pierwsze dziesięć procent. Musielibyśmy jednak odczytać całą tabelę z dysku na serwerze DB i przesłać ją przez sieć, aby odrzucić 90% tych danych. Przetwarzanie go bezpośrednio w DB jest prawie na pewno bardziej wydajne.
John M Gant,

-2

To działa dla mnie:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]

9
@ user537824, czy próbowałeś tego na SQL Server? RANDOM nie jest funkcją, a LIMIT nie jest słowem kluczowym. Składnia programu SQL Server dla tego, co robisz, byłaby select top 10 percent from table_name order by rand(), ale to również nie działa, ponieważ rand () zwraca tę samą wartość we wszystkich wierszach.
John M Gant,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.