Qual è il modo migliore per ottenere un ordine casuale?


27

Ho una query in cui desidero ordinare i record risultanti in modo casuale. Utilizza un indice cluster, quindi se non includo un order byprobabilmente restituirà i record nell'ordine di quell'indice. Come posso garantire un ordine di riga casuale?

Capisco che probabilmente non sarà "veramente" casuale, lo pseudo-casuale è abbastanza buono per le mie esigenze.

Risposte:


19

ORDER BY NEWID () ordina i record in modo casuale. Un esempio qui

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

7
ORDER BY NEWID () è effettivamente casuale, ma non statisticamente casuale. C'è una piccola differenza, e la maggior parte delle volte non importa.
mrdenny,

4
Dal punto di vista delle prestazioni, questo è piuttosto lento: puoi ottenere un miglioramento significativo ORDER BY CHECKSUM (NEWID ())
Miglia D

1
@mrdenny - Su cosa basi il "non statisticamente casuale"? La risposta qui dice che finisce per usare CryptGenRandomalla fine. dba.stackexchange.com/a/208069/3690
Martin Smith,

15

Il primo suggerimento di Pradeep Adiga ORDER BY NEWID(), va bene e qualcosa che ho usato in passato per questo motivo.

Fai attenzione all'uso RAND(): in molti contesti viene eseguito solo una volta per istruzione, quindi ORDER BY RAND()non avrà alcun effetto (poiché ottieni lo stesso risultato da RAND () per ogni riga).

Per esempio:

SELECT display_name, RAND() FROM tr_person

restituisce ogni nome dalla nostra tabella personale e un numero "casuale", che è lo stesso per ogni riga. Il numero varia ogni volta che si esegue la query, ma è lo stesso per ogni riga ogni volta.

Per dimostrare che lo stesso è il caso di RAND()usato in una ORDER BYclausola, provo:

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

I risultati sono ancora ordinati per nome indicando che il campo di ordinamento precedente (quello che si prevede sia casuale) non ha alcun effetto, quindi presumibilmente ha sempre lo stesso valore.

L'ordinamento per NEWID()funziona, tuttavia, perché se NEWID () non fosse sempre rivalutato, lo scopo degli UUID verrebbe interrotto quando si inserivano molte nuove righe in uno stato con identificatori univoci come chiave, quindi:

SELECT display_name FROM tr_person ORDER BY NEWID()

non ordinare i nomi "a caso".

Altro DBMS

Quanto sopra vale per MSSQL (almeno nel 2005 e nel 2008, e se ricordo bene anche il 2000). Una funzione che restituisce un nuovo UUID dovrebbe essere valutata ogni volta in tutti i DBMS NEWID () è sotto MSSQL ma vale la pena verificarlo nella documentazione e / o dai propri test. Il comportamento di altre funzioni con risultati arbitrari, come RAND (), ha maggiori probabilità di variare tra DBMS, quindi controlla di nuovo la documentazione.

Inoltre ho visto l'ordinamento in base a valori UUID ignorati in alcuni contesti poiché il DB presume che il tipo non abbia un ordinamento significativo. Se trovi che questo è il caso esplicito cast dell'UUID in un tipo di stringa nella clausola ordering o avvolgi alcune altre funzioni attorno ad esso come CHECKSUM()in SQL Server (potrebbe esserci anche una piccola differenza di prestazioni da questo poiché l'ordinamento verrà eseguito su valori a 32 bit e non a 128 bit, anche se il vantaggio di questo supera il costo di esecuzione CHECKSUM()per valore prima ti lascio testare).

Nota a margine

Se si desidera un ordinamento arbitrario ma in qualche modo ripetibile, ordinare da un sottoinsieme relativamente incontrollato dei dati nelle righe stesse. Ad esempio, uno di questi o questi restituiranno i nomi in un ordine arbitrario ma ripetibile:

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)

Gli ordini arbitrari ma ripetibili non sono spesso utili nelle applicazioni, anche se possono essere utili nel test se si desidera testare un po 'di codice sui risultati in una varietà di ordini ma si desidera poter ripetere ogni esecuzione allo stesso modo più volte (per ottenere tempi medi) risultati su più esecuzioni o test che una correzione apportata al codice rimuove un problema o un'inefficienza precedentemente evidenziata da un determinato set di risultati di input o solo per verificare che il codice sia "stabile" in quanto restituisce lo stesso risultato ogni volta se inviato gli stessi dati in un determinato ordine).

Questo trucco può anche essere usato per ottenere risultati più arbitrari da funzioni, che non consentono chiamate non deterministiche come NEWID () all'interno del loro corpo. Ancora una volta, questo non è qualcosa che probabilmente sarà utile nel mondo reale ma potrebbe tornare utile se vuoi che una funzione restituisca qualcosa di casuale e "random-ish" sia abbastanza buono (ma fai attenzione a ricordare le regole che determinano quando le funzioni definite dall'utente vengono valutate, cioè di solito solo una volta per riga, oppure i risultati potrebbero non essere quelli previsti / richiesti).

Prestazione

Come sottolinea EBarr, possono esserci problemi di prestazioni con uno qualsiasi dei precedenti. Per più di alcune righe sei quasi garantito di vedere lo spooling dell'output su tempdb prima che il numero richiesto di righe venga letto nel giusto ordine, il che significa che anche se stai cercando la top 10 potresti trovare un indice completo scan (o peggio, table scan) avviene insieme a un enorme blocco di scrittura su tempdb. Pertanto, può essere di vitale importanza, come nella maggior parte delle cose, fare un benchmark con dati realistici prima di utilizzarli in produzione.


14

Questa è una vecchia domanda, ma a mio avviso manca un aspetto della discussione: PERFORMANCE. ORDER BY NewId()è la risposta generale. Quando fantasia di qualcun get aggiungono che si dovrebbe davvero avvolgere NewID()in CheckSum(), si sa, per le prestazioni!

Il problema con questo metodo è che ti viene comunque garantita una scansione completa dell'indice e quindi un tipo completo di dati. Se hai lavorato con qualsiasi volume di dati serio, questo può rapidamente diventare costoso. Guarda questo tipico piano di esecuzione e nota come l'ordinamento richiede il 96% del tuo tempo ...

inserisci qui la descrizione dell'immagine

Per darti un'idea di come questa scala, ti darò due esempi da un database con cui lavoro.

  • Tabella A: dispone di 50.000 righe su 2500 pagine di dati. La query casuale genera 145 letture in 42ms.
  • Tabella B: ha 1,2 milioni di righe su 114.000 pagine di dati. L'esecuzione Order By newid()su questa tabella genera 53.700 letture e richiede 16 secondi.

La morale della storia è che se hai tabelle di grandi dimensioni (pensa a miliardi di righe) o devi eseguire questa query frequentemente il newid()metodo si rompe. Quindi cosa deve fare un ragazzo?

Scopri TABLESAMPLE ()

In SQL 2005 è TABLESAMPLEstata creata una nuova funzionalità chiamata . Ho visto solo un articolo che ne parla dell'uso ... dovrebbe essercene di più. Documenti MSDN qui . Innanzitutto un esempio:

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

L'idea alla base della tabella di esempio è quella di fornire approssimativamente la dimensione del sottoinsieme richiesta. SQL numera ogni pagina di dati e seleziona l'X percento di tali pagine. Il numero effettivo di righe che ottieni può variare in base a ciò che esiste nelle pagine selezionate.

Quindi come lo uso? Seleziona una dimensione del sottoinsieme che copre più del numero di righe necessarie, quindi aggiungi a Top(). L'idea è che puoi rendere la tua tabella enorme più piccola prima dell'ordinamento costoso.

Personalmente lo sto usando per limitare effettivamente le dimensioni del mio tavolo. Quindi su quel milione di righe che eseguono top(20)...TABLESAMPLE(20 PERCENT)la query scende a 5600 letture in 1600ms. C'è anche REPEATABLE()un'opzione in cui puoi passare un "Seme" per la selezione della pagina. Ciò dovrebbe comportare una selezione del campione stabile.

Comunque, ho pensato che questo dovrebbe essere aggiunto alla discussione. Spero che aiuti qualcuno.


Sarebbe bello poter scrivere una query di ordinamento casuale scalabile che non solo si ingrandisce ma funziona con piccoli set di dati. Sembra che tu debba passare manualmente tra avere e non avere in TABLESAMPLE()base alla quantità di dati che hai. Non penso che TABLESAMPLE(x ROWS)ciò assicurerebbe nemmeno che vengano restituite almeno le x righe perché la documentazione dice che "Il numero effettivo di righe restituite può variare in modo significativo. Se specifichi un numero piccolo, come 5, potresti non ricevere risultati nel campione. ”- Quindi la ROWSsintassi è ancora solo un mascherato PERCENTall'interno?
binki,

Certo, l'auto-magia è bella. In pratica, raramente ho visto una tabella a 5 righe scalare a milioni di righe senza preavviso. TABLESAMPLE () sembra basare la selezione del numero di pagine in una tabella, quindi la dimensione della riga data influenza ciò che ritorna. Il punto di esempio della tabella, almeno per come la vedo io, è darti un buon sottoinsieme da cui puoi selezionare - un po 'come una tabella derivata.
EBarr,

3

Molte tabelle hanno una colonna ID numerica indicizzata relativamente densa (pochi valori mancanti).

Questo ci consente di determinare l'intervallo di valori esistenti e di scegliere le righe utilizzando valori ID generati casualmente in quell'intervallo. Funziona meglio quando il numero di righe da restituire è relativamente piccolo e l'intervallo di valori ID è densamente popolato (quindi la possibilità di generare un valore mancante è abbastanza piccola).

Per illustrare, il codice seguente seleziona 100 utenti casuali distinti dalla tabella degli utenti Stack Overflow, che ha 8.123.937 righe.

Il primo passo è determinare l'intervallo di valori ID, un'operazione efficiente grazie all'indice:

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;

Intervallo di query

Il piano legge una riga da ciascuna estremità dell'indice.

Ora generiamo 100 ID casuali distinti nell'intervallo (con righe corrispondenti nella tabella degli utenti) e restituiamo quelle righe:

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

query a righe casuali

Il piano mostra che in questo caso erano necessari 601 numeri casuali per trovare 100 righe corrispondenti. È abbastanza veloce:

Tabella "Utenti". Conteggio scansioni 1, letture logiche 1937, letture fisiche 2, letture avanti 408
Tabella "Tavolo da lavoro". Conteggio scansioni 0, letture logiche 0, letture fisiche 0, letture avanti legge 0
Tabella "File di lavoro". Conteggio scansioni 0, letture logiche 0, letture fisiche 0, letture avanti legge 0

 Tempi di esecuzione di SQL Server:
   Tempo CPU = 0 ms, tempo trascorso = 9 ms.

Provalo su Stack Exchange Data Explorer.


0

Come ho spiegato in questo articolo , per mescolare il set di risultati SQL, è necessario utilizzare una chiamata di funzione specifica del database.

Notare che l'ordinamento di un set di risultati di grandi dimensioni utilizzando una funzione RANDOM potrebbe rivelarsi molto lento, quindi assicurarsi di farlo su set di risultati di piccole dimensioni.

Se devi mescolare un set di risultati di grandi dimensioni e limitarlo in seguito, è preferibile utilizzare SQL Server TABLESAMPLEin SQL Server anziché una funzione casuale nella clausola ORDER BY.

Quindi, supponendo che abbiamo la seguente tabella di database:

inserisci qui la descrizione dell'immagine

E le seguenti righe nella songtabella:

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

Su SQL Server, è necessario utilizzare la NEWIDfunzione, come illustrato dal seguente esempio:

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

Quando eseguiamo la suddetta query SQL su SQL Server, otteniamo il seguente set di risultati:

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

Si noti che i brani vengono elencati in ordine casuale, grazie alla NEWIDchiamata di funzione utilizzata dalla clausola ORDER BY.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.