在数据库开发中,日期处理几乎无处不在。查询近三十天的订单、统计某月的数据量、把日期格式化成指定字符串输出,这些操作都离不开日期函数。DB2提供了丰富的日期处理函数,但由于历史版本的原因,同一个需求往往有多种写法,不同写法之间又有细微差别,很多初学者容易搞混。本文将系统讲解DB2中日期加减运算与格式化输出的常用方法,并通过实例演示帮助读者掌握这些函数的正确用法。

一、DB2日期加减的几种常用方法
DB2中做日期加减最经典的方式是使用带关键字的算术表达式,也就是在日期值后面直接加减一个数值并跟上时间单位关键字,例如YEARS、MONTHS、DAYS、HOURS、MINUTES、SECONDS等。这种方式简洁直观,比如DATE('2024-03-15') + 1 MONTH就表示日期加一个月,结果为2024-04-15。如果需要同时加减多个单位,也可以连续书写,例如加一年零三个月可以写成+ 1 YEAR + 3 MONTHS。
除了算术表达式,DB2还提供了一系列专门的日期函数来完成同样的工作,主要包括ADD_MONTHS、以及使用TIMESTAMPADD等方式。ADD_MONTHS函数接收两个参数,第一个是日期,第二个是要加的月数,返回加月之后的日期。当传入负数时就是减去相应的月份。这种方式与Oracle兼容性较好,如果系统需要跨数据库迁移,使用ADD_MONTHS会让SQL更容易移植。
-- 方式一:使用算术表达式加减日期
VALUES DATE('2024-03-15') + 1 MONTH; -- 结果 2024-04-15
VALUES DATE('2024-03-15') + 3 MONTHS; -- 结果 2024-06-15
VALUES DATE('2024-03-15') + 1 YEAR - 2 DAYS; -- 结果 2025-03-13
VALUES TIMESTAMP('2024-03-15 10:30:00') + 2 HOURS; -- 结果 2024-03-15 12:30:00
-- 方式二:使用 ADD_MONTHS 函数
VALUES ADD_MONTHS(DATE('2024-01-31'), 1); -- 结果 2024-02-29(自动处理月末)
-- 计算两个日期之间相差的天数与月数
VALUES DAYS(DATE('2024-03-15')) - DAYS(DATE('2024-02-01')); -- 结果 43
VALUES MONTHS_BETWEEN(DATE('2024-03-15'), DATE('2024-01-10')); -- 结果约 2.16
需要注意的是,日期加减在月末边界上会自动做特殊处理。例如2024年1月31日加一个月,由于2月没有31日,DB2会把结果截断到2月的最后一天,也就是2月29日(闰年)。这一点在业务上通常是合理的,但如果你的系统依赖的是“进位到3月”的逻辑,就需要自己另外处理。此外,DAYS函数可以把日期转换成内部天数序号,两个日期分别取DAYS再相减,就能得到相差的天数,这是计算日期间隔的常用技巧。
二、日期格式化的常用函数与写法
DB2格式化日期最常用的函数是TO_CHAR,它把日期或时间戳按照格式模板转换成字符串。格式模板由一系列占位符组成,例如YYYY表示四位年份、MM表示两位月份、DD表示两位日期、HH24表示24小时制的小时、MI表示分钟、SS表示秒。这个函数的用法与Oracle基本一致,学习成本低,是格式化输出的首选方案。
除了TO_CHAR,还可以使用CHAR或VARCHAR函数配合格式码来转换日期。CHAR(CURRENT DATE, ISO)会按照ISO标准输出YYYY-MM-DD格式的字符串,可选的格式码还有EUR(DD.MM.YYYY)、USA(MM/DD/YYYY)、JIS(YYYY-MM-DD)等。这种方式适合只需要标准格式的场景,缺点是不如TO_CHAR灵活,无法自由拼接格式。
-- 使用 TO_CHAR 自定义格式
SELECT TO_CHAR(CURRENT TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS') FROM SYSIBM.SYSDUMMY1;
-- 输出示例:2024-03-15 14:30:25
SELECT TO_CHAR(CURRENT DATE, 'YYYYMMDD') FROM SYSIBM.SYSDUMMY1;
-- 输出示例:20240315
-- 使用 CHAR 函数按标准格式转换
SELECT CHAR(CURRENT DATE, ISO) FROM SYSIBM.SYSDUMMY1; -- 2024-03-15
SELECT CHAR(CURRENT DATE, EUR) FROM SYSIBM.SYSDUMMY1; -- 15.03.2024
SELECT CHAR(CURRENT DATE, USA) FROM SYSIBM.SYSDUMMY1; -- 03/15/2024
-- 从字符串按指定格式解析为日期
SELECT TO_DATE('20240315', 'YYYYMMDD') FROM SYSIBM.SYSDUMMY1;
-- 结果 2024-03-15
与格式化相对的是字符串转日期,DB2提供了TO_DATE(别名TIMESTAMP_FORMAT)函数,可以按指定格式把字符串解析成日期或时间戳。这在处理外部系统传来的非标准日期字符串时特别有用,比如接口传入YYYYMMDD格式的日期,直接用TO_DATE解析后再参与运算,比手工截取拼接安全得多。要提醒的是,如果格式模板与实际字符串不匹配,DB2会直接报错,因此解析前最好对数据格式做校验。
三、DATE与TIMESTAMP类型的转换及常见陷阱
DB2中DATE类型只包含年月日,而TIMESTAMP类型包含年月日时分秒甚至微秒,两者之间的转换非常频繁。把TIMESTAMP转成DATE可以直接用DATE()函数,它只截取日期部分;反过来把DATE转成TIMESTAMP,时分秒部分会补零。此外还有TIMESTAMP()、TIME()等函数分别用于构造完整时间戳和提取时间部分。
-- TIMESTAMP 转 DATE,只保留日期部分
SELECT DATE(CURRENT TIMESTAMP) FROM SYSIBM.SYSDUMMY1;
-- DATE 转 TIMESTAMP,时分秒补零
VALUES TIMESTAMP(DATE('2024-03-15')); -- 2024-03-15-00.00.00
-- 提取日期中的具体字段
VALUES YEAR(CURRENT DATE); -- 年份,例如 2024
VALUES MONTH(CURRENT DATE); -- 月份,例如 3
VALUES DAY(CURRENT DATE); -- 日期,例如 15
VALUES DAYOFWEEK(CURRENT DATE); -- 星期几,1 表示周日
VALUES DAYNAME(CURRENT DATE); -- 星期名称,例如 Friday
VALUES MONTHNAME(CURRENT DATE); -- 月份名称,例如 March
-- 计算当月最后一天
SELECT LAST_DAY(CURRENT DATE) FROM SYSIBM.SYSDUMMY1;
实际使用中有几个常见的坑值得注意。第一个是隐式转换问题,当DATE与TIMESTAMP做比较时,DB2会把DATE隐式提升为TIMESTAMP,此时日期的时分秒为零,如果字段里存的是TIMESTAMP,用WHERE order_time = DATE '2024-03-15'这样的条件几乎查不到数据,正确的做法是用范围条件>= DATE '2024-03-15' AND < DATE '2024-03-16',或者对字段做DATE()截断,但后者会让索引失效,数据量大时要注意性能。
第二个坑是格式化函数对日期差的格式化。DB2的TIMESTAMPDIFF函数只能估算两个时间戳之间的差值,且精度有限,跨月、跨年的计算往往不准确,精确计算建议先把时间戳转成日期再相减。第三个是时区问题,CURRENT DATE和CURRENT TIMESTAMP取的是数据库服务器本地时间,如果应用与数据库不在同一时区,或者系统需要支持多时区,建议使用CURRENT TIMESTAMP WITH TIME ZONE(新版本支持)或统一约定使用UTC时间存储,避免统计报表出现日期错位的情况。
四、综合实战:常见业务场景的日期SQL写法
掌握了基本函数之后,再看几个典型业务场景的综合运用。比如查询最近30天的订单、按月分组统计、获取本月第一天和最后一天、计算下个月15号的还款日期等,这些需求把前面讲过的加减运算、格式化、转换函数组合起来就能解决。
-- 场景一:查询最近30天的订单
SELECT * FROM orders
WHERE create_time >= CURRENT TIMESTAMP - 30 DAYS;
-- 场景二:本月第一天与最后一天
VALUES DATE_TRUNC('MM', CURRENT DATE); -- 本月第一天
VALUES LAST_DAY(CURRENT DATE); -- 本月最后一天
-- 场景三:按月分组统计订单量并格式化输出
SELECT TO_CHAR(create_time, 'YYYY-MM') AS month,
COUNT(*) AS order_count
FROM orders
GROUP BY TO_CHAR(create_time, 'YYYY-MM')
ORDER BY month;
-- 场景四:计算下单时间之后30天的到期日,输出 YYYYMMDD 格式
SELECT order_no,
TO_CHAR(DATE(create_time) + 30 DAYS, 'YYYYMMDD') AS expire_date
FROM orders;
-- 场景五:筛选下周一生成的数据
SELECT * FROM orders
WHERE DATE(create_time) = CURRENT DATE + (8 - DAYOFWEEK(CURRENT DATE)) DAYS;
从这些例子可以看出,DB2的日期处理思路是把“运算、转换、格式化”三类函数组合使用:加减用算术表达式或ADD_MONTHS,类型转换用DATE、TIMESTAMP系列函数,输出用TO_CHAR。写SQL时建议养成一个习惯,先在脑子里明确每个字段的类型是DATE、TIME还是TIMESTAMP,再决定是否需要转换,这样可以避开绝大多数隐式转换带来的问题。另外,不同版本的DB2(如DB2 for LUW与DB2 for i)在个别函数的支持上有差异,比如DATE_TRUNC和LAST_DAY需要较新版本才支持,上线前最好在目标环境验证一下函数可用性,保证SQL在生产环境稳定运行。