Domanda

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

È stato utile?

Soluzione

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 (quanto segue viene copiato da quel collegamento):

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

Altri suggerimenti

Soluzioni come Jeremyes:

SELECT * FROM table ORDER BY RAND() LIMIT 1

funzionano, ma necessitano di una scansione sequenziale di tutta la tabella (perché è necessario calcolare il valore casuale associato a ciascuna riga, in modo da poter determinare quello più piccolo), che può essere piuttosto lenta anche per le tabelle di medie dimensioni.Il mio consiglio sarebbe quello di utilizzare una sorta di colonna numerica indicizzata (molte tabelle le hanno come chiavi primarie) e quindi scrivere qualcosa come:

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

Funziona in tempo logaritmico, indipendentemente dalla dimensione della tabella, se num_value è indicizzato.Un avvertimento:questo lo presuppone num_value è 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 già 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 NEWID()

prende 7.4 milliseconds

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

prende 0.0065 milliseconds!

Utilizzerò sicuramente quest'ultimo metodo.

Non hai detto quale server stai utilizzando.Nelle versioni precedenti di SQL Server, puoi 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 ripetibile:

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

Per SQLServer

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

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

Per un vero campione casuale 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 utilizzando TABLESAMPLE:

Se desideri davvero un campione casuale di singole righe, modifica la tua query per filtrare le righe in modo casuale, invece di usare il campione di tabelle.Ad esempio, la seguente query utilizza la funzione nuova per restituire circa l'uno per cento delle righe delle vendite. Tabella 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 del checksum in modo che newId () valuti una volta per riga per ottenere il campionamento su base riga.L'espressione cast (checksum (newId (), SalesOrderId) e 0x7fffffff come float / cast (0x7fffffff come int) valuta 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 farla franca utilizzando TABLESAMPLE, otterrai le migliori prestazioni.Altrimenti usa il metodo newid()/filter.newid()/order by 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 creare un campo numerico record.

PREPARE RandomRecord FROM "SELECT * FROM table LIMIT ?,1";
SET @n=FLOOR(RAND()*(SELECT COUNT(*) FROM table));
EXECUTE RandomRecord USING @n;

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

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

Questa è la soluzione utilizzata dal codice MediaWiki.Naturalmente, c'è qualche distorsione nei confronti dei valori più piccoli, ma hanno scoperto che era sufficiente riportare il valore casuale attorno allo zero quando non viene recuperata alcuna riga.

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

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

Per SQL Server 2005 e 2008, se vogliamo un campione casuale di singole righe (from Libri in linea):

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

Invece di utilizzando RAND(), poiché non è incoraggiato, 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

Tieni presente che controllerà tutte le righe i cui ID sono UGUALI o SUPERIORI al valore scelto.È anche possibile cercare la riga in 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 funziona molto meglio (almeno con PostgreSQL 9.1) JOIN con un ordinamento casuale piuttosto che ordinare direttamente le righe effettive:per esempio.

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 che è unita ad essa, ma limita comunque il numero di righe di "r" ove possibile.

Il CAST come intero è particolarmente utile per PostgreSQL 9.2 che dispone di un'ottimizzazione di ordinamento specifica per i tipi interi e mobili a precisione singola.

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

Esiste anche un modo per evitare la scansione sequenziale passando alla scansione dell'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;

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

Il numero 10 è arbitrario: puoi usare 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 riserva :) In questo caso un puro vecchio ordine tramite query random().Quando combinato, l'ID appare così:

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 il unione TUTTO clausola.In questo caso se la prima parte restituisce qualche dato la seconda non viene MAI eseguita!

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

Un altro approccio consiste nell'utilizzare TOP due volte, con ordini alternati.Non so se è "SQL puro", perché utilizza una variabile nella TOP, ma funziona in SQL Server 2008.Ecco un esempio che utilizzo su una tabella di parole del dizionario, se voglio una parola a caso.

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, inclusa.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 una query di tipo NEWID() sulla stessa tabella.YYMV.

Puoi anche provare a utilizzare new id() funzione.

Basta scrivere la tua 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

Più dettaglio http://jan.kneschke.de/projects/mysql/order-by-rand/

Non ho ancora visto questa variazione nelle risposte.Avevo un vincolo aggiuntivo in cui dovevo, dato un seme iniziale, selezionare ogni volta lo stesso set di righe.

Per MSSQL:

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() è leggermente più lento di rand(checksum(*)), quindi potresti non volerlo utilizzare su 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 è necessario selezionare lo stesso set dato un seme, 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 piccolo numero, di righe.

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

Sono d'accordo con CD-MaN:L'uso di "ORDER BY RAND()" funzionerà bene per tabelle di piccole dimensioni o quando esegui la SELEZIONE solo poche volte.

Utilizzo anche la tecnica "num_value >= RAND() * ..." e se voglio davvero avere risultati casuali ho una colonna "casuale" speciale nella tabella che aggiorno circa una volta al giorno.Quella 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.Dirige la tua query per esaminare un campione casuale delle pagine da 8 KB che compongono la tua riga.Quindi, la tua query viene eseguita rispetto ai dati contenuti in queste pagine.A causa del modo in cui i dati possono essere raggruppati in queste pagine (ordine di inserimento, ecc.), ciò potrebbe portare a dati che non sono effettivamente un campione casuale.

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

Sembra che molte delle idee elencate utilizzino ancora l'ordinamento

Tuttavia, se utilizzi una tabella temporanea, puoi assegnare un indice casuale (come suggerito da molte soluzioni) e quindi prendere il primo che è maggiore di un numero arbitrario compreso 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 la scansione completa per ordinare le righe in base a dbms_random.value ed è piuttosto lenta per le tabelle di grandi dimensioni.

Usa questo invece:

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

Per Firebird:

Select FIRST 1 column from table ORDER BY RAND()

Con SQL Server 2012+ è possibile utilizzare il file Interrogazione FETCH OFFSET per farlo 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 numero casuale compreso tra 0 e count()-1 della tabella (dopo tutto, l'offset 0 è la prima riga)

Funziona con i buchi nei dati della tabella, purché si disponga di un indice con cui lavorare per la clausola ORDER BY.È anche molto positivo per la casualità: poiché riesci a risolverlo da solo per passare, ma i problemi in altri metodi non sono presenti.Inoltre, le prestazioni sono piuttosto buone, su un set di dati più piccolo reggono bene, anche se non ho provato test prestazionali seri su diversi milioni di righe.

Per SQL Server 2005 e versioni successive, estendendo la risposta di @GreyPanther per i casi in cui num_value non ha valori continui.Questo funziona anche nei casi in cui non abbiamo distribuito uniformemente i 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 limitare a una sola riga, aggiungila alla fine.

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top