Wählen Sie n zufällige Zeilen aus der SQL Server-Tabelle aus


309

Ich habe eine SQL Server-Tabelle mit ungefähr 50.000 Zeilen. Ich möchte ungefähr 5.000 dieser Zeilen zufällig auswählen. Ich habe mir einen komplizierten Weg überlegt, eine temporäre Tabelle mit einer Spalte "Zufallszahl" zu erstellen, meine Tabelle in diese zu kopieren, die temporäre Tabelle zu durchlaufen und jede Zeile mit zu aktualisieren RAND()und dann aus dieser Tabelle die Zufallszahlenspalte <auszuwählen 0,1. Ich suche nach einem einfacheren Weg, wenn möglich in einer einzigen Aussage.

In diesem Artikel wird die Verwendung der NEWID()Funktion vorgeschlagen. Das sieht vielversprechend aus, aber ich kann nicht sehen, wie ich einen bestimmten Prozentsatz von Zeilen zuverlässig auswählen kann.

Hat das schon mal jemand gemacht? Irgendwelche Ideen?


3
MSDN hat einen guten Artikel, der viele dieser Probleme behandelt: Zufällige Auswahl von Zeilen aus einer großen Tabelle
KyleMit

Antworten:


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

Als Antwort auf den Kommentar "Pure Trash" zu großen Tabellen: Sie können dies so tun, um die Leistung zu verbessern.

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

Die Kosten hierfür sind der Schlüssel-Scan der Werte zuzüglich der Verbindungskosten, die für eine große Tabelle mit einer kleinen prozentualen Auswahl angemessen sein sollten.


1
Ich mag diesen Ansatz viel besser als den Artikel, auf den er verwiesen hat.
JoshBerke

14
Es ist immer gut zu bedenken, dass newid () kein wirklich guter Pseudozufallszahlengenerator ist, zumindest nicht annähernd so gut wie rand (). Aber wenn Sie nur ein paar vage zufällige Stichproben benötigen und sich nicht für mathematische Qualitäten und dergleichen interessieren, ist dies gut genug. Andernfalls benötigen Sie: stackoverflow.com/questions/249301/…
user12861

1
Tut mir leid, wenn dies offensichtlich ist. Aber worauf bezieht [yourPk]sich das? EDIT: Nvm, habe es herausgefunden ... Primärschlüssel. Durrr
Snailer

4
newid - guid ist nicht eindeutig, aber nicht zufällig. Falscher Ansatz
Brans Ds

2
Bei einer großen Anzahl von Zeilen, z. B. über 1 Million newid()Sortierschätzung, sind die E / A-Kosten sehr hoch und wirken sich auf die Leistung aus.
aadi1295

81

Je nach Ihren Anforderungen TABLESAMPLEerhalten Sie eine nahezu ebenso zufällige und bessere Leistung. Dies ist auf MS SQL Server 2005 und höher verfügbar.

TABLESAMPLE gibt Daten von zufälligen Seiten anstelle von zufälligen Zeilen zurück und daher ruft deos nicht einmal Daten ab, die nicht zurückgegeben werden.

Auf einem sehr großen Tisch habe ich getestet

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

dauerte mehr als 20 Minuten.

select * from [tablename] tablesample(1 percent)

dauerte 2 Minuten.

Die Leistung wird sich auch bei kleineren Samples verbessern, TABLESAMPLEwährend dies bei nicht der Fall ist newid().

Bitte denken Sie daran, dass dies nicht so zufällig ist wie die newid()Methode, aber Sie eine anständige Stichprobe erhalten.

Siehe die MSDN-Seite .


7
Wie von Rob Boek weiter unten ausgeführt, klumpt Tablesampling zu Ergebnissen und ist daher kein guter Weg, um eine kleine Anzahl zufälliger Ergebnisse zu erhalten
Oskar Austegard,

Die Frage, wie dies funktioniert, stört Sie: Wählen Sie die oberste 1 Prozent * aus der Reihenfolge [Tabellenname] nach newid () aus, da newid () keine Spalte in [Tabellenname] ist. Hängt der SQL Server die interne Spalte newid () an jede Zeile an und führt dann eine Sortierung durch?
FrenkyB

Das Tabellenbeispiel war die beste Antwort für mich, da ich eine komplexe Abfrage für eine sehr große Tabelle durchführte. Keine Frage, dass es bemerkenswert schnell war. Ich habe eine Variation in der Anzahl der zurückgegebenen Datensätze erhalten, als ich dies mehrmals ausgeführt habe, aber alle waren innerhalb einer akzeptablen Fehlergrenze.
Jessier3

38

newid () / order by funktioniert, ist jedoch für große Ergebnismengen sehr teuer, da für jede Zeile eine ID generiert und anschließend sortiert werden muss.

TABLESAMPLE () ist vom Standpunkt der Leistung aus gut, aber Sie erhalten eine Zusammenfassung der Ergebnisse (alle Zeilen auf einer Seite werden zurückgegeben).

Für eine bessere Zufallsstichprobe mit besserer Leistung ist es am besten, Zeilen zufällig herauszufiltern. Ich habe das folgende Codebeispiel im SQL Server Books Online-Artikel Einschränken von Ergebnismengen mithilfe von TABLESAMPLE gefunden :

Wenn Sie wirklich eine zufällige Stichprobe einzelner Zeilen wünschen, ändern Sie Ihre Abfrage so, dass Zeilen zufällig herausgefiltert werden, anstatt TABLESAMPLE zu verwenden. In der folgenden Abfrage wird beispielsweise die NEWID-Funktion verwendet, um ungefähr ein Prozent der Zeilen der Sales.SalesOrderDetail-Tabelle zurückzugeben:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

Die SalesOrderID-Spalte ist im CHECKSUM-Ausdruck enthalten, sodass NEWID () einmal pro Zeile ausgewertet wird, um eine Stichprobenauswahl pro Zeile zu erzielen. Der Ausdruck CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) ergibt einen zufälligen Float-Wert zwischen 0 und 1.

Wenn ich gegen eine Tabelle mit 1.000.000 Zeilen laufe, sind hier meine Ergebnisse:

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

Wenn Sie mit TABLESAMPLE durchkommen, erhalten Sie die beste Leistung. Verwenden Sie andernfalls die Methode newid () / filter. newid () / order by sollte der letzte Ausweg sein, wenn Sie eine große Ergebnismenge haben.


Ich habe diesen Artikel auch gesehen und ihn in meinem Code ausprobiert. Es scheint, dass er NewID()nur einmal ausgewertet wird, anstatt pro Zeile, was mir nicht gefällt ...
Andrew Mao

23

Das zufällige Auswählen von Zeilen aus einer großen Tabelle in MSDN bietet eine einfache, gut artikulierte Lösung, die die großen Leistungsprobleme berücksichtigt.

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

Sehr interessant. Nachdem ich den Artikel gelesen habe, verstehe ich nicht wirklich, warum RAND()nicht für jede Zeile der gleiche Wert zurückgegeben wird (was die BINARY_CHECKSUM()Logik zunichte machen würde). Liegt es daran, dass es in einer anderen Funktion aufgerufen wird, anstatt Teil der SELECT-Klausel zu sein?
John M Gant

Diese Abfrage wurde in weniger als einer Sekunde für eine Tabelle mit 6 MM Zeilen ausgeführt.
Mark Melville

2
Ich habe diese Abfrage für eine Tabelle mit 35 Einträgen ausgeführt und immer wieder zwei davon in der Ergebnismenge. Dies könnte ein Problem mit rand()oder eine Kombination der oben genannten sein - aber ich habe mich aus diesem Grund von dieser Lösung abgewandt. Auch die Anzahl der Ergebnisse variierte von 1 bis 5, so dass dies in einigen Szenarien möglicherweise auch nicht akzeptabel ist.
Oliver

Gibt RAND () nicht für jede Zeile den gleichen Wert zurück?
Sarsaparilla

RAND()Gibt für jede Zeile den gleichen Wert zurück (weshalb diese Lösung schnell ist). Bei Zeilen mit binären Prüfsummen, die sehr nahe beieinander liegen, besteht jedoch ein hohes Risiko, dass ähnliche Prüfsummenergebnisse generiert werden, was zu Verklumpungen führt, wenn sie RAND()klein sind. ZB (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. Wenn Ihre Daten unter diesem Problem BINARY_CHECKSUMleiden , multiplizieren Sie mit 9923.
Brian

12

Dieser Link bietet einen interessanten Vergleich zwischen Orderby (NEWID ()) und anderen Methoden für Tabellen mit 1, 7 und 13 Millionen Zeilen.

Wenn in Diskussionsgruppen Fragen zur Auswahl zufälliger Zeilen gestellt werden, wird häufig die NEWID-Abfrage vorgeschlagen. Es ist einfach und funktioniert sehr gut für kleine Tische.

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

Die NEWID-Abfrage hat jedoch einen großen Nachteil, wenn Sie sie für große Tabellen verwenden. Die ORDER BY-Klausel bewirkt, dass alle Zeilen in der Tabelle in die Tempdb-Datenbank kopiert werden, wo sie sortiert werden. Dies verursacht zwei Probleme:

  1. Der Sortiervorgang ist normalerweise mit hohen Kosten verbunden. Das Sortieren kann viele Festplatten-E / A verwenden und lange ausgeführt werden.
  2. Im schlimmsten Fall kann tempdb nicht mehr genügend Speicherplatz haben. Im besten Fall kann tempdb eine große Menge an Speicherplatz beanspruchen, der ohne einen manuellen Verkleinerungsbefehl niemals zurückgefordert wird.

Was Sie brauchen, ist eine Möglichkeit, Zeilen zufällig auszuwählen, die kein Tempdb verwenden und nicht viel langsamer werden, wenn die Tabelle größer wird. Hier ist eine neue Idee, wie das geht:

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

Die Grundidee hinter dieser Abfrage ist, dass wir für jede Zeile in der Tabelle eine Zufallszahl zwischen 0 und 99 generieren und dann alle Zeilen auswählen möchten, deren Zufallszahl kleiner als der Wert des angegebenen Prozentsatzes ist. In diesem Beispiel möchten wir, dass ungefähr 10 Prozent der Zeilen zufällig ausgewählt werden. Daher wählen wir alle Zeilen aus, deren Zufallszahl kleiner als 10 ist.

Bitte lesen Sie den vollständigen Artikel in MSDN .


2
Hallo Deumber, schön gefunden, Sie könnten es ausarbeiten, da nur Link-Antworten wahrscheinlich gelöscht werden.
Bummi

1
@ Bummi Ich habe es geändert, um zu vermeiden, nur Link Antwort zu sein :)
QMaster

Dies ist die beste Antwort. 'ORDER BY NEWID ()' funktioniert in den meisten Fällen (kleinere Tabellen), aber wie die Benchmarks im aktualisierten Link deutlich zeigen, fällt es zurück, wenn die Tabelle wächst
pedram bashiri

10

Wenn Sie (im Gegensatz zum OP) eine bestimmte Anzahl von Datensätzen benötigen (was den CHECKSUM-Ansatz schwierig macht) und eine zufälligere Stichprobe wünschen, als TABLESAMPLE selbst bietet, und auch eine bessere Geschwindigkeit als CHECKSUM wünschen, können Sie mit einer Fusion des Datensatzes auskommen TABLESAMPLE- und NEWID () -Methoden wie folgt:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

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

SET STATISTICS TIME OFF

In meinem Fall ist dies der einfachste Kompromiss zwischen Zufälligkeit (ich weiß nicht wirklich) und Geschwindigkeit. Variieren Sie den Prozentsatz (oder die Zeilen) der TABLESAMPLE entsprechend - je höher der Prozentsatz, desto zufälliger die Stichprobe, aber erwarten Sie einen linearen Geschwindigkeitsabfall. (Beachten Sie, dass TABLESAMPLE keine Variable akzeptiert.)


9

Ordnen Sie die Tabelle einfach nach einer Zufallszahl und erhalten Sie die ersten 5.000 Zeilen mit TOP.

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

AKTUALISIEREN

Ich habe es einfach versucht und ein newid()Anruf ist ausreichend - keine Notwendigkeit für alle Besetzungen und alle Mathematik.


10
Der Grund, warum "alle Besetzungen und alle Mathematik" verwendet wird, ist für eine bessere Leistung.
HKF

6

Dies ist eine Kombination aus der ursprünglichen Startidee und einer Prüfsumme, die meiner Meinung nach ohne die Kosten von NEWID () richtig zufällige Ergebnisse liefert:

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

3

In MySQL können Sie dies tun:

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

3
Dies wird nicht funktionieren. Da die select-Anweisung atomar ist, erfasst sie nur eine Zufallszahl und dupliziert sie für jede Zeile. Sie müssten es in jeder Zeile neu säen, um eine Änderung zu erzwingen.
Tom H

4
Mmm ... liebe Lieferantenunterschiede. Select ist unter MySQL atomar, aber ich nehme an, auf eine andere Art und Weise. Dies funktioniert in MySQL.
Jeff Ferland

2

Ich habe diese Variation in den Antworten noch nicht ganz gesehen. Ich hatte eine zusätzliche Einschränkung, bei der ich bei einem anfänglichen Startwert jedes Mal denselben Satz von Zeilen auswählen musste.

Für MS SQL:

Minimales Beispiel:

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

Normalisierte Ausführungszeit: 1,00

NewId () Beispiel:

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

Normalisierte Ausführungszeit: 1.02

NewId()ist unwesentlich langsamer als rand(checksum(*)), daher möchten Sie es möglicherweise nicht für große Datensatzgruppen verwenden.

Auswahl mit Initial 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 */

Wenn Sie denselben Satz für einen Startwert auswählen müssen, scheint dies zu funktionieren.


Gibt es einen Vorteil der Verwendung von speziellem @seed gegen RAND ()?
QMaster

Absolut, Sie haben den Seed-Parameter verwendet und ihn mit dem Datumsparameter gefüllt. Die RAND () -Funktion macht dasselbe, außer dass Sie den vollständigen Zeitwert verwenden. Ich möchte wissen, ob die Verwendung eines handlich erstellten Parameters wie Seed über RAND () von Vorteil ist oder nicht.
QMaster

Ah!. OK, das war eine Anforderung des Projekts. Ich musste eine Liste von n zufälligen Zeilen auf deterministische Weise generieren. Grundsätzlich wollte die Führung wissen, welche "zufälligen" Zeilen wir einige Tage vor der Auswahl und Verarbeitung der Zeilen auswählen würden. Durch das Erstellen eines Startwerts basierend auf dem Jahr / Monat konnte ich garantieren, dass jeder Aufruf der Abfrage in diesem Jahr dieselbe "zufällige" Liste zurückgibt. Ich weiß, es war seltsam und es gab wahrscheinlich bessere Wege, aber es hat funktioniert ...
Klyd

HAHA :) Ich verstehe, aber ich denke, dass die allgemeine Bedeutung von zufällig ausgewählten Datensätzen nicht die gleichen Datensätze in verschiedenen laufenden Abfragen sind.
QMaster

1

Versuche dies:

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

0

Es scheint, dass newid () nicht in der where-Klausel verwendet werden kann, daher erfordert diese Lösung eine innere Abfrage:

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

0

Ich habe es in Unterabfragen verwendet und es hat mir dieselben Zeilen in Unterabfragen zurückgegeben

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

dann löste ich mit der Einbeziehung der übergeordneten Tabellenvariablen in wo

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

Beachten Sie den Where-Zustand


0

Die verwendete serverseitige Verarbeitungssprache (z. B. PHP, .net usw.) ist nicht angegeben. Wenn es sich jedoch um PHP handelt, greifen Sie auf die erforderliche Anzahl (oder alle Datensätze) zu und verwenden Sie anstelle der Zufallsgenerierung in der Abfrage die Zufallsfunktion von PHP. Ich weiß nicht, ob .net eine äquivalente Funktion hat, aber wenn dies der Fall ist, verwenden Sie diese, wenn Sie .net verwenden

ORDER BY RAND () kann je nach Anzahl der Datensätze erhebliche Leistungseinbußen nach sich ziehen.


Ich erinnere mich nicht genau, wofür ich das damals verwendet habe, aber ich habe wahrscheinlich in C # gearbeitet, vielleicht auf einem Server oder vielleicht in einer Client-Anwendung, nicht sicher. C # hat nichts direkt Vergleichbares zu PHPs Shuffle Afaik, aber es könnte getan werden, indem Funktionen aus dem Random-Objekt innerhalb einer Select-Operation angewendet, das Ergebnis sortiert und dann die Top-Ten-Prozent genommen werden. Wir müssten jedoch die gesamte Tabelle von der Festplatte auf dem DB-Server lesen und über das Netzwerk übertragen, um 90% dieser Daten zu verwerfen. Die direkte Verarbeitung in der DB ist mit ziemlicher Sicherheit effizienter.
John M Gant

-2

Das funktioniert bei mir:

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

9
@ user537824, haben Sie das auf SQL Server versucht? RANDOM ist keine Funktion und LIMIT ist kein Schlüsselwort. Die SQL Server-Syntax für das, was Sie tun, wäre select top 10 percent from table_name order by rand(), aber das funktioniert auch nicht, da rand () für alle Zeilen den gleichen Wert zurückgibt.
John M Gant
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.