我们的文章会在微信公众号IT民工的龙马人生和博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢!
由于博客中有大量代码,通过页面浏览效果更佳。
本文为个人学习《Expert Oracle Database Architecture Techniques and Solutions for High Performance and Productivity(第四版本》一书过程中的笔记与理解分享,仅用于学习与交流,部分内容参考原书观点并结合>实际经验进行整理。若涉及版权问题,请联系删除或沟通处理。也请大家支持购买原版书籍。
深入理解 Oracle 的 DATE 类型:存储、计算与最佳实践
Oracle 数据库中的 DATE
类型是一种功能强大且高效的日期时间数据类型。它不仅用于存储日期,还精确到秒级时间。本文将深入探讨其内部存储格式、如何进行日期运算,并提供实用的最佳实践建议。
1. DATE 类型的内部存储机制
DATE
是一种固定宽度、占用 7 个字节的数据类型。每个字节分别存储以下七个属性:
- 字节 1:世纪 (Century)
- 字节 2:年份 (Year)
- 字节 3:月份 (Month)
- 字节 4:日 (Day)
- 字节 5:小时 (Hour)
- 字节 6:分钟 (Minute)
- 字节 7:秒 (Second)
Oracle 使用一种特殊的内部格式存储这些值,并非直接存储我们看到的数字。我们可以使用 DUMP
函数来查看其内部实际存储的值。
示例:查看内部存储
SQL> CREATE TABLE t (x DATE);
SQL> INSERT INTO t (x) VALUES (TO_DATE('25-jun-2005 12:01:00', 'dd-mon-yyyy hh24:mi:ss'));
SQL> SELECT x, DUMP(x, 10) AS d FROM t;X D
--------- -----------------------------------
25-JUN-05 Typ=12 Len=7: 120,105,6,25,13,2,1
解码存储格式:
- 世纪和年份 (120,105):采用“excess-100”表示法。计算方式为
字节值 - 100
。- 世纪:
120 - 100 = 20
(20世纪) - 年份:
105 - 100 = 5
(05年) - 如果结果为负数,则表示公元前(BC)日期。
- 世纪:
- 月份和日期 (6,25):直接存储,无需转换。
6
代表六月,25
代表25日。 - 时间部分 (13,2,1):采用“excess-1”表示法。计算方式为
字节值 - 1
。- 小时:
13 - 1 = 12
- 分钟:
2 - 1 = 1
- 秒:
1 - 1 = 0
- 因此,
1,1,1
代表午夜 00:00:00。
- 小时:
这种精巧的存储格式使得日期能够自然地按二进制顺序排序,并且截断操作(如截取到天或月)非常高效,只需将相应字节设置为 1
即可。
2. 给 DATE 类型加减时间
为 DATE
类型增加或减少时间单位主要有三种方法:
- 直接加数字:
DATE + n
。n
代表天数,因此1
是一天,1/24
是一小时,1/24/60
是一分钟,依此类推。 - 使用 INTERVAL 数据类型:例如
NUMTODSINTERVAL(n, 'unit')
用于天/秒级单位,NUMTOYMINTERVAL(n, 'unit')
用于年月级单位。 - 使用内置函数:
ADD_MONTHS(date, n)
专门用于增加月份,能智能处理月末日期。
推荐的最佳实践:
- 秒、分钟、小时:使用
NUMTODSINTERVAL(n, 'second'/'minute'/'hour')
。代码更清晰易读。SELECT SYSDATE + NUMTODSINTERVAL(1, 'HOUR') FROM dual; -- 加1小时
- 天、周:直接加数字。
SELECT SYSDATE + 7 FROM dual; -- 加1周
- 月、年:强烈推荐使用
ADD_MONTHS(date, n)
。它能正确处理月末等特殊情况,避免错误。
为什么不推荐用 INTERVAL 加月/年?
ADD_MONTHS
会对月末日期进行智能舍入,而直接加 INTERVAL
可能导致错误。
-- ADD_MONTHS 能正确处理月末
SELECT ADD_MONTHS(TO_DATE('29-FEB-2000', 'DD-MON-YYYY'), 1) FROM dual;
-- 结果: 31-MAR-2000 (返回下个月的最后一天)-- 使用 INTERVAL 可能出错
SELECT TO_DATE('30-JAN-2001', 'DD-MON-YYYY') + NUMTOYMINTERVAL(1, 'MONTH') FROM dual;
-- 错误: ORA-01839: 日期对指定的月份无效 (因为二月没有30号)
3. 计算两个 DATE 之间的差异
直接相减即可得到两个日期之间相差的天数(包括小数部分)。
SELECT (SYSDATE - hire_date) AS days_diff FROM employees;
要获取更直观的“年-月-日 时:分:秒”格式的差异,可以结合使用函数:
SELECTNUMTOYMINTERVAL(TRUNC(MONTHS_BETWEEN(dt2, dt1)), 'month') AS years_months,NUMTODSINTERVAL(dt2 - ADD_MONTHS(dt1, TRUNC(MONTHS_BETWEEN(dt2, dt1))),'day') AS days_hours
FROM (SELECTTO_DATE('29-feb-2000 01:02:03', 'dd-mon-yyyy hh24:mi:ss') AS dt1,TO_DATE('15-mar-2001 11:22:33', 'dd-mon-yyyy hh24:mi:ss') AS dt2FROM dual
);-- 结果示例:
-- YEARS_MONTHS: +000000001-00 (1年0个月)
-- DAYS_HOURS: +000000015 10:20:30.000000000 (15天10小时20分30秒)
解释:
MONTHS_BETWEEN(dt2, dt1)
计算两个日期之间总月数(含小数)。TRUNC(...)
取整,得到完整的月数。NUMTOYMINTERVAL(...)
将整月数转换为“年-月”间隔。dt2 - ADD_MONTHS(dt1, 整月数)
计算扣除整月后剩余的时间差。NUMTODSINTERVAL(...)
将剩余的时间差转换为“天 时:分:秒”间隔。
总结
- 存储:Oracle
DATE
使用 7 字节高效存储,格式精巧,支持自然排序和快速截断。 - 运算:
- 加/减时间:使用
NUMTODSINTERVAL
处理小时/分钟/秒,直接加数字处理天,使用ADD_MONTHS
处理月/年。 - 避免使用
NUMTOYMINTERVAL
直接给日期加月份或年份,以免因月末日期导致错误。
- 加/减时间:使用
- 差异:日期相减得天数。组合
MONTHS_BETWEEN
,ADD_MONTHS
,NUMTOYMINTERVAL
和NUMTODSINTERVAL
函数可以计算出精确的年、月、日、时、分、秒差。
遵循这些最佳实践,您将能更加准确和高效地在 Oracle 中处理日期和时间数据。
------------------作者介绍-----------------------
姓名:黄廷忠
个人博客: (http://www.htz.pw)
CSDN地址: (https://blog.csdn.net/wwwhtzpw)
博客园地址: (https://www.cnblogs.com/www-htz-pw)