掌握MySQL这些函数,SQL水平直接起飞!

作为一名写了多年SQL的老兵,我见过太多开发者在面对复杂查询时,第一反应就是"把数据捞出来,用Java/Python慢慢算"。其实,MySQL内置了极其丰富的系统函数,掌握了它们,你不仅能写出更优雅的SQL,还能让数据库替你完成大量计算工作,性能远超应用层处理。

今天,我带你系统梳理MySQL的系统预定义函数。从简单的数学运算到复杂的窗口分析,从字符串截取到日期计算,每一类函数我都会结合实际业务场景给出示例。读完这篇文章,你会彻底明白:原来那些需要几十行代码才能搞定的业务逻辑,用一条SQL就能轻松拿下。


一、函数是什么?

在MySQL中,函数是一个封装好的、可复用的功能单元。你可以把它想象成一个"黑盒":扔进参数(也可以不扔),它经过内部运算后,吐出一个结果。

  • 函数必须有返回值(即使没有任何参数,也必须返回一个值)
  • 函数可以接受参数,也可以不接受
  • 函数不会修改原始数据,只对查询结果进行加工

根据处理方式不同,MySQL的系统预定义函数分为两大类:

  1. 单行函数:对每一行记录分别处理,输入多少行,输出多少行。例如把每个员工的名字转成大写,或者计算每个员工的实发工资。
  2. 分组函数(聚合函数):对多行记录一起运算,最终只返回一行或少数几行结果。例如计算全公司平均薪资,或者统计每个部门的员工人数。

下面我们逐一深入。


二、单行函数:逐行处理的利器

单行函数像生产线上的工人,每经过一行数据,就处理一行,最终输出同样行数的结果。

1. 数学函数:让数字计算更精确

|---------------|--------------------|---------------------------------------|
| 函数 | 说明 | 示例 |
| abs(x) | 绝对值 | ABS(-10) → 10 |
| ceil(x) | 向上取整 | CEIL(3.14) → 4 |
| floor(x) | 向下取整 | FLOOR(3.99) → 3 |
| mod(x,y) | 求余数 | MOD(10,3) → 1 |
| rand() | 返回0~1之间的随机数 | 常用于随机抽样 |
| round(x,y) | 四舍五入,保留y位小数 | ROUND(3.1415,2) → 3.14 |
| truncate(x,y) | 直接截断,不四舍五入 | TRUNCATE(3.1415,2) → 3.14 |
| format(x,y) | 格式化数字,每三位加逗号,返回字符串 | FORMAT(1234567.89,2) → '1,234,567.89' |
| sqrt(x) | 平方根 | SQRT(9) → 3 |
| pow(x,y) | x的y次方 | POW(2,3) → 8 |

实战场景:计算员工旷工一天的扣款金额(假设当月22个工作日)。

复制代码
SELECT 
    ename,
    salary/22 AS 原始扣款,
    CEIL(salary/22) AS 向上取整扣款,
    FLOOR(salary/22) AS 向下取整扣款,
    ROUND(salary/22,2) AS 四舍五入扣款,
    TRUNCATE(salary/22,2) AS 截断扣款
FROM t_employee;

在实际业务中,薪资计算通常使用ROUND,因为四舍五入更符合财务规范;而CEIL和FLOOR可能用于某些特殊场景,比如罚款最低按元计。

另一个场景:统计公司平均薪资,并分别用不同取整方式展示,方便汇报。

复制代码
SELECT 
    AVG(salary) AS 原始平均,
    CEIL(AVG(salary)) AS 向上取整平均,
    FLOOR(AVG(salary)) AS 向下取整平均,
    ROUND(AVG(salary),2) AS 四舍五入平均,
    TRUNCATE(AVG(salary),2) AS 截断平均
FROM t_employee;

2. 字符串函数:数据清洗的瑞士军刀

字符串处理是数据分析中最耗时的一环,MySQL提供了丰富的字符串函数,让你在SQL层面完成清洗、格式化、截取等操作。

|---------------------------------------|-------------------------|----------------------------------------------------------|
| 函数 | 说明 | 示例 |
| concat(s1,s2,...) | 拼接字符串 | CONCAT('Hello', ' ', 'World') → 'Hello World' |
| concat_ws(a,s1,s2,...) | 用指定分隔符拼接 | CONCAT_WS('-', '2026', '03', '28') → '2026-03-28' |
| char_length(s) | 返回字符数 | CHAR_LENGTH('你好') → 2 |
| length(s) | 返回字节数(与字符集相关) | LENGTH('你好') → 6(UTF8) |
| locate(s,str) 或 instr(str,s) | 返回子串首次出现的位置 | LOCATE('ab', 'abcab') → 1 |
| upper(s) / lower(s) | 转大写/小写 | UPPER('mysql') → 'MYSQL' |
| left(s,n) / right(s,n) | 取左边/右边n个字符 | LEFT('abcdef',3) → 'abc' |
| lpad(str,len,pad) / rpad(str,len,pad) | 用指定字符左/右填充至指定长度 | LPAD('123',5,'0') → '00123' |
| trim(s) | 去除两侧空格 | TRIM(' hello ') → 'hello' |
| replace(str,a,b) | 将str中的a替换为b | REPLACE('abcabc','a','x') → 'xbcxbc' |
| repeat(s,n) | 重复s n次 | REPEAT('ab',3) → 'ababab' |
| reverse(s) | 反转字符串 | REVERSE('abc') → 'cba' |
| substring(str,index,len) | 从index位置截取len个字符 | SUBSTRING('abcdef',2,3) → 'bcd' |
| substring_index(str,分隔符,count) | 按分隔符截取,count为正从左数,为负从右数 | SUBSTRING_INDEX('www.atguigu.com','.',2) → 'www.atguigu' |

实战场景1:脱敏显示姓名

将员工姓名处理成"张xx"的样式,只保留姓氏,其余用x替代。

复制代码
SELECT 
    ename,
    RPAD(LEFT(ename,1), CHAR_LENGTH(ename), 'x') AS 脱敏姓名
FROM t_employee;

实战场景2:统计字符串中逗号的数量

员工的work_place字段是用逗号分隔的工作地点,比如"北京,上海,广州"。要统计工作地点个数,可以用REPLACE巧妙实现。

复制代码
SELECT 
    ename,
    work_place,
    CHAR_LENGTH(work_place) - CHAR_LENGTH(REPLACE(work_place, ',', '')) + 1 AS 地点数量
FROM t_employee;

原理:原字符串长度减去去掉逗号后的长度,得到逗号个数,再加1就是地点数量。

实战场景3:提取邮箱前缀

从email字段中提取@前面的用户名。

复制代码
SELECT 
    ename,
    email,
    SUBSTRING(email, 1, LOCATE('@', email) - 1) AS 邮箱前缀
FROM t_employee;

实战场景4:去掉字符串两端指定字符

TRIM不仅可以去除空格,还可以去除任意字符。

复制代码
SELECT TRIM(BOTH '&' FROM '&&&&hello&&&&');   -- 结果:'hello'
SELECT TRIM(LEADING '&' FROM '&&&&hello&&&&'); -- 结果:'hello&&&&'
SELECT TRIM(TRAILING '&' FROM '&&&&hello&&&&');-- 结果:'&&&&hello'

3. 日期时间函数:时间维度的分析利器

日期时间处理是报表和统计中最常见的需求,MySQL提供了从获取当前时间到计算时间差的完整工具集。

|------------------------------------------|------------|-------------------------------------------------|
| 函数 | 说明 | 示例 |
| curdate() / current_date() | 当前系统日期 | CURDATE() → '2026-03-28' |
| curtime() / current_time() | 当前系统时间 | CURTIME() → '14:30:00' |
| now() / sysdate() / localtime() | 当前日期时间 | NOW() → '2026-03-28 14:30:00' |
| utc_date() / utc_time() | UTC日期/时间 | UTC_DATE() → '2026-03-28' |
| unix_timestamp(date) | 返回Unix时间戳 | UNIX_TIMESTAMP('2000-01-01') → 946684800 |
| year(date) / month(date) / day(date) | 提取年/月/日 | YEAR(NOW()) → 2026 |
| hour(time) / minute(time) / second(time) | 提取时/分/秒 | HOUR(NOW()) → 14 |
| extract(type from date) | 提取指定部分 | EXTRACT(YEAR_MONTH FROM NOW()) → 202603 |
| datediff(date1, date2) | 计算日期差(天数) | DATEDIFF('2026-03-28','2000-01-01') → 9582 |
| timediff(time1, time2) | 计算时间差(时分秒) | TIMEDIFF('12:00:00','09:00:00') → '03:00:00' |
| date_add(date, interval expr type) | 日期加法 | DATE_ADD(NOW(), INTERVAL 1 DAY) |
| date_format(datetime, fmt) | 格式化日期 | DATE_FORMAT(NOW(), '%Y年%m月%d日') → '2026年03月28日' |
| str_to_date(str, fmt) | 字符串转日期 | STR_TO_DATE('2026-03-28', '%Y-%m-%d') |

实战场景1:查询本月生日的员工

使用MONTH()提取生日月份,与当前月份比较。

复制代码
SELECT ename, birthday
FROM t_employee
WHERE MONTH(CURDATE()) = MONTH(birthday);

实战场景2:查询入职超过5年的员工

使用DATEDIFF计算天数差,与5*365比较(更精确的方式是用DATE_ADD)。

复制代码
SELECT ename, hiredate
FROM t_employee
WHERE DATEDIFF(CURDATE(), hiredate) > 365 * 5;

但注意,如果考虑闰年,更严谨的写法是:

复制代码
WHERE hiredate <= DATE_SUB(CURDATE(), INTERVAL 5 YEAR);

实战场景3:格式化日期输出

报表中经常需要将日期显示为特定格式。

复制代码
SELECT DATE_FORMAT(NOW(), '%Y年%m月%d日 %H:%i:%s') AS 当前时间;

常用格式符:

  • %Y:四位年份
  • %m:两位月份
  • %d:两位日期
  • %H:24小时制小时
  • %i:分钟
  • %s:秒

实战场景4:计算距离中午放学还有多久

学校场景,假设中午放学是12:00。

复制代码
SELECT TIMEDIFF('12:00:00', CURTIME()) AS 距离放学;

4. 加密函数:数据安全的守门员

在用户登录、敏感信息存储等场景,加密函数必不可少。需要注意的是,MySQL 8.0已移除PASSWORD()函数,推荐使用更安全的SHA2。

|------------------------|------------------------------------------|--------|
| 函数 | 说明 | 返回值长度 |
| md5(str) | 返回32位十六进制字符串 | 32字符 |
| sha(str) | 返回40位十六进制字符串 | 40字符 |
| sha2(str, hash_length) | hash_length可以是224/256/384/512/0(0等同于256) | 根据参数而定 |

实战场景:用户密码加密存储

假设用户注册时密码是"123456",我们存储其MD5值,登录时比对加密后的结果。

复制代码
-- 创建用户表
CREATE TABLE t_user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(20),
    password VARCHAR(100)
);

-- 插入时加密
INSERT INTO t_user VALUES(NULL, 'chai', MD5('123456'));

-- 登录验证
SELECT * FROM t_user WHERE username='chai' AND password=MD5('123456');

实际生产环境建议使用SHA2,因为MD5已不再安全,可通过彩虹表快速破解。

5. 系统信息函数:了解当前环境

有时我们需要在SQL中获取当前数据库名、版本号等信息,这些系统信息函数非常有用。

|------------|-------------|
| 函数 | 说明 |
| database() | 当前使用的数据库名 |
| version() | MySQL服务器版本 |
| user() | 当前登录用户名和主机名 |

复制代码
SELECT DATABASE(), VERSION(), USER();

在编写自动化脚本或调试时,这些函数可以帮助你快速确认当前环境。

6. 条件判断函数:让SQL拥有分支逻辑

条件判断函数是SQL实现复杂业务逻辑的关键。它们不是用来筛选记录的,而是根据条件的不同,返回不同的显示结果。

|-------------------------------------|---------------------|
| 函数 | 说明 |
| if(a, x, y) | 如果a为真,返回x,否则返回y |
| ifnull(x, y) | 如果x不为NULL,返回x,否则返回y |
| case when ... then ... else ... end | 多条件判断 |
| case 表达式 when 常量1 then 值1 ... end | 等值判断 |

实战场景1:根据薪资水平显示等级

复制代码
SELECT 
    ename,
    salary,
    CASE 
        WHEN salary > 20000 THEN '羡慕级别'
        WHEN salary > 15000 THEN '努力级别'
        WHEN salary > 10000 THEN '平均级别'
        ELSE '保底级别'
    END AS 等级
FROM t_employee;

注意:CASE按顺序匹配,一旦满足条件就停止,所以条件顺序很重要。

实战场景2:处理NULL值

计算实发工资时,如果commission_pct为NULL,则按0计算。

复制代码
SELECT 
    ename,
    salary,
    commission_pct,
    salary + salary * IFNULL(commission_pct, 0) AS 实发工资
FROM t_employee;

IFNULL在这里发挥了关键作用,避免了计算结果变成NULL。

实战场景3:用CASE表达式进行等值匹配

假设要根据工作地点数量判断员工出差频率。

复制代码
SELECT 
    ename,
    work_place,
    CASE CHAR_LENGTH(work_place) - CHAR_LENGTH(REPLACE(work_place, ',', '')) + 1
        WHEN 1 THEN '只在本地工作'
        WHEN 2 THEN '两地奔波'
        WHEN 3 THEN '三地流动'
        ELSE '频繁出差'
    END AS 出差情况
FROM t_employee;

三、分组函数(聚合函数):多行合并的力量

分组函数与单行函数不同,它们对多行数据进行聚合,最终返回一行结果。最常见的分组函数有:

|----------|------|
| 函数 | 说明 |
| avg(x) | 求平均值 |
| sum(x) | 求和 |
| max(x) | 求最大值 |
| min(x) | 求最小值 |
| count(x) | 计数 |

重要规则

  • count(*) 和 count(1) 都统计所有行,包括NULL值。
  • count(字段) 统计该字段非NULL的行数。
  • 分组函数会忽略NULL值(除了count(*))。

实战场景1:统计公司员工总数

复制代码
SELECT COUNT(*) FROM t_employee;
SELECT COUNT(1) FROM t_employee;
SELECT COUNT(eid) FROM t_employee;   -- 如果eid为主键,结果相同
SELECT COUNT(commission_pct) FROM t_employee;  -- 只统计有奖金的员工数

实战场景2:计算公司平均薪资、最高薪资、最低薪资、薪资总和

复制代码
SELECT 
    AVG(salary) AS 平均薪资,
    MAX(salary) AS 最高薪资,
    MIN(salary) AS 最低薪资,
    SUM(salary) AS 总薪资
FROM t_employee;

实战场景3:与GROUP BY结合,实现分组统计

查询每个部门的平均薪资,并保留两位小数。

复制代码
SELECT 
    did,
    ROUND(AVG(salary), 2) AS 部门平均薪资
FROM t_employee
GROUP BY did;

这里GROUP BY将数据按部门分组,分组函数在每个组内分别计算,最终每个部门返回一行结果。


四、窗口函数(OLAP函数):SQL中的数据分析神器

窗口函数是MySQL 8.0引入的强大特性,它能在不减少结果集行数的情况下,对每一行数据进行分组排序、前后行访问等操作。窗口函数也属于单行函数,因为输入多少行,输出还是多少行。

语法格式

复制代码
函数名(参数) OVER (
    [PARTITION BY 列名]   -- 分组
    [ORDER BY 列名]       -- 排序
    [ROWS BETWEEN <start> AND <end>]  -- 定义窗口范围
)

常用窗口函数:

|-----------------------------|-------------------------------|
| 函数 | 说明 |
| row_number() | 顺序编号,1,2,3,... |
| rank() | 跳跃排序,相同值编号相同,但会跳过后续编号(如1,1,3) |
| dense_rank() | 密集排序,相同值编号相同,不跳过(如1,1,2) |
| lag(expr, offset, default) | 访问当前行前offset行的值 |
| lead(expr, offset, default) | 访问当前行后offset行的值 |
| first_value(expr) | 窗口内第一个值 |
| last_value(expr) | 窗口内最后一个值 |
| sum() over() 等聚合函数 | 在窗口内做聚合计算 |

实战场景1:给每行记录编号

查询薪资在8000~10000之间的员工,并为每一行添加序号。

复制代码
SELECT 
    ROW_NUMBER() OVER() AS rn,
    ename,
    salary
FROM t_employee
WHERE salary BETWEEN 8000 AND 10000;

实战场景2:分组排序,比较三种编号函数的区别

查询女员工,按部门分组,在组内按薪资升序排序,分别用三种编号函数。

复制代码
SELECT 
    ename,
    did,
    salary,
    ROW_NUMBER() OVER(PARTITION BY did ORDER BY salary) AS row_num,
    RANK() OVER(PARTITION BY did ORDER BY salary) AS rank_num,
    DENSE_RANK() OVER(PARTITION BY did ORDER BY salary) AS dense_rank_num
FROM t_employee
WHERE gender = '女';

通过对比结果,你会清晰看到三者差异:

  • ROW_NUMBER 始终连续,即使薪资相同也会给不同编号。
  • RANK 遇到相同薪资编号相同,但会跳过后续编号。
  • DENSE_RANK 相同薪资编号相同,但后续编号连续。

实战场景3:查询每个部门薪资排名前三的员工

利用DENSE_RANK可以实现不跳过的排名,确保前三名可能包含并列。

复制代码
SELECT *
FROM (
    SELECT 
        ename,
        did,
        salary,
        DENSE_RANK() OVER(PARTITION BY did ORDER BY salary DESC) AS dr
    FROM t_employee
) t
WHERE dr <= 3;

实战场景4:查询全公司薪资排名前三的员工

不需要PARTITION BY,直接对整个结果集排序编号。

复制代码
SELECT *
FROM (
    SELECT 
        ename,
        did,
        salary,
        DENSE_RANK() OVER(ORDER BY salary DESC) AS dr
    FROM t_employee
) t
WHERE dr <= 3;

实战场景5:访问前后行数据

使用LAG和LEAD可以获取排序后的前后行信息,非常适合对比分析。

复制代码
SELECT 
    ename,
    salary,
    LAG(ename, 1, '-') OVER(ORDER BY salary) AS '上一位姓名',
    LAG(salary, 1, 0) OVER(ORDER BY salary) AS '上一位薪资',
    LEAD(ename) OVER(ORDER BY salary) AS '下一位姓名',
    LEAD(salary) OVER(ORDER BY salary) AS '下一位薪资',
    FIRST_VALUE(salary) OVER(ORDER BY salary) AS '首位薪资',
    LAST_VALUE(ename) OVER(ORDER BY salary 
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS '末位姓名'
FROM t_employee;

注意LAST_VALUE默认窗口范围是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,即当前行及之前所有行。若要获取整个分组的最后一行,必须显式指定窗口范围为UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。

实战场景6:计算部门平均薪资与全公司平均薪资的差值

使用聚合函数作为窗口函数,可以在保留所有行的情况下同时获得分组聚合值和整体聚合值。

复制代码
SELECT DISTINCT
    did,
    AVG(salary) OVER(PARTITION BY did) AS 部门平均,
    AVG(salary) OVER() AS 公司平均,
    ROUND(AVG(salary) OVER(PARTITION BY did) - AVG(salary) OVER(), 2) AS 差值
FROM t_employee;

DISTINCT是为了避免每个员工都重复显示相同部门的平均值,因为部门平均值在同一个部门内是相同的。


五、总结

通过上面的梳理,你应该已经看到,MySQL的系统函数涵盖了从基础运算到高级分析的方方面面。掌握这些函数,意味着你可以在数据库层面完成绝大部分数据加工任务,而不必依赖外部编程语言。

回顾一下核心要点

  1. 数学函数:处理数值计算,尤其注意ROUND、CEIL、FLOOR在财务和统计场景中的差异。
  2. 字符串函数:数据清洗的核心,SUBSTRING、REPLACE、TRIM是日常高频操作。
  3. 日期时间函数:时间维度的分析利器,DATEDIFF、DATE_ADD、DATE_FORMAT必须熟练。
  4. 加密函数:用户密码等敏感信息一定要用SHA2等强加密算法存储。
  5. 条件判断函数:IF、IFNULL、CASE让SQL拥有分支逻辑,实现复杂展示需求。
  6. 分组函数:配合GROUP BY实现聚合统计,注意COUNT与COUNT(*)的区别。
  7. 窗口函数:数据分析的进阶武器,在不减少行数的情况下进行排序、前后行访问、分组聚合。

最后,我想说的是:SQL不是简单的取数工具,而是一门功能完备的数据处理语言。当你遇到复杂需求时,先想一想:这个逻辑能不能用MySQL函数解决?很多时候,答案都是肯定的。

希望这篇文章能帮你建立起系统函数的全局认知,并在实际工作中大胆应用。如果你觉得有用,欢迎收藏、转发,也欢迎在评论区交流你在SQL使用中遇到的坑和技巧。

相关推荐
老苏畅谈运维2 小时前
Oracle AI Database 26ai 安装实战
数据库·oracle·oracle 26ai
Rick19932 小时前
SQL优化
数据库·sql
无忧智库2 小时前
破局与重构:基于“智慧大脑”的企业全面数据化经营深度解构(PPT)
数据库·重构
大嘴皮猴儿2 小时前
零基础入门:跨境电商产品图片多语言翻译的完整流程与跨马翻译实操
大数据·数据库·人工智能·自动翻译·教育电商
雷工笔记2 小时前
Navicat 备份与还原 PostgreSQL 数据库
数据库·postgresql·oracle
逸Y 仙X2 小时前
文章十四:ElasticSearch Reindex重建索引
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
冷血~多好2 小时前
mysql实现主从复制以及springboot实现读写分离
java·数据库·mysql·springboot
两年半的个人练习生^_^2 小时前
如何自己实现多数据源
数据库
AI周红伟2 小时前
周红伟:关于OpenClaw安全使用提醒
大数据·数据库·人工智能·安全·腾讯云·openclaw