it-swarm.com.de

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

Ich habe eine Abfrage, bei der die resultierenden Datensätze zufällig sortiert werden sollen. Es wird ein Clustered-Index verwendet. Wenn ich also kein order by Es 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, Pseudozufall ist gut genug für meine Bedürfnisse.

29
goric

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

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

Dies ist eine alte Frage, aber meiner Meinung nach fehlt ein Aspekt der Diskussion - LEISTUNG. ORDER BY NewId() ist die allgemeine Antwort. Wenn jemand Lust hat, fügt er hinzu, dass Sie NewID() wirklich in CheckSum() einwickeln sollten, wissen Sie, für die Leistung!

Das Problem bei dieser Methode ist, dass Sie immer noch einen vollständigen Index-Scan und dann eine vollständige Sortierung der Daten erhalten. Wenn Sie mit einem ernsthaften Datenvolumen gearbeitet haben, kann dies schnell teuer werden. Schauen Sie sich diesen typischen Ausführungsplan an und beachten Sie, dass die Sortierung 96% Ihrer Zeit in Anspruch nimmt ...

enter image description here

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

  • Tabelle A - hat 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. Das Ausführen von Order By newid() in dieser Tabelle generiert 53.700 Lesevorgänge und dauert 16 Sekunden.

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

Treffen Sie TABLESAMPLE ()

In SQL 2005 wurde eine neue Funktion namens TABLESAMPLE erstellt. Ich habe nur gesehen ein Artikel diskutiert seine Verwendung ... 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 dem Tabellenbeispiel besteht darin, Ihnen ungefähr die gewünschte Teilmengengröße zu geben. SQL nummeriert jede Datenseite und wählt X Prozent dieser Seiten aus. Die tatsächliche Anzahl der zurückgegebenen Zeilen kann je nach den auf den ausgewählten Seiten vorhandenen Zeilen variieren.

Wie benutze ich es? Wählen Sie eine Teilmengengröße aus, die mehr als die Anzahl der benötigten Zeilen abdeckt, und fügen Sie dann ein Top() hinzu. Die Idee ist, dass Sie Ihre gigantische Tabelle kleiner erscheinen lassen können vor der teuren Sorte.

Persönlich habe ich es verwendet, um die Größe meiner Tabelle tatsächlich zu begrenzen. In dieser Millionen-Zeilentabelle, die top(20)...TABLESAMPLE(20 PERCENT) ausführt, sinkt die Abfrage auf 5600 Lesevorgänge in 1600 ms. Es gibt auch eine REPEATABLE() Option, mit der Sie einen "Seed" für die Seitenauswahl übergeben können. Dies sollte zu einer stabilen Probenauswahl führen.

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

16
EBarr

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, sodass ORDER BY Rand() keine Auswirkung hat (da Sie für jede Zeile das gleiche Ergebnis aus Rand () erhalten ).

Zum Beispiel:

SELECT display_name, Rand() FROM tr_person

gibt jeden Namen aus unserer Personentabelle und eine "Zufallszahl" zurück, die für jede Zeile gleich ist. Die Anzahl variiert jedes Mal, wenn Sie die Abfrage ausführen, ist jedoch jedes Mal für jede Zeile gleich.

Um zu zeigen, dass dies auch bei Rand() der Fall ist, der in einer ORDER BY - Klausel verwendet wird, versuche ich:

SELECT display_name FROM tr_person ORDER BY Rand(), display_name

Die Ergebnisse sind weiterhin nach dem Namen geordnet, der angibt, dass das frühere Sortierfeld (das voraussichtlich zufällig ist) keine Auswirkung hat und daher vermutlich immer den gleichen Wert hat.

Die Bestellung nach NEWID() funktioniert jedoch, denn wenn NEWID () nicht immer neu bewertet wurde, würde der Zweck von UUIDs beim Einfügen vieler verletzt neue Zeilen in einem Statemnt mit eindeutigen Bezeichnern als Schlüssel, also:

SELECT display_name FROM tr_person ORDER BY NEWID()

ordnet die Namen "zufällig".

Anderes DBMS

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

Ich habe auch gesehen, dass die Reihenfolge nach UUID-Werten in einigen Kontexten ignoriert wird, da die Datenbank davon ausgeht, dass der Typ keine sinnvolle Reihenfolge hat. Wenn dies der Fall ist, wandeln Sie die UUID explizit in einen Zeichenfolgentyp in der Bestellklausel um oder wickeln Sie eine andere Funktion wie CHECKSUM() in SQL Server um sie (es kann auch einen kleinen Leistungsunterschied geben Da die Bestellung für 32-Bit-Werte und nicht für 128-Bit-Werte erfolgt, überlasse ich es Ihnen, zu testen, ob der Nutzen davon die Kosten für die Ausführung von CHECKSUM() pro Wert zuerst überwiegt.

Randnotiz

Wenn Sie eine beliebige, aber etwas wiederholbare Reihenfolge wünschen, ordnen Sie die Daten in den Zeilen selbst nach einer relativ unkontrollierten Teilmenge. Zum Beispiel geben entweder oder diese die Namen in einer beliebigen, 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 Ordnungen sind in Anwendungen nicht oft nützlich. Sie können jedoch beim Testen hilfreich sein, wenn Sie Code für Ergebnisse in einer Vielzahl von Ordnungen testen möchten, aber in der Lage sein möchten, jeden Lauf mehrmals auf dieselbe Weise zu wiederholen (um ein durchschnittliches Timing zu erhalten Ergebnisse über mehrere Läufe oder das Testen, dass eine Korrektur, die Sie am Code vorgenommen haben, ein Problem oder eine Ineffizienz beseitigt, die zuvor durch eine bestimmte Eingabe-Ergebnismenge hervorgehoben wurden, oder nur zum Testen, ob Ihr Code "stabil" ist und jedes Mal das gleiche Ergebnis zurückgibt wenn die gleichen Daten in einer bestimmten Reihenfolge gesendet werden).

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 in der realen Welt wahrscheinlich nicht oft nützlich, könnte sich jedoch als nützlich erweisen, wenn eine Funktion etwas Zufälliges zurückgeben soll und "zufällig" gut genug ist (aber achten Sie darauf, die Regeln zu beachten, die dies bestimmen Wenn benutzerdefinierte Funktionen bewertet werden, dh normalerweise nur einmal pro Zeile, oder wenn Ihre Ergebnisse möglicherweise nicht Ihren Erwartungen entsprechen.

Leistung

Wie EBarr hervorhebt, kann es bei den oben genannten Punkten 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 Der Scan (oder schlimmer noch der Tabellenscan) erfolgt zusammen mit einem großen Schreibblock in Tempdb. Daher kann es wie bei den meisten Dingen von entscheidender Bedeutung sein, mit realistischen Daten zu vergleichen, bevor diese in der Produktion verwendet werden.

16
David Spillett

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

Auf diese Weise können wir den Bereich vorhandener Werte bestimmen und Zeilen mithilfe zufällig generierter ID-Werte 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, eine effiziente Operation aufgrund des Index:

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;

(Range query

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'));

(random rows query

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 'Arbeitstabelle'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Vorauslesevorgänge 0 
 Tabelle 'Arbeitsdatei'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Vorauslesevorgänge 0 
 
 SQL Server-Ausführungszeiten: 
 CPU-Zeit = 0 ms, verstrichene Zeit = 9 ms. 

Probieren Sie es im Stack Exchange Data Explorer aus.

3
Paul White 9

Wie ich in diesem Artikel erklärt habe, müssen Sie einen datenbankspezifischen Funktionsaufruf verwenden, um die SQL-Ergebnismenge zu mischen.

Beachten Sie, dass sich das Sortieren einer großen Ergebnismenge mithilfe einer RANDOM-Funktion als sehr langsam herausstellen 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 TABLESAMPLE in SQL Server anstelle einer Zufallsfunktion in der ORDER BY-Klausel zu verwenden.

Angenommen, wir haben die folgende Datenbanktabelle:

(enter image description here

Und die folgenden Zeilen in der Tabelle song:

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

Unter SQL Server müssen Sie die Funktion NEWID 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 Funktionsaufrufs NEWID, der von der ORDER BY-Klausel verwendet wird, in zufälliger Reihenfolge aufgelistet werden.

0
Vlad Mihalcea