最近的一个项目中的一项要求是报告资源何时会被完全消耗。除了用尽日历日期外,我还被要求以类似英语的格式显示剩余时间,例如“1 year, 3 months to go”。
内置DATEDIFF
函数
返回指定开始日期和结束日期之间跨越的指定日期部分边界的计数。
如果按原样使用,可能会产生误导或混淆的结果。例如,使用 YEAR 的间隔将显示 1999-12-31 (YYYY-MM-DD) 和 2000-01-01 相隔一年,而常识会说这些日期仅相隔 1 天。相反,使用 DAY 1999-12-31 和 2010-12-31 的间隔相隔 4,018 天,而大多数人会将“11 年”视为更好的描述。
从天数开始计算月份和年份,容易出现闰年和月份大小错误。
我想知道如何在各种 SQL 方言中实现这一点?示例输出包括:
create table TestData(
FromDate date not null,
ToDate date not null,
ExpectedResult varchar(100) not null); -- exact formatting is unimportant
insert TestData (FromDate, ToDate, ExpectedResult)
values ('1999-12-31', '1999-12-31', '0 days'),
('1999-12-31', '2000-01-01', '1 day'),
('2000-01-01', '2000-02-01', '1 month'),
('2000-02-01', '2000-03-01', '1 month'), -- month length not important
('2000-01-28', '2000-02-29', '1 month, 1 day'), -- leap years to be accounted for
('2000-01-01', '2000-12-31', '11 months, 30 days'),
('2000-02-28', '2000-03-01', '2 days'),
('2001-02-28', '2001-03-01', '1 day'), -- not a leap year
('2000-01-01', '2001-01-01', '1 year'),
('2000-01-01', '2011-01-01', '11 years'),
('9999-12-30', '9999-12-31', '1 day'), -- catch overflow in date calculations
('1900-01-01', '9999-12-31', '8099 years 11 months 30 days'); -- min(date) to max(date)
我碰巧使用的是 SQL Server 2008R2,但我有兴趣了解其他方言如何处理这个问题。
此答案显示了使用 SQL Server (2005+) CLR 函数的实现。
组装和功能
用法
结果
资源
我不是 C# 程序员!
以下解决方案适用于 SQL Server。该方法与Serg 的方法相似,因为查询仅使用 DATEADD 和 DATEDIFF 函数。但是,它不考虑负间隔(FromDate > ToDate),它从总月差中得出年份和月份:
输出:
我的版本,在 SQL Server 2008R2 SP2 中实现。
使用给定的测试数据,结果是
解释
我的一般方法是从较早的日期开始,首先是几年,然后是几个月,然后是几天。在每个粒度级别,目标是尽可能接近结束日期而不超过它,然后在下一个较低级别继续。
我使用数字表来促进接近但未结束的计算。从这张表中,我可以找到代码中注释(B)
DATEADD
之前的最大年/月/天数。ToDate
由于我在寻找 MAX 数并且我的 Numbers 表聚集在它上面,因此优化器正在执行降序扫描,将值提供给 DATEADD。这会导致日期溢出错误,因为 Numbers 包含超过 100,000 行。
DATEADD(YEAR, 100000, @FromDate)
大于 9999-12-31 并引发错误。谓词 (A) 给出了向后扫描开始的 Number 值的上限,避免了日期溢出。因此,即使是非常大的日期范围,查询计划也会遍历非常少的行。这种方法用于查找年份和月份,除了我在第一个 CTE 中找到的年份,月份的起点提前了。DAYS 是我的最低粒度级别,所以一个简单的 DATEDIFF 就足够了。
这可以扩展到更精细的粒度,如果需要,以小时、分钟和秒为单位返回间隔。
PostgreSQL 支持
age
开箱即用的功能:这给出了期望的结果,给予或接受一些额外的时间值。
不需要
number
表格或计数的版本。对 Michael Green 的测试数据给出相同的结果。他们在数据上有所不同@FromDate > @ToDate
。ReadableInterval2
返回与空值相反的负值。