计算结束日期的年份:PostgreSQL

时间:2010-06-01 03:18:33

标签: php sql postgresql date

背景

用户可以选择日期,如以下屏幕截图所示:

任何开始月/日和结束月/日组合都有效,例如:

  • 3月22日至6月22日
  • 12月1日 2月28日

第二种组合很难(我称之为“棘手的日期情景”),因为结束月/日的年份应<<>> 年后的 < em>开始月/日。也就是说,对于1900年(也在上面的屏幕截图中显示),完整日期将是:

  • 12月22日 1900 至2月28日 1901
  • 12月22日 1901 至2月28日 1902
  • ...
  • 12月22日 2007 至2月28日, 2008
  • 12月22日, 2008 至2月28日, 2009

问题

编写一个SQL语句,选择日期介于开始月/日和结束月/日之间的表中的值,而不管选择开始日期和结束日期的方式。换句话说,这是一年包装问题。

输入

查询作为参数接收:

  • 第1年,第2年:全年,独立于月/日组合。
  • 第1天,第1天:收集数据的一年中的开始日。
  • 第2天,第2天:收集数据的一年(或下一年)的结束日。

以前的尝试

考虑以下MySQL代码(有效):

end_year = start_year +
  greatest( -1 *
    sign(
      datediff(
        date(
          concat_ws('-', year, end_month, end_day )
        ),
        date(
          concat_ws('-', year, start_month, start_day )
        )
      )
    ), 0
  )

关于棘手的日期场景如何运作:

  1. 在当年创建两个日期。
  2. 第一个日期 1900年12月22日,第二个日期 1900年2月28日
  3. 计算两个日期之间的差异,以天为单位。
  4. 如果结果为负,则表示第二个日期的年份必须加1。在这种情况下:
    • 在当前年份加1。
    • 创建新的结束日期: 1901年2月28日
    • 检查数据的日期范围是否介于开始日期和计算结束日期之间。
  5. 如果结果是肯定的,则按时间顺序提供日期,不需要做任何特殊的事情。
  6. 这在MySQL中起作用,因为日期的差异是正面的还是负面的。在PostgreSQL中,等效功能总是返回一个正数,而不管它们的相对时间顺序如何。 (但我减去日期的测试可能不正确。)

    问题

    如何为PostgreSQL重写以下(破损)代码,以考虑起始和结束月/日对的相对时间顺序(关于引入棘手的年度时间位移)?

    SELECT
      m.amount
    FROM
      measurement m
    WHERE
      (extract(MONTH FROM m.taken) >= month1 AND
      extract(DAY FROM m.taken) >= day1) AND
      (extract(MONTH FROM m.taken) <= month2 AND
      extract(DAY FROM m.taken) <= day2)
    

    有任何想法,评论或问题吗?

    (这些日期在PHP中预先解析为MM / DD格式。我的偏好是纯粹的PostgreSQL解决方案,但我愿意接受有关使用PHP可能使问题更简单的建议.SQL存在于存储过程中这是由JasperReports调用的,因此PHP可以触及的唯一项目是传递到报告引擎的日期。)

    更新#1

    以下代码几乎可以使用:

    select
      (extract(YEAR FROM m.taken)||'-12-12')::date as start_date,
      ((extract(YEAR FROM m.taken)+
          greatest(
            0,
            sign(
              (extract(YEAR FROM m.taken)||'-12-12')::date -
              (extract(YEAR FROM m.taken)||'-02-01')::date) ))||'-02-01')::date as end_date,
      m.taken,
      extract(YEAR FROM m.taken) as year_taken
    from
      measurement m
    where
      m.station_id = 200 AND
      m.category_id = 1 AND
      (m.taken, m.taken) OVERLAPS
        ((extract(YEAR FROM m.taken)||'-12-12')::date,
        ((extract(YEAR FROM m.taken)+
          greatest(
            0,
            sign(
              (extract(YEAR FROM m.taken)||'-12-12')::date -
              (extract(YEAR FROM m.taken)||'-02-01')::date) ))||'-02-01')::date)
    

    它产生正确的开始和结束日期约束:

    start_date  ;end_date    ;taken       ;year_taken
    "1969-12-12";"1970-02-01";"1969-12-12";1969
    "1969-12-12";"1970-02-01";"1969-12-13";1969
    ...
    "1969-12-12";"1970-02-01";"1969-12-31";1969
    "1970-12-12";"1971-02-01";"1970-12-12";1970
    

    但是,1970-01-011970-02-01之间的值缺失,但必须包含在内。

    版本

    PostgreSQL 8.4.4和PHP 5.2.10

1 个答案:

答案 0 :(得分:1)

SELECT
    m.amount
FROM
    measurement m,
    to_date('Dec 01 1900', 'Mon DD YYYY') AS A(d1),
    to_date('Feb 28 1900', 'Mon DD YYYY') AS B(d2),
    to_date('Feb 28 1901', 'Mon DD YYYY') AS C(d3)
WHERE m.taken
    BETWEEN
        d1 AND
        CASE WHEN d2 < d1 THEN d3 ELSE d2 END

参考文献:conditional expressionsdata type formatting functions

编辑:对不起,我以为你想要特定的一年:

SELECT
    amount
FROM
    (SELECT
        M.amount, M.taken,
        to_date('Dec 01 ' || extract(YEAR FROM M.taken), 'Mon DD YYYY'),
        to_date('Feb 28 ' || extract(YEAR FROM M.taken), 'Mon DD YYYY')
     FROM
        measurement AS M
     ) AS A(amount, taken, d1, d2)
WHERE
    (d2 >= d1 AND taken BETWEEN d1 AND d2)
    OR
    (d2 < d1 AND (taken <= d2 OR taken >= d1));

如果设置很大,则没有很多优化机会。在这种情况下,您可以使用SQL函数将所有日期转换为特定年份(say taken - (extract(year from taken) - 1900) * '1 year'::interval),然后与1900年12月2日和1900年2月28日进行比较。这样您可以索引此日期转换函数的结果和您不必为每个条目计算两个日期。