Come richiedere una riga casuale in SQL?


510

Come posso richiedere una riga casuale (o il più vicino possibile a veramente casuale) in puro SQL?


lo facevo sempre in php dopo i risultati della query da sql ... questo è probabilmente molto più veloce per l'elaborazione secondo l'appendice del limite 1 della soluzione
CheeseConQueso,


2
Sembra che non esista una soluzione "pure SQL" in esecuzione su ogni dbms ... esiste una soluzione per ognuno di essi.
Manu,

Risposte:


735

Vedi questo post: SQL per selezionare una riga casuale da una tabella del database . Passa attraverso i metodi per farlo in MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 e Oracle (da quel link viene copiato quanto segue):

Seleziona una riga casuale con MySQL:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

Seleziona una riga casuale con PostgreSQL:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1

Seleziona una riga casuale con Microsoft SQL Server:

SELECT TOP 1 column FROM table
ORDER BY NEWID()

Seleziona una riga casuale con IBM DB2

SELECT column, RAND() as IDX 
FROM table 
ORDER BY IDX FETCH FIRST 1 ROWS ONLY

Seleziona un record casuale con Oracle:

SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1

30
-1 per fare affidamento order by rand()o equivalenti in tutti i dbs: |. anche menzionato qui .
AD7

20
Dieci anni fa qualcuno ha detto che usare ORDER BY RAND()è sbagliato ...
Trejder,

ORDER BY NEWID () sembra essere notevolmente più lento su SQL Server. La mia query è simile: selezionare i primi 1000 C.CustomerId, CL.LoginName da Customer C inner join LinkedAccount LA su C.CustomerId = LA.CustomerId inner join CustomerLogin CL su C.CustomerId = CL.CustomerId group by C.CustomerId, CL. LoginName con conteggio (*)> 1 ordine per NEWID () La rimozione della riga "ordina per NEWID ()" restituisce i risultati molto più velocemente.
Ben Power,

3
Per SQLite utilizzare la funzione RANDOM ().
Sbatte il

10
Queste soluzioni non si ridimensionano. Sono O(n)con nil numero di record nella tabella. Immagina di avere 1 milione di record, vuoi davvero generare 1 milione di numeri casuali o ID univoci? Preferirei usarlo COUNT()e coinvolgerlo in una nuova LIMITespressione con un singolo numero casuale.
Christian Hujer,

174

Soluzioni come Jeremies:

SELECT * FROM table ORDER BY RAND() LIMIT 1

funzionano, ma hanno bisogno di una scansione sequenziale di tutta la tabella (perché il valore casuale associato a ciascuna riga deve essere calcolato - in modo che sia possibile determinare quello più piccolo), che può essere abbastanza lento anche per le tabelle di medie dimensioni. La mia raccomandazione sarebbe di usare un qualche tipo di colonna numerica indicizzata (molte tabelle hanno queste come chiavi primarie) e quindi scrivere qualcosa del tipo:

SELECT * FROM table WHERE num_value >= RAND() * 
    ( SELECT MAX (num_value ) FROM table ) 
ORDER BY num_value LIMIT 1

Funziona in tempo logaritmico, indipendentemente dalle dimensioni della tabella, se num_valueè indicizzato. Un avvertimento: questo presuppone che num_valuesia equamente distribuito nell'intervallo 0..MAX(num_value). Se il tuo set di dati si discosta fortemente da questo presupposto, otterrai risultati distorti (alcune righe appariranno più spesso di altre).


8
Il secondo suggerimento non è casuale. Non puoi prevedere la riga che verrà scelta, ma se dovessi scommettere, scommetteresti sulla seconda riga. E non avresti mai scommesso sull'ultima riga, è meno probabile che venga scelta qualunque sia la distribuzione del tuo num_value e quanto sia grande il tuo tavolo.
Etienne Racine,

1
So che di solito le funzioni RAND () non sono di altissima qualità, ma a parte questo puoi spiegare perché la selezione non sarebbe casuale?
Pantera grigia,

13
Il primo è SBAGLIATO in SQL Server. La funzione RAND () viene invocata una sola volta per query e non una volta per riga. Quindi seleziona sempre la prima riga (provalo).
Jeff Walker Code Ranger

3
Il secondo presuppone inoltre che tutte le righe siano considerate: è possibile che scelga una riga che è stata eliminata.
Sam Rueby,

3
@ Sam.Rueby In realtà, num_value> = RAND () ... il limite 1 garantisce che le righe vuote vengano ignorate fino a quando non trova una riga esistente.
ghord,

62

Non so quanto sia efficiente, ma l'ho usato prima:

SELECT TOP 1 * FROM MyTable ORDER BY newid()

Poiché i GUID sono piuttosto casuali, l'ordinamento significa che ottieni una riga casuale.


1
Sto usando MS SQL Server, SELEZIONA TOP 1 * DA some_table_name ORDER BY NEWID () ha funzionato benissimo per me, grazie per il consiglio ragazzi!

È esattamente la stessa cosa diORDER BY RAND() LIMIT 1
Ken Bloom il

6
Questo è anche molto specifico del database poiché utilizza TOP 1e newid().
Grigio

12
Questa è una cattiva idea. Questo metodo non utilizzerà un indice a meno che ogni colonna non sia indicizzata individualmente. La tabella con 100 milioni di record potrebbe richiedere molto tempo per ottenere un record.
Passa dal

1
@Switch e quale soluzione proporresti?
Akmal Salikhov,

31
ORDER BY NEWID()

prende 7.4 milliseconds

WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)

prende 0.0065 milliseconds!

Andrò sicuramente con quest'ultimo metodo.


2
La seconda opzione non sceglierà l'ultima riga. Non so perché - lo sto solo sottolineando.
Voldemort,

7
@Voldemort: rand()restituisce un numero in virgola mobile ndove 0 < n < 1. Supponendo che num_valuesia un numero intero, anche il valore di ritorno rand() * max(num_value)sarà costretto a un numero intero, troncando così qualsiasi cosa dopo il punto decimale. Quindi, rand() * max(num_value)sarà sempre inferiore a max(num_value), motivo per cui l'ultima riga non verrà mai selezionata.
Ian Kemp,

Non sarò efficiente se i miei dati vengono eliminati spesso - se trovo un divario, dovrò rieseguire l'intera query.
Loic Coenen,

1
@IanKemp Stupid question, quindi perché non usare semplicemente SELECT MAX (num_value) + 1 ?? Poiché rand (o RANDOM nella maggior parte dei casi) restituisce [0,1), otterrai l'intero intervallo di valori. Inoltre, sì, hai ragione, devo risolvere una query.
tekHedd il

13

Non hai detto quale server stai usando. Nelle versioni precedenti di SQL Server, è possibile utilizzare questo:

select top 1 * from mytable order by newid()

In SQL Server 2005 e versioni successive, è possibile utilizzare TABLESAMPLEper ottenere un campione casuale che è ripetibile:

SELECT FirstName, LastName
FROM Contact 
TABLESAMPLE (1 ROWS) ;

9
MSDN afferma che newid () è preferito su tableample per risultati veramente casuali: msdn.microsoft.com/en-us/library/ms189108.aspx
Andrew Hedges,

7
@Andrew Hedges: ORDER BY NEWID () è troppo costoso
Andrei Rînea

10

Per SQL Server

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 degli insiemi 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 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.


4

Se possibile, utilizzare le istruzioni memorizzate per evitare l'inefficienza di entrambi gli indici su RND () e la creazione di un campo numero record.

PREPARE RandomRecord FROM "SELECT * FROM table LIMIT?, 1";
SET @ n = FLOOR (RAND () * (SELEZIONA COUNT (*) DA tabella));
ESEGUI RandomRecord USANDO @n;

Questa soluzione si occupa anche di restituire righe casuali quando il valore numerico indicizzato utilizzato nella clausola where sopra non è equamente distribuito; quindi anche se impiega quasi lo stesso tempo (costante) dell'uso di dove id_value> = RAND () * MAX (id_value), è meglio.
guido,

Per quanto ne so, questo non funziona a tempo costante, ma funziona a tempo lineare. Nel caso peggiore, @n è uguale al numero di righe nella tabella e "SELECT * FROM table LIMIT?, 1" valuta @n - 1 righe fino a quando non arriva all'ultima.
Andres Riofrio,

3

Il modo migliore è mettere un valore casuale in una nuova colonna solo per quello scopo e usare qualcosa del genere (codice pseude + SQL):

randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")

Questa è la soluzione utilizzata dal codice MediaWiki. Naturalmente, vi è una certa distorsione rispetto a valori più piccoli, ma hanno scoperto che era sufficiente azzerare il valore casuale quando non vengono recuperate righe.

La soluzione newid () potrebbe richiedere una scansione completa della tabella in modo che ad ogni riga possa essere assegnato un nuovo guid, che sarà molto meno performante.

La soluzione rand () potrebbe non funzionare affatto (cioè con MSSQL) perché la funzione verrà valutata solo una volta e ad ogni riga verrà assegnato lo stesso numero "casuale".


1
Avvolgere quando si ottengono 0 risultati fornisce un campione dimostrabilmente casuale (non solo "abbastanza buono"). Questa soluzione si adatta quasi alle query a più righe (pensate a "shuffle del partito"). Il problema è che i risultati tendono a essere selezionati ripetutamente negli stessi gruppi. Per ovviare a questo, dovresti ridistribuire i numeri casuali che hai appena usato. Puoi imbrogliare tenendo traccia di randomNo e impostandolo su max (casualità) dai risultati, ma poi p (riga i sulla query 1 E riga i sulla query 2) == 0, che non è corretto. Lasciami fare un po 'di matematica e ti risponderò con uno schema veramente giusto.
alsuren,

3

Per SQL Server 2005 e 2008, se vogliamo un campione casuale di singole righe (dalla documentazione online ):

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

3

Istituito dall'uso di RAND (), poiché non è consigliato , potresti semplicemente ottenere l'ID massimo (= Max):

SELECT MAX(ID) FROM TABLE;

ottieni un numero casuale tra 1..Max (= My_Generated_Random)

My_Generated_Random = rand_in_your_programming_lang_function(1..Max);

e quindi eseguire questo SQL:

SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1

Si noti che controllerà per eventuali righe quali ID sono uguali o superiori al valore selezionato. È anche possibile cercare la riga verso il basso nella tabella e ottenere un ID uguale o inferiore a My_Generated_Random, quindi modificare la query in questo modo:

SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1

Cosa accadrebbe se l'ID casuale generato non esistesse più nella tabella? Le righe cancellate o passive che non desideri mostrare all'utente potrebbero causare problemi.
Ebleme

Niente. Ottieni il numero ID più vicino, non esatto. Se consideri id = 1 da rimuovere, scambia 1 con il minimo.
forsberg,

2

Come sottolineato nel commento di @ BillKarwin sulla risposta di @ cnu ...

Quando si combina con un LIMIT, ho scoperto che si comporta molto meglio (almeno con PostgreSQL 9.1) JOIN con un ordinamento casuale piuttosto che per ordinare direttamente le righe effettive: ad es.

SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
       FROM tbl_post
       WHERE create_time >= 1349928000
     ) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100

Assicurati solo che la 'r' generi un valore 'rand' per ogni possibile valore chiave nella query complessa a cui è unita, ma che comunque limiti il ​​numero di righe di 'r'.

CAST as Integer è particolarmente utile per PostgreSQL 9.2 che ha l'ottimizzazione specifica dell'ordinamento per i tipi interi e di precisione a virgola mobile.


1

La maggior parte delle soluzioni qui mirano a evitare l'ordinamento, ma devono comunque eseguire una scansione sequenziale su un tavolo.

C'è anche un modo per evitare la scansione sequenziale passando alla scansione indice. Se conosci il valore dell'indice della tua riga casuale puoi ottenere il risultato quasi istantaneamente. Il problema è: come indovinare un valore di indice.

La seguente soluzione funziona su PostgreSQL 8.4:

explain analyze select * from cms_refs where rec_id in 
  (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
   from generate_series(1,10))
  limit 1;

Nella soluzione sopra indovina 10 diversi valori di indice casuali dall'intervallo 0 .. [ultimo valore di id].

Il numero 10 è arbitrario: puoi usarne 100 o 1000 poiché (sorprendentemente) non ha un grande impatto sul tempo di risposta.

C'è anche un problema: se hai ID sparsi potresti perdere . La soluzione è avere un piano di backup :) In questo caso un puro vecchio ordine per query random (). Quando l'id combinato è simile al seguente:

explain analyze select * from cms_refs where rec_id in 
    (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
     from generate_series(1,10))
    union all (select * from cms_refs order by random() limit 1)
    limit 1;

Non la clausola ALL di unione . In questo caso se la prima parte restituisce dati, la seconda non viene MAI eseguita!


1

Alla fine, ma sono arrivato qui tramite Google, quindi per il bene dei posteri, aggiungerò una soluzione alternativa.

Un altro approccio è utilizzare TOP due volte, con ordini alternati. Non so se sia "SQL puro", perché utilizza una variabile in TOP, ma funziona in SQL Server 2008. Ecco un esempio che uso contro una tabella di parole del dizionario, se voglio una parola casuale.

SELECT TOP 1
  word
FROM (
  SELECT TOP(@idx)
    word 
  FROM
    dbo.DictionaryAbridged WITH(NOLOCK)
  ORDER BY
    word DESC
) AS D
ORDER BY
  word ASC

Naturalmente, @idx è un numero intero generato casualmente che varia da 1 a COUNT (*) sulla tabella di destinazione, inclusivamente. Se la tua colonna è indicizzata, ne trarrai vantaggio anche tu. Un altro vantaggio è che puoi usarlo in una funzione, poiché NEWID () non è consentito.

Infine, la query precedente viene eseguita in circa 1/10 del tempo di esecuzione di un tipo di query NEWID () sulla stessa tabella. YYMV.


1

Puoi anche provare a usare la new id()funzione.

Basta scrivere una query e utilizzare ordina per new id()funzione. È abbastanza casuale.


1

Per MySQL ottenere record casuali

 SELECT name
  FROM random AS r1 JOIN
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

Maggiori dettagli http://jan.kneschke.de/projects/mysql/order-by-rand/


Dopo aver testato molte delle risposte, credo che questa sia la migliore. Sembra essere veloce e sceglie ogni volta un buon numero casuale. Sembra simile al secondo suggerimento di @GreyPanther sopra, ma questa risposta prende più numeri casuali.
Jeff Baker,

1

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 set 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 set di record di grandi dimensioni.

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.


1

In MSSQL (testato su 11.0.5569) utilizzando

SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)

è significativamente più veloce di

SELECT TOP 100 * FROM employee ORDER BY NEWID()

1

In SQL Server puoi combinare TABLESAMPLE con NEWID () per ottenere una casualità piuttosto buona e avere comunque velocità. Ciò è particolarmente utile se vuoi davvero solo 1, o un numero limitato, di righe.

SELECT TOP 1 * FROM [table] 
TABLESAMPLE (500 ROWS) 
ORDER BY NEWID()

1

Con SQL Server 2012+ è possibile utilizzare la query OFFSET FETCH per eseguire questa operazione per una singola riga casuale

select  * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY

dove id è una colonna identità e n è la riga desiderata, calcolata come un numero casuale compreso tra 0 e count () - 1 della tabella (dopo tutto, l'offset 0 è la prima riga)

Funziona con buchi nei dati della tabella, purché si disponga di un indice con cui lavorare per la clausola ORDER BY. È anche molto buono per la casualità - mentre lavori per farti passare da solo, ma i nervosismi in altri metodi non sono presenti. Inoltre, le prestazioni sono piuttosto buone, su un set di dati più piccolo regge bene, anche se non ho provato seri test delle prestazioni su diversi milioni di righe.



0

Devo essere d'accordo con CD-MaN: l'uso di "ORDER BY RAND ()" funzionerà bene per i piccoli tavoli o quando fai il SELECT solo poche volte.

Uso anche la tecnica "num_value> = RAND () * ...", e se voglio davvero avere risultati casuali ho una speciale colonna "casuale" nella tabella che aggiorno una volta al giorno. La singola esecuzione di UPDATE richiederà del tempo (soprattutto perché dovrai avere un indice su quella colonna), ma è molto più veloce della creazione di numeri casuali per ogni riga ogni volta che viene eseguita la selezione.


0

Fai attenzione perché TableSample in realtà non restituisce un campione casuale di righe. Indirizza la tua query a guardare un campione casuale delle pagine da 8 KB che compongono la tua riga. Quindi, la query viene eseguita in base ai dati contenuti in queste pagine. A causa del modo in cui i dati possono essere raggruppati su queste pagine (ordine di inserzione, ecc.), Ciò potrebbe portare a dati che in realtà non sono un campione casuale.

Vedi: http://www.mssqltips.com/tip.asp?tip=1308

Questa pagina MSDN per TableSample include un esempio di come generare un campione di dati realmente casuale.

http://msdn.microsoft.com/en-us/library/ms189108.aspx


0

Sembra che molte delle idee elencate utilizzino ancora l'ordinamento

Tuttavia, se si utilizza una tabella temporanea, è possibile assegnare un indice casuale (come molte soluzioni hanno suggerito), quindi afferrare il primo che è maggiore di un numero arbitrario tra 0 e 1.

Ad esempio (per DB2):

WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY

2
Dopo aver considerato questa soluzione, ho trovato un difetto fondamentale nella mia logica. Ciò restituirebbe costantemente gli stessi piccoli valori di impostazione, vicino all'inizio della tabella, perché presumo che se vi fosse una distribuzione uniforme tra 0 e 1, c'è una probabilità del 50% che la prima riga soddisfi tali criteri.
DAVID


0

Esiste una soluzione migliore per Oracle invece di utilizzare dbms_random.value, mentre richiede una scansione completa per ordinare le righe in base a dbms_random.value ed è piuttosto lento per le tabelle di grandi dimensioni.

Usa questo invece:

SELECT *
FROM employee sample(1)
WHERE rownum=1

0

Per Firebird:

Select FIRST 1 column from table ORDER BY RAND()

0

Per SQL Server 2005 e versioni successive, estendere la risposta di @ GreyPanther per i casi in cui num_valuenon sono presenti valori continui. Questo funziona anche nei casi in cui non abbiamo distribuito uniformemente set di dati e quando num_valuenon è un numero ma un identificatore univoco.

WITH CTE_Table (SelRow, num_value) 
AS 
(
    SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
) 

SELECT * FROM table Where num_value = ( 
    SELECT TOP 1 num_value FROM CTE_Table  WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)

-1

La funzione casuale da sql potrebbe aiutare. Inoltre, se desideri limitarti a una sola riga, aggiungilo alla fine.

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
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.