
DATEDIFF函数怎么用?日期差计算方法与示例
计算两个日期相差多少天、多少个月或多少年,是报表、账期、订单超时判断和数据分析中的常见需求。许多数据库都提供名为 DATEDIFF 的函数,但不同数据库的参数顺序、返回值含义并不完全相同。即使在同一种数据库中,“相差 1 天”也可能指经过了 24 小时,或者跨过了一个日历日期边界,两者并非总是一回事。
本文重点介绍 SQL Server 和 MySQL 的 DATEDIFF,并给出 PostgreSQL、SQLite 的替代写法。示例均可直接在对应数据库中运行。使用前先确认数据库类型,并明确要计算的是经过时长,还是日历边界差。
一、DATEDIFF 用来做什么
DATEDIFF 用于根据指定的时间单位,计算两个日期或时间值之间的差异。常见场景包括:
- 计算订单距离当前日期多少天,筛选超期订单。
- 统计用户注册后经过多少天、多少月或多少年。
- 计算两个业务日期之间跨过了多少个周、月或季度边界。
- 生成按日期分组的报表,或检查数据是否落在某个时间范围内。
函数名称相同,不代表语义相同。最容易混淆的有两点:
- 参数顺序:SQL Server 通常先传开始值,再传结束值;MySQL 则是先传被减数,再传减数。
- 计算规则:SQL Server 的结果通常表示跨过了多少个指定单位的边界;MySQL 的
DATEDIFF只返回两个日期部分相减的天数,不提供“按月”或“按年”的单位参数。
二、SQL Server:按 datepart 计算边界差
2.1 基本语法
DATEDIFF ( datepart, startdate, enddate )
参数含义如下:
datepart:要计算的时间单位,例如年、月、日、小时或分钟。startdate:开始日期或时间。enddate:结束日期或时间。
结果的方向是 enddate - startdate:结束值晚于开始值时通常为正,早于开始值时通常为负。若任一日期参数为 NULL,结果为 NULL。
下面是一个可直接运行的例子:
SELECT
DATEDIFF(day, '2024-01-01', '2024-01-10') AS day_diff,
DATEDIFF(month, '2024-01-31', '2024-02-01') AS month_diff,
DATEDIFF(hour, '2024-01-01T10:00:00', '2024-01-01T12:30:00') AS hour_diff;
结果分别是 9、1 和 2。第二个结果尤其值得注意:两个日期只差一天,但跨过了一个月的边界,因此按月计算结果为 1。第三个结果按小时边界计算,跨过了 10 点、11 点和 12 点这两个小时边界,结果是 2,而不是精确时长的 2.5 小时。
2.2 SQL Server 的日期单位
常用的 datepart 名称和缩写包括:
| 单位 | 常见写法 | 说明 |
|---|---|---|
| 年 | year、yy、yyyy | 跨过的年边界数 |
| 季度 | quarter、qq、q | 跨过的季度边界数 |
| 月 | month、mm、m | 跨过的月边界数 |
| 年内日 | dayofyear、dy、y | 计算日边界差,效果与按日使用类似 |
| 日 | day、dd、d | 跨过的午夜边界数 |
| 周 | week、wk、ww | 跨过的周边界数 |
| 小时 | hour、hh | 跨过的小时边界数 |
| 分钟 | minute、mi、n | 跨过的分钟边界数 |
| 秒 | second、ss、s | 跨过的秒边界数 |
完整支持的单位与缩写可能受 SQL Server 版本和数据类型影响。需要毫秒、微秒等更细粒度时,应查阅目标版本的官方文档;不是所有数据类型都能表示所有精度。
2.3 边界差不等于经过时长
SQL Server 的 DATEDIFF 统计指定单位的边界被跨过多少次,并不计算两个时刻之间完整经过了多少个单位。下面的例子只相差一小段时间,却跨过了午夜:
SELECT
DATEDIFF(day, '2024-05-01T23:59:59', '2024-05-02T00:00:01') AS crossed_days,
DATEDIFF(hour, '2024-05-01T23:59:59', '2024-05-02T00:00:01') AS crossed_hours,
DATEDIFF(minute, '2024-05-01T23:59:59', '2024-05-02T00:00:01') AS crossed_minutes;
三个结果都是 1。实际只经过了两秒,但日期、小时和分钟边界各跨过一次。这种规则适合“跨过多少个日历边界”的问题,不适合直接用来回答“精确经过多少小时”。
若需要精确计算秒数,可使用 DATEDIFF_BIG,并选择合适的精度:
SELECT DATEDIFF_BIG(
millisecond,
'2024-05-01T23:59:59.000',
'2024-05-02T00:00:01.000'
) AS elapsed_milliseconds;
该示例返回经过的毫秒数。注意,DATEDIFF_BIG 仍是按所选单位计算边界差,只是返回 bigint,可容纳更大的数值;它不会自动把计算变成小数形式的精确时长。若要得到带小数的小时数,可以先求毫秒差,再除以毫秒数,并确保使用支持小数的类型:
SELECT
CAST(DATEDIFF_BIG(
millisecond,
'2024-05-01T10:00:00.000',
'2024-05-01T12:30:00.000'
) AS decimal(20, 3)) / 3600000 AS elapsed_hours;
2.4 按日判断订单是否超期
如果业务规则是“结束日期比到期日期晚至少一个日历日”,可以使用按日边界计算。不过筛选某个日期范围时,通常直接写范围条件更清楚,也更利于利用索引。
DECLARE @today date = '2024-06-15';
SELECT order_id, due_date
FROM dbo.Orders
WHERE due_date < @today;
如果规则确实是计算每条记录与指定日期相差的日历日数,再按结果筛选,可以这样写:
DECLARE @today date = '2024-06-15';
SELECT order_id, due_date
FROM dbo.Orders
WHERE DATEDIFF(day, due_date, @today) > 0;
但对列应用函数,往往会让数据库难以直接使用该列上的普通索引进行范围查找。能改写成 due_date < @today 时,通常优先采用直接比较。
2.5 SQL Server 的限制与溢出
DATEDIFF 返回 int。当指定单位较小、日期跨度较大时,差值可能超出 int 可表示范围,此时会发生溢出错误,而不是自动返回更大的整数。文档中针对毫秒等细粒度单位给出了相应的最大时间跨度限制。对可能跨度很大的数据,评估后使用 DATEDIFF_BIG;它返回 bigint,但也有自身的数值范围上限。
另外,SQL Server 的 datepart 不能像普通字符串参数那样用变量或字符串字面量传入。例如,下面的写法是错误的:
-- 不要这样写
SELECT DATEDIFF('day', '2024-01-01', '2024-01-02');
应写成不带引号的单位标识符:
SELECT DATEDIFF(day, '2024-01-01', '2024-01-02');
日期字面量建议使用明确、稳定的格式,例如 YYYY-MM-DD,时间戳可使用带 T 的 ISO 格式,避免受会话语言和日期格式设置影响。
三、MySQL:DATEDIFF 只计算日期部分
3.1 基本语法与参数顺序
DATEDIFF(expr1, expr2)
MySQL 返回 expr1 - expr2 的天数,结果是整数。它只使用参数的日期部分,忽略时间部分;函数本身没有用于指定月、年、小时等单位的 datepart 参数。
SELECT
DATEDIFF('2024-01-10', '2024-01-01') AS positive_days,
DATEDIFF('2024-01-01', '2024-01-10') AS negative_days,
DATEDIFF('2024-01-01 23:59:59', '2024-01-01 00:00:00') AS same_date;
结果分别为 9、-9 和 0。最后一项虽然时间不同,但日期部分相同,所以结果为 0。这也是 MySQL 与 SQL Server 的重要区别:MySQL 的这个函数不用于计算小时、分钟或秒数。
3.2 计算天数、月份和完整年数
MySQL 中计算天数可以直接使用 DATEDIFF。若要计算月份差,可使用 TIMESTAMPDIFF:
SELECT
DATEDIFF('2024-03-15', '2024-03-01') AS days_diff,
TIMESTAMPDIFF(MONTH, '2024-01-31', '2024-03-01') AS whole_months,
TIMESTAMPDIFF(YEAR, '2000-06-15', '2024-06-14') AS whole_years;
TIMESTAMPDIFF(unit, datetime_expr1, datetime_expr2) 的方向是第二个日期减去第一个日期。它适合按指定单位求差,但结果会按所选单位截取为整数,不适合直接作为精确时长的小数表达。示例中的完整年数为 23,因为结束日期还没有到当年的 6 月 15 日。
不要把“跨过多少个月份边界”和“完整经过多少个月”混为一谈。比如从 1 月 31 日到 2 月 1 日,按日历月份标签看跨过了一个月边界,但实际并没有经过完整一个月。应按业务定义选择函数和计算规则,并用月末、闰年等日期验证结果。
3.3 计算两个时间戳的时长
当参数包含时间时,DATEDIFF 仍会忽略时分秒。若要计算时间单位差异,可以使用 TIMESTAMPDIFF:
SELECT
TIMESTAMPDIFF(
SECOND,
'2024-05-01 10:00:00',
'2024-05-01 10:00:02'
) AS elapsed_seconds;
若需要精确到小数秒,应根据列类型和精度选用合适的函数、数据类型或时间差表达式,并确认返回单位和舍入规则。不要默认整数秒数就能满足所有时长统计需求。
3.4 MySQL 日期参数的处理
DATEDIFF 的参数应提供有效的日期、日期时间值,或数据库能够按日期规则解释的值。无效日期的行为会受到 MySQL 版本、SQL 模式和输入形式影响,可能产生警告、错误或特殊结果。不要依靠宽松模式把格式错误的字符串当作有效日期使用;应在写入前校验,并尽可能使用正确的日期列类型,而不是用任意文本存日期。
如果来自应用程序的输入是字符串,应使用参数绑定,并在应用层或数据库约束中验证格式和有效性。参数绑定可以避免把数据值拼接进 SQL,从而降低 SQL 注入风险,但它不能替代日期范围校验和业务规则检查。
四、PostgreSQL 与 SQLite 的替代写法
PostgreSQL 没有与 SQL Server、MySQL 完全同语义的内置 DATEDIFF。计算两个日期的天数差,可以直接相减:
SELECT DATE '2024-01-10' - DATE '2024-01-01' AS days_diff;
对于时间戳,可以相减得到一个 interval:
SELECT TIMESTAMP '2024-05-01 12:30:00'
- TIMESTAMP '2024-05-01 10:00:00' AS elapsed;
如果需要“完整经过的小时数”或“跨过的月边界数”,应根据具体定义处理 interval,不能直接把某个结果当作所有 DATEDIFF 方言的等价替代。涉及时区时,还要明确比较的是本地时间还是绝对时间,并选用合适的带时区类型和转换规则。
SQLite 没有内置的 DATEDIFF 函数。若两个值采用 SQLite 可识别的日期时间格式,可以使用 julianday 相减得到天数差:
SELECT julianday('2024-01-10') - julianday('2024-01-01') AS days_diff;
结果可能是浮点数。若业务只需要完整的日历日期差,可以将输入规范为日期后再计算,并按规则取整;不要未经确认就直接对浮点结果四舍五入。SQLite 的日期时间处理主要依赖内置日期函数和规定的文本、数值格式,应保持数据格式统一。
五、易错点与排查清单
5.1 搞错参数顺序
SQL Server 写法是 DATEDIFF(单位, 开始, 结束);MySQL 的写法是 DATEDIFF(结束, 开始),并且只计算天数。移植 SQL 时不要只替换函数名称,应逐项检查参数顺序和计算语义。
5.2 把边界数当成精确时长
SQL Server 按单位边界计数,MySQL 的 DATEDIFF 忽略时间部分。用于工资工时、计费时长、超时分钟数等场景时,先定义时区、精度、是否向上取整以及起止时刻是否包含,再选用正确的时间差计算方式。
5.3 忽略负数
两个日期的先后顺序会影响正负号。若业务只允许非负天数,不能假设数据一定按顺序传入;应明确校验、调整参数顺序,或对负值作出可解释的处理,而不是随意取绝对值。
5.4 用函数包住索引列
条件 DATEDIFF(day, date_column, @today) > 0 在逻辑上可用,但可能影响普通索引的范围查找。能写为 date_column < @today 时通常更有利于查询优化。是否使用索引仍取决于执行计划、统计信息、数据分布和数据库版本,重要查询应实际检查执行计划。
5.5 忽略时区与夏令时
本地时间的一天不一定等于固定的 24 小时:夏令时切换可能让当地时钟的一天少一小时或多一小时。跨时区数据应保存明确的时区语义,通常以 UTC 时间点进行时长计算,再按业务时区转换后做日历日期统计。只存“没有时区信息”的本地时间,无法可靠还原它对应的绝对时刻。
5.6 把周差理解成固定七天
SQL Server 的 DATEDIFF(week, ...) 计算周边界差,不等同于把总天数除以 7。周边界的解释还可能受到周起始日等设置影响。若要计算相差的完整七天周期,应明确起止时刻和取整方式,避免直接用周边界结果替代。
六、安全使用:日期计算不是安全校验
DATEDIFF 是计算函数,不会验证用户是否有权访问数据,也不会自动防止 SQL 注入。安全使用需要同时处理 SQL 构造、输入校验和权限控制。
- 对日期值使用参数绑定:不要把用户输入直接拼接进 SQL 字符串。参数化可以让数据库把输入当作值处理,而不是 SQL 语句的一部分。
- 参数化不能绑定 SQL Server 的 datepart:单位是语法标识符,不是普通数据值。若单位由用户选择,应在应用中把输入映射到固定白名单,例如只允许
day、month、year,再选择预先写好的 SQL 分支;不要把原始输入拼成单位名称。 - 限制日期范围:对外部输入检查格式、上下界和业务合理性,避免异常跨度造成溢出、错误统计或昂贵查询。
- 使用最小权限:查询账户只授予所需的数据读取权限。日期差函数本身不会代替数据库权限系统。
例如,在应用中可采用固定分支,而不是拼接用户提供的单位文本:
-- 示意:单位由程序白名单映射到固定语句
-- day 分支
SELECT DATEDIFF(day, @start_date, @end_date) AS diff;
-- month 分支
SELECT DATEDIFF(month, @start_date, @end_date) AS diff;
其中 @start_date 和 @end_date 应通过驱动提供的参数机制传入。不同语言和数据库驱动的占位符写法不同,应按所用驱动文档实现。
七、如何选择正确的计算方式
| 需求 | 建议方式 | 注意事项 |
|---|---|---|
| SQL Server 计算跨过的日、月、年边界 | DATEDIFF(datepart, start, end) | 结果不是完整经过时长 |
| SQL Server 计算较大范围的细粒度差值 | DATEDIFF_BIG | 返回 bigint,仍要考虑取值范围 |
| MySQL 计算日期部分相差的天数 | DATEDIFF(expr1, expr2) | 顺序为前者减后者,忽略时间 |
| MySQL 按年、月等单位求整数差 | TIMESTAMPDIFF(unit, start, end) | 明确单位和截取规则 |
| PostgreSQL 计算日期天数差 | 日期直接相减 | 时间戳相减得到 interval |
| SQLite 计算天数差 | julianday(end) - julianday(start) | 结果为浮点数,输入格式要统一 |
总结
使用 DATEDIFF 前,先回答三个问题:当前是哪种数据库?要计算边界差还是实际经过时长?结果单位和正负方向是什么?在 SQL Server 中,DATEDIFF 依照指定单位统计边界差,参数顺序为“单位、开始、结束”,并返回 int;超出范围时可评估 DATEDIFF_BIG。在 MySQL 中,DATEDIFF 的参数顺序为“前者减后者”,只计算日期部分的天数,按其他单位计算可考虑 TIMESTAMPDIFF。PostgreSQL 和 SQLite 则应使用各自的日期时间运算方式。
最后,日期差结果只有在业务定义明确时才有意义。特别是月末、闰年、时区切换、负数、精度和索引使用等情况,最好准备边界测试数据,并用实际执行结果验证。








