【问题标题】:Postgresql group month wise with missing values缺少值的 Postgresql 分组每月明智
【发布时间】:2014-08-01 02:43:58
【问题描述】:

首先是我的表格示例:

id_object;time;value;status
1;2014-05-22 09:30:00;1234;1
1;2014-05-22 09:31:00;2341;2
1;2014-05-22 09:32:00;1234;1
...
1;2014-06-01 00:00:00;4321;1
...

例如,现在我需要按月计算所有 status=1 和 id_object=1 的行。这是我的查询:

SELECT COUNT(*)
FROM my_table
WHERE id_object=1
  AND status=1
  AND extract(YEAR FROM time)=2014
GROUP BY extract(MONTH FROM time)

这个例子的结果是:

2
1

5 月 2 次,6 月 1 次,但我需要所有 12 个月的输出,也需要没有数据的月份。对于这个例子,我需要这个输出:

0 0 0 0 2 1 0 0 0 0 0 0

谢谢帮助。

【问题讨论】:

    标签: sql postgresql group-by


    【解决方案1】:

    你可以像这样使用generate_series()函数:

    select
        g.month,
        count(m)
    from generate_series(1, 12) as g(month)
        left outer join my_table as m on
            m.id_object = 1 and
            m.status = 1 and
            extract(year from m.time) = 2014 and
            extract(month from m.time) = g.month
    group by g.month
    order by g.month
    

    sql fiddle demo

    【讨论】:

    • 除非使用像EXTRACT(...) 这样的东西会导致数据库忽略索引(在大表上会很慢)。
    • @Clockwork-Muse 除非索引是使用extract()构建的
    • @Clockwork-Muse 正确,但这不是答案的重点:)
    【解决方案2】:

    与其与提取的值进行比较,不如使用范围表。看起来像这样的东西:

    month  startOfMonth  nextMonth
    1      '2014-01-01'  '2014-02-01'
    2      '2014-02-01'  '2014-03-01'
    ......
    12     '2014-12-01'  '2015-01-01'
    

    正如@Roman 的回答,我们将从generate_series() 开始,这次使用它来生成范围表:

    WITH Month_Range AS (SELECT EXTRACT(MONTH FROM month) AS month, 
                                month AS startOfMonth,
                                month + INTERVAL '1 MONTH' AS nextMonth
                         FROM generate_series(CAST('2014-01-01' AS DATE),
                                              CAST('2014-12-01' AS DATE),
                                              INTERVAL '1 month') AS mr(month))
    SELECT Month_Range.month, COUNT(My_Table) 
    FROM Month_Range
    LEFT JOIN My_Table
           ON My_Table.time >= Month_Range.startOfMonth
              AND My_Table.time < Month_Range.nextMonth
              AND my_table.id_object = 1
              AND my_table.status = 1
    GROUP BY Month_Range.month
    ORDER BY Month_Range.month
    

    (顺便说一句,我现在对 PostgreSQL 处理间隔的方式感到恼火)

    SQL Fiddle Demo

    使用范围将允许使用包括My_Table.time 在内的任何索引(尽管如果索引是在EXTRACTed 列上构建的,则不允许使用。

    编辑:

    修改查询以利用 generate_series(...) 也将处理日期/时间序列这一事实。

    【讨论】:

    • 这不是在 Postgresql 中完成区间运算的方式。你只需做'2014-01-01'::date + (month - 1) * interval '1 month'。没有铸造。没有字符串构建。
    • @ClodoaldoNeto - 感谢您指出这一点。我(仍然)习惯于 DB2,它不需要处理任何带有字符串的操作数。实际上,您表明 generate_series(...) 处理日期/时间/时间戳更有用(我可能需要阅读更多关于 PostgreSQL 的参考资料)。
    【解决方案3】:

    generate_series可以生成时间戳系列

    select
        g.month,
        count(t)
    from
        generate_series(
            (select date_trunc('year', min(t.time)) from t),
            (select date_trunc('year', max(t.time)) + interval '11 months' from t),
            interval '1 month'
        ) as g(month)
        left outer join
        t on
            t.id_object = 1 and
            t.status = 1 and
            date_trunc('month', t.time) = g.month
    where date_trunc('year', g.month) = '2014-01-01'::date
    group by g.month
    order by g.month
    

    【讨论】:

    • 除了,鉴于该表可能没有未来的日期,您不会有完整的月份范围。根据给定的id_object,您不知道可能会错过什么时间。除非您已生成包含 DATE_TRUNC(...) 的索引(不太可能包含基列),否则这将仍然忽略索引。
    • @Clockwork-Muse 好的,我修复了缺少的月份部分。关于 ignore indices 部分,这是一个有趣的观点,但我现在没有时间讨论它。
    猜你喜欢
    • 1970-01-01
    • 2019-01-24
    • 2020-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-07
    • 1970-01-01
    相关资源
    最近更新 更多