1. 为什么SQL日期函数如此重要
在日常数据库操作中,日期和时间处理占据了SQL查询的很大比重。根据我的经验,几乎90%的业务系统查询都涉及日期条件过滤、时间段统计或日期格式转换。日期函数就像是SQL工具箱里的瑞士军刀,看似简单但功能强大。
记得我刚入行时,曾花了两天时间手动计算季度数据,后来才发现用QUARTER()函数一行代码就能解决。这种痛彻心扉的经历让我深刻认识到:掌握日期函数不仅能提升工作效率,更能避免很多低级错误。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础日期函数详解
2.1 获取当前日期和时间
最基础的日期函数莫过于获取系统当前时间:
sql复制-- MySQL/PostgreSQL
SELECT NOW(); -- 返回当前日期和时间 '2023-08-20 14:30:45'
SELECT CURDATE(); -- 只返回日期部分 '2023-08-20'
SELECT CURTIME(); -- 只返回时间部分 '14:30:45'
-- SQL Server
SELECT GETDATE(); -- 等效于MySQL的NOW()
SELECT CURRENT_TIMESTAMP; -- 标准SQL写法
注意:在生产环境中,不同数据库服务器可能有时区差异,建议统一使用UTC时间存储,显示时再转换。
2.2 日期提取函数
从日期值中提取特定部分:
sql复制-- 提取年份
SELECT YEAR('2023-08-20'); -- 返回2023
-- 提取月份
SELECT MONTH('2023-08-20'); -- 返回8
SELECT MONTHNAME('2023-08-20'); -- 返回'August'
-- 提取日
SELECT DAY('2023-08-20'); -- 返回20
SELECT DAYNAME('2023-08-20'); -- 返回'Sunday'
-- 提取季度
SELECT QUARTER('2023-08-20'); -- 返回3
3. 日期计算与转换
3.1 日期加减运算
sql复制-- MySQL
SELECT DATE_ADD('2023-08-20', INTERVAL 7 DAY); -- 加7天
SELECT DATE_SUB('2023-08-20', INTERVAL 1 MONTH); -- 减1个月
-- PostgreSQL
SELECT '2023-08-20'::date + INTERVAL '1 year'; -- 加1年
-- SQL Server
SELECT DATEADD(day, 7, '2023-08-20'); -- 加7天
3.2 日期差值计算
sql复制-- MySQL
SELECT DATEDIFF('2023-08-25', '2023-08-20'); -- 返回5(天数差)
-- PostgreSQL/SQL Server
SELECT DATE_PART('day', '2023-08-25'::timestamp - '2023-08-20'::timestamp);
3.3 日期格式化
sql复制-- MySQL
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'); -- '2023-08-20 14:30:45'
SELECT DATE_FORMAT(NOW(), '%W, %M %e %Y'); -- 'Sunday, August 20 2023'
-- SQL Server
SELECT FORMAT(GETDATE(), 'yyyy-MM-dd HH:mm:ss');
4. 高级日期处理技巧
4.1 工作日计算
计算两个日期之间的工作日天数(排除周末):
sql复制-- MySQL实现
SELECT
(DATEDIFF('2023-08-31', '2023-08-01') + 1) -
(FLOOR((DATEDIFF('2023-08-31', '2023-08-01') + WEEKDAY('2023-08-01') + 1) / 7) * 2) -
(IF(WEEKDAY('2023-08-31') < WEEKDAY('2023-08-01'), 2, 0)) AS working_days;
4.2 月末日期获取
sql复制-- MySQL
SELECT LAST_DAY('2023-08-15'); -- 返回'2023-08-31'
-- SQL Server
SELECT EOMONTH('2023-08-15'); -- 返回'2023-08-31'
4.3 生成日期序列
生成某个月的所有日期:
sql复制-- MySQL 8.0+使用递归CTE
WITH RECURSIVE date_range AS (
SELECT '2023-08-01' AS date
UNION ALL
SELECT date + INTERVAL 1 DAY
FROM date_range
WHERE date < '2023-08-31'
)
SELECT * FROM date_range;
5. 实战应用场景
5.1 销售数据月度分析
sql复制SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31'
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month;
5.2 用户留存率计算
sql复制-- 计算7日留存
SELECT
DATE(register_date) AS reg_date,
COUNT(DISTINCT user_id) AS reg_users,
COUNT(DISTINCT CASE WHEN DATEDIFF(login_date, register_date) = 7 THEN user_id END) /
COUNT(DISTINCT user_id) AS retention_rate_7day
FROM users
LEFT JOIN logins ON users.user_id = logins.user_id
GROUP BY DATE(register_date);
5.3 节假日特殊处理
处理节假日订单的示例:
sql复制-- 创建节假日表
CREATE TABLE holidays (
holiday_date DATE PRIMARY KEY,
holiday_name VARCHAR(100)
);
-- 查询非节假日的工作日订单
SELECT *
FROM orders
WHERE
DAYOFWEEK(order_date) NOT IN (1, 7) -- 排除周末
AND order_date NOT IN (SELECT holiday_date FROM holidays);
6. 性能优化与常见问题
6.1 日期查询的性能陷阱
sql复制-- 不推荐:函数作用于列上会导致索引失效
SELECT * FROM orders WHERE YEAR(order_date) = 2023;
-- 推荐:使用范围查询
SELECT * FROM orders
WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31';
6.2 时区处理最佳实践
sql复制-- 存储时使用UTC
INSERT INTO events (event_time) VALUES (UTC_TIMESTAMP());
-- 查询时转换为本地时区
SELECT
event_time AS utc_time,
CONVERT_TZ(event_time, '+00:00', '+08:00') AS local_time
FROM events;
6.3 日期格式的隐式转换问题
sql复制-- 可能导致意外的结果
SELECT * FROM logs WHERE log_date = '08/20/2023'; -- 格式依赖系统设置
-- 明确指定格式
SELECT * FROM logs WHERE log_date = STR_TO_DATE('08/20/2023', '%m/%d/%Y');
7. 不同数据库的日期函数差异
7.1 MySQL vs PostgreSQL vs SQL Server
| 功能 | MySQL | PostgreSQL | SQL Server |
|---|---|---|---|
| 当前时间 | NOW() | NOW() | GETDATE() |
| 日期加减 | DATE_ADD/DATE_SUB | + INTERVAL | DATEADD |
| 日期差值 | DATEDIFF | DATE_PART | DATEDIFF |
| 月末日期 | LAST_DAY | (date_trunc+interval) | EOMONTH |
| 日期格式化 | DATE_FORMAT | TO_CHAR | FORMAT |
7.2 日期字面量语法差异
sql复制-- MySQL
SELECT * FROM events WHERE event_date = '2023-08-20';
-- PostgreSQL
SELECT * FROM events WHERE event_date = DATE '2023-08-20';
-- SQL Server
SELECT * FROM events WHERE event_date = CAST('2023-08-20' AS DATE);
8. 个人实战经验分享
在实际项目中,我总结了几个日期处理的黄金法则:
-
存储一致性:坚持使用DATE/DATETIME类型存储日期,避免用字符串。曾经有个项目用VARCHAR存日期,结果'2023-01-02'和'01/02/2023'混用导致无数bug。
-
时区意识:所有服务器设置为UTC时间,应用层处理时区转换。有次跨国项目因时区混乱导致财务日结差了一天,教训深刻。
-
函数封装:将常用的日期逻辑封装成数据库函数。比如我们创建了WORKDAY_ADD()函数,自动跳过周末和节假日计算工作日。
-
边界检查:处理月份日期时要特别注意月末边界。例如2月28/29日、4月30日等特殊日期经常是bug高发区。
-
性能监控:定期检查慢查询日志,特别关注包含日期函数的查询。我曾优化过一个使用YEAR()函数的查询,性能提升了100倍。
