Em um banco de dados de transações que abrangem milhares de entidades ao longo de 18 meses, eu gostaria de executar uma consulta para agrupar todos os períodos de 30 dias possíveis entity_id
com uma SOMA de seus valores de transação e COUNT de suas transações nesse período de 30 dias, e retornar os dados de uma maneira que eu possa consultar. Após muitos testes, esse código realiza muito do que eu quero:
SELECT id, trans_ref_no, amount, trans_date, entity_id,
SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
COUNT(id) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
FROM transactiondb;
E eu vou usar em uma consulta maior estruturada algo como:
SELECT * FROM (
SELECT id, trans_ref_no, amount, trans_date, entity_id,
SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
COUNT(id) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
FROM transactiondb ) q
WHERE trans_count >= 4
AND trans_total >= 50000;
O caso que esta consulta não cobre é quando a contagem da transação se estende por vários meses, mas ainda fica dentro de 30 dias um do outro. Esse tipo de consulta é possível com o Postgres? Nesse caso, agradeço qualquer contribuição. Muitos dos outros tópicos discutem agregados "em execução ", não rolando .
Atualizar
O CREATE TABLE
script:
CREATE TABLE transactiondb (
id integer NOT NULL,
trans_ref_no character varying(255),
amount numeric(18,2),
trans_date date,
entity_id integer
);
Os dados de amostra podem ser encontrados aqui . Estou executando o PostgreSQL 9.1.16.
A produção ideal incluiria SUM(amount)
e COUNT()
de todas as transações em um período contínuo de 30 dias. Veja esta imagem, por exemplo:
O destaque verde da data indica o que está sendo incluído na minha consulta. O destaque da linha amarela indica os registros que eu gostaria de fazer parte do conjunto.
Leitura anterior:
fonte
every possible 30-day period by entity_id
que você quer dizer o período pode começar a qualquer dia, para 365 possíveis períodos em um (não-bissexto) ano? Ou você deseja considerar apenas os dias com uma transação real como início de um período individualmente para algumentity_id
? De qualquer forma, forneça sua definição de tabela, versão do Postgres, alguns dados de amostra e o resultado esperado para a amostra.entity_id
em uma janela de 30 dias a partir de cada transação real. Pode haver várias transações para o mesmo(trans_date, entity_id)
ou essa combinação é definida como única? Sua definição da tabela não temUNIQUE
ou restrição PK, mas as restrições parecem estar faltando ...id
chave primária. Pode haver várias transações por entidade por dia.Respostas:
A consulta que você tem
Você pode simplificar sua consulta usando uma
WINDOW
cláusula, mas isso apenas reduz a sintaxe, não altera o plano de consulta.count(*)
, uma vez queid
certamente é definidoNOT NULL
?ORDER BY entity_id
já que você jáPARTITION BY entity_id
Porém, você pode simplificar ainda mais:
não adicione
ORDER BY
a definição da janela, pois ela não é relevante para sua consulta. Então você não precisa definir um quadro de janela personalizado:Mais simples, mais rápida, mas ainda assim uma versão melhor do que você tem , com meses estáticos .
A consulta que você pode querer
... não está claramente definido, por isso vou construir sobre essas suposições:
Contar transações e valor para cada período de 30 dias na primeira e na última transação de qualquer
entity_id
. Exclua os períodos inicial e final sem atividade, mas inclua todos os períodos possíveis de 30 dias dentro desses limites externos.Isso lista todos os períodos de 30 dias para cada um
entity_id
com seus agregados e comotrans_date
sendo o primeiro dia (incl.) Do período. Para obter valores para cada linha individual, junte-se à tabela base mais uma vez ...A dificuldade básica é a mesma discutida aqui:
A definição de quadro de uma janela não pode depender dos valores da linha atual.
E, em vez disso, ligue
generate_series()
com atimestamp
entrada:A consulta que você realmente deseja
Após a atualização e discussão da pergunta:
Acumule linhas do mesmo
entity_id
em uma janela de 30 dias, iniciando em cada transação real.Como seus dados são distribuídos esparsamente, deve ser mais eficiente executar uma auto-junção com uma condição de intervalo , ainda mais porque o Postgres 9.1 ainda não possui
LATERAL
junções:SQL Fiddle.
Uma janela rolante só poderia fazer sentido (com relação ao desempenho) com os dados da maioria dos dias.
Isso não agrega duplicatas
(trans_date, entity_id)
por dia, mas todas as linhas do mesmo dia são sempre incluídas na janela de 30 dias.Para uma tabela grande, um índice de cobertura como este poderia ajudar bastante:
A última coluna
amount
é útil apenas se você conseguir verificações somente de índice. Caso contrário, solte-o.Mas não será usado enquanto você selecionar a tabela inteira de qualquer maneira. Ele suportaria consultas para um pequeno subconjunto.
fonte
column "t0.amount" must appear in the GROUP BY clause...