Seleziona n righe casuali dalla tabella di SQL Server


309

Ho una tabella di SQL Server con circa 50.000 righe. Voglio selezionare casualmente circa 5.000 di quelle righe. Ho pensato a un modo complicato, creando una tabella temporanea con una colonna "numero casuale", copiando la mia tabella in quella, eseguendo il ciclo attraverso la tabella temporanea e aggiornando ogni riga con RAND(), quindi selezionando da quella tabella in cui la colonna di numeri casuali < 0.1. Sto cercando un modo più semplice per farlo, in una singola dichiarazione, se possibile.

Questo articolo suggerisce di utilizzare la NEWID()funzione. Sembra promettente, ma non riesco a vedere come potrei selezionare in modo affidabile una certa percentuale di righe.

Qualcuno l'ha mai fatto prima? Qualche idea?


3
MSDN ha un buon articolo che tratta molti di questi problemi: Selezione casuale delle righe da una tabella di grandi dimensioni
KyleMit,

Risposte:


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

In risposta al commento "puro cestino" riguardante le tabelle di grandi dimensioni: è possibile farlo in questo modo per migliorare le prestazioni.

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

Il costo di questo sarà la scansione chiave dei valori più il costo di join, che su una tabella di grandi dimensioni con una piccola percentuale di selezione dovrebbe essere ragionevole.


1
Mi piace molto meglio questo approccio rispetto all'uso dell'articolo a cui ha fatto riferimento.
JoshBerke,

14
È sempre bene tenere presente che newid () non è davvero un buon generatore di numeri pseudocasuali, almeno non altrettanto buono di rand (). Ma se hai solo bisogno di alcuni campioni vagamente casuali e non ti importa delle qualità matematiche e simili, sarà abbastanza buono. In caso contrario, è necessario: stackoverflow.com/questions/249301/...
user12861

1
Ehm, scusa se questo è ovvio .. ma a cosa si [yourPk]riferisce? EDIT: Nvm, capito ... Chiave primaria. Durrr
Snailer,

4
newid - guid è stato designato per essere unico ma non casuale .. approccio errato
Brans Ds

2
con un numero elevato di righe, ad esempio oltre 1 milione di newid()costi di I / O stimati per ordinamento sarà molto elevato e influirà sulle prestazioni.
aadi1295,

81

A seconda delle tue esigenze, TABLESAMPLEotterrai prestazioni altrettanto casuali e migliori. questo è disponibile su MS SQL Server 2005 e versioni successive.

TABLESAMPLE restituirà i dati da pagine casuali invece di righe casuali e quindi deos non recupera nemmeno i dati che non restituiranno.

Ho provato su un tavolo molto grande

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

ci sono voluti più di 20 minuti.

select * from [tablename] tablesample(1 percent)

ci sono voluti 2 minuti.

Le prestazioni miglioreranno anche su campioni più piccoli, TABLESAMPLEmentre non lo saranno newid().

Tieni presente che questo non è casuale come il newid()metodo, ma ti darà un campionamento decente.

Vedi la pagina MSDN .


7
Come sottolineato da Rob Boek di seguito, il campionamento dei tavoli raggruppa i risultati, e quindi non è un buon modo per ottenere un piccolo numero di risultati casuali
Oskar Austegard,

Ti dispiace sapere come funziona: seleziona l'1 percento superiore * dall'ordine [tablename] per newid () poiché newid () non è una colonna in [tablename]. Il server sql sta aggiungendo internamente la colonna newid () su ogni riga e quindi effettua un ordinamento?
FrenkyB,

Il tableample è stata la risposta migliore per me mentre stavo facendo una query complessa su una tabella molto grande. Non c'è dubbio che sia stato straordinariamente veloce. Ho ricevuto una variazione nel numero di record restituiti durante l'esecuzione più volte, ma tutti si trovavano in un margine di errore accettabile.
jessier3,

38

newid () / order by funzionerà, ma sarà molto costoso per grandi serie di risultati perché deve generare un ID per ogni riga e quindi ordinarle.

TABLESAMPLE () è buono dal punto di vista delle prestazioni, ma otterrai un raggruppamento dei risultati (verranno restituite tutte le righe di una pagina).

Per un campione casuale vero con prestazioni migliori, il modo migliore è filtrare le righe in modo casuale. Ho trovato il seguente esempio di codice nell'articolo della documentazione in linea di SQL Server Limitazione dei set di risultati tramite TABLESAMPLE :

Se desideri davvero un campione casuale di singole righe, modifica la query per filtrare le righe in modo casuale, anziché utilizzare TABLESAMPLE. Ad esempio, la query seguente utilizza la funzione NEWID per restituire circa l'uno percento delle righe della tabella Sales.SalesOrderDetail:

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

La colonna SalesOrderID è inclusa nell'espressione CHECKSUM in modo che NEWID () valuti una volta per riga per ottenere il campionamento su base per riga. L'espressione CAST (CHECKSUM (NEWID (), SalesOrderID) e 0x7fffffff AS float / CAST (0x7fffffff AS int) restituisce un valore float casuale compreso tra 0 e 1.

Quando eseguito contro una tabella con 1.000.000 di righe, ecco i miei risultati:

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

Se riesci a cavartela usando TABLESAMPLE, ti darà le migliori prestazioni. Altrimenti usa il metodo newid () / filter. newid () / ordina per dovrebbe essere l'ultima risorsa se si dispone di un set di risultati di grandi dimensioni.


Ho visto anche quell'articolo e provandolo sul mio codice, sembra che NewID()sia valutato solo una volta, anziché per riga, che non mi piace ...
Andrew Mao,

23

Selezione casuale delle righe da una tabella di grandi dimensioni su MSDN ha una soluzione semplice e ben articolata che risolve i problemi di prestazioni su larga scala.

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

Molto interessante. Dopo aver letto l'articolo, non capisco davvero perché RAND()non restituisca lo stesso valore per ogni riga (il che annullerebbe la BINARY_CHECKSUM()logica). È perché viene chiamato all'interno di un'altra funzione piuttosto che essere parte della clausola SELECT?
John M Gant,

Questa query è stata eseguita su una tabella con righe 6MM in meno di un secondo.
Mark Melville,

2
Ho eseguito questa query su una tabella con 35 voci e ho continuato ad averne due nel set di risultati molto spesso. Questo potrebbe essere un problema rand()o una combinazione di quanto sopra, ma per questo motivo mi sono allontanato da questa soluzione. Inoltre, il numero di risultati variava da 1 a 5, quindi potrebbe non essere accettabile in alcuni scenari.
Oliver,

RAND () non restituisce lo stesso valore per ogni riga?
Sarsaparilla,

RAND()restituisce lo stesso valore per ogni riga (motivo per cui questa soluzione è veloce). Tuttavia, le file con checksum binari molto vicine tra loro sono ad alto rischio di generare risultati di checksum simili, causando blocchi quando RAND()sono piccoli. Ad esempio, (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. Se i tuoi dati soffrono di questo problema, moltiplica BINARY_CHECKSUMper 9923.
Brian

12

Questo collegamento presenta un interessante confronto tra Orderby (NEWID ()) e altri metodi per le tabelle con 1, 7 e 13 milioni di righe.

Spesso, quando vengono poste domande su come selezionare righe casuali nei gruppi di discussione, viene proposta la query NEWID; è semplice e funziona molto bene per i tavolini.

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

Tuttavia, la query NEWID ha un grosso svantaggio quando la si utilizza per tabelle di grandi dimensioni. La clausola ORDER BY fa sì che tutte le righe della tabella vengano copiate nel database tempdb, dove vengono ordinate. Ciò causa due problemi:

  1. L'operazione di smistamento di solito comporta un costo elevato. L'ordinamento può utilizzare un sacco di I / O su disco e può funzionare a lungo.
  2. Nel peggiore dei casi, tempdb può esaurire lo spazio. Nel migliore dei casi, tempdb può occupare una grande quantità di spazio su disco che non sarà mai recuperato senza un comando di riduzione manuale.

Ciò di cui hai bisogno è un modo per selezionare casualmente le righe che non utilizzeranno tempdb e non diventeranno molto più lente man mano che la tabella diventa più grande. Ecco una nuova idea su come farlo:

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

L'idea di base alla base di questa query è che vogliamo generare un numero casuale compreso tra 0 e 99 per ogni riga della tabella e quindi scegliere tutte quelle righe il cui numero casuale sia inferiore al valore della percentuale specificata. In questo esempio, vogliamo circa il 10 percento delle righe selezionate casualmente; pertanto, scegliamo tutte le righe il cui numero casuale è inferiore a 10.

Si prega di leggere l'articolo completo in MSDN .


2
Ciao Deumber, bella scoperta, potresti risolverlo poiché è probabile che vengano eliminate solo le risposte del link.
bummi,

1
@bummi L'ho cambiato per evitare di rispondere solo al link :)
QMaster

Questa è la risposta migliore 'ORDER BY NEWID ()' funziona nella maggior parte dei casi (tabelle più piccole), ma poiché i parametri di riferimento nel link rifatto mostrano chiaramente che rimane indietro con il crescere della tabella
pedram bashiri

10

Se (a differenza del PO) hai bisogno di un numero specifico di record (il che rende difficile l'approccio CHECKSUM) e desideri un campione più casuale di quello che TABLESAMPLE fornisce da solo, e desideri anche una velocità migliore di CHECKSUM, puoi accontentarti di una fusione del Metodi TABLESAMPLE e NEWID (), come questo:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

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

SET STATISTICS TIME OFF

Nel mio caso questo è il compromesso più diretto tra casualità (non è davvero, lo so) e velocità. Varia la percentuale (o le righe) di TABLESAMPLE nel modo appropriato: più alta è la percentuale, più casuale è il campione, ma aspettati un calo lineare della velocità. (Nota che TABLESAMPLE non accetterà una variabile)


9

Basta ordinare la tabella con un numero casuale e ottenere le prime 5.000 righe usando TOP.

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

AGGIORNARE

Ho appena provato e una newid()chiamata è sufficiente - non è necessario per tutti i cast e tutta la matematica.


10
Il motivo per cui "tutti i cast e tutti i calcoli" viene utilizzato è per prestazioni migliori.
hkf,

6

Questa è una combinazione dell'idea iniziale iniziale e di una somma di controllo, che mi sembra dare risultati casuali senza il costo di NEWID ():

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

3

In MySQL puoi farlo:

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

3
Questo non funzionerà. Poiché l'istruzione select è atomica, prende solo un numero casuale e lo duplica per ogni riga. Dovresti ridimensionarlo su ogni riga per forzarlo a cambiare.
Tom H,

4
Mmm ... adoro le differenze con i venditori. Select è atomico su MySQL, ma suppongo in un modo diverso. Questo funzionerà in MySQL.
Jeff Ferland,

2

Non ho ancora visto questa variazione nelle risposte. Avevo un vincolo aggiuntivo dove avevo bisogno, dato un seme iniziale, di selezionare ogni volta lo stesso insieme di righe.

Per MS SQL:

Esempio minimo:

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

Tempo di esecuzione normalizzato: 1,00

Esempio NewId ():

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

Tempo di esecuzione normalizzato: 1,02

NewId()è insignificantemente più lento di rand(checksum(*)), quindi potresti non volerlo usare contro grandi set di record.

Selezione con seme iniziale:

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

Se devi selezionare lo stesso set dato un seme, questo sembra funzionare.


C'è qualche vantaggio nell'usare lo speciale @seed contro RAND ()?
QMaster,

assolutamente, hai usato il parametro seed e riempilo per parametro date, la funzione RAND () fa lo stesso tranne usare il valore del tempo completo, voglio sapere che c'è qualche vantaggio nell'usare un parametro creato a portata di mano come seed sopra RAND () o no?
QMaster

Ah !. OK, questo era un requisito del progetto. Avevo bisogno di generare un elenco di righe n casuali in modo deterministico. Fondamentalmente la leadership voleva sapere quali file "casuali" avremmo selezionato pochi giorni prima che le file fossero selezionate ed elaborate. Costruendo un valore seed basato sull'anno / mese, avrei potuto garantire che qualsiasi chiamata alla query quell'anno avrebbe restituito lo stesso elenco "casuale". Lo so, era strano e probabilmente c'erano modi migliori ma ha funzionato ...
klyd,

HAHA :) Capisco, ma penso che il significato generale dei record casuali selezionati non sia lo stesso su una query in esecuzione diversa.
QMaster,

1

Prova questo:

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

0

Sembra che newid () non possa essere usato nella clausola where, quindi questa soluzione richiede una query interna:

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

0

Lo stavo usando in subquery e mi ha restituito le stesse righe in subquery

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

poi ho risolto includendo la variabile della tabella padre in where

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

Nota la condizione dove


0

Il linguaggio di elaborazione lato server in uso (ad esempio PHP, .net, ecc.) Non è specificato, ma se è PHP, prendi il numero richiesto (o tutti i record) e invece di randomizzare nella query usa la funzione shuffle di PHP. Non so se .net ha una funzione equivalente, ma se lo fa allora usalo se stai usando .net

ORDER BY RAND () può avere una penalità piuttosto elevata, a seconda del numero di record coinvolti.


Non ricordo esattamente per cosa stavo usando questo in quel momento, ma probabilmente stavo lavorando in C #, forse su un server o forse in un'applicazione client, non sono sicuro. C # non ha nulla di direttamente paragonabile allo shuffle afaik di PHP, ma potrebbe essere fatto applicando le funzioni dall'oggetto Casuale all'interno di un'operazione Seleziona, ordinando il risultato e prendendo quindi il primo dieci percento. Ma dovremmo leggere l'intera tabella dal disco sul server DB e trasmetterla sulla rete, solo per scartare il 90% di quei dati. L'elaborazione direttamente nel DB è quasi sicuramente più efficiente.
John M Gant,

-2

Questo funziona per me:

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

9
@ user537824, l'hai provato su SQL Server? RANDOM non è una funzione e LIMIT non è una parola chiave. La sintassi di SQL Server per quello che stai facendo sarebbe select top 10 percent from table_name order by rand(), ma anche quella non funziona perché rand () restituisce lo stesso valore su tutte le righe.
John M Gant
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.