Sim, se você tiver lacunas potencialmente grandes nos IDs, a chance de os seus IDs mais baixos serem escolhidos aleatoriamente é muito menor do que seus IDs altos. De fato, a chance de a primeira identificação após a maior lacuna ser escolhida é realmente a mais alta. Portanto, isso não é aleatório por definição.
Lukeocodes
6
Como você obtém 10 linhas aleatórias diferentes? Você precisa definir o limite para 10 e repetir 10 vezes com mysqli_fetch_assoc($result)? Ou esses 10 resultados não são necessariamente distinguíveis?
19414 Adam
12
Random requer uma chance igual para qualquer resultado, em minha mente. ;)
lukeocodes 12/03
4
O artigo completo aborda questões como distribuições desiguais e resultados repetidos.
Bradd Szonye
1
especificamente, se você tiver uma lacuna no início de seus IDs, a primeira será escolhida (min / max-min) do tempo. Nesse caso, um simples ajuste é MAX () - MIN () * RAND + MIN (), o que não é muito lento.
Mateusz - prova pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10leva 0,0010, sem LIMIT 10 levou 0,0012 (nessa tabela 3500 palavras).
Arthur Kushman
26
@zeusakm 3500 palavras não é tanto assim; o problema é que ele explode além de um certo ponto, porque o MySQL precisa classificar TODOS os registros depois de ler cada um; uma vez que essa operação atinge o disco rígido, você pode sentir a diferença.
Jack
16
Não quero me repetir, mas novamente, isso é uma varredura completa da tabela. Na tabela grande, consome muito tempo e memória e pode causar a criação e operação na tabela temporária do disco, o que é muito lento.
Matt
10
Quando eu estava entrevistando o Facebook em 2010, eles me perguntaram como selecionar um registro aleatório de um arquivo enorme de tamanho desconhecido, em uma leitura. Depois de ter uma ideia, é fácil generalizá-la para selecionar vários registros. Então, sim, classificar o arquivo inteiro é ridículo. Ao mesmo tempo, é muito útil. Eu apenas usei essa abordagem para escolher 10 linhas aleatórias de uma tabela com mais de 1.000.000 de linhas. Claro, eu tive que esperar um pouco; mas eu só queria ter uma idéia de como são as linhas típicas nesta tabela ...
osa
27
Consulta simples que possui excelente desempenho e funciona com lacunas :
SELECT*FROM tbl AS t1 JOIN(SELECT id FROM tbl ORDERBY RAND() LIMIT 10)as t2 ON t1.id=t2.id
Essa consulta em uma tabela de 200K demora 0,08s e a versão normal (SELECT * FROM t ORDEM POR RAND () LIMITE 10) leva 0,35s na minha máquina.
Isso é rápido porque a fase de classificação usa apenas a coluna de ID indexado. Você pode ver esse comportamento na explicação:
SELECT * FROM t ORDEM POR RAND () LIMITE 10:
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND () LIMITE 10) como t2 ON t1.id = t2.id
Desculpe, eu testei! desempenho lento em registros de 600k.
Dylan B
@DylanB Atualizei a resposta com um teste.
Ali
17
Estou recebendo consultas rápidas (em torno de 0,5 segundos) com uma CPU lenta , selecionando 10 linhas aleatórias em um tamanho de 2 GB sem cache de 2 GB de banco de dados MySQL de registros de 400 K. Veja aqui meu código: Seleção rápida de linhas aleatórias no MySQL
<?php$time= microtime_float();$sql='SELECT COUNT(*) FROM pages';$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
ORDER BY RAND() LIMIT 0,10";$rquery= BD_Ejecutar($sql);while(list($id)=mysql_fetch_row($rquery)){if($id_in)$id_in.=",$id";else$id_in="$id";}
mysql_free_result($rquery);$sql="SELECT id,url FROM pages WHERE id IN($id_in)";$rquery= BD_Ejecutar($sql);while(list($id,$url)=mysql_fetch_row($rquery)){
logger("$id, $url",1);}
mysql_free_result($rquery);$time= microtime_float()-$time;
logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);?>
Dada a minha mesa mais de 14 milhões de discos, isso é tão lento comoORDER BY RAND()
Fabrizio
5
@ snippetsofcode No seu caso - 400k de linhas, você pode usar "ORDER BY rand ()" simples. Seu truque com 3 consultas é inútil. Você pode reescrevê-lo como "SELECT id, url FROM páginas WHERE id IN (SELECT id FROM páginas ORDER BY rand () LIMIT 10)"
Roman Podlinov
4
Sua técnica ainda faz uma varredura de tabela. Use FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';para ver.
Rick James
4
Tente também executar essa consulta na página de 200 req / s. A concorrência matará você.
Marki555
O benefício @RomanPodlinov sobre isso ORDER BY RAND()é simples: ele classifica apenas os IDs (não linhas completas), portanto, a tabela temporária é menor, mas ainda precisa classificar todos eles.
FYI, order by rand()é muito lenta se a tabela é grande
evilReiko
6
Às vezes, o SLOW é aceito se eu quiser mantê-lo SIMPLES #
A indexação deve ser aplicada na tabela, se for grande.
Muhammad Azeem
1
A indexação não vai ajudar aqui. Os índices são úteis para coisas muito específicas, e essa consulta não é uma delas.
Andrew
13
Do livro:
Escolha uma linha aleatória usando um deslocamento
Outra técnica que evita problemas encontrados nas alternativas anteriores é contar as linhas no conjunto de dados e retornar um número aleatório entre 0 e a contagem. Em seguida, use esse número como um deslocamento ao consultar o conjunto de dados
Isso ajuda um pouco para o MyISAM, mas não para o InnoDB (assumindo que o id é o cluster PRIMARY KEY).
Rick James
7
Bem, se você não possui lacunas em suas chaves e todas elas são numéricas, é possível calcular números aleatórios e selecionar essas linhas. mas isso provavelmente não será o caso.
o que basicamente garantirá que você obtenha um número aleatório no intervalo de suas teclas e selecione o próximo melhor que for maior. você tem que fazer isso 10 vezes.
no entanto, isso NÃO é realmente aleatório, porque suas chaves provavelmente não serão distribuídas uniformemente.
É realmente um grande problema e não é fácil de resolver cumprindo todos os requisitos, o rand () do MySQL é o melhor que você pode obter se você realmente deseja 10 linhas aleatórias.
No entanto, existe outra solução que é rápida, mas também tem uma troca quando se trata de aleatoriedade, mas pode ser mais adequada para você. Leia sobre isso aqui: Como otimizar a função ORDER BY RAND () do MySQL?
A questão é o quão aleatório você precisa.
Você pode explicar um pouco mais para que eu possa lhe dar uma boa solução.
Por exemplo, uma empresa com quem trabalhei tinha uma solução em que precisava de aleatoriedade absoluta extremamente rápida. Eles acabaram preenchendo previamente o banco de dados com valores aleatórios que foram selecionados descendentes e configurados para diferentes valores aleatórios posteriormente novamente.
Se você quase nunca atualizar, também poderá preencher um ID incremental para não ter lacunas e apenas calcular chaves aleatórias antes de selecionar ... Depende do caso de uso!
Oi Joe. Nesse caso em particular, as chaves não devem ter lacunas, mas com o tempo isso pode mudar. E enquanto sua resposta funcionar, ela gerará as 10 linhas aleatórias (desde que eu escreva o limite 10) que são consecutivas e que eu queira mais aleatoriedade, por assim dizer. :) Obrigado.
Francisc
Se você precisar de 10, use algum tipo de união para gerar 10 linhas exclusivas.
johno
Tahts o que eu disse. você precisa executar isso 10 vezes. combiná-lo wition union é uma maneira de colocá-lo em uma consulta. veja meu adendo há 2 minutos.
The Surrican
1
@ TheSurrican, esta solução parece legal, mas é altamente falha . Tente inserir apenas uma muito grande Ide todas as suas consultas aleatórias retornarão essa Id.
Pacerier 9/03/15
1
FLOOR(RAND()*MAX(id))está inclinado a retornar IDs maiores.
Rick James
3
Eu precisava de uma consulta para retornar um grande número de linhas aleatórias de uma tabela bastante grande. Isto é o que eu vim com. Primeiro, obtenha o ID máximo de registro:
SELECT MAX(id)FROM table_name;
Em seguida, substitua esse valor em:
SELECT*FROM table_name WHERE id > FLOOR(RAND()* max) LIMIT n;
Onde max é o ID máximo de registro na tabela en é o número de linhas que você deseja no seu conjunto de resultados. A suposição é que não há lacunas nos IDs de registro, embora eu duvide que isso afetaria o resultado se houvesse (ainda não tentei). Eu também criei esse procedimento armazenado para ser mais genérico; passe o nome da tabela e o número de linhas a serem retornadas. Estou executando o MySQL 5.5.38 no Windows 2008, 32GB, E5450 duplo de 3GHz, e em uma tabela com 17.361.264 linhas, é bastante consistente em ~ 0,03 s / ~ 11 s para retornar 1.000.000 de linhas. (os horários são do MySQL Workbench 6.1; você também pode usar CEIL em vez de FLOOR na segunda instrução de seleção, dependendo da sua preferência)
Quero apontar outra possibilidade de aceleração - cache . Pense por que você precisa obter linhas aleatórias. Provavelmente, você deseja exibir alguma postagem ou anúncio aleatório em um site. Se você está recebendo 100 solicitações / s, é realmente necessário que cada visitante obtenha linhas aleatórias? Normalmente, é perfeitamente bom armazenar em cache essas X linhas aleatórias por 1 segundo (ou até 10 segundos). Não importa se 100 visitantes únicos no mesmo 1 segundo recebem as mesmas postagens aleatórias, porque no segundo seguinte outros 100 visitantes receberão um conjunto diferente de postagens.
Ao usar esse cache, você também pode usar algumas das soluções mais lentas para obter os dados aleatórios, pois eles serão buscados no MySQL apenas uma vez por segundo, independentemente de seus requisitos / s.
Melhorei a resposta que @Riedsio tinha. Esta é a consulta mais eficiente que posso encontrar em uma tabela grande e uniformemente distribuída com lacunas (testada ao obter 1000 linhas aleatórias de uma tabela que possui> 2,6B linhas).
(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max :=(SELECT MAX(id)FROMtable))+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)
Deixe-me descompactar o que está acontecendo.
@max := (SELECT MAX(id) FROM table)
Estou calculando e salvando o lance máx. Para tabelas muito grandes, há uma pequena sobrecarga para calcular MAX(id)cada vez que você precisa de uma linha
SELECT FLOOR(rand() * @max) + 1 as rand)
Obtém um ID aleatório
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
Isso preenche as lacunas. Basicamente, se você selecionar aleatoriamente um número nas lacunas, ele escolherá o próximo ID. Supondo que as lacunas sejam distribuídas uniformemente, isso não deve ser um problema.
Fazer a união ajuda a ajustar tudo em uma consulta para evitar que você faça várias consultas. Também permite salvar a sobrecarga do cálculo MAX(id). Dependendo do seu aplicativo, isso pode importar muito ou muito pouco.
Observe que isso obtém apenas os IDs e os obtém em ordem aleatória. Se você quiser fazer algo mais avançado, recomendo que você faça o seguinte:
SELECT t.id, t.name -- etc, etcFROMtable t
INNERJOIN((SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max :=(SELECT MAX(id)FROMtable))+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)) x ON x.id = t.id
ORDERBY t.id
Eu preciso 30 registros aleatórios, de modo que eu deveria mudar LIMIT 1para LIMIT 30todos os lugares em consulta
Hassaan
@ Hassaan você não deveria, essa mudança LIMIT 1para LIMIT 30você obteria 30 registros seguidos de um ponto aleatório na tabela. Em vez disso, você deve ter 30 cópias da (SELECT id FROM ....peça no meio.
Hans Z
Eu tentei, mas não parece mais eficiente do que Riedsioresponder. Eu tentei com 500 por segundo acessos à página usando PHP 7.0.22 e MariaDB no centos 7, com a Riedsioresposta eu recebi mais de 500 respostas extra bem-sucedidas que a sua resposta.
Hassaan
1
A resposta do @Hassaan riedsio fornece 1 linha, esta fornece n linhas, além de reduzir a sobrecarga de E / S para consultas. Você pode conseguir linhas mais rapidamente, mas com mais carga no seu sistema.
DROP TEMPORARY TABLEIFEXISTS rands;CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt <1THEN
LEAVE loop_me;ENDIF;INSERTINTO rands
SELECT r1.id
FROM random AS r1 JOIN(SELECT(RAND()*(SELECT MAX(id)FROM random))AS id)AS r2
WHERE r1.id >= r2.id
ORDERBY r1.id ASC
LIMIT 1;SET cnt = cnt -1;END LOOP loop_me;
No artigo, ele resolve o problema de lacunas nos IDs, causando resultados não tão aleatórios mantendo uma tabela (usando gatilhos, etc ... consulte o artigo); Estou resolvendo o problema adicionando outra coluna à tabela, preenchida com números contíguos, iniciando em 1 ( editar: esta coluna é adicionada à tabela temporária criada pela subconsulta em tempo de execução, não afeta sua tabela permanente):
DROP TEMPORARY TABLEIFEXISTS rands;CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt <1THEN
LEAVE loop_me;ENDIF;SET@no_gaps_id :=0;INSERTINTO rands
SELECT r1.id
FROM(SELECT id,@no_gaps_id :=@no_gaps_id +1AS no_gaps_id FROM random)AS r1 JOIN(SELECT(RAND()*(SELECT COUNT(*)FROM random))AS id)AS r2
WHERE r1.no_gaps_id >= r2.id
ORDERBY r1.no_gaps_id ASC
LIMIT 1;SET cnt = cnt -1;END LOOP loop_me;
No artigo, posso ver que ele fez um grande esforço para otimizar o código; não tenho idéia se / quanto minhas alterações afetam o desempenho, mas funcionam muito bem para mim.
"não tenho idéia se / quanto minhas alterações afetam o desempenho" - bastante. Para o @no_gaps_idíndice no pode ser usado, portanto, se você procurar EXPLAINsua consulta, você tem Using filesorte Using where(sem índice) as subconsultas, em contraste com a consulta original.
Fabian Schmengler
2
Aqui está uma mudança de jogo que pode ser útil para muitos;
Eu tenho uma tabela com 200k linhas, com IDs seqüenciais , eu precisava escolher N linhas aleatórias, então optei por gerar valores aleatórios com base no maior ID da tabela, criei esse script para descobrir qual é a operação mais rápida:
logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();
Os resultados são:
Contagem: 36.8418693542479ms
Máx: 0.241041183472ms
Ordem: 0.216960906982ms
Com base nesses resultados, o pedido desc é a operação mais rápida para obter o ID máximo.
Aqui está minha resposta para a pergunta:
SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM(SELECT FLOOR(RAND()*(SELECT id FROM tbl ORDERBY id DESC LIMIT 1)) n FROM tbl LIMIT 10) a
...SELECT*FROM tbl WHERE id IN($result);
FYI: Para obter 10 linhas aleatórias de uma tabela de 200k, levei 1,78 ms (incluindo todas as operações no lado do php)
Eu estava pensando sobre a mesma solução, por favor me diga, é mais rápido que o método dos outros?
G. Adnane
@ G.Adnane não é mais rápido ou mais lento que a resposta aceita, mas a resposta aceita assume uma distribuição igual de IDs. Não consigo imaginar nenhum cenário em que isso possa ser garantido. Esta solução está em O (1) onde a solução SELECT column FROM table ORDER BY RAND() LIMIT 10está em O (nlog (n)). Então, sim, esta é a solução em jejum e funciona para qualquer distribuição de IDs.
Adam
não, porque no link postado para a solução aceita, existem outros métodos, quero saber se essa solução é mais rápida que as outras, de outras formas, podemos tentar encontrar outra, por isso estou perguntando, de qualquer forma, +1 para sua resposta. Eu estava usando o samething
G. Adnane
existe um caso em que você deseja obter x número de linhas, mas o deslocamento vai para o final da tabela que retornará <x linhas ou apenas 1 linha. Eu não vi sua resposta antes de postar a minha, mas
deixei
@ZOLDIK, parece que você escolhe as 10 primeiras linhas após o deslocamento x. Eu diria que essa não é uma geração aleatória de 10 linhas. Na minha resposta, você deve executar a consulta na etapa três 10 vezes, ou seja, apenas uma linha é executada por execução e não precisa se preocupar se o deslocamento estiver no final da tabela.
Adam
1
Se você tiver apenas uma solicitação de leitura
Combine a resposta de @redsio com uma tabela temporária (600K não é muito):
DROP TEMPORARY TABLEIFEXISTS tmp_randorder;CREATETABLE tmp_randorder (id int(11)notnull auto_increment primarykey, data_id int(11));INSERTINTO tmp_randorder (data_id)select id from datatable;
E então pegue uma versão do @redsios Answer:
SELECT dt.*FROM(SELECT(RAND()*(SELECT MAX(id)FROM tmp_randorder))AS id)AS rnd
INNERJOIN tmp_randorder rndo on rndo.id between rnd.id -10and rnd.id +10INNERJOIN datatable AS dt on dt.id = rndo.data_id
ORDERBY abs(rndo.id - rnd.id)
LIMIT 1;
Se a mesa for grande, você pode peneirar na primeira parte:
INSERTINTO tmp_randorder (data_id)select id from datatable where rand()<0.01;
Se você tiver muitos pedidos de leitura
Versão: você pode manter a tabela tmp_randorderpersistente, chame-a de datatable_idlist. Recrie essa tabela em determinados intervalos (dia, hora), pois ela também terá furos. Se sua mesa ficar muito grande, você também pode repor buracos
selecione l.data_id como um todo em datatable_idlist l junte à esquerda a tabela de dados dt em dt.id = l.data_id em que dt.id é nulo;
Versão: forneça ao conjunto de dados uma coluna random_sortorder diretamente na tabela de dados ou em uma tabela extra persistente datatable_sortorder. Indexe essa coluna. Gere um valor aleatório em seu aplicativo (eu vou chamá-lo $rand).
select l.*from datatable l
orderby abs(random_sortorder -$rand)desc
limit 1;
Esta solução discrimina as 'linhas de borda' com a ordem aleatória mais alta e mais baixa, portanto, reorganize-as em intervalos (uma vez por dia).
Outra solução simples seria classificar as linhas e buscar uma delas aleatoriamente e, com essa solução, você não precisará ter nenhuma coluna baseada em 'ID' na tabela.
SELECT d.*FROM(SELECT t.*,@rownum :=@rownum +1AS rank
FROM mytable AS t,(SELECT@rownum :=0)AS r,(SELECT@cnt :=(SELECT RAND()*(SELECT COUNT(*)FROM mytable)))AS n
) d WHERE rank >=@cnt LIMIT 10;
Você pode alterar o valor limite conforme sua necessidade de acessar quantas linhas desejar, mas isso geralmente seria valores consecutivos.
No entanto, se você não quiser valores aleatórios consecutivos, poderá buscar uma amostra maior e selecionar aleatoriamente nela. algo como ...
SELECT*FROM(SELECT d.*FROM(SELECT c.*,@rownum :=@rownum +1AS rank
FROM buildbrain.`commits`AS c,(SELECT@rownum :=0)AS r,(SELECT@cnt :=(SELECT RAND()*(SELECT COUNT(*)FROM buildbrain.`commits`)))AS rnd
) d
WHERE rank >=@cnt LIMIT 10000) t ORDERBY RAND() LIMIT 10;
Uma maneira que eu acho muito boa se houver um ID gerado automaticamente é usar o operador de módulo '%'. Por exemplo, se você precisar de 10.000 registros aleatórios em 70.000, poderá simplificar isso dizendo que precisa de 1 em cada 7 linhas. Isso pode ser simplificado nesta consulta:
SELECT*FROMtableWHERE
id %
FLOOR((SELECT count(1)FROMtable)/10000)=0;
Se o resultado da divisão das linhas de destino pelo total disponível não for um número inteiro, você terá algumas linhas extras além das solicitadas, portanto, adicione uma cláusula LIMIT para ajudá-lo a aparar o conjunto de resultados da seguinte forma:
SELECT*FROMtableWHERE
id %
FLOOR((SELECT count(1)FROMtable)/10000)=0
LIMIT 10000;
Isso requer uma verificação completa, mas é mais rápido que ORDER BY RAND e, na minha opinião, é mais simples de entender do que outras opções mencionadas neste tópico. Além disso, se o sistema que grava no banco de dados cria conjuntos de linhas em lotes, você pode não obter um resultado aleatório como o esperado.
Agora que penso assim, se você precisar de linhas aleatórias toda vez que chamá-lo, isso será inútil. Eu só estava pensando na necessidade de obter linhas aleatórias de um conjunto para fazer alguma pesquisa. Eu ainda acho que o módulo é uma boa coisa para ajudar no outro caso. Você poderia usar o módulo como um filtro de primeira passagem para reduzir o custo de uma operação ORDER BY RAND.
Nicolas Cohen
1
Se você deseja um registro aleatório (não importa se existem lacunas entre os IDs):
Examinei todas as respostas e acho que ninguém menciona essa possibilidade, e não sei por que.
Se você deseja maior simplicidade e velocidade, a um custo menor, para mim parece fazer sentido armazenar um número aleatório em cada linha do banco de dados. Basta criar uma coluna extra random_number, e defina como padrão RAND(). Crie um índice nesta coluna.
Então, quando você quiser recuperar uma linha, gere um número aleatório no seu código (PHP, Perl, qualquer que seja) e compare-o com a coluna.
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
Eu acho que, embora seja muito legal para uma única linha, dez linhas como o OP pediram que você tivesse que chamá-lo dez vezes separadas (ou criar um ajuste inteligente que me escapa imediatamente)
Esta é realmente uma abordagem muito agradável e eficiente. O único inconveniente é o fato de você ter trocado espaço por velocidade, o que parece ser um acordo justo na minha opinião.
Tochukwu Nkemdilim
Obrigado. Eu tinha um cenário em que a tabela principal da qual eu queria uma linha aleatória tinha 5 milhões de linhas e muitas junções, e depois de tentar a maioria das abordagens nesta questão, esse foi o argumento em que eu decidi. Uma coluna extra foi uma troca muito interessante para mim.
Codemonkey
0
O seguinte deve ser rápido, imparcial e independente da coluna id. No entanto, não garante que o número de linhas retornadas corresponda ao número de linhas solicitadas.
SELECT*FROM t
WHERE RAND()<(SELECT10/ COUNT(*)FROM t)
Explicação: supondo que você queira 10 linhas de 100, cada linha tem 1/10 de probabilidade de ser SELECTada, o que poderia ser alcançado WHERE RAND() < 0.1. Essa abordagem não garante 10 linhas; mas se a consulta for executada o suficiente, o número médio de linhas por execução será de cerca de 10 e cada linha da tabela será selecionada uniformemente.
PREPARE stm from'select * from table where available=true limit 10 offset ?';SET@total =(select count(*)fromtablewhere available=true);SET@_offset = FLOOR(RAND()*@total);EXECUTE stm using@_offset;
Testado em 600.000 linhas (700MB), a execução da consulta da tabela levou ~ 0,016s da unidade HDD
--EDIT--
O deslocamento pode levar um valor próximo ao final da tabela, o que resultará na instrução select retornando menos linhas (ou talvez apenas 1). linha), para evitar isso, podemos verificar offsetnovamente depois de declará-lo, assim
Inferno não, essa é uma das piores maneiras de obter linhas aleatórias da tabela. Isso é varredura de tabela completa + filesort + tabela tmp = desempenho ruim.
Matt
1
Além do desempenho, também está longe de ser perfeitamente aleatório; você está solicitando pelo produto do ID e um número aleatório, em vez de simplesmente solicitar por um número aleatório, o que significa que as linhas com IDs mais baixos terão tendência a aparecer mais cedo no seu conjunto de resultados.
Respostas:
Um ótimo post para lidar com vários casos, de simples a lacunas, a não uniformes com lacunas.
http://jan.kneschke.de/projects/mysql/order-by-rand/
Para o caso mais geral, eis como você faz isso:
Isso supõe que a distribuição dos IDs seja igual e que possa haver falhas na lista de IDs. Veja o artigo para exemplos mais avançados
fonte
mysqli_fetch_assoc($result)
? Ou esses 10 resultados não são necessariamente distinguíveis?Não é a solução eficiente, mas funciona
fonte
ORDER BY RAND()
é relativamente lentoSELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10
leva 0,0010, sem LIMIT 10 levou 0,0012 (nessa tabela 3500 palavras).Consulta simples que possui excelente desempenho e funciona com lacunas :
Essa consulta em uma tabela de 200K demora 0,08s e a versão normal (SELECT * FROM t ORDEM POR RAND () LIMITE 10) leva 0,35s na minha máquina.
Isso é rápido porque a fase de classificação usa apenas a coluna de ID indexado. Você pode ver esse comportamento na explicação:
SELECT * FROM t ORDEM POR RAND () LIMITE 10:
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND () LIMITE 10) como t2 ON t1.id = t2.id
Versão ponderada : https://stackoverflow.com/a/41577458/893432
fonte
Estou recebendo consultas rápidas (em torno de 0,5 segundos) com uma CPU lenta , selecionando 10 linhas aleatórias em um tamanho de 2 GB sem cache de 2 GB de banco de dados MySQL de registros de 400 K. Veja aqui meu código: Seleção rápida de linhas aleatórias no MySQL
fonte
ORDER BY RAND()
FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';
para ver.ORDER BY RAND()
é simples: ele classifica apenas os IDs (não linhas completas), portanto, a tabela temporária é menor, mas ainda precisa classificar todos eles.É uma consulta de linha muito simples e única.
fonte
order by rand()
é muito lenta se a tabela é grandeDo livro:
Escolha uma linha aleatória usando um deslocamento
Outra técnica que evita problemas encontrados nas alternativas anteriores é contar as linhas no conjunto de dados e retornar um número aleatório entre 0 e a contagem. Em seguida, use esse número como um deslocamento ao consultar o conjunto de dados
Use esta solução quando não puder assumir valores de chave contíguos e precisar garantir que cada linha tenha uma chance uniforme de ser selecionada.
fonte
SELECT count(*)
fica lento.Como selecionar linhas aleatórias de uma tabela:
A partir daqui: Selecione linhas aleatórias no MySQL
Uma rápida melhoria em relação à "verificação de tabela" é usar o índice para selecionar IDs aleatórios.
fonte
PRIMARY KEY
).Bem, se você não possui lacunas em suas chaves e todas elas são numéricas, é possível calcular números aleatórios e selecionar essas linhas. mas isso provavelmente não será o caso.
Portanto, uma solução seria a seguinte:
o que basicamente garantirá que você obtenha um número aleatório no intervalo de suas teclas e selecione o próximo melhor que for maior. você tem que fazer isso 10 vezes.
no entanto, isso NÃO é realmente aleatório, porque suas chaves provavelmente não serão distribuídas uniformemente.
É realmente um grande problema e não é fácil de resolver cumprindo todos os requisitos, o rand () do MySQL é o melhor que você pode obter se você realmente deseja 10 linhas aleatórias.
No entanto, existe outra solução que é rápida, mas também tem uma troca quando se trata de aleatoriedade, mas pode ser mais adequada para você. Leia sobre isso aqui: Como otimizar a função ORDER BY RAND () do MySQL?
A questão é o quão aleatório você precisa.
Você pode explicar um pouco mais para que eu possa lhe dar uma boa solução.
Por exemplo, uma empresa com quem trabalhei tinha uma solução em que precisava de aleatoriedade absoluta extremamente rápida. Eles acabaram preenchendo previamente o banco de dados com valores aleatórios que foram selecionados descendentes e configurados para diferentes valores aleatórios posteriormente novamente.
Se você quase nunca atualizar, também poderá preencher um ID incremental para não ter lacunas e apenas calcular chaves aleatórias antes de selecionar ... Depende do caso de uso!
fonte
Id
e todas as suas consultas aleatórias retornarão essaId
.FLOOR(RAND()*MAX(id))
está inclinado a retornar IDs maiores.Eu precisava de uma consulta para retornar um grande número de linhas aleatórias de uma tabela bastante grande. Isto é o que eu vim com. Primeiro, obtenha o ID máximo de registro:
Em seguida, substitua esse valor em:
Onde max é o ID máximo de registro na tabela en é o número de linhas que você deseja no seu conjunto de resultados. A suposição é que não há lacunas nos IDs de registro, embora eu duvide que isso afetaria o resultado se houvesse (ainda não tentei). Eu também criei esse procedimento armazenado para ser mais genérico; passe o nome da tabela e o número de linhas a serem retornadas. Estou executando o MySQL 5.5.38 no Windows 2008, 32GB, E5450 duplo de 3GHz, e em uma tabela com 17.361.264 linhas, é bastante consistente em ~ 0,03 s / ~ 11 s para retornar 1.000.000 de linhas. (os horários são do MySQL Workbench 6.1; você também pode usar CEIL em vez de FLOOR na segunda instrução de seleção, dependendo da sua preferência)
então
fonte
Todas as melhores respostas já foram publicadas (principalmente as que referenciam o link http://jan.kneschke.de/projects/mysql/order-by-rand/ ).
Quero apontar outra possibilidade de aceleração - cache . Pense por que você precisa obter linhas aleatórias. Provavelmente, você deseja exibir alguma postagem ou anúncio aleatório em um site. Se você está recebendo 100 solicitações / s, é realmente necessário que cada visitante obtenha linhas aleatórias? Normalmente, é perfeitamente bom armazenar em cache essas X linhas aleatórias por 1 segundo (ou até 10 segundos). Não importa se 100 visitantes únicos no mesmo 1 segundo recebem as mesmas postagens aleatórias, porque no segundo seguinte outros 100 visitantes receberão um conjunto diferente de postagens.
Ao usar esse cache, você também pode usar algumas das soluções mais lentas para obter os dados aleatórios, pois eles serão buscados no MySQL apenas uma vez por segundo, independentemente de seus requisitos / s.
fonte
Melhorei a resposta que @Riedsio tinha. Esta é a consulta mais eficiente que posso encontrar em uma tabela grande e uniformemente distribuída com lacunas (testada ao obter 1000 linhas aleatórias de uma tabela que possui> 2,6B linhas).
Deixe-me descompactar o que está acontecendo.
@max := (SELECT MAX(id) FROM table)
MAX(id)
cada vez que você precisa de uma linhaSELECT FLOOR(rand() * @max) + 1 as rand)
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
Fazer a união ajuda a ajustar tudo em uma consulta para evitar que você faça várias consultas. Também permite salvar a sobrecarga do cálculo
MAX(id)
. Dependendo do seu aplicativo, isso pode importar muito ou muito pouco.Observe que isso obtém apenas os IDs e os obtém em ordem aleatória. Se você quiser fazer algo mais avançado, recomendo que você faça o seguinte:
fonte
LIMIT 1
paraLIMIT 30
todos os lugares em consultaLIMIT 1
paraLIMIT 30
você obteria 30 registros seguidos de um ponto aleatório na tabela. Em vez disso, você deve ter 30 cópias da(SELECT id FROM ....
peça no meio.Riedsio
responder. Eu tentei com 500 por segundo acessos à página usando PHP 7.0.22 e MariaDB no centos 7, com aRiedsio
resposta eu recebi mais de 500 respostas extra bem-sucedidas que a sua resposta.Eu usei este http://jan.kneschke.de/projects/mysql/order-by-rand/ publicado por Riedsio (usei o caso de um procedimento armazenado que retorna um ou mais valores aleatórios):
No artigo, ele resolve o problema de lacunas nos IDs, causando resultados não tão aleatórios mantendo uma tabela (usando gatilhos, etc ... consulte o artigo); Estou resolvendo o problema adicionando outra coluna à tabela, preenchida com números contíguos, iniciando em 1 ( editar: esta coluna é adicionada à tabela temporária criada pela subconsulta em tempo de execução, não afeta sua tabela permanente):
No artigo, posso ver que ele fez um grande esforço para otimizar o código; não tenho idéia se / quanto minhas alterações afetam o desempenho, mas funcionam muito bem para mim.
fonte
@no_gaps_id
índice no pode ser usado, portanto, se você procurarEXPLAIN
sua consulta, você temUsing filesort
eUsing where
(sem índice) as subconsultas, em contraste com a consulta original.Aqui está uma mudança de jogo que pode ser útil para muitos;
Eu tenho uma tabela com 200k linhas, com IDs seqüenciais , eu precisava escolher N linhas aleatórias, então optei por gerar valores aleatórios com base no maior ID da tabela, criei esse script para descobrir qual é a operação mais rápida:
Os resultados são:
36.8418693542479
ms0.241041183472
ms0.216960906982
msCom base nesses resultados, o pedido desc é a operação mais rápida para obter o ID máximo.
Aqui está minha resposta para a pergunta:
FYI: Para obter 10 linhas aleatórias de uma tabela de 200k, levei 1,78 ms (incluindo todas as operações no lado do php)
fonte
LIMIT
ligeiramente - você pode obter duplicatas.Isso é super rápido e é 100% aleatório, mesmo se você tiver lacunas.
x
de linhas que você tem disponívelSELECT COUNT(*) as rows FROM TABLE
a_1,a_2,...,a_10
entre 0 ex
SELECT * FROM TABLE LIMIT 1 offset a_i
para i = 1, ..., 10Encontrei esse truque no livro Antipatterns SQL de Bill Karwin .
fonte
SELECT column FROM table ORDER BY RAND() LIMIT 10
está em O (nlog (n)). Então, sim, esta é a solução em jejum e funciona para qualquer distribuição de IDs.x
. Eu diria que essa não é uma geração aleatória de 10 linhas. Na minha resposta, você deve executar a consulta na etapa três 10 vezes, ou seja, apenas uma linha é executada por execução e não precisa se preocupar se o deslocamento estiver no final da tabela.Se você tiver apenas uma solicitação de leitura
Combine a resposta de @redsio com uma tabela temporária (600K não é muito):
E então pegue uma versão do @redsios Answer:
Se a mesa for grande, você pode peneirar na primeira parte:
Se você tiver muitos pedidos de leitura
Versão: você pode manter a tabela
tmp_randorder
persistente, chame-a de datatable_idlist. Recrie essa tabela em determinados intervalos (dia, hora), pois ela também terá furos. Se sua mesa ficar muito grande, você também pode repor buracosselecione l.data_id como um todo em datatable_idlist l junte à esquerda a tabela de dados dt em dt.id = l.data_id em que dt.id é nulo;
Versão: forneça ao conjunto de dados uma coluna random_sortorder diretamente na tabela de dados ou em uma tabela extra persistente
datatable_sortorder
. Indexe essa coluna. Gere um valor aleatório em seu aplicativo (eu vou chamá-lo$rand
).Esta solução discrimina as 'linhas de borda' com a ordem aleatória mais alta e mais baixa, portanto, reorganize-as em intervalos (uma vez por dia).
fonte
Outra solução simples seria classificar as linhas e buscar uma delas aleatoriamente e, com essa solução, você não precisará ter nenhuma coluna baseada em 'ID' na tabela.
Você pode alterar o valor limite conforme sua necessidade de acessar quantas linhas desejar, mas isso geralmente seria valores consecutivos.
No entanto, se você não quiser valores aleatórios consecutivos, poderá buscar uma amostra maior e selecionar aleatoriamente nela. algo como ...
fonte
Uma maneira que eu acho muito boa se houver um ID gerado automaticamente é usar o operador de módulo '%'. Por exemplo, se você precisar de 10.000 registros aleatórios em 70.000, poderá simplificar isso dizendo que precisa de 1 em cada 7 linhas. Isso pode ser simplificado nesta consulta:
Se o resultado da divisão das linhas de destino pelo total disponível não for um número inteiro, você terá algumas linhas extras além das solicitadas, portanto, adicione uma cláusula LIMIT para ajudá-lo a aparar o conjunto de resultados da seguinte forma:
Isso requer uma verificação completa, mas é mais rápido que ORDER BY RAND e, na minha opinião, é mais simples de entender do que outras opções mencionadas neste tópico. Além disso, se o sistema que grava no banco de dados cria conjuntos de linhas em lotes, você pode não obter um resultado aleatório como o esperado.
fonte
Se você deseja um registro aleatório (não importa se existem lacunas entre os IDs):
Fonte: https://www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/#comment-1266
fonte
Examinei todas as respostas e acho que ninguém menciona essa possibilidade, e não sei por que.
Se você deseja maior simplicidade e velocidade, a um custo menor, para mim parece fazer sentido armazenar um número aleatório em cada linha do banco de dados. Basta criar uma coluna extra
random_number
, e defina como padrãoRAND()
. Crie um índice nesta coluna.Então, quando você quiser recuperar uma linha, gere um número aleatório no seu código (PHP, Perl, qualquer que seja) e compare-o com a coluna.
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
Eu acho que, embora seja muito legal para uma única linha, dez linhas como o OP pediram que você tivesse que chamá-lo dez vezes separadas (ou criar um ajuste inteligente que me escapa imediatamente)
fonte
O seguinte deve ser rápido, imparcial e independente da coluna id. No entanto, não garante que o número de linhas retornadas corresponda ao número de linhas solicitadas.
Explicação: supondo que você queira 10 linhas de 100, cada linha tem 1/10 de probabilidade de ser SELECTada, o que poderia ser alcançado
WHERE RAND() < 0.1
. Essa abordagem não garante 10 linhas; mas se a consulta for executada o suficiente, o número médio de linhas por execução será de cerca de 10 e cada linha da tabela será selecionada uniformemente.fonte
Você pode usar facilmente um deslocamento aleatório com um limite
Você também pode aplicar uma cláusula where assim
Testado em 600.000 linhas (700MB), a execução da consulta da tabela levou ~ 0,016s da unidade HDD
--EDIT--
O deslocamento pode levar um valor próximo ao final da tabela, o que resultará na instrução select retornando menos linhas (ou talvez apenas 1). linha), para evitar isso, podemos verificar
offset
novamente depois de declará-lo, assimfonte
Eu uso esta consulta:
tempo de consulta: 0.016s
fonte
É assim que eu faço:
Gosto porque não requer outras tabelas, é simples de escrever e é muito rápido de executar.
fonte
Use a consulta simples abaixo para obter dados aleatórios de uma tabela.
fonte
Eu acho que essa é a melhor maneira possível ..
fonte