Postgres - como retornar as linhas com 0 contagem para os dados em falta?
-
19-08-2019 - |
Pergunta
Eu desigualmente distribuídas (data wrt) por alguns anos (2003-2008). Quero dados de consulta para um determinado conjunto de início e data final, que agrupa os dados por qualquer um dos intervalos suportados (dia, semana, mês, trimestre, ano) em PostgreSQL 8.3 ( http://www.postgresql.org/docs/8.3/static/functions-datetime.html # FUNÇÕES-DATETIME-TRUNC ).
O problema é que algumas das consultas dar resultados contínuos durante o período necessário, como este:
select to_char(date_trunc('month',date), 'YYYY-MM-DD'),count(distinct post_id)
from some_table where category_id=1 and entity_id = 77 and entity2_id = 115
and date <= '2008-12-06' and date >= '2007-12-01' group by
date_trunc('month',date) order by date_trunc('month',date);
to_char | count
------------+-------
2007-12-01 | 64
2008-01-01 | 31
2008-02-01 | 14
2008-03-01 | 21
2008-04-01 | 28
2008-05-01 | 44
2008-06-01 | 100
2008-07-01 | 72
2008-08-01 | 91
2008-09-01 | 92
2008-10-01 | 79
2008-11-01 | 65
(12 rows)
mas alguns deles perca alguns intervalos, porque não há dados presentes, como este:
select to_char(date_trunc('month',date), 'YYYY-MM-DD'),count(distinct post_id)
from some_table where category_id=1 and entity_id = 75 and entity2_id = 115
and date <= '2008-12-06' and date >= '2007-12-01' group by
date_trunc('month',date) order by date_trunc('month',date);
to_char | count
------------+-------
2007-12-01 | 2
2008-01-01 | 2
2008-03-01 | 1
2008-04-01 | 2
2008-06-01 | 1
2008-08-01 | 3
2008-10-01 | 2
(7 rows)
, onde o conjunto de resultados necessária é:
to_char | count
------------+-------
2007-12-01 | 2
2008-01-01 | 2
2008-02-01 | 0
2008-03-01 | 1
2008-04-01 | 2
2008-05-01 | 0
2008-06-01 | 1
2008-07-01 | 0
2008-08-01 | 3
2008-09-01 | 0
2008-10-01 | 2
2008-11-01 | 0
(12 rows)
A contagem de 0 para entradas em falta.
Eu vi discussões anteriores sobre estouro de pilha, mas eles não resolver o meu problema que parece, desde o meu período de agrupamento é um dos (dia, semana, mês, trimestre, ano) e decidiu, em tempo de execução pelo aplicativo. Assim, uma abordagem como LEFT JOIN com uma mesa de calendário ou tabela de seqüência não vai ajudar, eu acho.
Minha solução atual para isso é para preencher essas lacunas em Python (em um Turbogears App), utilizando o módulo de calendário.
Existe uma maneira melhor de fazer isso.
Solução
Você pode criar uma lista de todos os primeiros dias do último ano (digamos) com
select distinct date_trunc('month', (current_date - offs)) as date
from generate_series(0,365,28) as offs;
date
------------------------
2007-12-01 00:00:00+01
2008-01-01 00:00:00+01
2008-02-01 00:00:00+01
2008-03-01 00:00:00+01
2008-04-01 00:00:00+02
2008-05-01 00:00:00+02
2008-06-01 00:00:00+02
2008-07-01 00:00:00+02
2008-08-01 00:00:00+02
2008-09-01 00:00:00+02
2008-10-01 00:00:00+02
2008-11-01 00:00:00+01
2008-12-01 00:00:00+01
Em seguida, você pode se juntar com essa série.
Outras dicas
Esta questão é velho. Mas desde que outros usuários escolheu-o como mestre para um novo duplicar Estou adicionando uma resposta adequada.
solução adequada
SELECT *
FROM (
SELECT day::date
FROM generate_series(timestamp '2007-12-01'
, timestamp '2008-12-01'
, interval '1 month') day
) d
LEFT JOIN (
SELECT date_trunc('month', date_col)::date AS day
, count(*) AS some_count
FROM tbl
WHERE date_col >= date '2007-12-01'
AND date_col <= date '2008-12-06'
-- AND ... more conditions
GROUP BY 1
) t USING (day)
ORDER BY day;
-
Use
LEFT JOIN
, é claro. -
generate_series()
pode produzir uma tabela de timestamps on the fly, e muito rápido. -
Em geral, é mais rápido para agregados antes que você participar. Recentemente forneceu um caso de teste em sqlfiddle.com nesta resposta relacionada:
-
Lançai a
timestamp
paradate
(::date
) para um formato básico. Para mais usoto_char()
. -
GROUP BY 1
é sintaxe abreviada para fazer referência a primeira coluna de saída. Poderia serGROUP BY day
bem, mas que o conflito poder com uma coluna existente de mesmo nome. OuGROUP BY date_trunc('month', date_col)::date
mas isso é demasiado longo para o meu gosto. -
Funciona com os argumentos de intervalo disponíveis para
date_trunc()
. -
count()
nunca produzNULL
(0
para nenhuma linha), mas aLEFT JOIN
faz.
Para retornar0
vez deNULL
naSELECT
exterior, o usoCOALESCE(some_count, 0) AS some_count
. O manual. -
Para uma solução mais genérico ou intervalos de tempo arbitrários considerar esta resposta intimamente relacionados:
Você pode criar uma tabela temporária em tempo de execução e esquerda juntar-se sobre isso. Que parece fazer mais sentido.