Record casuale da una tabella di database (T-SQL)


85

Esiste un modo succinto per recuperare un record casuale da una tabella del server sql?

Vorrei randomizzare i miei dati di unit test, quindi sto cercando un modo semplice per selezionare un ID casuale da una tabella. In inglese, la selezione sarebbe "Seleziona un id dalla tabella in cui l'id è un numero casuale compreso tra l'ID più basso nella tabella e l'ID più alto nella tabella".

Non riesco a trovare un modo per farlo senza dover eseguire la query, testare un valore nullo, quindi rieseguire se nullo.

Idee?


ci sono un paio di metodi qui brettb.com/SQL_Help_Random_Numbers.asp
Mesh

2
Sei sicuro di voler adottare questo approccio? I dati degli unit test non dovrebbero essere casuali: in effetti, dovresti essere certo di ottenere gli stessi risultati indipendentemente dal numero di volte in cui esegui lo unit test. La disponibilità di dati casuali potrebbe violare questo principio fondamentale del test unitario.
redini

Il collegamento sopra da @Mesh non è più attivo.
Robert Sievers

Risposte:


146

Esiste un modo succinto per recuperare un record casuale da una tabella del server sql?

SELECT TOP 1 * FROM table ORDER BY NEWID()

Spiegazione

A NEWID()viene generato per ogni riga e la tabella viene quindi ordinata in base a essa. Viene restituito il primo record (ovvero il record con il GUID "più basso").

Appunti

  1. I GUID vengono generati come numeri pseudo-casuali dalla versione quattro:

    L'UUID della versione 4 è pensato per generare UUID da numeri veramente casuali o pseudo-casuali.

    L'algoritmo è il seguente:

    • Impostare i due bit più significativi (bit 6 e 7) di clock_seq_hi_and_reserved rispettivamente su zero e uno.
    • Impostare i quattro bit più significativi (bit da 12 a 15) del campo time_hi_and_version sul numero di versione a 4 bit della Sezione 4.1.3.
    • Imposta tutti gli altri bit su valori scelti in modo casuale (o pseudo-casuale).

    - Uno spazio dei nomi URN Universally Unique IDentifier (UUID) - RFC 4122

  2. L'alternativa SELECT TOP 1 * FROM table ORDER BY RAND()non funzionerà come si potrebbe pensare. RAND()restituisce un singolo valore per query, quindi tutte le righe condivideranno lo stesso valore.

  3. Sebbene i valori GUID siano pseudo-casuali, avrai bisogno di un PRNG migliore per le applicazioni più impegnative.

  4. Le prestazioni tipiche sono inferiori a 10 secondi per circa 1.000.000 di righe, ovviamente a seconda del sistema. Tieni presente che è impossibile raggiungere un indice, quindi le prestazioni saranno relativamente limitate.


Esattamente quello che stavo cercando. Avevo la sensazione che fosse più semplice di come lo stavo facendo.
Jeremy

1
Stai assumendo che NEWID produca valori pseudocasuali. C'è una buona probabilità che produca valori sequenziali. NEWID produce solo valori univoci. RAND, tuttavia, produce valori pseudo casuali.
Skizz

Lo sto eseguendo su una tabella fortemente indicizzata con 1.671.145 righe e ci vogliono 7 secondi per tornare. Anche la tabella è abbastanza ottimale: è virtualmente il cuore del nostro database, quindi ci si prende cura.
Tom Ritter

@ ÂviewAnew. 1,6 milioni di righe e 7 secondi su una selezione che non (e non può) raggiungere un indice non è male.
Sklivvz

7
@Skizz, rand non funziona così. Un SINGOLO valore casuale viene generato prima di SELECT. Quindi, se provi "SELEZIONA TOP 10 RAND () ..." ottieni sempre lo stesso valore
Sklivvz

27

Su tabelle più grandi puoi anche usarlo TABLESAMPLEper evitare di scansionare l'intera tabella.

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

Il ORDER BY NEWIDè ancora necessario per evitare le righe solo ritorno che appaiono prima della pagina dei dati.

Il numero da utilizzare deve essere scelto con attenzione per la dimensione e la definizione della tabella e potresti prendere in considerazione la logica di ripetizione se non viene restituita alcuna riga. La matematica alla base di questo e il motivo per cui la tecnica non è adatta ai piccoli tavoli è discussa qui


Ho trovato questo sul sito Web di Microsoft: È possibile utilizzare TABLESAMPLE per restituire rapidamente un campione da una tabella di grandi dimensioni quando è vera una delle seguenti condizioni: Il campione non deve essere un campione veramente casuale a livello di singole righe. Le righe sulle singole pagine della tabella non sono correlate con altre righe sulla stessa pagina.
Mark Entingh

1
@MarkEntingh - In questo caso TOP 1non importa se le righe sulla stessa pagina sono correlate o meno. Stai scegliendo solo uno di loro.
Martin Smith

9

Prova anche il tuo metodo per ottenere un ID casuale tra MIN (Id) e MAX (Id) e poi

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

Ti farà sempre ottenere una riga.


2
-1, funziona solo quando non ci sono ID mancanti tra il minimo e il massimo. Se ne viene eliminato uno, lo stesso ID viene generato dalla funzione casuale, restituirai zero record.
Neil N

6
@Neil, non proprio - ti darà la prima riga con un ID maggiore del numero casuale se mancano gli ID. Il problema qui è che la probabilità che ogni riga venga fuori non è costante. Ma poi di nuovo questo è sufficiente nella maggior parte dei casi.
Sklivvz

1
+1. Per unit test che dovrebbero raggiungere valori diversi che è abbastanza buono - se richiedi un vero casuale, allora questo è qualcos'altro. Ma nel contesto PO dovrebbe essere abbastanza buono.
TomTom

7

Se vuoi selezionare dati di grandi dimensioni, il modo migliore che conosco è:

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

Fonte: MSDN


Non ne sono sicuro, ma penso che l'utilizzo di RAND () piuttosto NEWID () per generare numeri veramente casuali potrebbe essere migliore a causa degli svantaggi dell'utilizzo di NEWID () nel processo di selezione.
QMaster

Provo a usare questo metodo con il numero esatto di record piuttosto che con una base percentuale, l'ho fatto con l'espansione dell'intervallo di selezione e la limitazione con TOP n, c'è qualche suggerimento?
QMaster

Ho trovato un altro problema con questo scenario, se usi group by otterrai sempre lo stesso ordine di righe selezionate casualmente, quindi sembra che nelle tabelle piccole l'approccio @skilvvz sia il più appropriato.
QMaster

0

Stavo cercando di migliorare i metodi che avevo provato e mi sono imbattuto in questo post. Mi rendo conto che è vecchio ma questo metodo non è elencato. Sto creando e applicando dati di test; questo mostra il metodo per "indirizzo" in un SP chiamato con @st (stato a due caratteri)

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

0

Se desideri davvero un campione casuale di singole righe, modifica la query per filtrare le righe in modo casuale, invece di 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 per riga. L'espressione CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) restituisce un valore float casuale compreso tra 0 e 1. "

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

Questo è ulteriormente spiegato di seguito:

Come funziona? Dividiamo la clausola WHERE e la spieghiamo.

La funzione CHECKSUM sta calcolando un checksum sugli elementi nell'elenco. È discutibile se SalesOrderID sia addirittura richiesto, poiché NEWID () è una funzione che restituisce un nuovo GUID casuale, quindi moltiplicando una cifra casuale per una costante dovrebbe risultare in un caso casuale in ogni caso. In effetti, escludere SalesOrderID sembra non fare alcuna differenza. Se sei un appassionato di statistica e puoi giustificare l'inclusione di questo, usa la sezione commenti qui sotto e fammi sapere perché mi sbaglio!

La funzione CHECKSUM restituisce un VARBINARIO. L'esecuzione di un'operazione AND bit per bit con 0x7fffffff, che è l'equivalente di (111111111 ...) in binario, produce un valore decimale che è effettivamente una rappresentazione di una stringa casuale di 0 e 1. La divisione per il coefficiente 0x7fffffff normalizza efficacemente questa cifra decimale a una cifra compresa tra 0 e 1. Quindi per decidere se ciascuna riga merita di essere inclusa nel set di risultati finali, viene utilizzata una soglia di 1 / x (in questo caso, 0,01) dove x è la percentuale dei dati da recuperare come campione.

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

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.