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

时间:2012-07-26 15:48:26

标签: 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个。

3 个答案:

答案 0 :(得分:3)

测试设置

有一些样本行(应在问题中提供):

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<>小提琴here
SQL Fiddle.

解释

您正在寻找两个日期之间的完整日历月。

这些查询以升序或降序的方式处理任何日期或时间戳,并且应该表现良好。

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

施放到timestamp使其更清洁,更快。理由:

答案 1 :(得分:0)

AFAIK你可以在postgresql中简单地减去/添加日期

'2001-06-27 14:43:21'::DATETIME - '2001-06-27 14:33:21'::DATETIME = '00:10:00'::INTERVAL

因此,在您的情况下,请求部分应该看起来像

DATE_PART('month', Date1::datetime - Date2::datetime) as "MonthInterval"

答案 2 :(得分:0)

年龄(timestamp1,timestamp2)=>返回间隔

我们尝试从间隔中提取年份和月份并相应地添加它们。

  

选择提取物(年龄(timestamp1,timestamp2))* 12 +提取物(月份来自   年龄(timestamp1,timestamp2))