Seleziona una riga casuale da una tabella sqlite


Risposte:


213

Dai un'occhiata a Selezione di una riga casuale da una tabella SQLite

SELECT * FROM table ORDER BY RANDOM() LIMIT 1;

1
Come estendere questa soluzione a un join? Quando uso SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;ottengo sempre la stessa riga.
Helmut Grohne

È possibile inizializzare il numero casuale. Ad esempio, il libro del giorno seminato con unix epoc per oggi a mezzogiorno in modo che mostri lo stesso libro tutto il giorno anche se la query viene eseguita più volte. Sì, lo so che la memorizzazione nella cache è più efficiente per questo caso d'uso, solo un esempio.
danielson317

FWIW la mia domanda trova effettivamente risposta qui. E la risposta è che non puoi seminare il numero casuale. stackoverflow.com/questions/24256258/…
danielson317

31

Le seguenti soluzioni sono molto più veloci di anktastic (il conteggio (*) costa molto, ma se puoi metterlo in cache, la differenza non dovrebbe essere così grande), che a sua volta è molto più veloce di "order by random ()" quando si dispone di un numero elevato di file, anche se presentano alcuni inconvenienti.

Se i tuoi rowid sono piuttosto compatti (cioè poche eliminazioni), puoi fare quanto segue (usando (select max(rowid) from foo)+1invece di max(rowid)+1fornisce prestazioni migliori, come spiegato nei commenti):

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1));

Se hai dei buchi, a volte proverai a selezionare un rowid inesistente e la selezione restituirà un set di risultati vuoto. Se ciò non è accettabile, puoi fornire un valore predefinito come questo:

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)) or rowid = (select max(rowid) from node) order by rowid limit 1;

Questa seconda soluzione non è perfetta: la distribuzione di probabilità è più alta nell'ultima riga (quella con il rowid più alto), ma se aggiungi spesso cose alla tabella, diventerà un bersaglio mobile e la distribuzione delle probabilità dovrebbe essere molto meglio.

Ancora un'altra soluzione, se selezioni spesso elementi casuali da una tabella con molti buchi, potresti voler creare una tabella che contenga le righe della tabella originale ordinate in ordine casuale:

create table random_foo(foo_id);

Quindi, periodicamente, riempi nuovamente la tabella random_foo

delete from random_foo;
insert into random_foo select id from foo;

E per selezionare una riga casuale, puoi usare il mio primo metodo (non ci sono buchi qui). Ovviamente, quest'ultimo metodo presenta alcuni problemi di concorrenza, ma la ricostruzione di random_foo è un'operazione di manutenzione che non è probabile che accada molto spesso.

Tuttavia, ancora un altro modo, che ho trovato di recente su una mailing list , è di attivare un trigger su Elimina per spostare la riga con il rowid più grande nella riga eliminata corrente, in modo che non vengano lasciati buchi.

Infine, nota che il comportamento di rowid e un numero intero autoincremento della chiave primaria non è identico (con rowid, quando viene inserita una nuova riga, viene scelto max (rowid) +1, dove è il valore più alto mai visto + 1 per una chiave primaria), quindi l'ultima soluzione non funzionerà con un autoincrement in random_foo, ma gli altri metodi lo faranno.


Come ho appena visto in una mailing list, invece di avere il metodo di fallback (metodo 2), puoi semplicemente usare rowid> = [random] invece di =, ma in realtà è incredibilmente lento rispetto al metodo 2.
Suzanne Dupéron,

3
Questa è un'ottima risposta; tuttavia ha un problema. SELECT max(rowid) + 1sarà una query lenta - richiede una scansione completa della tabella. sqlite ottimizza solo la query SELECT max(rowid). Pertanto, questa risposta sarebbe migliorata da: select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)); Vedi questo per maggiori informazioni: sqlite.1065341.n5.nabble.com/…
dasl

19

È necessario inserire "order by RANDOM ()" nella query.

Esempio:

select * from quest order by RANDOM();

Vediamo un esempio completo

  1. Crea una tabella:
CREATE TABLE  quest  (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    quest TEXT NOT NULL,
    resp_id INTEGER NOT NULL
);

Inserendo alcuni valori:

insert into quest(quest, resp_id) values ('1024/4',6), ('256/2',12), ('128/1',24);

Una selezione predefinita:

select * from quest;

| id |   quest  | resp_id |
   1     1024/4       6
   2     256/2       12
   3     128/1       24
--

A selezionare casuale:

select * from quest order by RANDOM();
| id |   quest  | resp_id |
   3     128/1       24
   1     1024/4       6
   2     256/2       12
--
* Ogni volta che si seleziona, l'ordine sarà diverso.

Se vuoi restituire solo una riga

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
* Ogni volta che selezioni, il reso sarà diverso.


Sebbene le risposte di solo codice non siano vietate, tieni presente che questa è una comunità di domande e risposte, piuttosto che di crowdsourcing, e che, di solito, se l'OP avesse compreso il codice pubblicato come risposta, sarebbe venuto fuori con una soluzione simile da solo e non avrebbe postato una domanda in primo luogo. Pertanto, fornisci un contesto alla tua risposta e / o al codice spiegando come e / o perché funziona.
XenoRo

2
Preferisco questa soluzione, poiché mi permette di cercare n righe. Nel mio caso, avevo bisogno di 100 campioni casuali dal database - ORDER BY RANDOM () combinato con LIMIT 100 fa esattamente questo.
mnr

17

Che dire:

SELECT COUNT(*) AS n FROM foo;

quindi scegli un numero casuale m in [0, n) e

SELECT * FROM foo LIMIT 1 OFFSET m;

Puoi anche salvare il primo numero ( n ) da qualche parte e aggiornarlo solo quando il conteggio del database cambia. In questo modo non devi eseguire il SELECT COUNT ogni volta.


1
È un bel metodo veloce. Non generalizza molto bene la selezione di più di 1 riga, ma l'OP ne ha chiesto solo 1, quindi immagino che vada bene.
Ken Williams,

Una cosa curiosa da notare è che il tempo necessario per trovare OFFSETsembra aumentare a seconda delle dimensioni dell'offset: la riga 2 è veloce, la riga 2 milioni richiede un po 'di tempo, anche quando tutti i dati in sono di dimensione fissa e dovrebbe essere in grado di cercarlo direttamente. Almeno, questo è quello che sembra in SQLite 3.7.13.
Ken Williams,

@KenWilliams Quasi tutti i database hanno lo stesso problema con `` OFFSET``. È un modo molto inefficiente per interrogare un database perché ha bisogno di leggere tante righe anche se restituirà solo 1.
Jonathan Allen

1
Si noti che stavo parlando di / dimensioni fisse / record: dovrebbe essere facile eseguire la scansione direttamente sul byte corretto nei dati ( non leggendo così tante righe), ma dovrebbero implementare l'ottimizzazione in modo esplicito.
Ken Williams

@KenWilliams: non ci sono record di dimensioni fisse in SQLite, vengono digitati dinamicamente e i dati non devono corrispondere alle affinità dichiarate ( sqlite.org/fileformat2.html#section_2_1 ). Tutto è memorizzato nelle pagine b-tree, quindi in entrambi i casi deve fare almeno una ricerca b-tree verso la foglia. Per ottenere ciò in modo efficiente, sarebbe necessario memorizzare la dimensione della sottostruttura insieme a ciascun puntatore figlio. Sarebbe un sovraccarico eccessivo per poco vantaggio, poiché non sarai ancora in grado di ottimizzare l'OFFSET per join, ordina per, ecc ... (e senza ORDER BY l'ordine non è definito.)
Yakov Galka

13
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1

11
Dal momento che selezionerà prima l'intero contenuto della tabella, non sarebbe molto dispendioso in termini di tempo per tabelle di grandi dimensioni?
Alex_coder

1
Non puoi limitare l'ambito utilizzando le condizioni "WHERE"?
jldupont

11

Ecco una modifica della soluzione di @ ank:

SELECT * 
FROM table
LIMIT 1 
OFFSET ABS(RANDOM()) % MAX((SELECT COUNT(*) FROM table), 1)

Questa soluzione funziona anche per gli indici con spazi vuoti, perché randomizziamo un offset in un intervallo [0, count). MAXviene utilizzato per gestire un caso con una tabella vuota.

Ecco i risultati dei test semplici su una tabella con 16k righe:

sqlite> .timer on
sqlite> select count(*) from payment;
16049
Run Time: real 0.000 user 0.000140 sys 0.000117

sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
14746
Run Time: real 0.002 user 0.000899 sys 0.000132
sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
12486
Run Time: real 0.001 user 0.000952 sys 0.000103

sqlite> select payment_id from payment order by random() limit 1;
3134
Run Time: real 0.015 user 0.014022 sys 0.000309
sqlite> select payment_id from payment order by random() limit 1;
9407
Run Time: real 0.018 user 0.013757 sys 0.000208

4

Ho trovato la seguente soluzione per i grandi database sqlite3 :

SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1; 

La funzione abs (X) restituisce il valore assoluto dell'argomento numerico X.

La funzione random () restituisce un numero intero pseudo-casuale compreso tra -9223372036854775808 e +9223372036854775807.

L'operatore% restituisce il valore intero del suo operando sinistro modulo l'operando destro.

Infine, aggiungi +1 per evitare che rowid sia uguale a 0.


1
Buon tentativo ma non credo che funzionerà. Cosa succede se una riga con rowId = 5 è stata eliminata, ma le rowIds 1,2,3,4,6,7,8,9,10 esistono ancora? Quindi, se il rowId casuale scelto è 5, questa query non restituirà nulla.
Calicoder
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.