Amostras aleatórias simples de uma base de Dados Sql

Como é que tiro uma amostra aleatória eficiente em SQL? O banco de dados em questão está executando MySQL; minha tabela é de pelo menos 200.000 linhas, e eu quero uma amostra aleatória simples de cerca de 10.000.

a resposta" óbvia " é para:

SELECT * FROM table ORDER BY RAND() LIMIT 10000

para mesas grandes, isso é muito lento: ele chama RAND() para cada linha (que já coloca em O(n)), e ordená-los, tornando-o o(n lg n) no melhor dos casos. Há alguma maneira de fazer isto mais rápido do que O(n)?

nota : como salienta Andrew Mao nos comentários, se estiver a usar esta abordagem no servidor SQL, deverá usar a função T-SQL NEWID (), porque o RAND () poderá devolver o mesmo valor para todas as linhas .

EDITAR: 5 ANOS DEPOIS

encontrei este problema novamente com uma mesa maior, e acabei usando uma versão da solução de @ignorant, com dois ajustes:

  • amostrar as linhas para 2-5x o meu tamanho de amostra desejado, para uma ordem barata por RAND ()
  • Guarde o resultado da RAND () para uma indexação coluna em cada inserção / actualização. (Se seu conjunto de dados não é muito update-heavy, você pode precisar encontrar outra maneira de manter esta coluna fresca.)

para obter uma amostra de 1000 itens de uma tabela, conto as linhas e a amostra do resultado até, em média, 10 000 linhas com a coluna frozen_rand:

SELECT COUNT(*) FROM table; -- Use this to determine rand_low and rand_high

  SELECT *
    FROM table
   WHERE frozen_rand BETWEEN %(rand_low)s AND %(rand_high)s
ORDER BY RAND() LIMIT 1000
(Minha implementação real envolve mais trabalho para garantir que eu não subestime, e para enrolar manualmente o rand_high, mas a idéia básica é " aleatoriamente cortar seu N para baixo para alguns mil.")

Embora isto faça alguns sacrifícios, permite-me analisar a base de dados usando uma pesquisa de índice, até que seja pequena o suficiente para pedir pela RAND() novamente.

Author: ojrac, 2008-10-30

9 answers

Há uma discussão muito interessante sobre este tipo de assunto aqui.: http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/ [[[2]] eu acho que sem qualquer suposição sobre a tabela que a sua solução O(n lg n) é a melhor. Embora na verdade com um bom otimizador ou uma técnica ligeiramente diferente a consulta que você lista pode ser um pouco melhor, O(m*n) onde m é o número de linhas aleatórias desejadas, como não seria necessário tenho que ordenar toda a grande matriz, ele poderia apenas procurar o menor M vezes. Mas para o tipo de números que você postou, m é maior que lg n de qualquer maneira. Três pressupostos que podemos experimentar:
  1. Há uma chave única, indexada, primária na tabela

  2. O número de linhas aleatórias que deseja seleccionar (m) é muito menor do que o número de linhas na tabela (n)

  3. A chave primária única é um inteiro que varia de 1 A n, sem aberturas

Com apenas suposições 1 e 2 eu acho que isso pode ser feito em O(n), Embora você vai precisar escrever um índice inteiro para a tabela para igualar a suposição 3, por isso não é necessário um rápido O(n). Se nós pudermos adicionalmente assumir algo agradável sobre a mesa, nós podemos fazer a tarefa em O(M log m). Suposição 3 seria uma fácil agradável propriedade adicional para trabalhar. Com um bom gerador de números aleatórios que garante nenhuma duplicação ao gerar números m em uma linha, uma solução O(m) seria possível.

Dadas as três hipóteses, a ideia básica é gerar m números aleatórios únicos entre 1 e n, e depois seleccionar as linhas com essas chaves da tabela. Não tenho mysql nem nada à minha frente neste momento, por isso, em pseudocódigo ligeiramente, isto pareceria algo como:


create table RandomKeys (RandomKey int)
create table RandomKeysAttempt (RandomKey int)

-- generate m random keys between 1 and n
for i = 1 to m
  insert RandomKeysAttempt select rand()*n + 1

-- eliminate duplicates
insert RandomKeys select distinct RandomKey from RandomKeysAttempt

-- as long as we don't have enough, keep generating new keys,
-- with luck (and m much less than n), this won't be necessary
while count(RandomKeys) &lt m
  NextAttempt = rand()*n + 1
  if not exists (select * from RandomKeys where RandomKey = NextAttempt)
    insert RandomKeys select NextAttempt

-- get our random rows
select *
from RandomKeys r
join table t ON r.RandomKey = t.UniqueKey

Se você estava realmente preocupado com a eficiência, você poderia considerar fazer a geração chave aleatória em algum tipo de linguagem processual e inserir os resultados no banco de dados, como quase qualquer outra coisa além de SQL provavelmente seria melhor no tipo de looping e geração aleatória de números necessários.

 20
Author: user12861, 2008-10-31 04:12:38

Acho que a solução mais rápida é

select * from table where rand() <= .3
Eis a razão pela qual acho que isto deve servir.
  • criará um número aleatório para cada linha. O número está entre 0 e 1
  • ele avalia se deve mostrar essa linha se o número gerado está entre 0 e .3 (30%).
Isto assume que rand() está gerando números em uma distribuição uniforme. É a forma mais rápida de o fazer. Vi que alguém tinha recomendado essa solução e eles ... foi abatido sem provas.. eis o que eu diria a isso ...
  • Isto é O(n) mas não é necessária nenhuma ordenação por isso é mais rápido que o o (n lg n)
  • O Mysql é muito capaz de gerar números aleatórios para cada linha. Tenta isto.

    Selecione rand () de INFORMATION_SCHEMA.Limite das tabelas 10;

Uma vez que o banco de dados em questão é o mySQL, esta é a solução certa.
 36
Author: ignorant, 2014-11-24 16:11:59

Mais rápido que a ordem de RAND ()

Eu testei este método para ser muito mais rápido do que {[[2]}, por isso ele funciona em o(n) tempo, e faz isso impressionantemente rápido.

De http://technet.microsoft.com/en-us/library/ms189108%28v=sql.105%29.aspx:

Non-MSSQL version -- I did not test this

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= RAND()

Versão MSSQL:

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

Isto irá seleccionar ~1% dos registos. Por isso, se precisar de um número exacto de percentagens ou registos a seleccionar, estimar a sua percentagem com alguma margem de segurança e, em seguida, arrancar aleatoriamente os registos de excesso do conjunto resultante, usando o método mais caro ORDER BY RAND().

Ainda Mais Rápido

Fui capaz de melhorar este método ainda mais porque tinha um conhecido intervalo de valores indexados.

Por exemplo, se tiver uma coluna indexada com inteiros uniformemente distribuídos [0..max], você pode usar isso para selecionar aleatoriamente N pequenos intervalos. Faça isso dinamicamente em seu programa para obter um diferente definir para cada pesquisa executada. Esta seleção de subconjuntos será o (n) , que pode muitas ordens de magnitude menor do que o seu conjunto completo de dados.

No meu teste reduzi o tempo necessário para obter 20 (20 mil) registos de amostras de 3 minutos usando a ordem de RAND () para 0, 0 segundos!

 5
Author: Muposat, 2014-09-10 20:29:05

Aparentemente em algumas versões do SQL há um comando {[[0]}, mas não está em todas as implementações do SQL (notavelmente, Redshift).

Http://technet.microsoft.com/en-us/library/ms189108 (V=sql.105).aspx

 3
Author: gatoatigrado, 2014-05-01 00:24:10

Usa apenas

WHERE RAND() < 0.1 

Para obter 10% dos registos ou

WHERE RAND() < 0.01 

Para obter 1% dos registos, etc.

 2
Author: David F Mayer, 2014-04-29 06:03:44

Começando com a observação de que podemos recuperar os ids de uma tabela (por exemplo. Contagem 5) com base num conjunto:

select *
from table_name
where _id in (4, 1, 2, 5, 3)

Podemos chegar ao resultado de que se pudéssemos gerar a cadeia "(4, 1, 2, 5, 3)", então teríamos uma maneira mais eficiente do que RAND().

Por exemplo, em Java:

ArrayList<Integer> indices = new ArrayList<Integer>(rowsCount);
for (int i = 0; i < rowsCount; i++) {
    indices.add(i);
}
Collections.shuffle(indices);
String inClause = indices.toString().replace('[', '(').replace(']', ')');

Se os ids têm lacunas, então o arraylist inicial indices é o resultado de uma consulta sql sobre ids.

 0
Author: KitKat, 2013-09-07 07:53:52
Quero salientar que todas estas soluções parecem ser Amostras sem substituição. Se seleccionar as linhas de topo K de uma ordenação aleatória ou se juntar a uma tabela que contenha chaves únicas em ordem aleatória, irá obter uma amostra aleatória gerada sem substituição. Se quer que a sua amostra seja independente, terá de a substituir. Ver questão 25451034 por um exemplo de como fazer isto usando uma junção de uma forma semelhante à solução do utilizador12861. A solução é escrito para T-SQL, mas o conceito funciona em qualquer SQL db.
 0
Author: gazzman, 2017-05-23 12:32:21

Se precisar exactamente m linhas, realisticamente irá gerar o seu subconjunto de IDs fora do SQL. A maioria dos métodos requerem em algum ponto para selecionar a entrada "nth", e tabelas SQL realmente não são arrays em tudo. A suposição de que as chaves são consecutivas, a fim de apenas juntar ints aleatórios entre 1 e a contagem também é difícil de satisfazer - MySQL por exemplo não suporta nativamente, e as condições de bloqueio são... Complicado.

Aqui está um tempo, um espaço. solução assumindo apenas as teclas BTREE simples:
  1. obtenha todos os valores da coluna-chave da tabela de dados em qualquer ordem para uma lista na sua linguagem de programação favorita em O(n)
  2. execute um baralhar Fisher-Yates , parando depois de swaps m, e extraia o subarray [0:m-1] em ϴ(m)
  3. "juntar" o subarray com o conjunto de dados original (por exemplo SELECT ... WHERE id IN (<subarray>)) em O(m lg n)

Qualquer método que gera o subconjunto Aleatório fora da SQL deve ter pelo menos isto complexidade. A junção não pode ser mais rápida do que O(m lg n) com o BTREE (então O(m) reivindicações são Fantasia para a maioria dos motores) e a baralha é limitada abaixo de n e m lg n e não afeta o comportamento assintótico.

Em pseudocódigo Pythónico:

ids = sql.query('SELECT id FROM t')
for i in range(m):
  r = int(random() * (len(ids) - i))
  ids[i], ids[i + r] = ids[i + r], ids[i]

results = sql.query('SELECT * FROM t WHERE id IN (%s)' % ', '.join(ids[0:m-1])
 0
Author: concat, 2017-11-22 17:39:40
Talvez pudesses fazer ...
SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)
 -2
Author: staticsan, 2008-10-30 05:29:34