Estou lidando com uma tabela Postgres (chamada "vidas") que contém registros com colunas para time_stamp, usr_id, transaction_id e lives_remaining. Preciso de uma consulta que me dê o total de vidas_remanentes mais recentes para cada usr_id
- Existem vários usuários (usr_id's distintos)
- time_stamp não é um identificador único: às vezes os eventos do usuário (um por linha na tabela) ocorrerão com o mesmo time_stamp.
- trans_id é único apenas para intervalos de tempo muito pequenos: com o tempo, ele se repete
- restantes_vidas (para um determinado usuário) podem aumentar e diminuir ao longo do tempo
exemplo:
time_stamp | lives_remaining | usr_id | trans_id ----------------------------------------- 07:00 | 1 | 1 | 1 09:00 | 4 2 | 2 10:00 | 2 | 3 | 3 10:00 | 1 | 2 | 4 11:00 | 4 1 | 5 11:00 | 3 | 1 | 6 13:00 | 3 | 3 | 1
Como precisarei acessar outras colunas da linha com os dados mais recentes para cada usr_id fornecido, preciso de uma consulta que forneça um resultado como este:
time_stamp | lives_remaining | usr_id | trans_id ----------------------------------------- 11:00 | 3 | 1 | 6 10:00 | 1 | 2 | 4 13:00 | 3 | 3 | 1
Conforme mencionado, cada usr_id pode ganhar ou perder vidas e, às vezes, esses eventos com carimbo de data / hora ocorrem tão próximos que têm o mesmo carimbo de data / hora! Portanto, esta consulta não funcionará:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp) AS max_timestamp
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp = b.time_stamp
Em vez disso, preciso usar time_stamp (primeiro) e trans_id (segundo) para identificar a linha correta. Também preciso passar essas informações da subconsulta para a consulta principal que fornecerá os dados para as outras colunas das linhas apropriadas. Esta é a consulta hackeada que comecei a trabalhar:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp || '*' || trans_id)
AS max_timestamp_transid
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp_transid = b.time_stamp || '*' || b.trans_id
ORDER BY b.usr_id
Ok, isso funciona, mas eu não gosto. Requer uma consulta dentro de uma consulta, uma auto-junção, e parece-me que poderia ser muito mais simples capturando a linha que MAX descobriu ter o maior carimbo de data / hora e trans_id. A tabela "vidas" tem dezenas de milhões de linhas para analisar, então eu gostaria que essa consulta fosse o mais rápida e eficiente possível. Eu sou novo em RDBM e Postgres em particular, então eu sei que preciso fazer uso efetivo dos índices apropriados. Estou um pouco perdido em como otimizar.
Eu encontrei uma discussão semelhante aqui . Posso executar algum tipo de Postgres equivalente a uma função analítica Oracle?
Qualquer conselho sobre como acessar informações de colunas relacionadas usadas por uma função agregada (como MAX), criar índices e criar consultas melhores seria muito apreciado!
PS Você pode usar o seguinte para criar meu caso de exemplo:
create TABLE lives (time_stamp timestamp, lives_remaining integer,
usr_id integer, trans_id integer);
insert into lives values ('2000-01-01 07:00', 1, 1, 1);
insert into lives values ('2000-01-01 09:00', 4, 2, 2);
insert into lives values ('2000-01-01 10:00', 2, 3, 3);
insert into lives values ('2000-01-01 10:00', 1, 2, 4);
insert into lives values ('2000-01-01 11:00', 4, 1, 5);
insert into lives values ('2000-01-01 11:00', 3, 1, 6);
insert into lives values ('2000-01-01 13:00', 3, 3, 1);
fonte
MAX
BY
2 colunas!Respostas:
Em uma tabela com 158k linhas pseudo-aleatórias (usr_id uniformemente distribuído entre 0 e 10k,
trans_id
uniformemente distribuído entre 0 e 30),Por custo de consulta, abaixo, estou me referindo à estimativa de custo do otimizador baseado em custo do Postgres (com os
xxx_cost
valores padrão do Postgres ), que é uma estimativa de função ponderada de recursos de I / O e CPU necessários; você pode obter isso abrindo PgAdminIII e executando "Query / Explain (F7)" na consulta com "Query / Explain options" definido como "Analyze"usr_id
,trans_id
,time_stamp
))usr_id
,trans_id
))usr_id
,trans_id
,time_stamp
))usr_id
,EXTRACT(EPOCH FROM time_stamp)
,trans_id
))usr_id
,time_stamp
,trans_id
)); ele tem a vantagem de verificar alives
tabela apenas uma vez e, se você aumentar temporariamente (se necessário) work_mem para acomodar a classificação na memória, será de longe a mais rápida de todas as consultas.Todos os tempos acima incluem a recuperação de todo o conjunto de resultados de 10 mil linhas.
Seu objetivo é a estimativa de custo mínimo e o tempo mínimo de execução da consulta, com ênfase no custo estimado. A execução da consulta pode depender significativamente das condições de tempo de execução (por exemplo, se as linhas relevantes já estão totalmente armazenadas em cache na memória ou não), ao passo que a estimativa de custo não está. Por outro lado, tenha em mente que a estimativa de custo é exatamente isso, uma estimativa.
O melhor tempo de execução de consulta é obtido ao executar em um banco de dados dedicado sem carga (por exemplo, jogando com pgAdminIII em um PC de desenvolvimento). O tempo de consulta irá variar na produção com base na carga real da máquina / distribuição de acesso aos dados. Quando uma consulta parece um pouco mais rápida (<20%) do que a outra, mas tem um custo muito maior, geralmente será mais sensato escolher aquela com maior tempo de execução, mas menor custo.
Quando você espera que não haja competição pela memória em sua máquina de produção no momento em que a consulta for executada (por exemplo, o cache RDBMS e o cache do sistema de arquivos não serão prejudicados por consultas simultâneas e / ou atividade do sistema de arquivos), então o tempo de consulta obtido no modo autônomo (por exemplo, pgAdminIII em um PC de desenvolvimento) será representativo. Se houver contenção no sistema de produção, o tempo de consulta será reduzido proporcionalmente à relação de custo estimada, pois a consulta com o custo mais baixo não depende tanto do cache, enquanto a consulta com custo mais alto revisitará os mesmos dados repetidamente (acionando E / S adicional na ausência de um cache estável), por exemplo:
Não se esqueça de executar
ANALYZE lives
uma vez após criar os índices necessários.Consulta # 1
Consulta # 2
Atualização de 29/01/2013
Finalmente, a partir da versão 8.4, o Postgres suporta a função de janela, o que significa que você pode escrever algo tão simples e eficiente como:
Consulta # 3
fonte
Eu proporia uma versão limpa com base em
DISTINCT ON
(ver documentos ):fonte
Aqui está outro método, que não usa subconsultas correlacionadas ou GROUP BY. Não sou especialista em ajuste de desempenho do PostgreSQL, então sugiro que você experimente isso e as soluções fornecidas por outras pessoas para ver qual funciona melhor para você.
Estou assumindo que
trans_id
é único pelo menos em relação a qualquer valor detime_stamp
.fonte
Gosto do estilo da resposta de Mike Woodhouse na outra página que você mencionou. É especialmente conciso quando o que está sendo maximizado é apenas uma única coluna, caso em que a subconsulta pode usar apenas
MAX(some_col)
eGROUP BY
as outras colunas, mas no seu caso você tem uma quantidade de 2 partes a ser maximizada, você ainda pode fazer isso usandoORDER BY
mais emLIMIT 1
vez disso (como feito por Quassnoi):Acho bom usar a sintaxe do construtor de linha
WHERE (a, b, c) IN (subquery)
porque ela reduz a quantidade de verbosidade necessária.fonte
Na verdade, há uma solução hacky para esse problema. Digamos que você queira selecionar a maior árvore de cada floresta em uma região.
Quando você agrupa árvores por florestas, haverá uma lista não classificada de árvores e você precisa encontrar a maior. A primeira coisa que você deve fazer é classificar as linhas por seus tamanhos e selecionar a primeira de sua lista. Pode parecer ineficiente, mas se você tiver milhões de linhas, será muito mais rápido do que as soluções que incluem
JOIN
's e asWHERE
condições.BTW, observe que
ORDER_BY
forarray_agg
é introduzido no Postgresql 9.0fonte
SELECT usr_id, (array_agg(time_stamp ORDER BY time_stamp DESC))[1] AS timestamp, (array_agg(lives_remaining ORDER BY time_stamp DESC))[1] AS lives_remaining, (array_agg(trans_id ORDER BY time_stamp DESC))[1] AS trans_id FROM lives GROUP BY usr_id
Há uma nova opção no Postgressql 9.5 chamada DISTINCT ON
Ele elimina linhas duplicadas e deixa apenas a primeira linha, conforme definido na cláusula ORDER BY.
veja a documentação oficial
fonte
Criação de um índice em
(usr_id, time_stamp, trans_id)
irá melhorar muito esta consulta.Você deve sempre, sempre ter algum tipo de
PRIMARY KEY
em suas mesas.fonte
Acho que você tem um grande problema aqui: não há um "contador" monotonicamente crescente para garantir que uma determinada linha tenha ocorrido mais tarde do que outra. Veja este exemplo:
Você não pode determinar a partir desses dados qual é a entrada mais recente. É o segundo ou o último? Não há função sort ou max () que você possa aplicar a qualquer um desses dados para fornecer a resposta correta.
Aumentar a resolução do timestamp seria uma grande ajuda. Como o mecanismo de banco de dados serializa as solicitações, com resolução suficiente, você pode garantir que dois carimbos de data / hora não serão iguais.
Como alternativa, use um trans_id que não irá acumular por muito, muito tempo. Ter um trans_id que rola significa que você não pode dizer (para o mesmo timestamp) se trans_id 6 é mais recente do que trans_id 1, a menos que você faça algumas contas complicadas.
fonte
Outra solução que você pode achar útil.
fonte