【问题标题】:Calculate year for end date: PostgreSQL计算结束日期的年份:PostgreSQL
【发布时间】:2011-02-26 04:30:43
【问题描述】:

背景

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

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

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

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

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

问题

编写一个 SQL 语句,该语句从日期介于开始月/日和结束月/日之间的表中选择值,无论开始日和结束日是如何选择的。换句话说,这是一个年结问题。

输入

查询作为参数接收:

  • Year1、Year2:完整的年份范围,独立于月/日组合。
  • Month1, Day1:一年中收集数据的开始日期。
  • Month2, Day2:一年内(或下一年)收集数据的结束日。

上一次尝试

考虑以下 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. 如果结果是肯定的,则日期已按时间顺序提供,无需执行任何特殊操作。

这在 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)

有什么想法、cmets 或问题吗?

(日期在 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

【问题讨论】:

    标签: php sql postgresql date


    【解决方案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 日进行比较。这样,您可以索引此日期转换函数的结果,但您不需要不必为每个条目计算两个日期。

    【讨论】:

    • 这只会返回单次迭代的值。你如何让它返回所有年份的值?谢谢,这是一个比检查日期差异更优雅的解决方案。
    • 仅供参考:您不需要调用 to_date()。 PostgreSQL 自动识别各种日期格式,只要年-月-日的顺序很明显(即月份名称)或至少与您的 DATESTYLE 设置一致。例如,您应该能够改用以下语法:date ('Dec 01 ' || extract(YEAR FROM M.taken))。这里的“日期”不是一个函数,而是一个 SQL 标准指示符,表示下一个表达式应该被解释为一个日期,而不是它的正常数据类型(尽管您需要括号,因为类型转换优先于串联)。
    猜你喜欢
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多