Como solicitar uma linha aleatória no SQL?

510

Como posso solicitar uma linha aleatória (ou o mais próximo possível verdadeiramente aleatória) em SQL puro?

sverrejoh
fonte
Eu costumava sempre fazer isso em php após os resultados da consulta de sql ... esta é provavelmente muito mais rápido para processamento conforme apêndice limite 1 da solução
CheeseConQueso
2
Parece que não existe uma solução "pura SQL" que seja executada em todos os dbms ... existe uma solução para cada um deles.
Manu
Versão de desempenho: stackoverflow.com/questions/4329396/…
Ciro Santilli escreveu

Respostas:

735

Veja esta postagem: SQL para selecionar uma linha aleatória de uma tabela de banco de dados . Ele passa por métodos para fazer isso no MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 e Oracle (o seguinte é copiado desse link):

Selecione uma linha aleatória com o MySQL:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

Selecione uma linha aleatória com o PostgreSQL:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1

Selecione uma linha aleatória com o Microsoft SQL Server:

SELECT TOP 1 column FROM table
ORDER BY NEWID()

Selecione uma linha aleatória com o IBM DB2

SELECT column, RAND() as IDX 
FROM table 
ORDER BY IDX FETCH FIRST 1 ROWS ONLY

Selecione um registro aleatório com o Oracle:

SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
Yaakov Ellis
fonte
30
-1 para confiar order by rand()ou equivalentes em todos os dbs: |. também mencionado aqui .
AD7six
20
Dez anos atrás, um cara disse que o uso ORDER BY RAND()é errado ...
Trejder
ORDER BY NEWID () parece ser notavelmente mais lento no SQL Server. Minha consulta se parece com: selecione as 1000 principais C.CustomerId, CL.LoginName do grupo C de associação interna LinkedAccount LA em C.CustomerId = LA.CustomerId associação interna CustomerLogin CL no grupo C.CustomerId = CL.CustomerId por C.CustomerId, CL. LoginName com contagem (*)> 1 pedido por NEWID () A remoção da linha "pedido por NEWID ()" retorna os resultados muito mais rapidamente.
Ben Power
3
Para SQLite, use a função RANDOM ().
Slam
10
Essas soluções não escalam. Eles são O(n)com nsendo o número de registros na tabela. Imagine que você tem 1 milhão de registros. Deseja realmente gerar 1 milhão de números aleatórios ou IDs exclusivos? Prefiro usar COUNT()e envolver isso em uma nova LIMITexpressão com um único número aleatório.
Christian Hujer
174

Soluções como Jeremies:

SELECT * FROM table ORDER BY RAND() LIMIT 1

funcionam, mas precisam de uma varredura seqüencial de toda a tabela (porque o valor aleatório associado a cada linha precisa ser calculado - para que seja possível determinar o menor), o que pode ser bastante lento para tabelas de tamanho médio. Minha recomendação seria usar algum tipo de coluna numérica indexada (muitas tabelas têm essas como chaves primárias) e depois escrever algo como:

SELECT * FROM table WHERE num_value >= RAND() * 
    ( SELECT MAX (num_value ) FROM table ) 
ORDER BY num_value LIMIT 1

Isso funciona em tempo logarítmico, independentemente do tamanho da tabela, se num_valueestiver indexado. Uma ressalva: isso assume que num_valueé igualmente distribuído no intervalo 0..MAX(num_value). Se o seu conjunto de dados se desviar fortemente dessa suposição, você obterá resultados distorcidos (algumas linhas aparecerão com mais frequência do que outras).

Pantera Cinza
fonte
8
A segunda sugestão não é aleatória. Você não pode prever a linha que será escolhida, mas se tivesse que apostar, apostaria na segunda linha. E você nunca apostaria na última linha, é menos provável que seja escolhido, independentemente da distribuição do seu num_value e do tamanho da sua mesa.
Etienne Racine
1
Eu sei que geralmente as funções RAND () não são de alta qualidade, mas, além disso, você pode explicar por que a seleção não seria aleatória?
Grey Panther
13
O primeiro está errado no SQL Server. A função RAND () é invocada apenas uma vez por consulta, não uma vez por linha. Por isso, sempre seleciona a primeira linha (tente).
Jeff Walker Code Ranger
3
O segundo também assume que todas as linhas são contabilizadas: é possível escolher uma linha que foi excluída.
Sam Rueby
3
@ Sam.Rueby Na verdade, num_value> = RAND () ... o limite 1 garante que as linhas vazias sejam ignoradas até encontrar a linha existente.
Ghord
62

Não sei o quão eficiente isso é, mas já o usei antes:

SELECT TOP 1 * FROM MyTable ORDER BY newid()

Como os GUIDs são bastante aleatórios, a ordem significa que você obtém uma linha aleatória.

Matt Hamilton
fonte
1
Estou usando o servidor MS SQL, SELECT TOP 1 * FROM some_table_name ORDER BY NEWID () funcionou muito bem para mim, obrigado pelo conselho pessoal!
Isso é exatamente a mesma coisa queORDER BY RAND() LIMIT 1
Ken Bloom
6
Isso também é muito específico do banco de dados, pois usa TOP 1e newid().
Cinza
12
Esta é uma má ideia. Este método não usará um índice, a menos que cada coluna seja indexada individualmente. A tabela com 100 milhões de registros pode levar muito tempo para obter um registro.
Interruptor
1
@Switch e que solução você proporia?
Akmal Salikhov
31
ORDER BY NEWID()

leva 7.4 milliseconds

WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)

leva 0.0065 milliseconds!

Definitivamente vou com o último método.

Neel
fonte
2
A segunda opção não seleciona a última linha. Eu não sei por que - apenas apontando.
Voldemort
7
@Voldemort: rand()retorna um número de ponto flutuante em nque 0 < n < 1. Supondo que num_valueseja um número inteiro, o valor de retorno de rand() * max(num_value)também será coagido a um número inteiro, truncando qualquer coisa após o ponto decimal. Por isso, rand() * max(num_value)vai sempre ser inferior max(num_value), razão pela qual a última linha nunca será selecionado.
22615 Ian Kemp
Não serei eficiente se meus dados forem excluídos com frequência - se encontrar uma lacuna, terei que executar novamente a consulta inteira.
Loic Coenen
1
@IanKemp Pergunta estúpida, então por que não usar simplesmente SELECT MAX (num_value) + 1 ?? Como rand (ou RANDOM na maioria dos casos) retorna [0,1), você obtém toda a gama de valores. Além disso, sim, você está certo, precisa corrigir uma consulta.
tekHedd
13

Você não disse qual servidor está usando. Nas versões mais antigas do SQL Server, você pode usar isso:

select top 1 * from mytable order by newid()

No SQL Server 2005 e superior, você pode usar TABLESAMPLEpara obter uma amostra aleatória que pode ser repetida:

SELECT FirstName, LastName
FROM Contact 
TABLESAMPLE (1 ROWS) ;
Jon Galloway
fonte
9
MSDN diz newid () é preferido sobre TABLESAMPLE para resultados verdadeiramente aleatórios: msdn.microsoft.com/en-us/library/ms189108.aspx
Andrew Hedges
7
@Andrew Hedges: ORDER BY NEWID () é muito caro #
Andrei Rînea
10

Para SQL Server

newid () / order by funcionará, mas será muito caro para grandes conjuntos de resultados porque ele precisa gerar um ID para cada linha e, em seguida, classificá-los.

TABLESAMPLE () é bom do ponto de vista de desempenho, mas você obterá agrupamentos de resultados (todas as linhas em uma página serão retornadas).

Para uma amostra aleatória verdadeira com melhor desempenho, a melhor maneira é filtrar as linhas aleatoriamente. Encontrei o seguinte exemplo de código no artigo Manuais Online do SQL Server Limitando os conjuntos de resultados usando o TABLESAMPLE :

Se você realmente deseja uma amostra aleatória de linhas individuais, modifique sua consulta para filtrar linhas aleatoriamente, em vez de usar TABLESAMPLE. Por exemplo, a consulta a seguir usa a função NEWID para retornar aproximadamente um por cento das linhas da tabela Sales.SalesOrderDetail:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

A coluna SalesOrderID é incluída na expressão CHECKSUM para que NEWID () avalie uma vez por linha para obter amostragem por linha. A expressão CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) é avaliada como um valor flutuante aleatório entre 0 e 1.

Quando executado em uma tabela com 1.000.000 de linhas, eis meus resultados:

SET STATISTICS TIME ON
SET STATISTICS IO ON

/* newid()
   rows returned: 10000
   logical reads: 3359
   CPU time: 3312 ms
   elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()

/* TABLESAMPLE
   rows returned: 9269 (varies)
   logical reads: 32
   CPU time: 0 ms
   elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)

/* Filter
   rows returned: 9994 (varies)
   logical reads: 3359
   CPU time: 641 ms
   elapsed time: 627 ms
*/    
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float) 
              / CAST (0x7fffffff AS int)

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

Se você conseguir usar o TABLESAMPLE, ele fornecerá o melhor desempenho. Caso contrário, use o método newid () / filter. newid () / order by deve ser o último recurso se você tiver um grande conjunto de resultados.

Rob Boek
fonte
4

Se possível, use instruções armazenadas para evitar a ineficiência de ambos os índices no RND () e criar um campo de número de registro.

PREPARE RandomRecord FROM "SELECT * FROM tabela LIMIT?, 1";
SET @ n = FLOOR (RAND () * (SELECIONE CONTAGEM (*) DA tabela));
EXECUTE RandomRecord USING @n;
ldrut
fonte
Essa solução também cuida do retorno de linhas aleatórias quando o valor numérico indexado usado na cláusula where acima não é distribuído igualmente; portanto, mesmo que leve quase o mesmo tempo (constante) do uso de onde id_value> = RAND () * MAX (id_value), é melhor.
guido
Tanto quanto posso dizer, isso não é executado em tempo constante, é executado em tempo linear. Na pior das hipóteses, @n é igual ao número de linhas da tabela e "SELECT * FROM table LIMIT?, 1" avalia as linhas @n - 1 até chegar à última.
Andres Riofrio
3

A melhor maneira é colocar um valor aleatório em uma nova coluna apenas para esse fim e usar algo como isto (código pseudo + SQL):

randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")

Esta é a solução empregada pelo código MediaWiki. Obviamente, existe algum viés em relação a valores menores, mas eles descobriram que era suficiente agrupar o valor aleatório em torno de zero quando nenhuma linha é buscada.

A solução newid () pode exigir uma varredura completa da tabela para que cada linha possa receber um novo guia, que terá muito menos desempenho.

A solução rand () pode não funcionar de todo (ou seja, com MSSQL) porque a função será avaliada apenas uma vez e a cada linha será atribuído o mesmo número "aleatório".

Ishmaeel
fonte
1
O agrupamento ao obter 0 resultado fornece uma amostra comprovadamente aleatória (não apenas "suficientemente boa"). Essa solução é quase escalável para consultas com várias linhas (pense em "reprodução aleatória"). O problema é que os resultados tendem a ser selecionados nos mesmos grupos repetidamente. Para contornar isso, você precisaria redistribuir os números aleatórios que você acabou de usar. Você pode trapacear mantendo o controle de randomNo e definindo-o como max (randomness) a partir dos resultados, mas depois p (linha i na consulta 1 E linha i na consulta 2) == 0, o que não é justo. Deixe-me fazer algumas contas e eu voltarei a você com um esquema realmente justo.
31410 alsuren
3

Para o SQL Server 2005 e 2008, se quisermos uma amostra aleatória de linhas individuais (dos Manuais Online ):

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
Santiago Cepas
fonte
3

No caso de usar RAND (), como não é recomendado , você pode simplesmente obter o ID máximo (= Max):

SELECT MAX(ID) FROM TABLE;

obter um aleatório entre 1..Max (= My_Generated_Random)

My_Generated_Random = rand_in_your_programming_lang_function(1..Max);

e, em seguida, execute este SQL:

SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1

Observe que ele verificará todas as linhas com IDs iguais ou superiores ao valor escolhido. Também é possível procurar a linha abaixo da tabela e obter um ID igual ou menor que o My_Generated_Random, e modificar a consulta da seguinte maneira:

SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1
forsberg
fonte
O que aconteceria se o ID aleatório gerado não existir mais na tabela? Linhas excluídas ou passivas que você não deseja mostrar ao usuário podem causar problemas.
Ebleme
Nada. Você obtém o número de identificação mais próximo, não exato. Se você considerar id = 1 a ser removido, troque 1 pelo mínimo.
forsberg
2

Como apontado no comentário de @ BillKarwin na resposta de @ cnu ...

Ao combinar com um LIMIT, descobri que ele tem um desempenho muito melhor (pelo menos com o PostgreSQL 9.1) para JOIN com uma ordem aleatória em vez de ordenar diretamente as linhas reais: por exemplo

SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
       FROM tbl_post
       WHERE create_time >= 1349928000
     ) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100

Apenas certifique-se de que o 'r' gere um valor de 'rand' para todos os possíveis valores de chave na consulta complexa associada a ele, mas ainda assim limite o número de linhas de 'r' sempre que possível.

O CAST como número inteiro é especialmente útil para o PostgreSQL 9.2, que possui otimização de classificação específica para tipos flutuantes de precisão inteira e única.

karmakaze
fonte
1

A maioria das soluções aqui visa evitar a classificação, mas elas ainda precisam fazer uma varredura seqüencial sobre uma tabela.

Também há uma maneira de evitar a varredura seqüencial alternando para a varredura de índice. Se você conhece o valor do índice da sua linha aleatória, pode obter o resultado quase instantaneamente. O problema é - como adivinhar um valor de índice.

A seguinte solução funciona no PostgreSQL 8.4:

explain analyze select * from cms_refs where rec_id in 
  (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
   from generate_series(1,10))
  limit 1;

Acima da solução, você adivinha 10 valores de índice aleatórios diferentes do intervalo 0 .. [último valor do id].

O número 10 é arbitrário - você pode usar 100 ou 1000, pois (surpreendentemente) não tem um grande impacto no tempo de resposta.

Há também um problema - se você tiver identificações esparsas, poderá perder . A solução é ter um plano de backup :) Nesse caso, uma ordem antiga pura por consulta random (). Quando o ID combinado se parece com isso:

explain analyze select * from cms_refs where rec_id in 
    (select (random()*(select last_value from cms_refs_rec_id_seq))::bigint 
     from generate_series(1,10))
    union all (select * from cms_refs order by random() limit 1)
    limit 1;

Não é a cláusula ALL da união . Nesse caso, se a primeira parte retornar algum dado, a segunda NUNCA será executada!

hegemon
fonte
1

No final, mas cheguei aqui pelo Google, então, por motivos de posteridade, adicionarei uma solução alternativa.

Outra abordagem é usar TOP duas vezes, com pedidos alternados. Não sei se é "SQL puro", porque usa uma variável no TOP, mas funciona no SQL Server 2008. Aqui está um exemplo que utilizo em uma tabela de palavras do dicionário, se quiser uma palavra aleatória.

SELECT TOP 1
  word
FROM (
  SELECT TOP(@idx)
    word 
  FROM
    dbo.DictionaryAbridged WITH(NOLOCK)
  ORDER BY
    word DESC
) AS D
ORDER BY
  word ASC

Obviamente, @idx é um número inteiro gerado aleatoriamente que varia de 1 a COUNT (*) na tabela de destino, inclusive. Se sua coluna estiver indexada, você também será beneficiado. Outra vantagem é que você pode usá-lo em uma função, pois NEWID () não é permitido.

Por fim, a consulta acima é executada em cerca de 1/10 do tempo de execução de uma consulta do tipo NEWID () na mesma tabela. YYMV.

alphadogg
fonte
1

Você também pode tentar usar a new id()função

Basta escrever uma consulta e usar ordem por new id()função. É bastante aleatório.

Jai - gotaninterviewcall
fonte
1

Para o MySQL obter registro aleatório

 SELECT name
  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

Mais detalhes http://jan.kneschke.de/projects/mysql/order-by-rand/

Sophy
fonte
Depois de testar muitas das respostas, acredito que essa seja a melhor. Parece ser rápido e escolhe um bom número aleatório de cada vez. Parece semelhante à segunda sugestão de @GreyPanther acima, mas essa resposta seleciona mais números aleatórios.
Jeff Baker
1

Ainda não vi essa variação nas respostas. Eu tinha uma restrição adicional onde precisava, com base em uma semente inicial, para selecionar o mesmo conjunto de linhas a cada vez.

Para MS SQL:

Exemplo mínimo:

select top 10 percent *
from table_name
order by rand(checksum(*))

Tempo de execução normalizado: 1,00

Exemplo NewId ():

select top 10 percent *
from table_name
order by newid()

Tempo de execução normalizado: 1,02

NewId()é insignificantemente mais lento que rand(checksum(*)), portanto, você pode não querer usá-lo em grandes conjuntos de registros.

Seleção com semente inicial:

declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */

select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */

Se você precisar selecionar o mesmo conjunto dado uma semente, isso parece funcionar.

klyd
fonte
1

No MSSQL (testado em 11.0.5569) usando

SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)

é significativamente mais rápido que

SELECT TOP 100 * FROM employee ORDER BY NEWID()
David Knight
fonte
1

No SQL Server, você pode combinar TABLESAMPLE com NEWID () para obter uma boa aleatoriedade e ainda assim ter velocidade. Isso é especialmente útil se você realmente deseja apenas 1 ou um número pequeno de linhas.

SELECT TOP 1 * FROM [table] 
TABLESAMPLE (500 ROWS) 
ORDER BY NEWID()
Chris Arbogast
fonte
1

Com o SQL Server 2012+, você pode usar a consulta OFFSET FETCH para fazer isso em uma única linha aleatória

select  * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY

onde id é uma coluna de identidade e n é a linha que você deseja - calculada como um número aleatório entre 0 e count () - 1 da tabela (o deslocamento 0 é a primeira linha, afinal)

Isso funciona com buracos nos dados da tabela, desde que você tenha um índice para trabalhar com a cláusula ORDER BY. Também é muito bom para a aleatoriedade - enquanto você trabalha para passar, mas as imperfeições de outros métodos não estão presentes. Além disso, o desempenho é muito bom, em um conjunto de dados menor ele se mantém bem, embora eu não tenha tentado testes de desempenho sérios em vários milhões de linhas.

gbjbaanb
fonte
0
 SELECT * FROM table ORDER BY RAND() LIMIT 1
Jeremy Ruten
fonte
Dez anos atrás (2005) um cara disse que o uso ORDER BY RAND()é errado ...
Trejder
0

Eu tenho que concordar com o CD-MaN: Usar "ORDER BY RAND ()" funcionará bem em pequenas tabelas ou quando você fizer o SELECT apenas algumas vezes.

Também uso a técnica "num_value> = RAND () * ..." e, se realmente quero ter resultados aleatórios, tenho uma coluna "aleatória" especial na tabela que atualizo uma vez por dia. Essa única execução UPDATE levará algum tempo (especialmente porque você precisará ter um índice nessa coluna), mas é muito mais rápido do que criar números aleatórios para cada linha sempre que a seleção for executada.

BlaM
fonte
0

Tenha cuidado porque o TableSample não retorna realmente uma amostra aleatória de linhas. Ele direciona sua consulta para examinar uma amostra aleatória das páginas de 8 KB que compõem sua linha. Em seguida, sua consulta é executada com base nos dados contidos nessas páginas. Devido à forma como os dados podem ser agrupados nessas páginas (pedido de inserção etc.), isso pode levar a dados que não são realmente uma amostra aleatória.

Consulte: http://www.mssqltips.com/tip.asp?tip=1308

Esta página do MSDN para TableSample inclui um exemplo de como gerar uma amostra de dados realmente aleatória.

http://msdn.microsoft.com/en-us/library/ms189108.aspx

Sean Turner
fonte
0

Parece que muitas das idéias listadas ainda usam pedidos

No entanto, se você usar uma tabela temporária, poderá atribuir um índice aleatório (como muitas das soluções sugeriram) e pegar o primeiro que seja maior que um número arbitrário entre 0 e 1.

Por exemplo (para DB2):

WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY
DAVID
fonte
2
Depois de considerar esta solução, encontrei uma falha fundamental em minha lógica. Isso retornaria consistentemente os mesmos pequenos valores de configuração, perto do início da tabela, porque presumo que, se houver uma distribuição uniforme entre 0 e 1, há 50% de chance de a primeira linha atender a esse critério.
DAVID
0

Existe uma solução melhor para o Oracle em vez de usar dbms_random.value, enquanto requer varredura completa para ordenar linhas por dbms_random.value e é bastante lento para tabelas grandes.

Use isto:

SELECT *
FROM employee sample(1)
WHERE rownum=1
sev3ryn
fonte
0

Para Firebird:

Select FIRST 1 column from table ORDER BY RAND()
Luigi04
fonte
0

Para o SQL Server 2005 e superior, estenda a resposta do @ GreyPanther para os casos em num_valueque não houver valores contínuos. Isso funciona também para casos em que não distribuímos conjuntos de dados uniformemente e quando num_valuenão é um número, mas um identificador exclusivo.

WITH CTE_Table (SelRow, num_value) 
AS 
(
    SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
) 

SELECT * FROM table Where num_value = ( 
    SELECT TOP 1 num_value FROM CTE_Table  WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)
Endri
fonte
-1

A função aleatória do sql pode ajudar. Além disso, se você deseja limitar a apenas uma linha, basta adicioná-la no final.

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
nvnvashisth
fonte