📊 SQL 入门 Day 15:日期与字符串函数 — SQL 中的数据处理瑞士军刀

0 阅读7分钟

场景引入

做数据分析时,原始数据很少是"拿来就能用"的。日期字段一般是完整的 DATETIME,但你只需要月份;名字字段可能是 " 张三 "(带空格),或者 "Zhang San" 需要统一小写。你需要的是一套数据清洗与格式化的工具,这就是日期函数和字符串函数。

如果说 JOIN 和窗口函数是 SQL 的"大杀器",那日期与字符串函数就是日常用得最频繁的"瑞士军刀"。


一、日期函数速览

1. 提取日期分量

从日期中提取年、月、日、季度、周等。

SELECT
    NOW()                                        AS 当前时间,
    CURDATE()                                    AS 当前日期,
    YEAR('2026-07-23')                           AS 年份,
    MONTH('2026-07-23')                          AS 月份,
    DAY('2026-07-23')                            AS 日,
    QUARTER('2026-07-23')                        AS 季度,
    WEEKDAY('2026-07-23')                        AS 周几索引,  -- 0=周一, 6=周日
    DAYNAME('2026-07-23')                        AS 星期名称,
    DAYOFYEAR('2026-07-23')                      AS 年中第几天;

执行结果:

当前时间当前日期年份月份日季度周几索引星期名称年中第几天
2026-07-23 20:59:002026-07-23202672333Thursday204

2. 日期计算

SELECT
    DATE_ADD('2026-07-23', INTERVAL 30 DAY)        AS 加30天,
    DATE_SUB('2026-07-23', INTERVAL 1 MONTH)       AS 减1个月,
    DATEDIFF('2026-12-31', '2026-07-23')            AS 相差天数,
    TIMESTAMPDIFF(MONTH, '2026-01-01', '2026-07-23') AS 相差月数;

执行结果:

加30天减1个月相差天数相差月数
2026-08-222026-06-231616

常用 INTERVAL 单位: DAY, MONTH, YEAR, HOUR, MINUTE, WEEK, QUARTER

3. 日期格式化 — DATE_FORMAT

这是最实用的日期函数,没有之一。

SELECT
    DATE_FORMAT('2026-07-23 14:30:00', '%Y-%m-%d')           AS 日期,
    DATE_FORMAT('2026-07-23 14:30:00', '%Y年%m月%d日')        AS 中文日期,
    DATE_FORMAT('2026-07-23 14:30:00', '%H:%i:%s')           AS 时间,
    DATE_FORMAT('2026-07-23 14:30:00', '%Y-%m')              AS 月份聚合键,
    DATE_FORMAT('2026-07-23 14:30:00', '%W')                 AS 星期几;

执行结果:

日期中文日期时间月份聚合键星期几
2026-07-232026年07月23日14:30:002026-07Thursday

常用格式化符一览:

符号含义示例
%Y4位年份2026
%y2位年份26
%m2位月份07
%c月份(无前导0)7
%d2位日23
%e日(无前导0)23
%H24小时(00-23)14
%i分钟30
%s秒00
%W星期全名Thursday
%a星期缩写Thu
%M月份全名July

二、字符串函数速览

1. 大小写与空白处理 — 清洗必备

SELECT
    UPPER('hello world')   AS 大写,
    LOWER('HELLO WORLD')   AS 小写,
    TRIM('  SQL 笔记  ')   AS 去两端空格,
    LTRIM('  左边')        AS 去左空格,
    RTRIM('右边  ')        AS 去右空格;

执行结果:

大写小写去两端空格去左空格去右空格
HELLO WORLDhello worldSQL 笔记左边右边

⚠️ TRIM 默认去掉空格,也可以指定要去掉的字符:TRIM('x' FROM 'xxhelloxx') → 'hello'

2. 截取与拼接

SELECT
    CONCAT('SQL', ' ', '笔记')                        AS 拼接,
    CONCAT_WS('-', '2026', '07', '23')                AS 带分隔符拼接,
    SUBSTRING('MySQL数据库', 2, 3)                    AS 截取,    -- 从第2位取3个字符
    LEFT('MySQL数据库', 4)                            AS 左侧取4位,
    RIGHT('MySQL数据库', 3)                           AS 右侧取3位;

执行结果:

拼接带分隔符拼接截取左侧取4位右侧取3位
SQL 笔记2026-07-23ySQMySQ据库

补充说明:

  • SUBSTRING(s, pos) — 从 pos 取到末尾
  • SUBSTRING(s, pos, len) — 从 pos 取 len 个字符
  • SUBSTRING_INDEX(s, delim, count) — 按分隔符截取,超级实用
SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2);  -- 'a.b'
SELECT SUBSTRING_INDEX('a.b.c.d', '.', -2); -- 'c.d'

3. 查找与替换

SELECT
    LOCATE('SQL', 'MySQL数据库教程')     AS 位置,         -- 返回 2
    POSITION('SQL' IN 'MySQL数据库教程') AS 位置2,        -- 等价
    INSTR('MySQL数据库教程', 'SQL')     AS 位置3,         -- 等价
    REPLACE('2026/07/23', '/', '-')     AS 替换分隔符;

执行结果:

位置位置2位置3替换分隔符
2222026-07-23

字符串长度:

SELECT
    LENGTH('SQL笔记')   AS 字节数,    -- UTF-8下中文占3字节, 结果 9
    CHAR_LENGTH('SQL笔记') AS 字符数; -- 结果 5

✅ 处理中文字符串时,CHAR_LENGTH() 比 LENGTH() 更符合直觉。


三、实战:数据清洗与格式化

场景 — 用户注册表分析

-- 原始数据
CREATE TABLE users (
    id INT,
    full_name VARCHAR(50),      -- 可能有空格、大小写混用
    email VARCHAR(100),         -- 可能有空格
    registered_at DATETIME      -- yyyy-MM-dd HH:mm:ss 格式
);

INSERT INTO users VALUES
    (1, '  Zhang San  ', 'zhangsan@example.com ', '2026-03-15 08:30:00'),
    (2, 'LI SI', 'lisi@example.com', '2026-06-20 14:15:00'),
    (3, '  Wang wu  ', 'WANGWU@EXAMPLE.COM', '2026-07-01 09:00:00');

需求:清洗数据并输出月报格式

SELECT
    id,
    CONCAT(
        UPPER(LEFT(TRIM(full_name), 1)),                     -- 姓氏首字母大写
        LOWER(SUBSTRING(TRIM(full_name), 2))                  -- 其余小写
    ) AS clean_name,
    LOWER(TRIM(email)) AS clean_email,
    DATE_FORMAT(registered_at, '%Y-%m') AS reg_month,
    DATE_FORMAT(registered_at, '%Y年%c月%d日') AS reg_date_cn,
    TIMESTAMPDIFF(DAY, registered_at, NOW()) AS reg_days_ago
FROM users;

执行结果:

idclean_nameclean_emailreg_monthreg_date_cnreg_days_ago
1zhang sanzhangsan@example.com2026-032026年3月15日130
2li silisi@example.com2026-062026年6月20日33
3wang wuwangwu@example.com2026-072026年7月1日22

💡 注意:Zhang San 经过处理后姓氏和名字之间的空格还在,名字的中间空格不会被 TRIM 去掉(这是正确的行为)。


四、综合实战:按月份统计并格式化输出

SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month,
    COUNT(*) AS order_count,
    CONCAT('¥', FORMAT(SUM(amount), 2)) AS total_revenue,
    CONCAT(
        DATE_FORMAT(MIN(order_date), '%m/%d'),
        ' ~ ',
        DATE_FORMAT(MAX(order_date), '%m/%d')
    ) AS date_range
FROM orders
WHERE order_date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month DESC;

输出示例:

monthorder_counttotal_revenuedate_range
2026-07156¥48,320.5007/01 ~ 07/23
2026-06203¥62,110.0006/01 ~ 06/30
2026-05178¥55,899.8005/01 ~ 05/31

这个查询同时用到了:

  • DATE_FORMAT → 按月份分组
  • DATE_SUB + CURDATE → 筛选最近6个月
  • CONCAT + FORMAT → 格式化货币输出
  • MIN/MAX + DATE_FORMAT → 生成日期范围字符串

⚠️ 注意事项

  1. DATE_FORMAT 大小写敏感

    • %Y = 4位年份,%y = 2位年份
    • %m = 01-12,%M = January-December(月份名)
    • %i = 分钟,别跟 %s(秒)搞混
  2. 字符串函数在 WHERE 中的性能

    -- ❌ 低效:无法使用索引
    WHERE DATE_FORMAT(order_date, '%Y-%m') = '2026-07'
    
    -- ✅ 高效:可走索引
    WHERE order_date >= '2026-07-01' AND order_date < '2026-08-01'
    
  3. CONCAT 遇到 NULL 返回 NULL

    SELECT CONCAT('Hello', NULL, 'World');  -- NULL
    -- 用 CONCAT_WS 安全:
    SELECT CONCAT_WS(' ', 'Hello', NULL, 'World');  -- 'Hello World'
    
  4. 不同数据库的兼容性差异

    • MySQL:DATE_FORMAT、LOCATE
    • PostgreSQL:TO_CHAR(代替 DATE_FORMAT)、POSITION(代替 LOCATE)
    • SQL Server:FORMAT、CHARINDEX
    • 迁移数据库时,日期格式化函数几乎必定要改
  5. 中文字符串排序问题

    ORDER BY name COLLATE utf8mb4_unicode_ci;  -- 按拼音排序
    

总结

场景核心函数使用频率
按月/年分组统计DATE_FORMAT()⭐⭐⭐⭐⭐
日期加减计算DATE_ADD() / DATE_SUB()⭐⭐⭐⭐
计算时间差DATEDIFF() / TIMESTAMPDIFF()⭐⭐⭐⭐
拼接字段CONCAT() / CONCAT_WS()⭐⭐⭐⭐⭐
清洗空格TRIM() / LTRIM() / RTRIM()⭐⭐⭐⭐
大小写统一UPPER() / LOWER()⭐⭐⭐⭐
截取子串SUBSTRING() / SUBSTRING_INDEX()⭐⭐⭐⭐
替换字符REPLACE()⭐⭐⭐

Day 15 的核心心法是:日期函数帮你按时间做聚合和计算,字符串函数帮你清洗和重组数据。两者结合,能让任何脏数据变得规整、可分析。 这是每天写 SQL 都会用到的基本功,值得反复练习。