【问题标题】:Postgres - how to return rows with 0 count for missing data?Postgres - 如何返回缺失数据计数为 0 的行?
【发布时间】:2010-09-25 15:48:01
【问题描述】:

我有几年(2003-2008)的数据分布不均(日期)。我想查询一组给定的开始和结束日期的数据,并按 PostgreSQL 8.3 (http://www.postgresql.org/docs/8.3/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC) 中支持的任何时间间隔(日、周、月、季度、年)对数据进行分组。

问题在于,某些查询会在要求的时间段内给出连续的结果, 作为这个:

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)

但他们中的一些人错过了一些间隔,因为没有数据存在,就像这个:

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)

所需的结果集在哪里:

  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)

缺失条目的计数为 0。

我已经看到早期关于 Stack Overflow 的讨论,但它们似乎并没有解决我的问题,因为我的分组期是(日、周、月、季度、年)之一,并由应用程序决定运行时。因此,我猜像左连接与日历表或序列表这样的方法无济于事。

我目前的解决方案是使用日历模块在 Python(在 Turbogears 应用中)填补这些空白。

有没有更好的方法来做到这一点。

【问题讨论】:

    标签: python database postgresql left-join generate-series


    【解决方案1】:

    这个问题很老了。但是由于其他用户选择它作为新副本的主人,我正在添加一个正确的答案。

    适当的解决方案

    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;
    
    • 当然是LEFT JOIN

    • generate_series() 可以动态生成时间戳表,而且速度非常快。

    • 在您加入之前聚合通常会更快。我最近在这个相关答案中在 sqlfiddle.com 上提供了一个测试用例:

    • timestamp 转换为date (::date) 以获得基本格式。更多使用to_char()

    • GROUP BY 1 是引用第一个输出列的语法简写。也可以是GROUP BY day,但这可能与现有的同名列冲突。或者GROUP BY date_trunc('month', date_col)::date,但这对我来说太长了。

    • 使用date_trunc() 的可用间隔参数。

    • count() never produces NULL0 表示没有行),但 LEFT JOIN 可以。
      要在外部SELECT 中返回0 而不是NULL,请使用COALESCE(some_count, 0) AS some_countThe manual.

    • 对于更通用的解决方案或任意时间间隔,请考虑以下密切相关的答案:

    【讨论】:

    • 是否可以使用 CTE 来完成此任务?
    • @zam6ak:当然,以各种方式。但你为什么想要? CTE 会更慢。只有在 Postgres 中需要它们时才使用 CTE。如果您有用例,我建议您提出一个新问题。您可以随时链接到此答案的上下文,并在此处发表评论,链接到相关问题并通知我。
    • 谢谢,我发了一个新的question
    • 这个查询正是我要找的,谢谢你的回答。有没有办法让 some_count 返回 0 而不是空数据?
    • @Paul:是的,你错过了重要的部分。 COALESCE(count(*), 0) 从不 有意义。我写道:在外部SELECT 使用COALESCE(some_count, 0) AS some_countcount(*) 发生在 LEFT JOIN 之前的子查询中。
    【解决方案2】:

    您可以使用

    创建去年(比如说)所有第一天的列表
    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
    

    然后你就可以加入那个系列了。

    【讨论】:

    • 我试过这个:pastebin.com/f6f44e58b 仍然是 7 行。加入有什么问题吗
    • 虽然我认为 (start, stop , step) 必须在进行查询之前仔细填写,并且我怀疑在足够长的时间内 28 的步骤可能会失败,你怎么看?
    • 以28天为步长,每个月都会至少命中一次,因为最短的月份是28天;不过,请随意使用任何较小的步骤。它偶尔会在同一个月出现两次,因此使用了 DISTINCT 过滤器。
    • 我希望你没有删除 pastebin 以获得正确答案:(
    【解决方案3】:

    您可以在运行时创建一个临时表并在其上留下连接。这似乎是最有意义的。

    【讨论】:

    • 对不起,我没有时间研究实际可行性
    猜你喜欢
    • 1970-01-01
    • 2021-10-23
    • 1970-01-01
    • 1970-01-01
    • 2012-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多