Como posso solicitar uma linha aleatória (ou o mais próximo possível verdadeiramente aleatória) em SQL puro?
Como posso solicitar uma linha aleatória (ou o mais próximo possível verdadeiramente aleatória) em SQL puro?
Respostas:
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
order by rand()
ou equivalentes em todos os dbs: |. também mencionado aqui .
ORDER BY RAND()
é errado ...
O(n)
com n
sendo 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 LIMIT
expressão com um único número aleatório.
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_value
estiver 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).
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.
ORDER BY RAND() LIMIT 1
TOP 1
e newid()
.
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.
rand()
retorna um número de ponto flutuante em n
que 0 < n < 1
. Supondo que num_value
seja 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.
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 TABLESAMPLE
para obter uma amostra aleatória que pode ser repetida:
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
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.
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;
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".
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)
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
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.
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!
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.
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.
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/
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.
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()
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()
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.
SELECT * FROM table ORDER BY RAND() LIMIT 1
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.
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.
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
Uma maneira simples e eficiente de http://akinas.com/pages/en/blog/mysql_random_row/
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
Para o SQL Server 2005 e superior, estenda a resposta do @ GreyPanther para os casos em num_value
que 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_value
nã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)
)
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