Zufälliger Datensatz aus einer Datenbanktabelle (T-SQL)

83

Gibt es eine prägnante Möglichkeit, einen zufälligen Datensatz aus einer SQL Server-Tabelle abzurufen?

Ich möchte meine Unit-Test-Daten randomisieren und suche daher nach einer einfachen Möglichkeit, eine zufällige ID aus einer Tabelle auszuwählen. Auf Englisch lautet die Auswahl "Wählen Sie eine ID aus der Tabelle aus, wobei die ID eine Zufallszahl zwischen der niedrigsten ID in der Tabelle und der höchsten ID in der Tabelle ist."

Ich kann keinen Weg finden, dies zu tun, ohne die Abfrage ausführen, auf einen Nullwert testen und dann erneut ausführen zu müssen, wenn Null.

Ideen?

Jeremy
quelle
Es gibt ein paar Methoden hier brettb.com/SQL_Help_Random_Numbers.asp
Mesh
2
Sind Sie sicher, dass Sie diesen Ansatz verfolgen möchten? Unit-Test-Daten sollten nicht zufällig sein - tatsächlich sollten Sie garantiert die gleichen Ergebnisse erhalten, unabhängig davon, wie oft Sie den Unit-Test ausführen. Zufällige Daten können gegen dieses Grundprinzip des Unit-Tests verstoßen.
Rein
Der obige Link von @Mesh ist nicht mehr aktiv.
Robert Sievers

Antworten:

143

Gibt es eine prägnante Möglichkeit, einen zufälligen Datensatz aus einer SQL Server-Tabelle abzurufen?

Ja

SELECT TOP 1 * FROM table ORDER BY NEWID()

Erläuterung

NEWID()Für jede Zeile wird A generiert und die Tabelle danach sortiert. Der erste Datensatz wird zurückgegeben (dh der Datensatz mit der "niedrigsten" GUID).

Anmerkungen

  1. GUIDs werden seit Version vier als Pseudozufallszahlen generiert:

    Die UUID der Version 4 dient zum Generieren von UUIDs aus wirklich zufälligen oder pseudozufälligen Zahlen.

    Der Algorithmus ist wie folgt:

    • Setzen Sie die beiden höchstwertigen Bits (Bits 6 und 7) von clock_seq_hi_and_reserved auf Null bzw. Eins.
    • Setzen Sie die vier höchstwertigen Bits (Bits 12 bis 15) des Felds time_hi_and_version auf die 4-Bit-Versionsnummer aus Abschnitt 4.1.3.
    • Setzen Sie alle anderen Bits auf zufällig (oder pseudozufällig) ausgewählte Werte.

    - Ein UUID-URN-Namespace (Universally Unique IDentifier) ​​- RFC 4122

  2. Die Alternative SELECT TOP 1 * FROM table ORDER BY RAND()wird nicht so funktionieren, wie man denkt. RAND()Gibt einen einzelnen Wert pro Abfrage zurück, sodass alle Zeilen denselben Wert haben.

  3. Während GUID-Werte pseudozufällig sind, benötigen Sie für die anspruchsvolleren Anwendungen ein besseres PRNG.

  4. Die typische Leistung für weniger als 1.000.000 Zeilen beträgt weniger als 10 Sekunden - natürlich je nach System. Beachten Sie, dass es unmöglich ist, einen Index zu erreichen, sodass die Performance relativ begrenzt ist.

Sklivvz
quelle
Genau das, wonach ich gesucht habe. Ich hatte das Gefühl, es sei einfacher als ich es mache.
Jeremy
1
Sie gehen davon aus, dass NEWID Pseudozufallswerte erzeugt. Es besteht eine gute Chance, dass sequentielle Werte erzeugt werden. NEWID erzeugt nur eindeutige Werte. RAND erzeugt jedoch Pseudozufallswerte.
Skizz
Ich führe es in einer stark indizierten Tabelle mit 1.671.145 Zeilen aus und es dauert 7 Sekunden, bis es zurückkommt. Die Tabelle ist auch ziemlich optimal - sie ist praktisch das Herzstück unserer Datenbank, also ist sie erledigt.
Tom Ritter
@ ViewAnew. 1,6 Millionen Zeilen und 7 Sekunden bei einer Auswahl, die keinen Index trifft (und nicht treffen kann), sind nicht schlecht.
Sklivvz
7
@ Skizz, Rand funktioniert so nicht. Vor SELECT wird ein SINGLE-Zufallswert generiert. Wenn Sie also "SELECT TOP 10 RAND () ..." versuchen, erhalten Sie immer den gleichen Wert
Sklivvz
27

Bei größeren Tabellen können Sie dies auch verwenden TABLESAMPLE, um das Scannen der gesamten Tabelle zu vermeiden.

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

Dies ORDER BY NEWIDist weiterhin erforderlich, um zu vermeiden, dass nur Zeilen zurückgegeben werden, die zuerst auf der Datenseite angezeigt werden.

Die zu verwendende Nummer muss für die Größe und Definition der Tabelle sorgfältig ausgewählt werden. Wenn keine Zeile zurückgegeben wird, können Sie eine Wiederholungslogik in Betracht ziehen. Die Mathematik dahinter und warum die Technik nicht für kleine Tabellen geeignet ist , wird hier diskutiert

Martin Smith
quelle
Ich habe dies auf der Microsoft-Website gefunden: Mit TABLESAMPLE können Sie schnell eine Stichprobe aus einer großen Tabelle zurückgeben, wenn eine der folgenden Bedingungen erfüllt ist: Die Stichprobe muss keine wirklich zufällige Stichprobe auf der Ebene einzelner Zeilen sein. Zeilen auf einzelnen Seiten der Tabelle sind nicht mit anderen Zeilen auf derselben Seite korreliert.
Mark Entingh
1
@MarkEntingh - Im Falle spielt TOP 1es keine Rolle, ob Zeilen auf derselben Seite korreliert sind oder nicht. Sie wählen nur einen von ihnen aus.
Martin Smith
9

Versuchen Sie auch Ihre Methode, um eine zufällige ID zwischen MIN (Id) und MAX (Id) zu erhalten

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

Sie erhalten immer eine Zeile.

Sklivvz
quelle
2
-1, Dies würde nur funktionieren, wenn zwischen min und max keine IDs fehlen. Wenn eine gelöscht wird, wird dieselbe ID von der Zufallsfunktion generiert, und Sie erhalten keine Datensätze zurück.
Neil N
6
@Neil, nicht wirklich - Sie erhalten die erste Zeile mit einer ID, die größer als die Zufallszahl ist, wenn IDs fehlen. Das Problem hierbei ist, dass die Wahrscheinlichkeit, dass jede Zeile herauskommt, nicht konstant ist. Andererseits reicht dies in den meisten Fällen aus.
Sklivvz
1
+1. Für Unit-Tests, die unterschiedliche Werte erreichen sollten, ist das gut genug - wenn Sie einen echten Zufall benötigen, ist dies etwas anderes. Aber im OP-Kontext sollte es gut genug sein.
TomTom
7

Wenn Sie große Datenmengen auswählen möchten, ist der beste Weg, den ich kenne, Folgendes:

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

Quelle: MSDN

hmfarimani
quelle
Ich bin mir nicht sicher, aber ich denke, dass die Verwendung von RAND () anstelle von NEWID () zur Erzeugung wirklich zufälliger Zahlen aufgrund der Nachteile der Verwendung von NEWID () im Auswahlprozess möglicherweise besser ist.
QMaster
Ich versuche, diese Methode mit der exakten Anzahl von Datensätzen zu verwenden, eher mit der prozentualen Basis. Ich habe sie mit erweitertem Auswahlbereich und Begrenzung mit TOP n durchgeführt. Gibt es einen Vorschlag?
QMaster
Ich habe ein weiteres Problem mit diesem Szenario gefunden. Wenn Sie group by verwenden, erhalten Sie immer die gleiche Reihenfolge zufällig ausgewählter Zeilen. In kleinen Tabellen scheint der @ skilvvz-Ansatz also am besten geeignet zu sein.
QMaster
0

Ich wollte die Methoden verbessern, die ich ausprobiert hatte, und bin auf diesen Beitrag gestoßen. Mir ist klar, dass es alt ist, aber diese Methode ist nicht aufgeführt. Ich erstelle und wende Testdaten an. Dies zeigt die Methode für "Adresse" in einem SP, der mit @st aufgerufen wird (Zwei-Zeichen-Status).

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
quelle
0

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. Die folgende Abfrage verwendet beispielsweise die NEWID-Funktion, 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. "

Quelle: http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx

Dies wird weiter unten erklärt:

Wie funktioniert das? Teilen wir die WHERE-Klausel auf und erklären sie.

Die CHECKSUM-Funktion berechnet eine Prüfsumme für die Elemente in der Liste. Es ist fraglich, ob SalesOrderID überhaupt erforderlich ist, da NEWID () eine Funktion ist, die eine neue zufällige GUID zurückgibt. Daher sollte das Multiplizieren einer Zufallszahl mit einer Konstanten in jedem Fall zu einem Zufall führen. In der Tat scheint das Ausschließen von SalesOrderID keinen Unterschied zu machen. Wenn Sie ein begeisterter Statistiker sind und die Aufnahme rechtfertigen können, verwenden Sie bitte den Kommentarbereich unten und lassen Sie mich wissen, warum ich falsch liege!

Die CHECKSUM-Funktion gibt ein VARBINARY zurück. Das Ausführen einer bitweisen UND-Operation mit 0x7fffffff, was (111111111 ...) in Binärform entspricht, ergibt einen Dezimalwert, der effektiv eine Darstellung einer zufälligen Zeichenfolge von 0s und 1s ist. Durch Teilen durch den Koeffizienten 0x7fffffff wird diese Dezimalzahl effektiv auf eine Zahl zwischen 0 und 1 normalisiert. Um dann zu entscheiden, ob jede Zeile in die endgültige Ergebnismenge aufgenommen werden sollte, wird ein Schwellenwert von 1 / x (in diesem Fall 0,01) verwendet, wobei x ist der Prozentsatz der Daten, die als Beispiel abgerufen werden sollen.

Quelle: https://www.mssqltips.com/sqlservertip/3157/different-ways-to-get-random-data-for-sql-server-data-sampling

XpiritO
quelle