代码之家  ›  专栏  ›  技术社区  ›  Dave Jarvis James Eichele

计算结束日期的年份:PostgreSQL

  •  2
  • Dave Jarvis James Eichele  · 技术社区  · 14 年前

    背景

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

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

    • 3月22日至6月22日
    • dec 1 to feb 28

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

    • 12月22日, 1900 至2月28日, 1901
    • 12月22日, 1901 至2月28日, 1902
    • …/LI>
    • 12月22日, 2007年至2月28日, 2008年
    • 12月22日, 2008年至2月28日, 2009年

    问题

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

    输入

    查询作为参数接收:

    • 第1年,第2年:年的全部范围,独立于月/日组合。
    • Month1,Day1:一年中收集数据的开始日期。
    • Month2,Day2:收集数据的一年(或下一年)内的结束日。

    上一次尝试

    考虑下面的mysql代码(有效的):。

    end_year=start_year+
    最大(1)
    符号(符号)
    DATEDIFF(
    日期(
    concat_ws(“-”,年,月底,日底)
    )
    日期(
    concat_ws(“-”,year,start_month,start_day)
    )
    )
    ),0
    )
    < /代码> 
    
    

    它是如何工作的,对于复杂的日期场景:

    1. 在当前年份中创建两个日期。
    2. 第一个日期是19001222日,第二个日期是1900228日。
    3. 计算两个日期之间的差额(以天为单位)。
    4. 如果结果为负数,则表示第二个日期的年份必须增加1。在这种情况下:
      • 在当前年份中添加1
      • 创建一个新的结束日期:1901228日。
      • 检查数据的日期范围是否介于开始日期和计算的结束日期之间。
    5. 如果结果是肯定的,日期是按时间顺序提供的,不需要做任何特殊的事情。

    这在MySQL中有效,因为日期的差异是正的或负的。在PostgreSQL中,等效功能总是返回一个正数,不管它们的相对时间顺序如何。(不过,我对减法日期的测试可能不正确。)

    问题

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

    选择
    M.量
    从
    测量M
    哪里
    (摘录(从m.take开始的月份)>=month1和
    提取(从m.take开始的天)>=day1)和
    (提取(从m.take开始的月份)<=month2和
    提取(从m.take算起的天数)<=day2)
    < /代码> 
    
    

    有什么想法、意见或问题吗?

    (这些日期在PHP中被预先解析为mm/dd格式。我喜欢纯PostgreSQL解决方案,但是我愿意接受关于使用PHP使问题简单化的建议。SQL存在于JasperReports调用的存储过程中,因此PHP只能接触传递到报告引擎的日期。)

    更新1

    以下代码几乎可以工作:

    选择
    (摘录(从m.take开始的年份)‘-12-12’):日期作为开始日期,
    (摘录(从m.taken开始的年份)+
    最大的(
    0,
    符号(符号)
    (摘录(从m.take开始的年份)‘-12-12’):日期-
    (摘录(从m.take开始的年份)-02-01')::日期))-02-01'):日期作为结束日期,
    M.拍摄,
    提取(从m.taken开始的年份)作为\u采取的年份
    从
    测量M
    哪里
    M.Station_id=200和
    M.category_id=1和
    (m.taken,m.taken)重叠
    ((摘录(从m.take开始年份)‘-12-12’):日期,
    (摘录(从m.taken开始的年份)+
    最大的(
    0,
    符号(符号)
    (摘录(从m.take开始的年份)‘-12-12’):日期-
    (摘录(从m.take开始的年份)‘-02-01’::日期))‘-02-01’::日期)
    < /代码> 
    
    

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

    开始日期;结束日期;采取;年采取
    “1969-12-12”;“1970-02-01”;“1969-12-12”;19691969-12-12”;“1970-02-01”;“1969-12-13”;1969
    …
    “1969-12-12”;“1970-02-01”;“1969-12-31”;19691970-12-12”;“1971-02-01”;“1970-12-12”;1970年
    < /代码> 
    
    

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

    版本

    PostgreSQL 8.4.4和PHP 5.2.10

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

    • 322日至622
    • 121228

    第二种组合很困难(我称之为“棘手的日期方案”),因为月末/日应该来之后起始月/日. 也就是说,对于1900年(也显示在上面的屏幕截图中),完整日期为:

    • 1222日,一千九百228日,一千九百零一
    • 1222日,一千九百零一228日,一千九百零二
    • 1222日,二千零七228日,二千零八
    • 1222日,二千零八228日,二千零九

    问题

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

    输入

    查询作为参数接收:

    • 1年,第2年:年的全部范围,独立于月/日组合。
    • 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. 第一次约会是19001222第二次约会是1900228.
    3. 计算两个日期之间的差额(以天为单位)。
    4. 如果结果为负数,则表示第二个日期的年份必须增加1。在这种情况下:
      • 1添加到当前年份。
      • 创建新的结束日期:1901228.
      • 检查数据的日期范围是否介于开始日期和计算的结束日期之间。
    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)
    

    有什么想法、意见或问题吗?

    (这些日期在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 回复  |  直到 8 年前
        1
  •  1
  •   Dave Jarvis James Eichele    14 年前
    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 expressions , data 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月02日和1900年2月28日进行比较。这样,您就可以索引这个日期转换函数的结果,而不必为每个条目计算两个日期。