Amostras aleatórias simples de um banco de dados SQL

votos
58

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

A resposta óbvio é:

SELECT * FROM table ORDER BY RAND() LIMIT 10000

Para grandes tabelas, que é muito lento: ele chama RAND () para cada linha (que já o coloca em O (n)), e classifica-los, tornando-O (n lg n) na melhor das hipóteses. Existe uma maneira de fazer isso mais rápido do que O (n)?

Nota : Como assinala Andrew Mao nos comentários, Se você estiver usando esta abordagem em SQL Server, você deve usar a função NEWID T-SQL (), porque RAND () pode retornar o mesmo valor para todas as linhas .

EDIT: 5 ANOS DEPOIS

Corri para este problema novamente com uma mesa maior, e acabou usando uma versão do @ solução de ignorante, com dois ajustes:

  • Amostra as linhas a 2-5x meu tamanho da amostra desejada, para a ordem de forma barata, RAND ()
  • Salve o resultado da RAND () para uma coluna indexada em cada insert / update. (Se o seu conjunto de dados não é muito update-pesado, você pode precisar de encontrar outra maneira de manter esta coluna fresco.)

Para ter uma amostra de 1000 peça de uma mesa, eu conto as linhas e provar o resultado para baixo para, 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

(Meu aplicação efectiva envolve mais trabalho para se certificar de que eu não undersample, e para envolver manualmente rand_high ao redor, mas a idéia básica é cortar aleatoriamente seu N até alguns milhares.)

Enquanto isso faz alguns sacrifícios, que me permite experimentar o banco de dados para baixo usando uma varredura de índice, até que é pequeno o suficiente para ORDER BY RAND () novamente.

Publicado 30/10/2008 em 05:48
fonte usuário
Em outras línguas...                            


10 respostas

votos
-2

Talvez você poderia fazer

SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)
Respondeu 30/10/2008 em 06:29
fonte usuário

votos
19

Há uma discussão muito interessante deste tipo de problema aqui: http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

Eu acho que com absolutamente nenhuma suposições sobre a tabela que a sua solução O (n lg n) é o melhor. Embora, na verdade, com um bom otimizador ou uma técnica ligeiramente diferente a consulta que lista pode ser um pouco melhor, O (m * n) onde m é o número de linhas aleatórias desejado, uma vez que não necesssarily tem que classificar a matriz inteira grande , ele só poderia procurar os menores m vezes. Mas, para o tipo de números que você postou, m é maior do que lg n qualquer maneira.

Três asumptions podemos experimentar:

  1. há um, indexado chave primária única na tabela

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

  3. a chave primária única é um número inteiro que varia de 1 a n com o sem lacunas

Com apenas hipóteses 1 e 2 Acho que isso pode ser feito em O (n), embora você precisará escrever um índice de toda a tabela para corresponder suposição 3, por isso não é necesarily a O rápido (n). Se nós pode ainda assumir outra coisa agradável sobre a mesa, nós podemos fazer a tarefa em O (m log m). Assunção 3 seria uma propriedade adicional agradável fácil de trabalhar. Com um gerador de números aleatórios agradável que garantida não há duplicados ao gerar números m consecutivas, uma (m) ó solução seria possível.

Dadas as três hipóteses, a idéia básica é gerar m números aleatórios exclusivos entre 1 e n, e, em seguida, selecione as linhas com as chaves da tabela. Eu não tenho mysql ou qualquer coisa na minha frente agora, então em pouco Pseudocódigo isso seria algo parecido com:


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) < 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ê estivesse realmente preocupado com eficiência, você pode considerar fazer a geração chave aleatória em algum tipo de linguagem procedural e inserir os resultados no banco de dados, como quase qualquer coisa diferente de SQL provavelmente seria melhor para o tipo de looping e geração de números aleatórios necessários .

Respondeu 31/10/2008 em 04:59
fonte usuário

votos
2

Apenas use

WHERE RAND() < 0.1 

para obter 10% dos registros ou

WHERE RAND() < 0.01 

para obter 1% dos registros, etc.

Respondeu 18/05/2012 em 18:11
fonte usuário

votos
33

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

select * from table where rand() <= .3

Aqui é por isso que eu acho que isso deve fazer o trabalho.

  • Ele vai criar um número aleatório para cada linha. O número situa-se entre 0 e 1
  • Ele avalia se deve exibir essa linha se o número gerado é entre 0 e 0,3 (30%).

Isto assume que rand () é a geração de números de uma distribuição uniforme. É a maneira mais rápida de fazer isso.

Eu vi que alguém tinha recomendado essa solução e eles ficou abatido sem prova .. aqui é o que eu diria a isso -

  • Este é O (n), mas nenhuma classificação é necessária por isso, é mais rápido do que o S (n lg n)
  • mysql é muito capaz de gerar números aleatórios para cada linha. Tente isto -

    seleccionar rand () INFORMATION_SCHEMA.TABLES de limite 10;

Desde que o banco de dados em questão é mySQL, esta é a solução certa.

Respondeu 31/01/2013 em 16:43
fonte usuário

votos
0

Começando com a observação de que podemos recuperar os ids de uma tabela (. Por exemplo, contar 5) com base em um conjunto:

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

podemos chegar ao resultado que, se pudéssemos gerar a string "(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 ids têm lacunas, em seguida, o ArrayList inicial indicesé o resultado de uma consulta SQL em ids.

Respondeu 07/09/2013 em 08:53
fonte usuário

votos
3

Aparentemente, em algumas versões do SQL há um TABLESAMPLEcomando, mas não é em todas as implementações de SQL (nomeadamente, Redshift).

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

Respondeu 01/05/2014 em 01:24
fonte usuário

votos
0

Quero salientar que todas essas soluções parecem provar sem substituição. Selecionar as linhas superiores K a partir de um tipo aleatório ou aderir a uma tabela que contém chaves únicas em ordem aleatória irá produzir uma amostra aleatória gerada sem substituição.

Se você quiser que o seu exemplo para ser independente, você precisa provar com a substituição. Veja Pergunta 25451034 para um exemplo de como fazer isso usando um JOIN de uma maneira semelhante à solução das user12861. A solução é escrito para T-SQL, mas o conceito funciona em qualquer db SQL.

Respondeu 02/09/2014 em 21:40
fonte usuário

votos
5

Faster Than ORDER BY RAND ()

Testei este método seja muito mais rápido do que ORDER BY RAND(), por conseguinte, que é executado em O (n) de tempo, e fá-lo de forma impressionante rápido.

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

Versão não-MSSQL - Eu não testei este

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)

Isso irá selecionar ~ 1% dos registros. Então, se você precisa exata # de porcentagens ou registros a ser selecionado, estimar o percentual com alguma margem de segurança, então arrancar aleatoriamente registros excesso de resultando set, usando o mais caro ORDER BY RAND()método.

Ainda mais rápido

I foi capaz de melhorar esse método ainda mais porque tinha um intervalo de valores coluna indexada bem conhecido.

Por exemplo, se tiver uma coluna indexada com números inteiros uniformemente distribuídos [0..max], pode utilizar que para seleccionar aleatoriamente N pequenos intervalos. Faça isso de forma dinâmica no seu programa para obter um conjunto diferente para cada execução da consulta. Esta seleção subconjunto será O (N) , que pode muitas ordens de magnitude menor do que o conjunto de dados completo.

No meu teste eu reduziu o tempo necessário para obter 20 (fora 20 mil) registros de amostra de 3 minutos utilizando ORDER BY RAND () até 0,0 segundos !

Respondeu 10/09/2014 em 21:29
fonte usuário

votos
0

Se você precisar exatamente mlinhas, realisticamente você vai gerar seu subconjunto de IDs fora do SQL. A maioria dos métodos exigem em algum ponto para selecionar a entrada "enésimo", e tabelas SQL realmente não são matrizes em tudo. A suposição de que as chaves são consecutivos, a fim de juntar-se apenas ints aleatórios entre 1 e a contagem também é difícil de satisfazer - MySQL, por exemplo, não o suporta nativamente, e as condições de bloqueio são ... complicado .

Aqui está um O(max(n, m lg n))-time, O(n)solução -espaço assumindo chaves BTREE apenas simples:

  1. Obtêm todos os valores da coluna de chave da tabela de dados em qualquer ordem em uma matriz em sua linguagem de programação favorita em O(n)
  2. Executar uma Shuffle Fisher-Yates , parando depois de mswaps, e extrair o subarray [0:m-1]emϴ(m)
  3. "Junte-se" o subarray com o conjunto de dados originais (por exemplo SELECT ... WHERE id IN (<subarray>)) emO(m lg n)

Qualquer método que gera o subconjunto aleatório fora do SQL tem de ter, pelo menos, esta complexidade. A junção não pode ser qualquer mais rápido do que O(m lg n)com BTREE (assim O(m)reivindicações são fantasia para a maioria dos motores) e o shuffle é delimitada abaixo ne m lg ne não afeta o comportamento assintótica.

Em pseudocódigo Pythonic:

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])
Respondeu 22/11/2017 em 17:39
fonte usuário

votos
0

Selecione 3000 registros aleatórios em Netezza:

WITH IDS AS (
     SELECT ID
     FROM MYTABLE;
)

SELECT ID FROM IDS ORDER BY mt_random() LIMIT 3000
Respondeu 28/02/2020 em 22:30
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more