Was ist der beste Weg, um eine zufällige Bestellung zu bekommen?


27

Ich habe eine Frage, wo ich die resultierenden Aufzeichnungen nach dem Zufallsprinzip bestellen möchte. Es wird ein Clustered-Index verwendet. Wenn ich also keinen einbeziehe order by, werden wahrscheinlich Datensätze in der Reihenfolge dieses Index zurückgegeben. Wie kann ich eine zufällige Zeilenreihenfolge sicherstellen?

Ich verstehe, dass es wahrscheinlich nicht "wirklich" zufällig sein wird, Pseudo-Zufall ist gut genug für meine Bedürfnisse.

Antworten:


19

ORDER BY NEWID () sortiert die Datensätze nach dem Zufallsprinzip. Ein Beispiel hier

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()

7
ORDER BY NEWID () ist effektiv zufällig, aber nicht statistisch zufällig. Es gibt einen kleinen Unterschied, und meistens spielt der Unterschied keine Rolle.
Mrdenny

4
Aus Sicht der Leistung ist dies recht langsam - Sie können eine signifikante Verbesserung durch ORDER BY CHECKSUM (NEWID ())
Miles D

1
@mrdenny - Worauf stützen Sie die "statistisch nicht zufälligen"? Die Antwort hier sagt, dass es CryptGenRandomam Ende verwendet wird. dba.stackexchange.com/a/208069/3690
Martin Smith

15

Pradeep Adigas erster Vorschlag ORDER BY NEWID()ist in Ordnung und etwas, das ich in der Vergangenheit aus diesem Grund verwendet habe.

Seien Sie vorsichtig bei der Verwendung von RAND()- In vielen Kontexten wird es nur einmal pro Anweisung ausgeführt, so ORDER BY RAND()dass es keine Auswirkungen hat (da Sie für jede Zeile dasselbe Ergebnis aus RAND () erhalten).

Zum Beispiel:

SELECT display_name, RAND() FROM tr_person

gibt jeden Namen aus unserer Personentabelle und eine "Zufalls" -Zahl zurück, die für jede Zeile gleich ist. Die Anzahl variiert bei jeder Ausführung der Abfrage, ist jedoch für jede Zeile gleich.

Um zu zeigen, dass dies RAND()auch in einer ORDER BYKlausel der Fall ist, versuche ich:

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

Die Ergebnisse werden weiterhin nach dem Namen sortiert, was darauf hinweist, dass das frühere Sortierfeld (das voraussichtlich zufällig ist) keine Auswirkung hat und daher vermutlich immer denselben Wert hat.

Die Sortierung nach NEWID()funktioniert jedoch, da, wenn NEWID () nicht immer neu bewertet wurde, der Zweck von UUIDs beim Einfügen vieler neuer Zeilen in eine Anweisung mit eindeutigen Bezeichnern als Schlüssel nicht funktioniert.

SELECT display_name FROM tr_person ORDER BY NEWID()

ordnet die Namen "zufällig".

Andere DBMS

Das obige gilt für MSSQL (mindestens 2005 und 2008, und wenn ich mich recht erinnere auch 2000). Eine Funktion, die eine neue UUID zurückgibt, sollte jedes Mal ausgewertet werden, wenn sich in allen DBMS NEWID () unter MSSQL befindet. Es lohnt sich jedoch, dies in der Dokumentation und / oder durch Ihre eigenen Tests zu überprüfen. Es ist wahrscheinlicher, dass das Verhalten anderer Funktionen mit beliebigen Ergebnissen wie RAND () zwischen DBMS variiert. Überprüfen Sie daher erneut die Dokumentation.

Außerdem habe ich gesehen, dass die Sortierung nach UUID-Werten in einigen Kontexten ignoriert wird, da die Datenbank davon ausgeht, dass der Typ keine aussagekräftige Sortierung aufweist. Wenn dies der Fall ist, wandeln Sie die UUID explizit in einen Zeichenfolgentyp in der ordering-Klausel um, oder schließen Sie eine andere Funktion wie CHECKSUM()in SQL Server ein (möglicherweise besteht auch hier ein geringer Leistungsunterschied, da die Bestellung am ausgeführt wird 32-Bit-Werte, keine 128-Bit-Werte, aber ob der Nutzen davon die Betriebskosten CHECKSUM()pro Wert überwiegt, überlasse ich Ihnen zu testen.

Randnotiz

Wenn Sie eine willkürliche, aber etwas wiederholbare Reihenfolge wünschen, ordnen Sie die Daten in den Zeilen selbst nach einer relativ unkontrollierten Teilmenge. Entweder oder diese geben die Namen in einer willkürlichen, aber wiederholbaren Reihenfolge zurück:

SELECT display_name FROM tr_person ORDER BY CHECKSUM(display_name), display_name -- order by the checksum of some of the row's data
SELECT display_name FROM tr_person ORDER BY SUBSTRING(display_name, LEN(display_name)/2, 128) -- order by part of the name field, but not in any an obviously recognisable order)

Beliebige, aber wiederholbare Anordnungen sind in Anwendungen nicht oft nützlich. Sie können jedoch beim Testen hilfreich sein, wenn Sie einen Code auf Ergebnisse in einer Vielzahl von Anordnungen testen möchten, aber jeden Durchlauf mehrmals auf die gleiche Weise wiederholen möchten (um das durchschnittliche Timing zu erhalten) Ergebnisse über mehrere Läufe oder das Testen, dass Sie den Code korrigiert haben, behebt ein Problem oder eine Ineffizienz, die zuvor von einer bestimmten Eingabe-Ergebnismenge hervorgehoben wurden, oder dient nur zum Testen, dass Ihr Code "stabil" ist und jedes Mal dasselbe Ergebnis zurückgibt wenn die gleichen Daten in einer bestimmten Reihenfolge gesendet).

Dieser Trick kann auch verwendet werden, um willkürlichere Ergebnisse von Funktionen zu erhalten, die keine nicht deterministischen Aufrufe wie NEWID () in ihrem Körper zulassen. Auch dies ist wahrscheinlich nicht sehr nützlich in der realen Welt, kann sich aber als nützlich erweisen, wenn Sie möchten, dass eine Funktion etwas Zufälliges zurückgibt und "random-ish" gut genug ist (achten Sie jedoch darauf, die Regeln zu beachten, die dies bestimmen wenn benutzerdefinierte Funktionen ausgewertet werden, dh normalerweise nur einmal pro Zeile, oder Ihre Ergebnisse möglicherweise nicht den Erwartungen / Anforderungen entsprechen.

Performance

Wie EBarr hervorhebt, kann es bei den oben genannten Problemen zu Leistungsproblemen kommen. Bei mehr als ein paar Zeilen ist es fast garantiert, dass die Ausgabe auf Tempdb gespoolt wird, bevor die angeforderte Anzahl von Zeilen in der richtigen Reihenfolge zurückgelesen wird. Dies bedeutet, dass Sie möglicherweise einen vollständigen Index finden, selbst wenn Sie nach den Top 10 suchen scan (oder schlimmer noch, table scan) passiert zusammen mit einem riesigen Block, in den tempdb geschrieben wird. Daher kann es wie bei den meisten Dingen von entscheidender Bedeutung sein, ein Benchmarking mit realistischen Daten durchzuführen, bevor diese in der Produktion verwendet werden.


14

Dies ist eine alte Frage, aber meiner Meinung nach fehlt ein Aspekt der Diskussion - LEISTUNG. ORDER BY NewId()ist die allgemeine Antwort. Wenn Phantasie jemand bekommen sie hinzufügen , dass Sie wirklich einpacken sollten NewID()in CheckSum(), wissen Sie, für die Leistung!

Das Problem bei dieser Methode ist, dass Ihnen immer noch ein vollständiger Index-Scan und dann eine vollständige Sortierung der Daten garantiert ist. Wenn Sie mit ernsthaften Datenmengen gearbeitet haben, kann dies schnell teuer werden. Sehen Sie sich diesen typischen Ausführungsplan an und stellen Sie fest, wie die Sortierung 96% Ihrer Zeit in Anspruch nimmt ...

Bildbeschreibung hier eingeben

Um Ihnen einen Eindruck davon zu geben, wie sich das skaliert, gebe ich Ihnen zwei Beispiele aus einer Datenbank, mit der ich arbeite.

  • Tabelle A - enthält 50.000 Zeilen auf 2500 Datenseiten. Die zufällige Abfrage generiert 145 Lesevorgänge in 42 ms.
  • Tabelle B - enthält 1,2 Millionen Zeilen auf 114.000 Datenseiten. Die Order By newid()Ausführung in dieser Tabelle generiert 53.700 Lesevorgänge und dauert 16 Sekunden.

Die Moral der Geschichte ist, dass, wenn Sie große Tabellen haben (denken Sie an Milliarden von Zeilen) oder diese Abfrage häufig ausführen müssen, die newid()Methode zusammenbricht. Also, was soll ein Junge tun?

Treffen Sie TABLESAMPLE ()

In SQL 2005 wurde eine neue Funktion namens TABLESAMPLEerstellt. Ich habe nur einen Artikel über seine Verwendung gesehen ... es sollte mehr geben. MSDN Docs hier . Zuerst ein Beispiel:

SELECT Top (20) *
FROM Northwind..Orders TABLESAMPLE(20 PERCENT)
ORDER BY NEWID()

Die Idee hinter der Tabelle sample ist, Sie ungefähr zu geben die Teilmengengröße anzugeben, nach der Sie fragen. SQL nummeriert jede Datenseite und wählt X Prozent dieser Seiten aus. Die tatsächliche Anzahl der Zeilen, die Sie zurückerhalten, hängt davon ab, was auf den ausgewählten Seiten vorhanden ist.

Wie verwende ich es? Wählen Sie eine Teilmenge aus, die mehr als die Anzahl der benötigten Zeilen abdeckt, und fügen Sie a hinzu Top(). Die Idee ist, dass Sie Ihren ginormösen Tisch vor der teuren Sortierung verkleinern können.

Persönlich habe ich es benutzt, um die Größe meines Tisches zu begrenzen. In dieser Million-Zeilen-Tabelle top(20)...TABLESAMPLE(20 PERCENT)sinkt die Abfrage in 1600 ms auf 5600 Lesevorgänge. Es gibt auch eine REPEATABLE()Option, bei der Sie einen "Seed" für die Seitenauswahl übergeben können. Dies sollte zu einer stabilen Probenauswahl führen.

Jedenfalls dachte ich nur, dass dies zur Diskussion hinzugefügt werden sollte. Hoffe es hilft jemandem.


Es wäre schön, eine skalierbare Abfrage in zufälliger Reihenfolge schreiben zu können, die nicht nur skaliert, sondern auch mit kleinen Datenmengen funktioniert. Es hört sich so an, als müssten Sie manuell zwischen "Haben" und "Nicht Haben" wechseln, je nachdem TABLESAMPLE(), wie viele Daten Sie haben. Ich denke nicht, dass TABLESAMPLE(x ROWS)dies sogar sicherstellen würde, dass mindestens x Zeilen zurückgegeben werden, da in der Dokumentation Folgendes steht: „Die tatsächliche Anzahl der zurückgegebenen Zeilen kann erheblich variieren. Wenn Sie eine kleine Zahl wie 5 angeben, erhalten Sie möglicherweise keine Ergebnisse in der Stichprobe. “- Ist die ROWSSyntax also wirklich immer noch nur eine maskierte PERCENTInnenseite?
Binki

Sicher, Automagie ist nett. In der Praxis habe ich selten eine 5-Zeilen-Tabelle gesehen, die ohne Vorankündigung auf Millionen von Zeilen skaliert wurde. TABLESAMPLE () basiert anscheinend auf der Auswahl der Anzahl der Seiten in einer Tabelle, sodass die angegebene Zeilengröße Einfluss darauf hat, was zurückkommt. Der Sinn des Tabellenbeispiels ist, zumindest so wie ich es sehe, Ihnen eine gute Untergruppe zu geben, aus der Sie auswählen können - eine Art abgeleitete Tabelle.
EBarr

3

Viele Tabellen haben eine relativ dichte (wenige fehlende Werte) indizierte numerische ID-Spalte.

Auf diese Weise können wir den Bereich der vorhandenen Werte bestimmen und Zeilen mit zufällig generierten ID-Werten in diesem Bereich auswählen. Dies funktioniert am besten, wenn die Anzahl der zurückzugebenden Zeilen relativ gering ist und der Bereich der ID-Werte dicht gefüllt ist (die Wahrscheinlichkeit, einen fehlenden Wert zu generieren, ist also gering genug).

Zur Veranschaulichung wählt der folgende Code 100 verschiedene zufällige Benutzer aus der Stapelüberlauf-Benutzertabelle mit 8.123.937 Zeilen aus.

Der erste Schritt besteht darin, den Bereich der ID-Werte zu bestimmen. Dies ist aufgrund des Index eine effiziente Operation:

DECLARE 
    @MinID integer,
    @Range integer,
    @Rows bigint = 100;

--- Find the range of values
SELECT
    @MinID = MIN(U.Id),
    @Range = 1 + MAX(U.Id) - MIN(U.Id)
FROM dbo.Users AS U;

Bereichsabfrage

Der Plan liest eine Zeile von jedem Ende des Index.

Jetzt generieren wir 100 verschiedene zufällige IDs im Bereich (mit übereinstimmenden Zeilen in der Benutzertabelle) und geben diese Zeilen zurück:

WITH Random (ID) AS
(
    -- Find @Rows distinct random user IDs that exist
    SELECT DISTINCT TOP (@Rows)
        Random.ID
    FROM dbo.Users AS U
    CROSS APPLY
    (
        -- Random ID
        VALUES (@MinID + (CONVERT(integer, CRYPT_GEN_RANDOM(4)) % @Range))
    ) AS Random (ID)
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.Users AS U2
            -- Ensure the row continues to exist
            WITH (REPEATABLEREAD)
        WHERE U2.Id = Random.ID
    )
)
SELECT
    U3.Id,
    U3.DisplayName,
    U3.CreationDate
FROM Random AS R
JOIN dbo.Users AS U3
    ON U3.Id = R.ID
-- QO model hint required to get a non-blocking flow distinct
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

zufällige Zeilenabfrage

Der Plan zeigt, dass in diesem Fall 601 Zufallszahlen benötigt wurden, um 100 übereinstimmende Zeilen zu finden. Es ist ziemlich schnell:

Tabelle 'Benutzer'. Scananzahl 1, logische Lesevorgänge 1937, physische Lesevorgänge 2, Vorauslesevorgänge 408
Tabelle 'Arbeitstisch'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Vorauslesevorgänge 0
Tabelle 'Workfile'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Vorauslesevorgänge 0

 SQL Server-Ausführungszeiten:
   CPU-Zeit = 0 ms, abgelaufene Zeit = 9 ms.

Probieren Sie es im Stack Exchange Data Explorer aus.


0

Wie in diesem Artikel erläutert , müssen Sie einen datenbankspezifischen Funktionsaufruf verwenden, um die SQL-Ergebnismenge zu mischen.

Beachten Sie, dass das Sortieren einer großen Ergebnismenge mit einer RANDOM-Funktion sehr langsam sein kann. Stellen Sie daher sicher, dass Sie dies bei kleinen Ergebnismengen tun.

Wenn Sie eine große Ergebnismenge mischen und anschließend einschränken müssen, ist es besser, den SQL Server TABLESAMPLEin SQL Server anstelle einer Zufallsfunktion in der ORDER BY-Klausel zu verwenden.

Angenommen, wir haben die folgende Datenbanktabelle:

Bildbeschreibung hier eingeben

Und die folgenden Zeilen in der songTabelle:

| id | artist                          | title                              |
|----|---------------------------------|------------------------------------|
| 1  | Miyagi & Эндшпиль ft. Рем Дигга | I Got Love                         |
| 2  | HAIM                            | Don't Save Me (Cyril Hahn Remix)   |
| 3  | 2Pac ft. DMX                    | Rise Of A Champion (GalilHD Remix) |
| 4  | Ed Sheeran & Passenger          | No Diggity (Kygo Remix)            |
| 5  | JP Cooper ft. Mali-Koa          | All This Love                      |

In SQL Server müssen Sie die NEWIDFunktion verwenden, wie im folgenden Beispiel dargestellt:

SELECT
    CONCAT(CONCAT(artist, ' - '), title) AS song
FROM song
ORDER BY NEWID()

Wenn Sie die oben genannte SQL-Abfrage unter SQL Server ausführen, erhalten Sie die folgende Ergebnismenge:

| song                                              |
|---------------------------------------------------|
| Miyagi & Эндшпиль ft. Рем Дигга - I Got Love      |
| JP Cooper ft. Mali-Koa - All This Love            |
| HAIM - Don't Save Me (Cyril Hahn Remix)           |
| Ed Sheeran & Passenger - No Diggity (Kygo Remix)  |
| 2Pac ft. DMX - Rise Of A Champion (GalilHD Remix) |

Beachten Sie, dass die Songs dank des NEWIDvon der ORDER BY-Klausel verwendeten Funktionsaufrufs in zufälliger Reihenfolge aufgelistet werden .

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.