Pergunta

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

Foi útil?

Solução

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 em MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 e Oracle (o seguinte foi copiado desse link):

Selecione uma linha aleatória com MySQL:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

Selecione uma linha aleatória com 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 IBM DB2

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

Selecione um registro aleatório com Oracle:

SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1

Outras dicas

Soluções como Jeremias:

SELECT * FROM table ORDER BY RAND() LIMIT 1

funcionam, mas precisam de uma varredura sequencial de toda a tabela (porque o valor aleatório associado a cada linha precisa ser calculado - para que o menor possa ser determinado), o que pode ser bastante lento, mesmo para tabelas de tamanho médio.Minha recomendação seria usar algum tipo de coluna numérica indexada (muitas tabelas as têm 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, independente do tamanho da tabela, se num_value está indexado.Uma advertência:isso pressupõe que num_value está 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á 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 NEWID()

leva 7.4 milliseconds

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

leva 0.0065 milliseconds!

Definitivamente irei com o último método.

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

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 seja repetível:

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 precisa gerar um ID para cada linha e depois classificá-los.

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

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

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

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

A coluna SalesOrderID está incluída na expressão de soma de verificação para que o newid () avalie uma vez por linha para obter amostragem por fila.A expressão fundida (soma de verificação (newid (), SalesOrderID) e 0x7ffffff como float / fundd (0x7fffffff como int) avalia para um valor de flutuação aleatório entre 0 e 1.

Quando executado em uma tabela com 1.000.000 de linhas, aqui estão 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 TABLESAMPLE, ele terá 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 em RND() e a criação de um campo de número de registro.

PREPARE RandomRecord FROM "SELECT * FROM table LIMIT ?,1";
SET @n=FLOOR(RAND()*(SELECT COUNT(*) FROM table));
EXECUTE RandomRecord USING @n;

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

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

Esta é a solução empregada pelo código MediaWiki.É claro que há algum preconceito contra valores menores, mas eles descobriram que era suficiente agrupar o valor aleatório em zero quando nenhuma linha é buscada.

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

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

Para SQL Server 2005 e 2008, se quisermos uma amostra aleatória de linhas individuais (de Livros on-line):

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

Instado de usando RAND(), pois não é recomendado, você pode simplesmente obter o ID máximo (=Max):

SELECT MAX(ID) FROM TABLE;

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

My_Generated_Random = rand_in_your_programming_lang_function(1..Max);

e execute este SQL:

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

Observe que ele verificará todas as linhas cujos IDs sejam IGUAL ou SUPERIOR ao valor escolhido.Também é possível procurar a linha na tabela e obter um ID igual ou inferior a My_Generated_Random e, em seguida, modificar a consulta assim:

SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1

Conforme apontado no comentário de @BillKarwin sobre a 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 'rand' para cada valor de chave possível na consulta complexa que está associada a ele, mas ainda limite o número de linhas de 'r' sempre que possível.

O CAST as Integer é especialmente útil para PostgreSQL 9.2, que possui otimização de classificação específica para tipos flutuantes inteiros e de precisão simples.

A maioria das soluções aqui visa evitar a classificação, mas ainda precisam fazer uma varredura sequencial em uma tabela.

Também existe uma maneira de evitar a varredura sequencial alternando para a varredura de índice.Se você souber o valor do índice da sua linha aleatória, poderá obter o resultado quase instantaneamente.O problema é: como adivinhar um valor de índice.

A solução a seguir 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;

Na solução acima, 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 grande impacto no tempo de resposta.

Há também um problema: se você tiver IDs esparsos você pode sentir falta.A solução é tenha um plano de backup :) Neste caso, uma consulta de ordem antiga pura por random().Quando o ID combinado fica assim:

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 o União TODOS cláusula.Neste caso se a primeira parte retornar algum dado a segunda NUNCA será executada!

Tarde, mas cheguei aqui pelo Google, então, para o bem da posteridade, adicionarei uma solução alternativa.

Outra abordagem é usar o TOP duas vezes, com ordens alternadas.Não sei se é "SQL puro", pois usa uma variável no TOP, mas funciona no SQL Server 2008.Aqui está um exemplo que uso 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

Claro, @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 se beneficiará com isso.Outra vantagem é que você pode usá-lo em uma função, já que NEWID() não é permitido.

Por último, 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 new id() função.

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

Para o MySQL obter registros aleatórios

 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, dada uma semente inicial, selecionar o mesmo conjunto de linhas todas as vezes.

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 de NewId():

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

Tempo de execução normalizado:1.02

NewId() é insignificantemente mais lento do que rand(checksum(*)), então talvez você não queira 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 com 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 do 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 ter velocidade.Isso é especialmente útil se você realmente deseja apenas 1 ou um pequeno número de linhas.

SELECT TOP 1 * FROM [table] 
TABLESAMPLE (500 ROWS) 
ORDER BY NEWID()
 SELECT * FROM table ORDER BY RAND() LIMIT 1

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

Eu também uso a técnica "num_value >= RAND() * ..." e, se eu realmente quiser obter resultados aleatórios, tenho uma coluna "aleatória" especial na tabela que atualizo uma vez por dia ou mais.Essa única execução de 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 cada vez que a seleção for executada.

Tenha cuidado porque TableSample na verdade não retorna 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 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.

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

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

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

Parece que muitas das ideias listadas ainda usam ordenação

No entanto, se você usar uma tabela temporária, poderá atribuir um índice aleatório (como muitas das soluções sugeriram) e, em seguida, pegar o primeiro que for 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;

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

Use isto em vez disso:

SELECT *
FROM employee sample(1)
WHERE rownum=1

Para Firebird:

Select FIRST 1 column from table ORDER BY RAND()

Com o SQL Server 2012+ você pode usar o Consulta OFFSET FETCH fazer isso para 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 (afinal, o deslocamento 0 é a primeira linha)

Isso funciona com lacunas nos dados da tabela, desde que você tenha um índice para trabalhar para a cláusula ORDER BY.Também é muito bom para a aleatoriedade - conforme você mesmo resolve isso, mas as pequenas imperfeições em 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 sérios de desempenho em vários milhões de linhas.

Para SQL Server 2005 e superior, estendendo a resposta do @GreyPanther para os casos em que num_value não tem valores contínuos.Isso também funciona para casos em que não temos conjuntos de dados distribuídos 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ê quiser limitar a apenas uma linha, basta adicioná-la no final.

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top