背景
用户可以选择日期,如下面的屏幕截图所示:
任何开始月/日和结束月/日组合均有效,例如:
- 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
)
它是如何工作的,关于棘手的日期场景:
- 在当年创建两个日期。
- 第一个日期是1900 年 12 月 22 日,第二个日期是1900 年 2 月 28 日。
- 计算两个日期之间相差的天数。
- 如果结果为负,则意味着第二个日期的年份必须递增 1。在这种情况下:
- 将当前年份加 1。
- 创建一个新的结束日期:1901 年 2 月 28 日。
- 检查数据的日期范围是否在开始日期和计算的结束日期之间。
- 如果结果是肯定的,则日期已按时间顺序提供,无需执行任何特殊操作。
这在 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-01
和 1970-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/