php - 计算结束日期 : PostgreSQL 的年份

标签 php sql postgresql date

背景

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

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

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

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

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

问题

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

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

最佳答案

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 月 2 日和 1900 年 2 月 28 日进行比较。这样您就可以为这个日期转换函数的结果编制索引,而不必为每个条目计算两个日期。

关于php - 计算结束日期 : PostgreSQL 的年份,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2947105/

相关文章:

php - MySQL的threads_connected数量减慢了我的网站速度

php - 将图像保存为 blob 类型

sql - 如何包装以下 CTE,以便我可以将其输出与 INSERT INTO 语句一起使用?

sql - 格式化为百分比

SQL - 根据存储为 VARCHAR 的 10 位整数创建唯一的 AlphaNumeric

postgresql - 删除行后 Heroku Postgres 数据库大小不会下降

postgresql - pgAdmin:如何在输出的单元格中查看完整值

php - Sublime text 2 中像 Dreamweaver 一样的默认页面设置?

sql - 如何从 Postgres 的预订中找到第一个免费开始时间

php - mysql php 选择范围内的表时间戳