Postgres - como retornar as linhas com 0 contagem para a falta de dados?

votos
12

Tenho desigualmente distribuído de dados (data wrt) por alguns anos (2003-2008). Quero consultar dados 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#FUNCTIONS-DATETIME-TRUNC ).

O problema é que algumas das consultas dar resultados contínua ao longo do período exigido, 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)

Uma contagem de 0 para entradas faltando.

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 associação à esquerda 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.

Publicado 06/12/2008 em 10:32
fonte usuário
Em outras línguas...                            


3 respostas

votos
0

Você poderia criar uma tabela temporária em tempo de execução e deixou juntar-se sobre isso. Que parece fazer mais sentido.

Respondeu 06/12/2008 em 11:54
fonte usuário

votos
17

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

Então você pode se juntar com essa série.

Respondeu 06/12/2008 em 12:30
fonte usuário

votos
20

Esta questão é velho. Mas desde que outros usuários escolheu-o como mestre para um novo duplicado 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 marcas de tempo em tempo real, e muito rápido.

  • É geralmente mais rápido para agregada antes de se juntar. Recentemente forneceu um caso de teste em sqlfiddle.com nesta resposta relacionada:

  • Lançai a timestampde date( ::date) para um formato básico. Para mais uso to_char().

  • GROUP BY 1é abreviada sintaxe para fazer referência à primeira coluna de saída. Poderia ser GROUP BY dayassim, mas que podem entrar em conflito com uma coluna existente de mesmo nome. Ou GROUP BY date_trunc('month', date_col)::datemas isso é demasiado longo para o meu gosto.

  • Funciona com os argumentos de intervalo disponíveis para date_trunc().

  • count()nunca produzNULL ( 0em nenhuma linha), mas o LEFT JOINfaz.
    Para retornar 0ao invés de NULLno exterior SELECT, o uso COALESCE(some_count, 0) AS some_count. O manual.

  • Para uma solução mais genérica ou intervalos de tempo arbitrários considerar esta resposta intimamente relacionados:

Respondeu 31/03/2013 em 19:44
fonte usuário

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