Come posso richiedere una riga casuale (o il più vicino possibile a veramente casuale) in puro SQL?
Come posso richiedere una riga casuale (o il più vicino possibile a veramente casuale) in puro SQL?
Risposte:
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
order by rand()
o equivalenti in tutti i dbs: |. anche menzionato qui .
ORDER BY RAND()
è sbagliato ...
O(n)
con n
il 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 LIMIT
espressione con un singolo numero casuale.
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_value
sia 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).
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.
ORDER BY RAND() LIMIT 1
TOP 1
e newid()
.
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.
rand()
restituisce un numero in virgola mobile n
dove 0 < n < 1
. Supponendo che num_value
sia 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.
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 TABLESAMPLE
per ottenere un campione casuale che è ripetibile:
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
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.
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;
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".
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)
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
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.
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!
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.
Puoi anche provare a usare la new id()
funzione.
Basta scrivere una query e utilizzare ordina per new id()
funzione. È abbastanza casuale.
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/
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.
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()
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()
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.
SELECT * FROM table ORDER BY RAND() LIMIT 1
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.
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.
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
Un modo semplice ed efficace da http://akinas.com/pages/en/blog/mysql_random_row/
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
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
Per SQL Server 2005 e versioni successive, estendere la risposta di @ GreyPanther per i casi in cui num_value
non sono presenti valori continui. Questo funziona anche nei casi in cui non abbiamo distribuito uniformemente set di dati e quando num_value
non è 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)
)
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