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.
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:
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:
createtable random_foo(foo_id);
Então, periodicamente, preencha novamente a tabela random_foo
deletefrom random_foo;insertinto 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.
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 orderby RANDOM();
Vamos ver um exemplo completo
Crie uma tabela:
CREATETABLE quest (
id INTEGER PRIMARYKEY AUTOINCREMENT,
quest TEXT NOTNULL,
resp_id INTEGER NOTNULL);
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.
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.)
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.000user0.000140 sys 0.000117
sqlite>select payment_id from payment limit 1 offset abs(random())%(select count(*)from payment);14746
Run Time: real 0.002user0.000899 sys 0.000132
sqlite>select payment_id from payment limit 1 offset abs(random())%(select count(*)from payment);12486
Run Time: real 0.001user0.000952 sys 0.000103
sqlite>select payment_id from payment orderby random() limit 1;3134
Run Time: real 0.015user0.014022 sys 0.000309
sqlite>select payment_id from payment orderby random() limit 1;9407
Run Time: real 0.018user0.013757 sys 0.000208
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.
Respostas:
Dê uma olhada em Selecionando uma linha aleatória de uma tabela SQLite
fonte
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.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)+1
vez demax(rowid)+1
oferece melhor desempenho, conforme explicado nos comentários):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:
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:
Então, periodicamente, preencha novamente a tabela random_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.
fonte
SELECT max(rowid) + 1
será uma consulta lenta - requer uma verificação completa da tabela. sqlite apenas otimiza a consultaSELECT 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/…Você precisa colocar "ordem por RANDOM ()" em sua consulta.
Exemplo:
Vamos ver um exemplo completo
Inserindo alguns valores:
Uma seleção padrão:
Uma seleção aleatória:
* Cada vez que você selecionar, o pedido será diferente.Se você quiser retornar apenas uma linha
* Cada vez que você selecionar, o retorno será diferente.fonte
A respeito:
em seguida, escolha um número aleatório m em [0, n) e
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.
fonte
OFFSET
parece 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.fonte
Aqui está uma modificação da solução de @ank:
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:
fonte
Eu vim com a seguinte solução para os grandes bancos de dados sqlite3 :
Finalmente, você adiciona +1 para evitar que rowid seja igual a 0.
fonte