sql - 计算两个日期之间的完整月份

标签 sql postgresql datetime postgresql-9.1 generate-series

我已经为此工作了几个小时,但运气不佳,结果碰壁了。我的数据如下所示:

Date1          Date2
2012-05-06     2012-05-05
2012-03-20     2012-01-05

我想做的是将两个日期之间每个月的计数加 1。所以我的输出理想情况下是这样的:

Year    Month    Sum
2012    2        1

换句话说,它应该检查两个日期之间是否有“空”月份,并将它们加 1。

这是我到目前为止编写的代码。它基本上会计算两个日期之间的月数,并将它们分组为月和年。

SELECT 
    EXTRACT(YEAR FROM Date2::date) as "Year", 
    EXTRACT(MONTH FROM Date2::date) as "Month",
    SUM(DATE_PART('year', Date1::date) - DATE_PART('year', Date2::date)) * 12 +
    (DATE_PART('month', Date1::date) - DATE_PART('month', Date2::date))
FROM
    test
GROUP BY 
    "Year", 
    "Month",
ORDER BY
    "Year" DESC, 
    "Month" DESC;

这就是我卡住的地方 - 我不知道如何为每个“空白”月份实际加 1。

最佳答案

测试设置

带有一些示例行(应在问题中提供):

CREATE TABLE test (
   test_id serial PRIMARY KEY
 , date1   date NOT NULL
 , date2   date NOT NULL
);

INSERT INTO test(date1, date2)
VALUES
   ('2012-03-20', '2012-01-05')  -- 2012-02 lies in between
 , ('2012-01-20', '2012-03-05')  -- 2012-02 (reversed)
 , ('2012-05-06', '2012-05-05')  -- nothing
 , ('2012-05-01', '2012-06-30')  -- still nothing
 , ('2012-08-20', '2012-11-05')  -- 2012-09 - 2012-10
 , ('2012-11-20', '2013-03-05')  -- 2012-12 - 2013-02
;

Postgres 9.3 或更新版本

使用LATERAL 连接:

SELECT to_char(mon, 'YYYY') AS year
     , to_char(mon, 'MM')   AS month
     , count(*) AS ct
FROM  (
   SELECT date_trunc('mon',    least(date1, date2)::timestamp) + interval '1 mon' AS d1
        , date_trunc('mon', greatest(date1, date2)::timestamp) - interval '1 mon' AS d2
   FROM   test
   ) sub1
 , generate_series(d1, d2, interval '1 month') mon  -- implicit CROSS JOIN LATERAL
WHERE  d2 >= d1 -- exclude ranges without gap right away
GROUP  BY mon
ORDER  BY mon;

Postgres 9.2 或更早版本

还没有 LATERAL。改用子查询:

SELECT to_char(mon, 'YYYY') AS year
     , to_char(mon, 'MM')   AS month
     , count(*) AS ct
FROM  (
   SELECT generate_series(d1, d2, interval '1 month') AS mon
   FROM  (
      SELECT date_trunc('mon',    least(date1, date2)::timestamp) + interval '1 mon' AS d1
           , date_trunc('mon', greatest(date1, date2)::timestamp) - interval '1 mon' AS d2
      FROM   test
      ) sub1
   WHERE  d2 >= d1 -- exclude ranges without gap right away
   ) sub2
GROUP  BY mon
ORDER  BY mon;

结果

 year | month | ct
------+-------+----
 2012 |     2 |  2
 2012 |     9 |  1
 2012 |    10 |  1
 2012 |    12 |  1
 2013 |     1 |  1
 2013 |     2 |  1

db<> fiddle here
SQL Fiddle.

说明

您要查找两个日期之间完整 个日历月。

这些查询适用于按升序或降序排列的任何日期或时间戳,并且应该表现良好。

WHERE 子句是可选的,因为 generate_series()如果开始 > 结束,则不返回任何行。但是先验地排除空范围应该会更快一些。

转换为 timestamp 使其更简洁、更快速。理由:

关于sql - 计算两个日期之间的完整月份,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11673136/

相关文章:

SQL查询所有项目匹配

python - 如何在 Django 管理 View 中显示整个现有的 postgres 数据库

sql - 如何在 PostgreSQL 中进行完全匹配后跟 ORDER BY

c# - 如何将日期时间字符串转换为日期时间对象

sql - 如何在postgres中对月份的日期记录进行分组后填补时间间隙

sql - 如何在 DataGrip 中设置服务器输出

java - 如何使用JOOQ "SELECT SQL_NO_CACHE"?

sql - Oracle 到 Derby - ConnectBy 并从 Derby 中的等效项开始

sql - Ecto 查询 - 日期 + Postgres 间隔 + 查询插值

c# - 当 DateTime 传递给采用 Nullable DateTime 的方法时,为什么我会丢失 DateTime 的毫秒数?