MySQL seleciona 10 linhas aleatórias de 600K linhas rapidamente


Respostas:


386

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:

SELECT name
  FROM random AS r1 JOIN
       (SELECT CEIL(RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

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


52
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.
Code Abominator

343
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

Não é a solução eficiente, mas funciona


139
ORDER BY RAND()é relativamente lento
Mateusz Charytoniuk

7
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 ORDER BY 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: Simple Explain

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND () LIMITE 10) como t2 ON t1.id = t2.id insira a descrição da imagem aqui

Versão ponderada : https://stackoverflow.com/a/41577458/893432


1
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);
?>

11
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.
Marki555

16

É uma consulta de linha muito simples e única.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;

21
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

<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();

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.


1
para mesas muito grandes, SELECT count(*)fica lento.
Hans Z

7

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.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;

1
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.

Portanto, uma solução seria a seguinte:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

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)

DELIMITER $$

USE [schema name] $$

DROP PROCEDURE IF EXISTS `random_rows` $$

CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN

SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @t = CONCAT(
    'SELECT * FROM ',
    tab_name,
    ' WHERE id>FLOOR(RAND()*@max) LIMIT ',
    num_rows);

PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$

então

CALL [schema name].random_rows([table name], n);

3

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.


3

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 FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)

Deixe-me descompactar o que está acontecendo.

  1. @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
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Obtém um ID aleatório
  3. 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, etc
FROM table t
INNER JOIN (
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY 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.
Hans Z

3

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

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        INSERT INTO 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
            ORDER BY 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 TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        SET @no_gaps_id := 0;

        INSERT INTO rands
           SELECT r1.id
             FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS 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
            ORDER BY 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 ORDER BY 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)


3
Sugira que você aumente LIMITligeiramente - você pode obter duplicatas.
Rick James

2

Isso é super rápido e é 100% aleatório, mesmo se você tiver lacunas.

  1. Conte o número xde linhas que você tem disponívelSELECT COUNT(*) as rows FROM TABLE
  2. Escolha 10 números aleatórios distintos a_1,a_2,...,a_10entre 0 ex
  3. Consulte suas linhas assim: SELECT * FROM TABLE LIMIT 1 offset a_ipara i = 1, ..., 10

Encontrei esse truque no livro Antipatterns SQL de Bill Karwin .


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 TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO 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
 INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
 INNER JOIN datatable AS dt on dt.id = rndo.data_id
 ORDER BY abs(rndo.id - rnd.id)
 LIMIT 1;

Se a mesa for grande, você pode peneirar na primeira parte:

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

Se você tiver muitos pedidos de leitura

  1. 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;

  2. 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 
    order by 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).


1

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 + 1 AS 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 + 1 AS 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 ORDER BY RAND() LIMIT 10;

1

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 * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 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 * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 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.


2
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

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() < (SELECT 10 / 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.


0

Você pode usar facilmente um deslocamento aleatório com um limite

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

Você também pode aplicar uma cláusula where assim

PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where 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

SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;

-1

Eu uso esta consulta:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

tempo de consulta: 0.016s


Ter PKs como 1,2,9,15. pela consulta acima, você obterá linhas como 4, 7, 14, 11 que são insuficientes!
Junaid Atari

-2

É assim que eu faço:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

Gosto porque não requer outras tabelas, é simples de escrever e é muito rápido de executar.


5
Essa é a varredura completa da tabela e não usa nenhum índice. Para mesas grandes e ambiente movimentado, isso é grande, não, não.
Matt

-2

Use a consulta simples abaixo para obter dados aleatórios de uma tabela.

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10

Se você deseja usar qualquer instrução de junção e o filtro de onde você pode usar.
MANOJ 24/02

3
De qual parte da consulta você obtém a aleatoriedade?
Marki555

-4

Eu acho que essa é a melhor maneira possível ..

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no

8
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.
Mark Amery
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.