Selecione a linha aleatória de uma tabela sqlite

119

Tenho uma sqlitetabela com o seguinte esquema:

CREATE TABLE foo (bar VARCHAR)

Estou usando esta tabela como armazenamento para uma lista de strings.

Como seleciono uma linha aleatória desta tabela?

Alex_coder
fonte
multiple stackoverflow.com/questions/4114940/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respostas:

213

Dê uma olhada em Selecionando uma linha aleatória de uma tabela SQLite

SELECT * FROM table ORDER BY RANDOM() LIMIT 1;
Adriaan Stander
fonte
1
Como estender essa solução para um join? Quando uso SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;, sempre obtenho a mesma linha.
Helmut Grohne
É possível semear o número aleatório. por exemplo, Livro do dia semeado com unix epoc para hoje ao meio-dia, de modo que mostra o mesmo livro o dia todo, mesmo se a consulta for executada várias vezes. Sim, eu sei que o cache é mais eficiente para este caso de uso, apenas um exemplo.
danielson317
FWIW minha pergunta está realmente respondida aqui. E a resposta é que você não pode semear o número aleatório. stackoverflow.com/questions/24256258/…
danielson317
31

As soluções a seguir são muito mais rápidas do que as da anktastic (a contagem (*) custa muito, mas se você pode armazená-la em cache, a diferença não deve ser tão grande), que por si só é muito mais rápida do que "ordenar por acaso ()" quando você tem um grande número de linhas, embora tenham alguns inconvenientes.

Se seus rowids estiverem bastante compactados (ou seja, poucas exclusões), você pode fazer o seguinte (usar em (select max(rowid) from foo)+1vez de max(rowid)+1oferece melhor desempenho, conforme explicado nos comentários):

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

Se você tiver buracos, às vezes tentará selecionar um rowid inexistente e o select retornará um conjunto de resultados vazio. Se isso não for aceitável, você pode fornecer um valor padrão como este:

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;

Esta segunda solução não é perfeita: a distribuição de probabilidade é mais alta na última linha (aquela com o rowid mais alto), mas se você adicionar coisas frequentemente à tabela, ela se tornará um alvo móvel e a distribuição de probabilidades deve ser muito melhor.

Ainda outra solução, se você costuma selecionar coisas aleatórias de uma mesa com muitos buracos, então você pode querer criar uma tabela que contém as linhas da tabela original classificadas em ordem aleatória:

create table random_foo(foo_id);

Então, periodicamente, preencha novamente a tabela random_foo

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

E para selecionar uma linha aleatória, você pode usar meu primeiro método (não há buracos aqui). Claro, este último método tem alguns problemas de simultaneidade, mas a reconstrução de random_foo é uma operação de manutenção que provavelmente não acontecerá com frequência.

Ainda, outra maneira, que encontrei recentemente em uma lista de e-mails , é colocar um gatilho em delete para mover a linha com o maior rowid para a linha excluída atual, de modo que nenhum buraco seja deixado.

Por último, observe que o comportamento de rowid e um incremento automático de chave primária inteira não é idêntico (com rowid, quando uma nova linha é inserida, max (rowid) +1 é escolhido, onde é o valor mais alto já visto + 1 para uma chave primária), então a última solução não funcionará com um incremento automático em random_foo, mas os outros métodos sim.

Suzanne Dupéron
fonte
Como acabei de ver em uma lista de e-mails, em vez de ter o método fallback (método 2), você pode apenas usar rowid> = [random] em vez de =, mas na verdade é extremamente lento em comparação com o método 2.
Suzanne Dupéron
3
Esta é uma ótima resposta; no entanto, tem um problema. SELECT max(rowid) + 1será uma consulta lenta - requer uma verificação completa da tabela. sqlite apenas otimiza a consulta SELECT max(rowid). Assim, esta resposta seria melhorada por: select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)); Veja isto para mais informações: sqlite.1065341.n5.nabble.com/…
dasl
19

Você precisa colocar "ordem por RANDOM ()" em sua consulta.

Exemplo:

select * from quest order by RANDOM();

Vamos ver um exemplo completo

  1. Crie uma tabela:
CREATE TABLE  quest  (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    quest TEXT NOT NULL,
    resp_id INTEGER NOT NULL
);

Inserindo alguns valores:

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

Uma seleção padrão:

select * from quest;

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

Uma seleção aleatória:

select * from quest order by RANDOM();
| id |   quest  | resp_id |
   3     128/1       24
   1     1024/4       6
   2     256/2       12
--
* Cada vez que você selecionar, o pedido será diferente.

Se você quiser retornar apenas uma linha

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
* Cada vez que você selecionar, o retorno será diferente.

Roberto Góes
fonte
Embora as respostas apenas de código não sejam proibidas, por favor, entenda que esta é uma comunidade de perguntas e respostas, e não uma comunidade de crowdsourcing e que, normalmente, se o OP entendesse o código postado como uma resposta, ele / ela teria surgido com uma solução semelhante por conta própria e não teria postado uma pergunta em primeiro lugar. Sendo assim, forneça contexto para sua resposta e / ou código explicando como e / ou por que funciona.
XenoRo
2
Prefiro essa solução, pois me permite pesquisar n linhas. No meu caso, eu precisava de 100 amostras aleatórias do banco de dados - ORDER BY RANDOM () combinado com LIMIT 100 faz exatamente isso.
mnr
17

A respeito:

SELECT COUNT(*) AS n FROM foo;

em seguida, escolha um número aleatório m em [0, n) e

SELECT * FROM foo LIMIT 1 OFFSET m;

Você pode até salvar o primeiro número ( n ) em algum lugar e apenas atualizá-lo quando a contagem do banco de dados mudar. Dessa forma, você não precisa fazer SELECT COUNT todas as vezes.

Andres Kievsky
fonte
1
Esse é um bom método rápido. Ele não generaliza muito bem para selecionar mais de 1 linha, mas o OP pede apenas 1, então acho que está tudo bem.
Ken Williams
Uma coisa curiosa a notar é que o tempo necessário para encontrar o OFFSETparece aumentar dependendo do tamanho do deslocamento - a linha 2 é rápida, a linha 2 milhões demora um pouco, mesmo quando todos os dados no são de tamanho fixo e deve ser capaz de procurá-lo diretamente. Pelo menos, é o que parece no SQLite 3.7.13.
Ken Williams
@KenWilliams Praticamente todos os bancos de dados têm o mesmo problema com `OFFSET``. É uma maneira muito ineficiente de consultar um banco de dados porque ele precisa ler esse número de linhas, embora retorne apenas 1.
Jonathan Allen
1
Observe que eu estava falando sobre / tamanho fixo / registros - deve ser fácil escanear diretamente para o byte correto nos dados ( não lendo tantas linhas), mas eles teriam que implementar a otimização explicitamente.
Ken Williams,
@KenWilliams: não há registros de tamanho fixo no SQLite, ele é digitado dinamicamente e os dados não precisam corresponder às afinidades declaradas ( sqlite.org/fileformat2.html#section_2_1 ). Tudo é armazenado nas páginas da árvore b, então de qualquer forma ele tem que fazer pelo menos uma pesquisa da árvore b em direção à folha. Para fazer isso com eficiência, seria necessário armazenar o tamanho da subárvore junto com cada ponteiro filho. Seria uma sobrecarga para poucos benefícios, já que você ainda não será capaz de otimizar o OFFSET para junções, ordenar por, etc ... (e sem ORDENAR POR a ordem é indefinida.)
Yakov Galka
13
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1
Svetlozar Angelov
fonte
11
Como ele selecionará todo o conteúdo da tabela primeiro, isso não consumiria muito tempo para tabelas grandes?
Alex_coder
1
Você não pode simplesmente limitar o escopo usando condição (ões) "WHERE"?
jldupont
11

Aqui está uma modificação da solução de @ank:

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

Essa solução também funciona para índices com lacunas, porque randomizamos um deslocamento em um intervalo [0, contagem). MAXé usado para lidar com um caso com mesa vazia.

Aqui estão resultados de teste simples em uma tabela com 16 mil linhas:

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
vokilam
fonte
4

Eu vim com a seguinte solução para os grandes bancos de dados sqlite3 :

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

A função abs (X) retorna o valor absoluto do argumento numérico X.

A função random () retorna um número inteiro pseudoaleatório entre -9223372036854775808 e +9223372036854775807.

O operador% produz o valor inteiro de seu operando esquerdo módulo de seu operando direito.

Finalmente, você adiciona +1 para evitar que rowid seja igual a 0.

Max
fonte
1
Boa tentativa, mas acho que não vai funcionar. E se uma linha com rowId = 5 fosse excluída, mas rowIds 1,2,3,4,6,7,8,9,10 ainda existisse? Então, se o rowId aleatório escolhido for 5, esta consulta não retornará nada.
Calicoder de