Eu quero uma seleção aleatória de linhas no PostgreSQL, tentei o seguinte:
select * from table where random() < 0.01;
Mas alguns outros recomendam isso:
select * from table order by random() limit 1000;
Eu tenho uma tabela muito grande com 500 milhões de linhas, quero que seja rápida.
Qual abordagem é melhor? Quais são as diferenças? Qual é a melhor maneira de selecionar linhas aleatórias?
sql
performance
postgresql
random
nanounanue
fonte
fonte
Respostas:
Dadas as suas especificações (além de informações adicionais nos comentários),
A consulta abaixo não precisa de uma varredura seqüencial da tabela grande, apenas uma varredura de índice.
Primeiro, obtenha estimativas para a consulta principal:
A única parte possivelmente cara é a
count(*)
(para mesas enormes). Dadas as especificações acima, você não precisa disso. Uma estimativa funcionará perfeitamente, disponível quase sem custo ( explicação detalhada aqui ):Desde que
ct
não seja muito menorid_span
, a consulta superará outras abordagens.Gere números aleatórios no
id
espaço. Você tem "poucas lacunas", então adicione 10% (o suficiente para cobrir facilmente os espaços em branco) ao número de linhas a serem recuperadas.Cada um
id
pode ser escolhido várias vezes por acaso (embora seja muito improvável com um grande espaço de identificação), portanto, agrupe os números gerados (ou useDISTINCT
).Junte os
id
s à mesa grande. Isso deve ser muito rápido com o índice no lugar.Finalmente, apare os excedentes
id
que não foram consumidos por burros e lacunas. Cada linha tem uma chance completamente igual de ser escolhida.Versão curta
Você pode simplificar esta consulta. O CTE na consulta acima é apenas para fins educacionais:
Refinar com rCTE
Especialmente se você não tiver tanta certeza sobre lacunas e estimativas.
Podemos trabalhar com um excedente menor na consulta base. Se houver muitas lacunas para não encontrar linhas suficientes na primeira iteração, o rCTE continuará a iterar com o termo recursivo. Ainda precisamos de relativamente poucas lacunas no espaço de identificação ou a recursão pode ficar seca antes que o limite seja atingido - ou precisamos começar com um buffer grande o suficiente que desafie o objetivo de otimizar o desempenho.
As duplicatas são eliminadas pelo
UNION
no rCTE.O externo
LIMIT
faz o CTE parar assim que tivermos linhas suficientes.Essa consulta é cuidadosamente elaborada para usar o índice disponível, gerar linhas realmente aleatórias e não parar até cumprirmos o limite (a menos que a recursão se esgote). Existem várias armadilhas aqui, se você deseja reescrevê-lo.
Enrole em função
Para uso repetido com vários parâmetros:
Ligar:
Você pode até fazer esse genérico funcionar em qualquer tabela: Pegue o nome da coluna PK e a tabela como tipo polimórfico e use
EXECUTE
... Mas isso está além do escopo desta pergunta. Vejo:Alternativa possível
Se seus requisitos permitirem conjuntos idênticos para chamadas repetidas (e estamos falando de chamadas repetidas), consideraria uma visão materializada . Execute a consulta acima uma vez e escreva o resultado em uma tabela. Os usuários obtêm uma seleção quase aleatória na velocidade da luz. Atualize sua escolha aleatória em intervalos ou eventos de sua escolha.
O Postgres 9.5 apresenta
TABLESAMPLE SYSTEM (n)
Onde
n
está uma porcentagem. O manual:Negrito ênfase minha. É muito rápido , mas o resultado não é exatamente aleatório . O manual novamente:
O número de linhas retornadas pode variar bastante. Para o nosso exemplo, para obter aproximadamente 1000 linhas:
Relacionado:
Ou instale o módulo adicional tsm_system_rows para obter exatamente o número de linhas solicitadas (se houver o suficiente) e permitir a sintaxe mais conveniente:
Vejo a resposta de Evan para detalhes.
Mas isso ainda não é exatamente aleatório.
fonte
JOIN bigtbl t
que é a abreviação deJOIN bigtbl AS t
.t
é um alias de tabela parabigtbl
. Seu objetivo é reduzir a sintaxe, mas não seria necessário nesse caso específico. Simplifiquei a consulta na minha resposta e adicionei uma versão simples.Você pode examinar e comparar o plano de execução de ambos usando
Um teste rápido em uma tabela grande 1 mostra que a
ORDER BY
primeira classifica a tabela completa e depois escolhe os primeiros 1000 itens. A classificação de uma tabela grande não apenas lê essa tabela, mas também envolve a leitura e gravação de arquivos temporários. Awhere random() < 0.1
única varre a tabela completa uma vez.Para tabelas grandes, isso pode não ser o que você deseja, pois mesmo uma verificação completa da tabela pode levar muito tempo.
Uma terceira proposta seria
Esse procedimento interrompe a verificação da tabela assim que 1000 linhas forem encontradas e, portanto, retorna mais cedo. É claro que isso atrapalha um pouco a aleatoriedade, mas talvez isso seja bom o suficiente no seu caso.
Editar: Além dessas considerações, você pode conferir as perguntas já feitas para isso. O uso da consulta
[postgresql] random
retorna alguns hits.E um artigo vinculado de depez descrevendo várias outras abordagens:
1 "grande" como em "a tabela completa não cabe na memória".
fonte
random() < 0.02
e depois embaralhar essa listalimit 1000
! A classificação será mais barata em alguns milhares de linhas (risos).ordem do postgresql por random (), selecione as linhas em ordem aleatória:
ordem do postgresql por random () com uma distinta:
ordem do postgresql por limite aleatório uma linha:
fonte
select your_columns from your_table ORDER BY random() limit 1
tomar ~ 2 minutos para exec em linhas 45milA partir do PostgreSQL 9.5, há uma nova sintaxe dedicada a obter elementos aleatórios de uma tabela:
Este exemplo fornece 5% de elementos de
mytable
.Veja mais explicações nesta postagem do blog: http://www.postgresql.org/docs/current/static/sql-select.html
fonte
TABLESAMPLE SYSTEM_ROWS(400)
para obter uma amostra de 400 linhas aleatórias. Você precisa ativar o built-intsm_system_rows
de extensão para usar esta declaração.Aquele com o ORDER BY será o mais lento.
select * from table where random() < 0.01;
vai registro por registro e decide filtrá-lo aleatoriamente ou não. Isso aconteceO(N)
porque ele só precisa verificar cada registro uma vez.select * from table order by random() limit 1000;
vai ordenar a mesa inteira e depois escolher as primeiras 1000. Além de qualquer magia vodu nos bastidores, a ordem éO(N * log N)
.A desvantagem
random() < 0.01
é que você obterá um número variável de registros de saída.Observe que há uma maneira melhor de embaralhar um conjunto de dados do que classificar aleatoriamente: o Fisher-Yates Shuffle , que é executado
O(N)
. A implementação do shuffle no SQL parece um grande desafio.fonte
Aqui está uma decisão que funciona para mim. Eu acho que é muito simples de entender e executar.
fonte
ORDER BY random()
que funciona, mas pode não ser eficiente ao trabalhar com uma tabela grande.Se você souber quantas linhas deseja, confira
tsm_system_rows
.tsm_system_rows
Primeiro instale a extensão
Então sua consulta,
fonte
SYSTEM
método interno.tsm_system_rows
etsm_system_time
. Tanto quanto posso ver, eles são praticamente inúteis para qualquer coisa, exceto a seleção absolutamente mínima de linhas aleatórias. Ficaria muito grato se você pudesse dar uma rápida olhada e comentar sobre a validade ou não da minha análise.Se você quiser apenas uma linha, poderá usar um calculado
offset
derivado decount
.fonte
É possível uma variação da visão materializada "Possível alternativa" descrita por Erwin Brandstetter .
Diga, por exemplo, que você não deseja duplicatas nos valores aleatórios retornados. Portanto, você precisará definir um valor booleano na tabela principal que contém seu conjunto de valores (não randomizados).
Supondo que esta seja a tabela de entrada:
Preencha a
ID_VALUES
tabela conforme necessário. Em seguida, conforme descrito por Erwin, crie uma visão materializada que randomize aID_VALUES
tabela uma vez:Observe que a visualização materializada não contém a coluna usada, porque isso rapidamente se tornará desatualizado. A visualização também não precisa conter outras colunas que possam estar no
id_values
tabela.Para obter (e "consumir") valores aleatórios, use um UPDATE-RETURNING
id_values
, selecionando aid_values
partir deid_values_randomized
uma junção e aplicando os critérios desejados para obter apenas possibilidades relevantes. Por exemplo:Altere
LIMIT
conforme necessário - se você precisar apenas de um valor aleatório por vez, mudeLIMIT
para1
.Com os índices adequados ativados
id_values
, acredito que o UPDATE-RETURNING deve ser executado muito rapidamente com pouca carga. Retorna valores aleatórios com um ida e volta ao banco de dados. Os critérios para linhas "elegíveis" podem ser tão complexos quanto necessário. Novas linhas podem ser adicionadas àid_values
tabela a qualquer momento e elas se tornarão acessíveis ao aplicativo assim que a exibição materializada for atualizada (o que provavelmente pode ser executado em um horário fora do pico). A criação e a atualização da visualização materializada serão lentas, mas só precisam ser executadas quando novos IDs forem adicionados àid_values
tabela.fonte
Uma lição da minha experiência:
offset floor(random() * N) limit 1
não é mais rápido queorder by random() limit 1
.Eu pensei que a
offset
abordagem seria mais rápida, pois economizaria o tempo de classificação no Postgres. Acontece que não era.fonte
Adicionar uma coluna chamada
r
com o tiposerial
. Índicer
.Suponha que temos 200.000 linhas, vamos gerar um número aleatório
n
, onde 0 <n
<<= 200.000.Selecione as linhas com
r > n
, classifique-asASC
e selecione a menor.Código:
O código é auto-explicativo. A subconsulta no meio é usada para estimar rapidamente a contagem de linhas da tabela em https://stackoverflow.com/a/7945274/1271094 .
No nível do aplicativo, você precisa executar a instrução novamente se
n
> o número de linhas ou precisar selecionar várias linhas.fonte
Eu sei que estou um pouco atrasado para a festa, mas eu encontrei esta ferramenta incrível chamada pg_sample :
Eu tentei isso com um banco de dados de 350 milhões de linhas e foi muito rápido, não sei sobre a aleatoriedade .
fonte