2009-05-11 19 views
256

Mam tabela programu SQL Server z około 50 000 wierszy w nim. Chcę wybrać losowo około 5000 z tych rzędów. Myślałem o skomplikowanym sposobie tworzenia tabeli tymczasowej z kolumną "losowej liczby", kopiowania do niej tabeli, przechodzenia przez tabelę tymczasową i aktualizowania każdego wiersza za pomocą RAND(), a następnie wybierania z tej tabeli, w której znajduje się kolumna z liczbami losowymi < 0.1. Szukam prostszego sposobu, aby to zrobić, w jednym wyciągu, jeśli to możliwe.Wybierz n losowych wierszy z tabeli programu SQL Server

This article sugeruje użycie funkcji NEWID(). To wygląda obiecująco, ale nie widzę sposobu, w jaki mógłbym wiarygodnie wybrać określony procent wierszy.

Ktoś kiedyś to zrobił? Jakieś pomysły?

+2

MSDN ma dobry artykuł, który obejmuje wiele z tych zagadnień: [Wybór wierszy losowo z dużym stołem] (https://msdn.microsoft.com/en-us/library/cc441928.aspx) – KyleMit

+0

Możliwy duplikat [Jak zażądać losowego wiersza w SQL?] (http://stackoverflow.com/questions/19412/how- to-request-a-random-row-in-sql) –

Odpowiedz

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

W odpowiedzi na komentarz "czystego śmietnika" dotyczący dużych tabel: można 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()) 

Koszt ten będzie kluczem skanowania wartości plus koszty przyłączenia, który na dużym stole z małym wyborem procentowy powinien być rozsądny.

+1

Podobało mi się to podejście znacznie lepiej niż przy użyciu artykułu, do którego się odwołał. – JoshBerke

+10

Zawsze dobrze jest 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 interesują cię matematyczne cechy i takie, to będzie wystarczająco dobre. W przeciwnym razie potrzebujesz: http://stackoverflow.com/questions/249301/simple-random-samples-from-a-mysql-database – user12861

+0

Um, przepraszam, jeśli to jest oczywiste .. ale do czego odnosi się "[yourPk]"? EDYCJA: Nvm, zorientowałem się ... Klucz podstawowy. Durrr – Snailer

4

W MySQL można to zrobić:

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

To nie zadziała. Ponieważ instrukcja select jest atomowa, pobiera tylko jedną liczbę losową i duplikuje ją dla każdego wiersza. W każdym rzędzie musiałbyś go ponownie ustawić, aby wymusić zmianę. –

+4

Mmm ... kocham różnice sprzedawcy. Select jest atomiczny w MySQL, ale przypuszczam, że w inny sposób. To zadziała w MySQL. –

8

Wystarczy zamówić stolik losową liczbę i uzyskać pierwsze 5000 wierszy przy użyciu TOP.

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

UPDATE

Właśnie spróbowałem i newid() Połączenie jest sporą ilością - nie ma potrzeby dla wszystkich odlewów i całej matematyki.

+5

Powód, dla którego używane są "wszystkie rzuty i wszystkie matematykę", zapewnia lepszą wydajność. – hkf

62

W zależności od potrzeb, TABLESAMPLE zapewnia niemal bezład i lepszą wydajność. jest to dostępne na serwerze MS SQL 2005 i nowszych wersjach.

TABLESAMPLE będzie zwracać dane z losowych stron zamiast losowych wierszy, a zatem deos nawet nie pobierze danych, których nie zwróci.

Na bardzo dużym stole Testowałem

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

trwało ponad 20 minut.

select * from [tablename] tablesample(1 percent) 

zajął 2 minuty.

Wydajność poprawi się również na mniejszych próbkach w TABLESAMPLE, natomiast nie będzie z newid().

Należy pamiętać, że nie jest to tak przypadkowa metoda, jak metoda newid(), ale zapewnia przyzwoitą próbkę.

Zobacz MSDN page.

+7

Jak zauważył Rob Boek poniżej, tabelampling klastra wyników, a zatem nie jest dobrym sposobem, aby uzyskać * małą * liczbę losowych wyników –

+0

Masz pytanie, jak to działa: wybierz top 1 procent * z [nazwa tablicy] porządku przez newid(), ponieważ newid() nie jest kolumną w [tablename]. Czy serwer sql dodaje wewnętrznie kolumnę newid() w każdym wierszu, a następnie sortuje? – FrenkyB

+0

The tablesample był dla mnie najlepszą odpowiedzią, ponieważ wykonywałem złożone zapytanie na bardzo dużym stole. Bez wątpienia było to niezwykle szybkie. Dostałem zmianę w liczbie rekordów, które zostały zwrócone, ponieważ uruchomiłem to wiele razy, ale wszystkie były w dopuszczalnym marginesie błędu. – jessier3

33

newid()/order by will work, ale będzie bardzo kosztowny 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 otrzymasz zbicie wyników (wszystkie wiersze na stronie zostaną zwrócone).

Aby uzyskać skuteczniejszą próbkę losową, najlepszym sposobem jest losowe odfiltrowanie wierszy. Znalazłem następujący przykładowy kod w artykule SQL Server Books Online Limiting Results Sets by Using TABLESAMPLE:

Jeśli naprawdę chcesz losową próbkę poszczególnych wierszy, modyfikowania zapytanie do odfiltrować wiersze losowo zamiast użyciu TABLESAMPLE . Na przykład, następujące zapytanie wykorzystuje funkcję NEWID powrót w przybliżeniu jedną procent 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 w wyrażenie tak, że suma kontrolna NEWID() ocenia raz na wiersz, aby uzyskać uzyskiwać próbkowanie dla poszczególnych wierszy. CAST wyrażenie (sumy kontrolnej (NEWID() SalesOrderID) & 0x7fffffff CO pływaka/ węglowej (0x7fffffff int) ocenia się losową liczbą rzeczywistą z zakresu od 0 do 1.

Po uruchomieniu na stole 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żna uciec z użyciem TABLESAMPLE, to daje najlepszą wydajność przeciwnym razie użyj newID()/metody filtra newid()/zamów przez powinno być w ostateczności, jeśli masz duży zestaw wyników:

+0

Widziałem też ten artykuł i próbowałem go na moim kodzie, wydaje się, że 'NewID()' jest oceniany tylko raz, zamiast na wiersz, który mi się nie podoba ... –

0

Działa to dla mnie:

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

@ user537824, czy próbowałeś tego na SQL? Serwer? RANDOM nie jest funkcją, a LIMIT nie jest słowem kluczowym. Składnia SQL Server dla tego, co robisz, byłaby "wybierz 10% najlepszych od nazwy tabeli nazwa-tabeli przez rand()", ale to też nie działa, ponieważ rand() zwraca tę samą wartość we wszystkich wierszach. –

2

Spróbuj tego:

SELECT TOP 10 Field1, ..., FieldN 
FROM Table1 
ORDER BY NEWID() 
18

Selecting Rows Randomly from a Large Table na MSDN ma proste i precyzyjne rozwiązanie, które rozwiązuje się z wydajnością dużych obaw.

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

Bardzo interesujące. Po przeczytaniu tego artykułu, tak naprawdę nie rozumiem, dlaczego 'RAND()' nie zwraca tej samej wartości dla każdego wiersza (która mogłaby pokonać logikę 'BINARY_CHECKSUM()'). Czy to dlatego, że jest on nazywany wewnątrz innej funkcji, a nie częścią klauzuli SELECT? –

+0

To zapytanie działało na tabeli z 6-milionowymi wierszami w mniej niż sekundę. –

+1

Uruchomiłem to zapytanie na stole z 35 hasłami i często miałem ich dwa w zestawie wyników. Może to być problem z 'rand()' lub kombinacją powyższych, 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 przyjęcia w niektórych scenariuszach. – Oliver

8

Jeśli (w przeciwieństwie do PO) wymagają określonej liczby rekordów (co sprawia, że ​​podejście CHECKSUM trudne) i pragną bardziej losową próbkę niż TABLESAMPLE zapewnia sam, a także chcą lepszą prędkość niż suma kontrolna, możesz zrobić z połączeniem TABLESAMPLE i NEWID() metod, takich 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 najprostsza kompromis pomiędzy losowości (to naprawdę nie wiem) i prędkość. Zmieniaj procent TABLESAMPLE (lub wiersze) - im wyższy odsetek, tym bardziej losowa jest próbka, ale spodziewany jest liniowy spadek prędkości. (Zauważ, że TABLESAMPLE nie zaakceptuje zmiennej)

2

Ta odmiana odpowiedzi jeszcze się nie pojawiła. Miałem dodatkowe ograniczenie tam, gdzie potrzebowałem, biorąc pod uwagę początkowe nasiono, aby za każdym razem wybrać ten sam zestaw wierszy.

Dla MS SQL:

Minimalna przykład:

select top 10 percent * 
from table_name 
order by rand(checksum(*)) 

Znormalizowany czas wykonania 1.00

newID(), na przykład:

select top 10 percent * 
from table_name 
order by newid() 

Znormalizowany czas realizacji: 1,02

NewId() jest nieznacznie wolniejszy niż rand(checksum(*)), więc może nie chcieć go użyć przeciwko dużych zestawów płytowych.

Wybór z Początkowa Seed:

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 trzeba wybrać ten sam zestaw danych nasienie, to wydaje się działać.

+0

Czy jest jakaś korzyść z używania specjalnego @seed przeciwko RAND()? – QMaster

+0

Nie jesteś w 100% pewien, o co pytasz, wyjaśniając umysł? – klyd

+0

absolutnie, użyłeś parametru seed i wypełniłeś go parametrem daty, funkcja RAND() zrobiła to samo z wyjątkiem użycia pełnej wartości czasu, chcę wiedzieć, czy jest jakaś korzyść z używania poręcznego stworzonego parametru, jak seed powyżej RAND() czy nie? – QMaster

5

Związek ten ma ciekawe porównanie między orderby (NEWID()) i inne metody tabelach 1, 7 i 13 milionów rzędach.

Często, kiedy pytania o jak wybrać losowo wiersze są zadawane w grupach dyskusyjnych, zapytanie NEWID proponuje; jest prosty i działa bardzo dobrze na małe stoły.

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

Jednak kwerenda NEWID ma dużą wadę, gdy używa się 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 wysokim kosztem. Sortowanie może korzystać z wielu dyskowych operacji we/wy i może działać przez długi czas.
  2. W najgorszym scenariuszu, tempdb może zabraknąć miejsca. W najlepszym scenariuszu, , tempdb może zająć dużo miejsca na dysku , które nigdy nie zostaną odzyskane bez ręcznego polecenia zmniejszania.

Potrzebny jest sposób losowego wybierania wierszy, które nie będą używać tempdb i nie będą się znacznie wolniejsze, gdy tabela będzie wię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ć losową liczbę między 0 a 99 dla każdego wiersza w tabeli, a następnie wybrać wszystkie te wiersze, których losowa liczba 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 dokumencie: MSDN.

+2

Cześć Deumber, miło znaleźć, możesz go odtworzyć, ponieważ odpowiedzi tylko link są prawdopodobnie usunięte. – bummi

+1

@bummi Zmieniłem go, aby uniknąć odpowiedzi tylko link :) – QMaster

0

Wydaje newid() nie mogą być stosowane w przypadkach, gdy klauzula, więc to rozwiązanie wymaga kwerendy wewnętrzna:

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

Jest to połączenie pomysłu nasion i kontrolną, która wygląda mi dać właściwie przypadkowe wyniki bez kosztów NEWID():

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

używałem go w podzapytania i wrócił do mnie same wiersze w podzapytanie

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

Potem rozwiązał z tym nadrzędnego tabeli zmienną gdzie

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

Zanotuj gdzie condtition

Powiązane problemy