MsSql

关注公众号 jb51net

关闭
首页 > 数据库 > MsSql > sql日期特殊性

SQL 中日期的特殊性总结(格式符严格要求全大写)

作者:穆金秋

文章总结了SQL中日期数据类型的特殊性,并详细介绍了日期与字符串的转换、日期比较和加减运算的注意事项,强调了索引使用与避免函数修饰筛选列的重要性,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

SQL中日期的特殊性总结

在SQL中,日期是一种特殊的数据类型,既有数值的特性,又有字符串的表现形式,使用时有诸多需要注意的地方。

一、日期数据类型的特点

特性说明示例
存储格式内部存储为数字(从某个基准日期开始的天数/秒数)Oracle: 4712-01-01 起的天数
显示格式由数据库参数控制,不一定是输入时的格式Oracle: 17-12月-80
运算能力支持加减运算(天数/月数/年数)HIREDATE + 30(30天后)
比较能力支持 <, >, =, BETWEEN 等比较操作HIREDATE > TO_DATE('1981-01-01')

二、日期与字符串的转换(最重要)

核心函数

函数方向用途
TO_DATE(字符串, 格式)字符串 → 日期将字符串按指定格式解析为日期类型
TO_CHAR(日期, 格式)日期 → 字符串将日期按指定格式转换为字符串

常用日期格式元素

格式符含义示例
YYYY四位年份1981
YY两位年份81
MM两位月份05
MON月份缩写(中文环境为'5月')'5月'
MONTH月份全称'5月'
DD两位日期01
DAY星期几'星期三'
HH2424小时制14
MI分钟30
SS秒钟45

在SQL中,日期格式符是区分大小写的,这是一个非常重要的细节,写错了会导致转换失败或结果错误。

核心规则:格式符严格区分大小写

格式符含义正确示例错误示例(大小写错误)
MM月份(01-12)TO_CHAR(date, 'MM') → 04mm → ❌ 报错或无效
MI分钟(00-59)TO_CHAR(date, 'MI') → 30Mi / mi → ❌
HH2424小时制(00-23)TO_CHAR(date, 'HH24') → 14hh24 → ❌
HH12 / HH12小时制(01-12)TO_CHAR(date, 'HH12') → 02hh12 → ❌
YYYY四位年份TO_CHAR(date, 'YYYY') → 2026yyyy → ❌
YY两位年份TO_CHAR(date, 'YY') → 26yy → ❌
MON月份缩写(如'4月')TO_CHAR(date, 'MON') → 4月Mon / mon → ❌
MONTH月份全称(如'4月')TO_CHAR(date, 'MONTH') → 4月Month → ❌
DD日期(01-31)TO_CHAR(date, 'DD') → 23dd → ❌
DY星期缩写(如'周三')TO_CHAR(date, 'DY') → 周三dy → ❌
DAY星期全称(如'星期三')TO_CHAR(date, 'DAY') → 星期三Day → ❌

示例代码

-- TO_DATE:字符串转日期
TO_DATE('1981-05-01', 'YYYY-MM-DD')     -- 返回日期:1981年5月1日
TO_DATE('19810501', 'YYYYMMDD')         -- 返回日期:1981年5月1日
TO_DATE('1981-05', 'YYYY-MM')           -- 返回日期:1981年5月1日(默认当月1号)
-- TO_CHAR:日期转字符串
TO_CHAR(HIREDATE, 'YYYY-MM-DD')         -- '1981-05-01'
TO_CHAR(HIREDATE, 'YYYYMM')             -- '198105'
TO_CHAR(HIREDATE, 'MON DD, YYYY')       -- '5月 01, 1981'

三、日期比较的特殊性

1. 不能直接用字符串比较日期

-- ❌ 错误:字符串 '1981' 和日期类型不能直接比较
SELECT * FROM EMP WHERE HIREDATE = '1981';
-- ✅ 正确方式1:转换日期为字符串比较
SELECT * FROM EMP WHERE TO_CHAR(HIREDATE, 'YYYY') = '1981';
-- ✅ 正确方式2:字符串转日期比较
SELECT * FROM EMP WHERE HIREDATE >= TO_DATE('1981-01-01', 'YYYY-MM-DD')
                     AND HIREDATE < TO_DATE('1982-01-01', 'YYYY-MM-DD');

2. 日期比较的边界问题(重要⚠️)

-- 查询1981年入职的员工(错误写法)
SELECT * FROM EMP 
WHERE TO_CHAR(HIREDATE, 'YYYY') = 1981;  -- ✅ 可行,但效率低
-- 查询1981年入职的员工(正确写法 - 使用范围)
SELECT * FROM EMP 
WHERE HIREDATE >= TO_DATE('1981-01-01', 'YYYY-MM-DD')
  AND HIREDATE <  TO_DATE('1982-01-01', 'YYYY-MM-DD');
-- 查询1981年5月入职(错误写法)
WHERE HIREDATE BETWEEN TO_DATE('1981-05-01', 'YYYY-MM-DD')
                   AND TO_DATE('1981-05-31', 'YYYY-MM-DD');  -- ⚠️ 漏掉了5月31日23:59:59之后的数据
-- 查询1981年5月入职(正确写法)
WHERE HIREDATE >= TO_DATE('1981-05-01', 'YYYY-MM-DD')
  AND HIREDATE <  TO_DATE('1981-06-01', 'YYYY-MM-DD');

边界写法参考

需求TO_CHAR 写法TO_DATE 范围写法
年份 = 1981TO_CHAR(HIREDATE,'YYYY') = 1981HIREDATE >= TO_DATE('1981-01-01','YYYY-MM-DD') AND HIREDATE < TO_DATE('1982-01-01','YYYY-MM-DD')
年份 < 1982TO_CHAR(HIREDATE,'YYYY') < 1982HIREDATE < TO_DATE('1982-01-01','YYYY-MM-DD')
年份 <= 1982TO_CHAR(HIREDATE,'YYYY') <= 1982HIREDATE < TO_DATE('1983-01-01','YYYY-MM-DD')
年份 > 1981TO_CHAR(HIREDATE,'YYYY') > 1981HIREDATE >= TO_DATE('1982-01-01','YYYY-MM-DD')
年份 >= 1982TO_CHAR(HIREDATE,'YYYY') >= 1982HIREDATE >= TO_DATE('1982-01-01','YYYY-MM-DD')

四、日期的加减运算

运算含义示例
日期 + 数字增加天数HIREDATE + 30(30天后)
日期 - 数字减少天数HIREDATE - 7(7天前)
日期1 - 日期2相差天数SYSDATE - HIREDATE(入职天数)
ADD_MONTHS(日期, 数字)增加月份ADD_MONTHS(HIREDATE, 6)(6个月后)
MONTHS_BETWEEN(日期1, 日期2)相差月数MONTHS_BETWEEN(SYSDATE, HIREDATE)

示例代码

-- 计算员工入职天数
SELECT ENAME, SYSDATE - HIREDATE AS 工作天数 FROM EMP;
-- 计算员工入职月数
SELECT ENAME, MONTHS_BETWEEN(SYSDATE, HIREDATE) AS 工作月数 FROM EMP;
-- 查询入职超过30年的员工
SELECT * FROM EMP 
WHERE ADD_MONTHS(HIREDATE, 30*12) < SYSDATE;

五、日期函数对比(Oracle vs MySQL)

功能OracleMySQL
当前日期时间SYSDATENOW() / CURDATE()
提取年份TO_CHAR(date, 'YYYY')YEAR(date)
提取月份TO_CHAR(date, 'MM')MONTH(date)
日期加减天数date + 10DATE_ADD(date, INTERVAL 10 DAY)
日期差(天数)date1 - date2DATEDIFF(date1, date2)
增加月份ADD_MONTHS(date, 6)DATE_ADD(date, INTERVAL 6 MONTH)

六、常见陷阱与最佳实践

❌ 常见错误

-- 1. 直接比较字符串和日期
WHERE HIREDATE = '1981-05-01'           -- 隐式转换可能失败
-- 2. 使用 BETWEEN 包含结束日期(会丢失当天23:59:59后的数据)
WHERE HIREDATE BETWEEN '1981-05-01' AND '1981-05-31'
-- 3. TO_CHAR 写在 WHERE 条件的左边(无法使用索引)
WHERE TO_CHAR(HIREDATE, 'YYYY') = '1981'
-- 4. 忽略时区问题
WHERE CREATE_TIME = '2026-04-23'        -- 可能漏掉带时分秒的记录

✅ 最佳实践

-- 1. 始终使用显式转换
WHERE HIREDATE >= TO_DATE('1981-05-01', 'YYYY-MM-DD')
  AND HIREDATE <  TO_DATE('1981-06-01', 'YYYY-MM-DD')
-- 2. 范围查询使用左闭右开区间
WHERE HIREDATE >= TRUNC(SYSDATE - 30)   -- 30天前零点
  AND HIREDATE <  TRUNC(SYSDATE)        -- 今天零点
-- 3. 让函数作用在常量上,保持索引有效
WHERE HIREDATE >= TO_DATE('1981-01-01', 'YYYY-MM-DD')  -- ✅ 索引有效
WHERE TO_CHAR(HIREDATE, 'YYYY') = '1981'                -- ❌ 索引失效
-- 4. 使用 TRUNC 去掉时间部分
WHERE TRUNC(HIREDATE) = TO_DATE('1981-05-01', 'YYYY-MM-DD')

使用 TRUNC 去掉时间部分是什么意思

TRUNC 是一个用于截断日期或数字的函数。在日期处理中,"去掉时间部分"是指将日期中的时、分、秒清零,只保留年、月、日。

为什么需要"去掉时间部分"?问题场景:日期比较的陷阱
-- 假设表中有一条记录,HIREDATE = 1981-05-01 14:30:00
-- ❌ 错误:这条记录会被漏掉!
SELECT * FROM EMP 
WHERE HIREDATE = TO_DATE('1981-05-01', 'YYYY-MM-DD');
-- 因为左边有 14:30:00,右边是 00:00:00,不相等
-- ✅ 解法1:使用 TRUNC 去掉时间部分
SELECT * FROM EMP 
WHERE TRUNC(HIREDATE) = TO_DATE('1981-05-01', 'YYYY-MM-DD');
-- ✅ 解法2:使用范围查询(更推荐,索引友好)
SELECT * FROM EMP 
WHERE HIREDATE >= TO_DATE('1981-05-01', 'YYYY-MM-DD')
  AND HIREDATE < TO_DATE('1981-05-02', 'YYYY-MM-DD');

TRUNC 的常用格式

用法结果说明
TRUNC(SYSDATE)2026-04-24 00:00:00截断到当天开始(默认)
TRUNC(SYSDATE, 'DD')2026-04-24 00:00:00同上,DD表示天
TRUNC(SYSDATE, 'MM')2026-04-01 00:00:00截断到当月第一天
TRUNC(SYSDATE, 'Q')2026-04-01 00:00:00截断到当季第一天
TRUNC(SYSDATE, 'YYYY')2026-01-01 00:00:00截断到当年第一天
TRUNC(SYSDATE, 'HH24')2026-04-24 14:00:00截断到当前小时开始

对比总结

函数作用示例输入示例输出
TRUNC(date)去掉时间部分(归零)2026-04-24 14:35:282026-04-24 00:00:00
TO_CHAR(date, 'YYYY-MM-DD')转为字符串(丢失时间)2026-04-24 14:35:28'2026-04-24'
ROUND(date)四舍五入到天2026-04-24 14:35:282026-04-25 00:00:00

TRUNC 会让索引失效(类似 TO_CHAR)

-- ❌ 索引失效
WHERE TRUNC(HIREDATE) = TO_DATE('1981-05-01', 'YYYY-MM-DD')
-- ✅ 推荐:范围查询(索引有效)
WHERE HIREDATE >= TO_DATE('1981-05-01', 'YYYY-MM-DD')
  AND HIREDATE < TO_DATE('1981-05-02', 'YYYY-MM-DD')

原则:能不用 TRUNC 在 WHERE 条件中就不用,除非数据量很小或没有时间精度要求。

TRUNC 常用于 GROUP BY 分组

-- 按天统计(即使数据库存了时分秒)
SELECT TRUNC(HIREDATE) AS 入职日期, COUNT(*) AS 人数
FROM EMP
GROUP BY TRUNC(HIREDATE);

一句话总结

TRUNC 去掉时间部分 = 把 14:35:28 变成 00:00:00
用于忽略时分秒的干扰,让日期比较只看年月日。
但在 WHERE 中要小心使用,因为它和 TO_CHAR 一样会让索引失效,大数据量时建议用范围查询替代。

七、快速参考卡片

需求SQL写法
当前系统日期SYSDATE(Oracle)/ CURDATE()(MySQL)
年月日格式'YYYY-MM-DD'
字符串→日期TO_DATE('1981-05-01', 'YYYY-MM-DD')
日期→字符串TO_CHAR(HIREDATE, 'YYYY-MM-DD')
提取年份TO_CHAR(HIREDATE, 'YYYY')
提取年月TO_CHAR(HIREDATE, 'YYYYMM')
某月第一天TRUNC(HIREDATE, 'MM')
某年第一天TRUNC(HIREDATE, 'YYYY')
月底最后一天LAST_DAY(HIREDATE)
下个月同一天ADD_MONTHS(HIREDATE, 1)

八、你在作业中的日期问题总结

-- 第3题 ✅ 正确
WHERE TO_CHAR(HIREDATE, 'YYYYMM') < 198210
-- 第5题 ⚠️ 缺少括号(结果正确但不规范)
WHERE DEPTNO=20 AND TO_CHAR(HIREDATE,'YYYY')<1982
   OR DEPTNO=30 AND TO_CHAR(HIREDATE,'YYYY')<1985
-- 第11题 ❌ 完全遗漏WHERE条件
-- 应该加:WHERE TO_CHAR(HIREDATE, 'YYYY') > 1981

核心要点

TO_CHAR 会让索引失效,大数据量时慎用。是什么意思?

索引就像一本书的"目录",它能帮你快速翻到需要的页码。但如果对"目录"里的文字做了修改(比如加了格式),那原来的目录就失效了,你只能一页一页地翻完整本书来找内容。

1. 为什么TO_CHAR会让索引失效?

SQL的执行顺序决定了索引的生效机制。

当你执行 WHERE TO_CHAR(HIREDATE, 'YYYY') = '1981' 时,数据库的处理过程是这样的:

索引为什么没起作用?

因为索引里存的是原始的、未经过任何处理的 HIREDATE 值,而你在查询时使用的是 TO_CHAR(HIREDATE) 这个函数的返回值。数据库没法用原始的日期值去匹配一个函数的返回值,所以只能放弃索引,从头到尾把整张表的数据都处理一遍。

这正是你在笔记中看到的高级查询优化问题。

2. 另一种写法:为什么索引能生效?

如果你换一种写法,比如 WHERE HIREDATE >= TO_DATE('1981-01-01', 'YYYY-MM-DD'),过程完全不同:

索引生效的关键在于:
列本身HIREDATE)没有被任何函数、计算所改变,数据库可以直接用你在 WHERE 里给的值去跟索引里的值做比对。

3. “大数据量时慎用”是什么意思?

数据量影响程度说明
小数据量 (几十、几百条)影响极微即便没有索引,逐行扫描也快如闪电,用户完全感受不到差异。
中等数据量 (几万、几十万条)影响显著逐行扫描开始变慢,可能需要几秒甚至更久,用户能明显感觉到"卡"。
大数据量 (百万、千万条以上)灾难性影响逐行扫描会让查询耗时从毫秒级(有索引)变成分钟甚至小时级(无索引,全表扫描)

举个生活化的例子:

4. 优化建议与最佳实践

核心原则永远不要让函数去修饰被筛选的列本身

要避免的写法 (❌ 索引失效)推荐写法 (✅ 索引生效)说明
WHERE TO_CHAR(HIREDATE, 'YYYY') = '1981'WHERE HIREDATE >= TO_DATE('1981-01-01', 'YYYY-MM-DD') AND HIREDATE < TO_DATE('1982-01-01', 'YYYY-MM-DD')用范围查询代替函数
WHERE TO_CHAR(HIREDATE, 'YYYYMM') = '198105'WHERE HIREDATE >= TO_DATE('1981-05-01', 'YYYY-MM-DD') AND HIREDATE < TO_DATE('1981-06-01', 'YYYY-MM-DD')同上
WHERE SAL * 12 > 50000WHERE SAL > 50000 / 12让函数作用在常量上
WHERE SUBSTR(ENAME, 1, 1) = 'S'WHERE ENAME LIKE 'S%'使用 LIKE 前缀匹配(也能利用索引)

总结一句话:

写 WHERE 条件时,让“列”自己待着,别碰它。如果需要处理,去处理等号右边的“值”。 这样你的查询才能在大数据量下保持高效。

你之前写的 WHERE TO_CHAR(HIREDATE, 'YYYY') = 1981 在小数据量练习时完全没问题,但在真正的企业生产环境中(数据量可能上千万),这种写法几乎是被明令禁止的。

初学阶段掌握规则即可,但养成好习惯会很有帮助。

到此这篇关于SQL 中日期的特殊性总结(格式符严格要求全大写)的文章就介绍到这了,更多相关sql日期特殊性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文