开篇介绍:
hello 大家,那么我们知道,我们平时对MySQL的操作,其实是用的SQL语言,那么就如我们平时的c嘎嘎一样,一个语言,总会有其自己已经封装好了的函数来帮助使用,提高效率,那么MySQL也是,它也提供了一系列函数供使用者使用,接下来,我们就来看看是什么吧。
一、为什么必须掌握 MySQL 内置函数?
在正式讲函数之前,先解决一个疑问:我直接写逻辑不行吗?为什么非要学函数?
举个直观的例子:计算两个日期之间的天数差。
- 不用函数:你需要手动拆解年、月、日,判断平年闰年,计算每个月的天数,处理跨月跨年 ------ 写 50 行代码都不一定能覆盖所有边界。
- 用函数 :
select datediff('2024-01-01', '2023-01-01')------一行代码,直接出结果。
这就是内置函数的核心价值:
- 简化代码:把复杂的通用逻辑封装成函数,一行调用替代几十行冗余代码;
- 提升效率:函数是 MySQL 底层用 C 语言实现的,经过海量优化,执行效率远高于手动拼接的 SQL 逻辑;
- 降低出错率:函数覆盖了所有边界情况(比如日期计算的闰年、字符串截取的下标越界),避免手动写逻辑的漏洞;
- 增强可读性 :
date_add(create_time, interval 30 day)比 "手动计算 30 天后的日期" 更直观,同事看代码一眼就能懂。
学习原则:不用死记硬背所有函数,重点掌握 "高频函数 + 使用场景 + 避坑点",冷门函数用到时查官方文档即可
还有就是大家要知道,内置函数在哪里都能用,不论是select语句,还是在insert语句中等等,函数嘛,就是万能的啦,但是不能单独使用哦,想要单独使用还是要前面加个select语句才行。
二、第一核心类:日期时间函数(处理 "时间" 的万能工具箱)
日期时间是业务开发中最常用的数据类型:用户生日、订单创建时间、留言发布时间、活动有效期...... 日期时间函数就是专门处理这些 "时间数据" 的工具

2.1 基础中的基础:获取当前时间(看 "时钟 / 日历")
这是最常用的日期函数,作用是获取 MySQL 服务器当前的时间信息,就像你掏出手机看时间 / 日历一样,随时能拿到精准的时间数据。
核心函数对比表
| 函数名 | 作用 | 语法 | 返回类型 | 通俗解释 | 实际输出(示例) |
|---|---|---|---|---|---|
current_date() |
获取当前日期(只含年月日) | select current_date(); |
DATE |
只看日历,不管几点几分 | 2024-01-20 |
current_time() |
获取当前时间(只含时分秒) | select current_time(); |
TIME |
只看时钟,不管哪年哪月 | 15:30:45 |
current_timestamp() |
获取当前完整时间(年月日 + 时分秒) | select current_timestamp(); |
DATETIME |
既看日历又看时钟 | 2024-01-20 15:30:45 |
now() |
等价于current_timestamp() |
select now(); |
DATETIME |
日常开发的简写版 | 2024-01-20 15:30:45 |
sysdate() |
获取函数执行时的时间 | select sysdate(); |
DATETIME |
比now()更精准(用于长 SQL) |
2024-01-20 15:30:46 |

代码示例 + 解析
sql
-- 1. 获取当前日期(年月日)
select current_date();
-- 输出:2024-01-20
-- 解析:返回DATE类型,格式固定为'YYYY-MM-DD',不含任何时间信息,适合存储"生日""订单日期"等场景
-- 2. 获取当前时间(时分秒)
select current_time();
-- 输出:15:30:45
-- 解析:返回TIME类型,格式固定为'HH:MM:SS',不含日期信息,适合存储"上班打卡时间"等场景
-- 3. 获取当前完整时间(最常用)
select current_timestamp();
select now(); -- 和上面完全等价,开发中优先用这个
-- 输出:2024-01-20 15:30:45
-- 解析:返回DATETIME类型,包含完整的日期和时间,适合存储"订单创建时间""用户注册时间"等场景
-- 4. 对比now()和sysdate()的区别(关键坑点)
select now(), sysdate(), sleep(2), now(), sysdate();
-- 输出:2024-01-20 15:30:45 | 2024-01-20 15:30:45 | 0 | 2024-01-20 15:30:45 | 2024-01-20 15:30:47
-- 解析:
-- - now():SQL语句**开始执行时**的时间,整个语句执行过程中值不变;
-- - sysdate():函数**执行时**的时间,会随语句执行进度变化;
-- - 场景:长SQL(比如包含sleep、复杂计算)中,需要精准时间用sysdate(),否则用now()。
典型应用场景
-
记录操作时间 :用户注册时,用
now()插入注册时间;订单创建时,用now()记录下单时间。sqlinsert into user (name, register_time) values ('张三', now()); -
生成时间维度报表 :统计 "今日订单量""近 7 天活跃用户数"。
-- 今日订单量 select count(*) from order where date(create_time) = current_date(); -- 近7天活跃用户数 select count(distinct user_id) from user_action where action_time >= date_sub(now(), interval 7 day); -
验证服务器时间 :排查 "数据时间和本地时间不一致" 的问题 ------ 比如用户反馈 "订单时间显示昨天",先查
select now();看服务器时间是否正确。
避坑要点
- 时区问题 :函数返回的是MySQL 服务器的时间 ,不是你的客户端(Navicat、DBeaver)本地时间。如果服务器在国外,可能存在时区差(比如东八区和 UTC 时区差 8 小时)。
- 解决:查看服务器时区
select @@time_zone;,设置时区set time_zone = '+8:00';(东八区)。
- 解决:查看服务器时区
- 无参数限制 :这些函数不需要传任何参数,直接调用即可,传参会报错。
2.2 核心功能:日期的加减运算(给时间 "做加减法")
业务中最常见的需求之一:计算 N 天后 / 前的日期、N 分钟后的时间。比如 "订单 30 天后自动过期""验证码 5 分钟内有效""活动开始前 2 天提醒",这些需求都可以用date_add()(加时间)和date_sub()(减时间)实现 ------ 就像给时间用 "计算器做加减法"。
核心语法
sql
-- 给日期/时间加指定时长
date_add(原始日期/时间, interval 数值 时间单位)
-- 给日期/时间减指定时长
date_sub(原始日期/时间, interval 数值 时间单位)
add------加,sub------减
参数深度解析
| 参数 | 说明 | 取值示例 |
|---|---|---|
原始日期/时间 |
要计算的基础时间 | 1. 字符串:'2024-01-01'、'2024-01-01 10:00:00';2. 日期类型字段:create_time、birthday,是的,我们表中的字段也是可以丢进函数中去进行计算的,其实也是很好理解的,毕竟select的执行顺序偏后,等轮到它了,早就已经生成了一张不怎么会修改的表了,那么这个时候色了自然也就能获取到表中的字段所代表的数据了,那么自然也就可以用于函数中进行计算了哦,这一点还是希望大家要注意;3. 时间函数:now()、current_date() |
interval |
固定关键字 | 必须写,代表 "时间间隔",interval,多念几遍就会啦 |
数值 |
要加减的数量 | 整数,可正可负 ------ 比如date_add(..., interval -2 day)等价于date_sub(..., interval 2 day),注意,加减就是看数字前面的符号,如果不带符号,那就是加,如果带-号,那就是减 |
时间单位 |
要加减的时间粒度 | 常用:year(年)、month(月)、day(天)、hour(小时)、minute(分钟)、second(秒),写英语哦 |
oi,对于这些参数,大家还是要知道的哦,interval~~~
代码示例 + 场景化解读
sql
-- 示例1:计算订单过期时间(创建时间+30天)
select date_add('2024-01-01', interval 30 day) as 过期时间;
-- 输出:2024-01-31
-- 场景:电商订单"30天未支付自动取消",用这个函数计算过期时间
-- 示例2:计算活动提醒时间(开始时间-2天)
select date_sub('2024-02-01', interval 2 day) as 提醒时间;
-- 输出:2024-01-30
-- 场景:活动开始前2天给用户发提醒,用这个函数计算提醒日期
-- 示例3:计算验证码失效时间(生成时间+5分钟)
select date_add(now(), interval 5 minute) as 失效时间;
-- 输出:2024-01-20 15:35:45(当前时间15:30:45)
-- 场景:短信验证码"5分钟内有效",验证时判断当前时间是否小于失效时间
-- 示例4:用负数实现"减时间"(等价于date_sub)
select date_add('2024-01-01', interval -2 day) as 两天前;
-- 输出:2023-12-30
-- 解析:数值为负时,date_add等价于date_sub,可灵活选择写法
-- 示例5:跨月/跨年自动处理(不用手动算天数)
select date_add('2024-02-28', interval 1 day) as 加1天; -- 2024是闰年
-- 输出:2024-02-29
select date_add('2023-12-31', interval 1 day) as 加1天;
-- 输出:2024-01-01
-- 解析:MySQL会自动处理平年闰年、月份天数差异,不用开发者操心

传入字段的例子:

典型应用场景
- 订单时效管理 :
date_add(create_time, interval 30 day)= 订单过期时间; - 验证码有效期 :
date_add(generate_time, interval 5 minute)= 验证码失效时间; - 活动时间计算 :
date_sub(start_time, interval 1 hour)= 活动预热时间; - 数据筛选 :
date_sub(now(), interval 1 hour)= 1 小时前的时间,用于 "查询近 1 小时的日志"。
避坑要点
-
时间单位要匹配数据类型 :
-
给
DATE类型(如2024-01-01)加minute,MySQL 会自动补全时间为00:00:00,再计算:select date_add('2024-01-01', interval 30 minute); -- 输出:2024-01-01 00:30:00
-
-
避免用模糊的时间单位 :比如不要用
week(周)、quarter(季度)------ 不同业务对 "周" 的定义不同(比如周一到周日 vs 周日到周六),容易产生歧义。
2.3 实用功能:计算日期差(量 "时间的距离")
想知道 "两个日期之间隔了多少天"?比如 "用户注册至今多少天""订单从创建到支付用了多少天""活动持续了多少小时"------ 用datediff()函数,就像用 "尺子量两个时间点的距离"。
核心语法
sql
datediff(结束日期, 开始日期)
⚠️ 天坑预警:参数顺序是 结束日期在前,开始日期在后,返回值 = 结束日期 - 开始日期(单位:天),oi,这个大家还是要注意一下的,因为按照我们的平时习惯,可能会反过来,容易出错哦
代码示例 + 深度解析
sql
-- 示例1:计算2024-01-10和2023-09-01的天数差
select datediff('2024-01-10', '2023-09-01') as 天数差;
-- 输出:131
-- 解析:从2023-09-01到2024-01-10,总共131天(MySQL自动计算跨月跨年)
-- 示例2:参数顺序反了(结果为负数)
select datediff('2023-09-01', '2024-01-10') as 天数差;
-- 输出:-131
-- 解析:结束日期早于开始日期,差值为负------新手最容易犯的错误!
-- 示例3:计算用户注册天数(结合表字段)
-- 假设user表有register_date字段(DATE类型)
-- select username, datediff(now(), register_date) as 注册天数 from user;
-- 输出:张三 | 180(表示张三注册了180天)
-- 示例4:忽略时分秒,只算日期差
select datediff('2024-01-01 23:59:59', '2023-12-31 00:00:00') as 天数差;
-- 输出:1
-- 解析:不管时分秒,只要日期不同,即使只差一秒,就算1天------这是`datediff()`的特性

拓展:计算小时 / 分钟差(timediff())
datediff()只能算天数差,如果要算更细粒度的时间差(小时、分钟),用timediff()函数:
sql
-- 语法:timediff(结束时间, 开始时间)(返回TIME类型)
select timediff('2024-01-01 12:30:00', '2024-01-01 10:00:00') as 时间差;
-- 输出:02:30:00(表示差2小时30分钟)
-- 换算成小时:TIME转数值,除以3600
select timediff('2024-01-01 12:30:00', '2024-01-01 10:00:00')/3600 as 小时差;
-- 输出:2.5
典型应用场景
- 用户画像分析:计算用户 "注册天数""活跃天数""留存天数";
- 订单流程优化:计算 "下单到支付的时长""支付到发货的时长",分析流程瓶颈;
- 活动效果评估:计算 "活动开始到结束的天数",评估活动周期是否合理。
避坑要点
-
参数顺序绝对不能反 :新手经常把
datediff(开始日期, 结束日期),导致结果为负 ------ 记住口诀:结束在前,开始在后。 -
参数格式必须正确 :必须是 MySQL 能识别的日期格式(
YYYY-MM-DD或YYYY-MM-DD HH:MM:SS),否则返回NULL:select datediff('2024/01/01', '2023/09/01'); -- 输出:NULL(格式错误,要用-分隔)
2.4 进阶功能:日期的格式化与解析("时间的翻译官")
我们经常遇到两个需求:
- 把 MySQL 的日期转成人类可读的格式 :比如把
2024-01-20转成2024年01月20日; - 把人类输入的日期转成 MySQL 能识别的格式 :比如把
20240120转成2024-01-20。
这时候需要两个函数:date_format()(日期转字符串)和str_to_date()(字符串转日期)------ 就像 "时间的翻译官",在 "MySQL 时间格式" 和 "人类时间格式" 之间搭桥。
2.4.1 date_format():日期转字符串(MySQL → 人类)
核心语法
date_format(日期/时间, 格式符)
常用格式符
| 格式符 | 作用 | 示例 |
|---|---|---|
%Y |
四位年份 | 2024 |
%y |
两位年份 | 24 |
%m |
两位月份(补 0) | 01、12 |
%c |
月份(不补 0) | 1、12 |
%d |
两位日期(补 0) | 01、31 |
%e |
日期(不补 0) | 1、31 |
%H |
24 小时制(补 0) | 08、15、23 |
%h |
12 小时制(补 0) | 08、03、11 |
%i |
两位分钟(补 0) | 05、30 |
%s |
两位秒数(补 0) | 01、59 |
%W |
星期(英文全称) | Sunday、Monday |
%w |
星期(数字,0 = 周日) | 0(周日)、1(周一) |
代码示例 + 场景化解读
sql
-- 示例1:转成"年-月-日"格式(常用)
select date_format(now(), '%Y-%m-%d') as 日期;
-- 输出:2024-01-20
-- 示例2:转成"年/月/日 时:分:秒"格式(适合展示)
select date_format(now(), '%Y/%m/%d %H:%i:%s') as 完整时间;
-- 输出:2024/01/20 15:30:45
-- 示例3:转成中文格式(用户友好)
select date_format(now(), '%Y年%m月%d日 %H时%i分%s秒') as 中文时间;
-- 输出:2024年01月20日 15时30分45秒
-- 示例4:获取星期几
select date_format(now(), '%W') as 星期英文, date_format(now(), '%w') as 星期数字;
-- 输出:Saturday | 6(假设当前是周六,%w中6=周六)
-- 示例5:结合表字段格式化(实战常用)
-- 假设order表有create_time字段,要展示成"2024年01月20日"
-- select order_id, date_format(create_time, '%Y年%m月%d日') as 下单日期 from order;

2.4.2 str_to_date():字符串转日期(人类 → MySQL)
核心语法
sql
str_to_date(字符串, 格式符)
⚠️ 关键:格式符必须和字符串的格式完全匹配 ,否则返回NULL。
代码示例 + 场景化解读
sql
-- 示例1:把"20240120"转成DATE类型
select str_to_date('20240120', '%Y%m%d') as 日期;
-- 输出:2024-01-20(DATE类型,可用于日期计算)
-- 示例2:把"2024/01/20 15:30:45"转成DATETIME类型
select str_to_date('2024/01/20 15:30:45', '%Y/%m/%d %H:%i:%s') as 时间;
-- 输出:2024-01-20 15:30:45(DATETIME类型)
-- 示例3:格式不匹配,返回NULL(新手必踩坑)
select str_to_date('2024/01/20', '%Y-%m-%d') as 日期;
-- 输出:NULL(字符串用/分隔,格式符用-分隔,不匹配)
-- 示例4:实战场景------用户输入日期查询订单
-- 用户输入"20240120",转成MySQL日期格式后查询
-- select * from order where date(create_time) = str_to_date('20240120', '%Y%m%d');

典型应用场景
- 前端展示优化 :用
date_format()把 MySQL 的日期转成用户友好的格式(如2024年01月20日); - 用户输入处理 :用
str_to_date()把用户输入的日期字符串(如20240120)转成 MySQL 能识别的日期类型; - 数据导入导出 :导入时用
str_to_date()处理外部数据的日期格式,导出时用date_format()格式化日期。
避坑要点
- 格式符必须完全匹配 :
str_to_date()的格式符和字符串的分隔符、位数必须一致,否则返回NULL; - 区分
%Y和%y:%Y是四位年(2024),%y是两位年(24)------ 存储时优先用%Y,避免年份歧义; - 区分
%H和%h:%H是 24 小时制(15 = 下午 3 点),%h是 12 小时制(03 = 下午 3 点)------ 结合业务选择,避免时间混淆。
2.5 实战案例:日期函数的综合应用
光看单个函数不够,结合真实业务场景,完整演示日期函数的组合使用
案例 1:创建 "生日表" 并实现生日提醒
需求:
- 创建一张用户生日表,存储用户 ID 和生日;
- 插入测试数据,生日为当前日期;
- 查询 "今天过生日的用户" 和 "未来 7 天过生日的用户"。
实现 SQL:
sql
-- 步骤1:创建生日表
create table user_birthday (
id int primary key auto_increment comment '主键ID',
user_id int not null comment '用户ID',
birthday date not null comment '生日(只存年月日)',
create_time datetime default now() comment '创建时间'
) comment '用户生日表';
-- 步骤2:插入测试数据(生日分别为今天、3天后、10天后)
insert into user_birthday (user_id, birthday) values
(1, current_date()), -- 今天过生日
(2, date_add(current_date(), interval 3 day)), -- 3天后过生日
(3, date_add(current_date(), interval 10 day)); -- 10天后过生日
-- 步骤3:查询今天过生日的用户
-- 关键:忽略年份,只比较月和日
select user_id, birthday from user_birthday
where date_format(birthday, '%m-%d') = date_format(current_date(), '%m-%d');
-- 步骤4:查询未来7天过生日的用户
select user_id, birthday, datediff(
str_to_date(concat(year(now()), '-', date_format(birthday, '%m-%d')), '%Y-%m-%d'),
now()
) as 剩余天数 from user_birthday
where
-- 拼接今年的生日日期
str_to_date(concat(year(now()), '-', date_format(birthday, '%m-%d')), '%Y-%m-%d')
between now() and date_add(now(), interval 7 day);
执行结果:
- 步骤 3:返回 user_id=1 的用户(今天过生日);
- 步骤 4:返回 user_id=1 和 user_id=2 的用户(今天和 3 天后过生日,都在未来 7 天内)。
核心解析:
- 用
date_format(birthday, '%m-%d')提取生日的 "月 - 日",忽略年份 ------ 解决 "每年都要提醒" 的需求; - 用
concat(year(now()), '-', date_format(birthday, '%m-%d'))拼接 "今年的生日日期",再用str_to_date()转成 DATE 类型 ------ 解决跨年份的日期计算问题。
案例 2:创建 "留言表" 并筛选 2 分钟内的留言
需求:
- 创建一张留言表,存储留言内容和发布时间;
- 插入两条测试数据,发布时间为当前时间;
- 查询所有留言,只显示发布日期(隐藏时间);
- 筛选 "2 分钟内发布的留言"(用于 "留言 2 分钟内可编辑" 的业务逻辑)。
实现 SQL:
sql
-- 步骤1:创建留言表
create table message (
id int primary key auto_increment comment '主键ID',
content varchar(100) not null comment '留言内容',
send_time datetime default now() comment '发布时间',
user_id int not null comment '发布用户ID'
) comment '用户留言表';
-- 步骤2:插入测试数据(两条留言,间隔1分钟)
insert into message (content, user_id) values ('Hello MySQL', 1);
-- 等待1分钟后插入第二条
insert into message (content, user_id) values ('函数真好用', 2);
-- 步骤3:查询所有留言,只显示发布日期
select id, content, date_format(send_time, '%Y年%m月%d日') as 发布日期 from message;
-- 步骤4:筛选2分钟内发布的留言
-- 核心逻辑:发布时间 + 2分钟 > 当前时间
select * from message
where date_add(send_time, interval 2 minute) > now();
执行结果:
- 步骤 3:发布日期只显示 "2024 年 01 月 20 日",隐藏时分秒;
- 步骤 4:返回两条留言(都在 2 分钟内发布),如果等待 2 分钟后再查,只返回第二条留言。
核心解析:
- 步骤 3 用
date_format()格式化发布时间,满足 "只显示日期" 的需求; - 步骤 4 的核心逻辑:
date_add(send_time, interval 2 minute)= 留言的 "可编辑截止时间",如果当前时间小于这个时间,说明还能编辑 ------ 这是业务中最常用的时间判断逻辑。
2.6 日期函数速查表
| 函数 | 作用 | 核心用法 | 场景 |
|---|---|---|---|
current_date() |
获取当前日期 | select current_date(); |
存储生日、订单日期 |
now() |
获取当前完整时间 | select now(); |
存储注册时间、创建时间 |
date_add() |
日期加时间 | date_add(now(), interval 30 day) |
计算过期时间 |
date_sub() |
日期减时间 | date_sub(now(), interval 7 day) |
计算 7 天前的时间 |
datediff() |
计算天数差 | datediff(结束日期, 开始日期) |
计算注册天数 |
date_format() |
日期转字符串 | date_format(now(), '%Y年%m月%d日') |
前端展示 |
str_to_date() |
字符串转日期 | str_to_date('20240120', '%Y%m%d') |
处理用户输入 |
year()/month()/day() |
提取年 / 月 / 日 | year(now()) |
按年 / 月统计数据 |
三、第二核心类:字符串函数(处理 "文字" 的全能编辑工具)
字符串是 MySQL 中最基础的数据类型:用户名、商品名称、留言内容、手机号、地址...... 字符串函数就像 "文字编辑软件"------ 能拼接、替换、截取、计算长度、清洗数据,满足各种文字处理需求。

3.1 基础功能:查看字符集(排查乱码的 "第一步")
charset()函数用于查看字符串或字段的字符集------ 这是排查 "中文乱码" 的关键第一步。就像你打开文件时,需要知道文件的编码格式(UTF-8/GBK)一样,处理字符串前,先知道它的字符集。
核心语法
sql
charset(字符串/字段名)
代码示例 + 深度解析
sql
-- 示例1:查看字符串的字符集
select charset('MySQL内置函数') as 字符集;
-- 输出:utf8mb4
-- 解析:MySQL 8.0默认的字符集是utf8mb4,支持所有中文和表情符号
select charset('MySQL') as 字符集;
-- 输出:binary(二进制字符集,不区分大小写)
-- 示例2:查看表字段的字符集(以emp表的ename字段为例)
-- 假设emp表是旧表,字符集为latin1
-- select charset(ename) from emp limit 1;
-- 输出:latin1
-- 解析:latin1不支持中文,存储中文会变成"???",需要改成utf8mb4
典型应用场景
- 排查中文乱码 :如果查询结果中中文显示为 "???",先查字段字符集
select charset(字段名) from 表名 limit 1;------ 如果是 latin1,改成 utf8mb4 即可; - 数据迁移:确认源表和目标表的字符集一致 ------ 比如源表是 utf8mb4,目标表也必须是 utf8mb4,否则迁移后中文乱码;
- 字符集验证 :新建表后,用
charset()验证字段字符集是否符合预期。
避坑要点
utf8和utf8mb4的区别 :MySQL 中的utf8只支持 3 字节的 UTF-8 编码,不支持表情符号;utf8mb4支持 4 字节的 UTF-8 编码,支持所有字符 ------新建表时优先用 utf8mb4。
3.2 核心功能:字符串拼接("文字的胶水")
concat()函数是最常用的字符串函数,作用是把多个字符串拼接成一个 ------ 就像用胶水把 "张三""的语文成绩是""90""分" 粘在一起,变成 "张三的语文成绩是 90 分"。
核心语法
sql
concat(字符串1, 字符串2, 字符串3, ...)
注意函数里面是可变参数,就是要传入多少个参数都行,那么concat函数就会把传入的所有的字符串拼接成一个大字符串,顺序就是从左往右。
这里我们要严格注意,咱传入什么参数都行,但是要用单引号包裹,当然,传入字段名的话就不用
**还有一个比较重要的点就是里面的参数也可以传入数据类型为字符串的表中的字段哦,然后传入字段名的话,那么其实就不会限制数据类型,换句话说,我们就是传入整型之类的字段进去,concat函数也照样能进行拼接,**这一点想必大家也很容易就能理解到,所谓一法通万法通。

大家,这个函数我们还是要熟知的,因为它确实是最常用的一个函数,毕竟拼接字符串这一块,肯定还是嘎嘎出名的,
代码示例 + 场景化解读
sql
-- 示例1:基础拼接(字符串+字符串)
select concat('张三', '的语文成绩是', '90', '分') as 拼接结果;
-- 输出:张三的语文成绩是90分
-- 示例2:拼接字符串+数值(自动转换类型)
select concat('数学成绩:', 85) as 拼接结果;
-- 输出:数学成绩:85
-- 解析:MySQL会自动把数值85转换成字符串,再拼接
-- 示例3:拼接表字段(实战核心用法)
-- 假设exam_result表有name、chinese、math、english字段
select concat(
name, '的语文是', chinese, '分,数学是', math, '分,英语是', english, '分,总分是',
chinese + math + english, '分'
) as 成绩详情 from exam_result;
执行结果(模拟):
sql
+-------------------------------------------------------------+
| 成绩详情 |
+-------------------------------------------------------------+
| 张三的语文是90分,数学是85分,英语是95分,总分是270分 |
| 李四的语文是80分,数学是92分,英语是88分,总分是260分 |
+-------------------------------------------------------------+

拓展:concat_ws()------ 带分隔符的拼接(更高效)
如果要拼接多个字符串,且用固定分隔符分隔(比如用,分隔),用concat_ws()(concat with separator):
sql
-- 语法:concat_ws(分隔符, 字符串1, 字符串2, ...)
select concat_ws(',', '张三', '男', '20岁') as 拼接结果;
-- 输出:张三,男,20岁
-- 实战场景:拼接用户信息
-- select concat_ws(' | ', username, phone, email) as 用户信息 from user;
说白了就是按照我们传入的第一个参数,也就是分隔符,然后后面我们所传入的所有参数都按照这个分隔符进行分割。
典型应用场景
- 生成可读性高的报表:把字段和文字拼接,直接展示给用户(如成绩详情、订单信息);
- 生成唯一标识 :拼接多个字段生成唯一编号(如
concat(order_id, '_', user_id)= 订单唯一标识); - 数据导出 :用
concat_ws()拼接字段,导出成 CSV 格式(分隔符为,)。
避坑要点
-
NULL 值的 "致命影响" :
concat()中只要有一个参数是NULL,整个结果就是NULLsqlselect concat('张三', null, '分'); -- 输出:NULL -- 解决:用ifnull()处理NULL值 select concat('张三', ifnull(null, ''), '分'); -- 输出:张三分 -
空字符串和 NULL 的区别 :空字符串(
'')不会影响拼接,NULL 会 ------ 处理字段时,优先用ifnull(字段, '')把 NULL 转成空字符串。
3.3 实用功能:字符串长度计算("文字的尺子")
length()函数用于计算字符串的字节数,char_length()函数用于计算字符串的字符数------ 就像用尺子量文字的 "长度",前者量的是 "字节长度",会受字符集影响,可能会出现一个字符却返回3的情况,那就是因为这个字符占3个字节,后者量的是 "字符个数",有几个字符,就是几个
这里其实就是要注意,不同字符集的对于一个字符所占的字节数是不同的哦,但是英文的话,那就就是一个字母一个字节。
核心语法
sql
-- 计算字节数(受字符集影响)
length(字符串/字段名)
-- 计算字符数(不受字符集影响)
char_length(字符串/字段名)
代码示例 + 深度对比
sql
-- 示例1:计算英文字符的长度(1个字母=1字节)
select
length('MySQL') as 字节数,
char_length('MySQL') as 字符数;
-- 输出:5 | 5(字节数=字符数)
-- 示例2:计算中文字符的长度(UTF-8下1个中文=3/4字节)
select
length('MySQL函数') as 字节数, -- MySQL=5字节,函数=4字节(utf8mb4)
char_length('MySQL函数') as 字符数; -- 5+2=7个字符
-- 输出:9 | 7(字节数≠字符数)
-- 示例3:实战场景------计算姓名字节数(用于字段长度校验)
-- select name, length(name) as 姓名字节数, char_length(name) as 姓名字符数 from student;

关键区别:length() vs char_length()
| 函数 | 计算单位 | 受字符集影响 | 中文示例(UTF-8) | 应用场景 |
|---|---|---|---|---|
length() |
字节 | 是 | "函数"=4 字节 | 校验字段的字节长度(如 varchar (20) 字段,最多存 20 字节) |
char_length() |
字符 | 否 | "函数"=2 字符 | 校验字段的字符个数(如用户名最多 10 个字符) |
典型应用场景
- 字段长度校验 :用户名字段是
varchar(20)(UTF-8),用length(用户名)<=20判断是否超长; - 数据清洗:筛选出 "姓名字符数> 10" 的异常数据(可能是乱码或超长名称);
- 字符集验证 :如果
length(中文)=3*char_length(中文),说明字符集是 utf8;如果 = 4 倍,说明是 utf8mb4。
避坑要点
- 字符集影响
length()的结果 :同一个中文字符,在 latin1 下是 1 字节(乱码),在 utf8 下是 3 字节,在 utf8mb4 下是 4 字节 ------ 必须先确认字符集(使用charset函数),再用length()。
3.4 常用功能:字符串替换("文字的橡皮擦")
replace()函数用于把字符串中的指定内容替换成新内容------ 就像用橡皮擦把 "张三" 改成 "李四",把 "错误" 改成 "正确",把敏感词改成 "***"。
核心语法
replace(原字符串, 要替换的子串, 新子串)
代码示例 + 场景化解读
sql
-- 示例1:基础替换
select replace('MySQL函数真好用', '好用', '强大') as 替换结果;
-- 输出:MySQL函数真强大
-- 示例2:替换表字段内容(实战常用)
-- 假设emp表的ename字段有值'SMITH',把'S'替换成'上海'
-- select replace(ename, 'S', '上海') as 替换后名称, ename as 原名称 from emp;
-- 输出:上海MITH | SMITH
-- 注意:这里的替换只会更改显示结果,而表中的数据不会被更改
-- 示例3:数据清洗------替换敏感词
select replace('这个商品真垃圾', '垃圾', '***') as 清洗结果;
-- 输出:这个商品真***
-- 示例4:批量修改字段内容(结合update)
-- 把product表中name字段的'特价'替换成'促销'
-- update product set name = replace(name, '特价', '促销');

典型应用场景
- 数据清洗:替换敏感词、错误内容(如把 "垃圾" 改成 "***",把 "张三" 改成 "匿名");
- 批量修改:修改表字段中的固定内容(如把所有商品名称的 "特价" 改成 "促销");
- 格式标准化:替换字符串中的分隔符(如把 "2024/01/20" 改成 "2024-01-20")。
避坑要点
-
替换是全局的 :
replace()会替换原字符串中所有匹配的子串,不是只替换第一个:select replace('aaa', 'a', 'b'); -- 输出:bbb(所有a都被替换成b) -
大小写敏感 :MySQL 默认区分大小写,
replace('MySQL', 'm', 'M')不会替换 ------ 如果要忽略大小写,先转成小写 / 大写:select replace(lower('MySQL'), 'm', 'M'); -- 输出:MYsql
3.5 常用功能:字符串截取("文字的剪刀")
substring()函数用于截取字符串的一部分------ 就像用剪刀把 "MySQL 函数真强大" 中的 "函数" 剪出来,变成 "函数"。
核心语法
sql
-- 写法1:substring(原字符串, 起始位置, 截取长度(单位:字符))
-- 写法2:substr(原字符串, 起始位置, 截取长度(单位:字符))(简写,等价)
#忽略第三个参数,也就是截取长度时,MySQL默认截取到最后
其实就是我们之前在c++中学的substr函数,相信大家不陌生。
⚠️ 天坑预警:MySQL 中字符串的起始位置是 1,不是 0!新手经常写成 0,导致截取结果错误。
这一点我们要和数组进行区分,因为数组是从开始的,而在MySQL中,字符串的第一个字符则是从开始,至于截取长度,那也就更简单了,单位是字符,长度为几,就代表要截取几个字符,还有需要注意的是,我们所填的起始位置那一个字符,也是会被截取的,所以说截取长度就是从其实位置开始算的哦
总结一下就是:实际截取的字符串是:起始位置字符+起始位置字符后的截取长度-1个字符
大家可以看着下面的示例进行理解。
代码示例 + 场景化解读
sql
-- 示例1:截取从第2个字符开始,长度为2的子串
select substring('MySQL函数', 2, 2) as 截取结果;
-- 输出:yS
-- 解析:第1位=M,第2位=y,第3位=S,截取2位→yS
-- 示例2:截取从第6个字符开始,到末尾的子串(省略长度参数)
select substring('MySQL函数', 6) as 截取结果;
-- 输出:函数
-- 解析:省略长度参数时,截取从起始位置到字符串末尾的所有字符
-- 示例3:实战场景------手机号脱敏(隐藏中间4位)
select concat(
substring('13800138000', 1, 3), -- 前3位
'****',
substring('13800138000', 8) -- 后4位
) as 脱敏手机号;
-- 输出:138****8000
-- 示例4:截取表字段内容(以emp表的ename字段为例)
-- select substring(ename, 2, 2) as 截取结果, ename as 原名称 from emp;
-- 输出:MI | SMITH

拓展:left()/right()------ 截取左边 / 右边字符(更简洁)
如果只需要截取字符串的前 N 个或后 N 个字符,用left()/right()更高效:
sql
-- 截取左边N个字符
select left('MySQL函数', 5) as 左边5个字符; -- 输出:MySQL
-- 截取右边N个字符
select right('MySQL函数', 2) as 右边2个字符; -- 输出:函数
典型应用场景
- 数据脱敏:隐藏手机号中间 4 位、身份证号中间 6 位;
- 提取关键信息:从地址字段中提取省份(如前 2 个字符)、从订单编号中提取日期;
- 格式标准化:截取字符串的固定长度,统一数据格式(如截取前 8 位作为编号)。
避坑要点
- 起始位置从 1 开始 :新手最容易写成 0,导致截取结果错误 ------ 记住口诀:MySQL 字符串,下标从 1 起;
- 截取长度超出字符串长度 :自动截取到末尾,不会报错 ------ 比如
substring('MySQL', 2, 10)返回ySQL; - 负数起始位置 :表示从字符串末尾开始计数 ------ 比如
substring('MySQL函数', -2)返回函数(最后 2 个字符)。
3.6 实用功能:字符串大小写转换("文字的转换器")
lower()/lcase()(转小写)、upper()/ucase()(转大写)函数用于统一字符串的大小写------ 就像把 "MySQL" 转成 "mysql",把 "name" 转成 "NAME"。
核心语法
sql
-- 转小写
lower(字符串/字段名)
lcase(字符串/字段名) -- 等价
-- 转大写
upper(字符串/字段名)
ucase(字符串/字段名) -- 等价
其实就和我们之前C语言所学的upper、lower一样的作用,简简单单轻轻松松。
代码示例 + 场景化解读
sql
-- 示例1:转小写
select lower('MySQL函数') as 小写结果; -- 输出:mysql函数
-- 示例2:转大写
select upper('MySQL函数') as 大写结果; -- 输出:MYSQL函数
-- 示例3:实战场景------首字母小写(结合substring和concat)
-- 需求:把emp表的ename字段首字母小写,其余不变(如SMITH→sMITH)
-- select concat(
-- lower(substring(ename, 1, 1)), -- 首字母转小写
-- substring(ename, 2) -- 其余字符不变
-- ) as 首字母小写名称 from emp;

将函数符复合使用,以后大家会经常用到。
典型应用场景
-
统一用户名格式:用户输入的用户名可能是 "MySQL""mysql",转成小写后再存储,避免重复;
-
忽略大小写查询 :查询时把字段和关键词都转成小写,实现不区分大小写的搜索:
sql-- 查询姓名包含"smith"的用户(不区分大小写) select * from user where lower(name) like lower('%smith%'); -
数据导出:把字符串转成大写,统一报表格式(如把 "name" 转成 "NAME" 作为表头)。
3.7 进阶功能:字符串查找与定位("文字的指南针")
instr()函数用于查找子字符串在原字符串中的位置------ 就像用指南针找到 "函数" 在 "MySQL 函数真强大" 中的位置,返回 6(第 6 个字符)。
核心语法
sql
instr(原字符串, 要查找的子串)
- 返回值:子串在原字符串中的起始位置(从 1 开始);如果没找到,返回 0。
一样的,要和数组进行区分哦。
代码示例 + 场景化解读
sql
-- 示例1:查找子串存在的情况
select instr('MySQL函数真强大', '函数') as 位置; -- 输出:6
-- 解析:"函数"从第6个字符开始,返回6
-- 示例2:查找子串不存在的情况
select instr('MySQL函数真强大', 'Java') as 位置; -- 输出:0
-- 解析:原字符串中没有"Java",返回0
-- 示例3:实战场景------判断字段是否包含指定内容
-- 查找姓名中包含"小"字的用户
-- select * from user where instr(name, '小') > 0;

典型应用场景
- 内容过滤:判断字段是否包含指定关键词(如查找姓名含 "小" 的用户);
- 数据清洗:筛选出包含非法字符的记录(如查找内容含 "垃圾" 的留言);
- 位置辅助截取 :结合
substring(),根据子串位置截取字符串。
避坑要点
-
大小写敏感 :
instr('MySQL', 'm')返回 0(没找到),instr('MySQL', 'M')返回 1------ 忽略大小写时,先转成小写 / 大写:sqlselect instr(lower('MySQL'), lower('m')); -- 输出:1
3.8 实战案例:字符串函数的综合应用(数据清洗与格式化)
需求 :有一张用户信息表user_info,字段如下:
| 字段名 | 类型 | 存在的问题 |
|---|---|---|
| name | varchar(20) | 首尾有空格(如 " 张三 ") |
| phone | varchar(11) | 部分为 NULL |
| address | varchar(100) | 格式不统一(如 "北京市朝阳区""上海市浦东新区") |
目标:
- 清洗姓名:去除首尾空格;
- 处理手机号:NULL 值替换为 "未绑定",非 NULL 值脱敏(隐藏中间 4 位);
- 格式化地址:提取省份(前 2 个字符),生成 "省份 - 地址" 的格式;
- 生成用户标签:拼接 "姓名 - 省份 - 手机号状态"。
实现 SQL:
sql
-- 步骤1:创建测试表并插入数据
create table user_info (
id int primary key auto_increment,
name varchar(20),
phone varchar(11),
address varchar(100)
);
insert into user_info (name, phone, address) values
(' 张三 ', '13800138000', '北京市朝阳区'),
(' 李四 ', null, '上海市浦东新区'),
(' 王五 ', '13900139000', '广东省深圳市');
-- 步骤2:数据清洗与格式化
select
id,
trim(name) as 清洗后姓名, -- 去除首尾空格
-- 手机号处理:NULL→未绑定,非NULL→脱敏
if(phone is null, '未绑定', concat(left(phone,3), '****', right(phone,4))) as 脱敏手机号,
left(address, 2) as 省份, -- 提取省份(前2个字符)
concat(left(address,2), '-', address) as 格式化地址,
-- 生成用户标签
concat(trim(name), '-', left(address,2), '-', if(phone is null, '未绑定', '已绑定')) as 用户标签
from user_info;
执行结果:
sql
+----+--------------+--------------+--------+------------------+----------------+
| id | 清洗后姓名 | 脱敏手机号 | 省份 | 格式化地址 | 用户标签 |
+----+--------------+--------------+--------+------------------+----------------+
| 1 | 张三 | 138****8000 | 北京 | 北京-北京市朝阳区 | 张三-北京-已绑定 |
| 2 | 李四 | 未绑定 | 上海 | 上海-上海市浦东新区 | 李四-上海-未绑定 |
| 3 | 王五 | 139****9000 | 广东 | 广东-广东省深圳市 | 王五-广东-已绑定 |
+----+--------------+--------------+--------+------------------+----------------+
核心解析:
- 用
trim()去除姓名首尾空格,解决 "空格污染" 问题; - 用
if()判断手机号是否为 NULL,实现 "未绑定 / 脱敏" 的分支逻辑; - 用
left()提取地址的省份,结合concat()生成格式化地址; - 组合多个字符串函数,生成包含多维度信息的用户标签 ------ 这就是函数的 "威力"!
3.9 字符串函数速查表
| 函数 | 作用 | 核心用法 | 场景 |
|---|---|---|---|
concat() |
拼接字符串 | concat('张三', '的成绩是', 90) |
生成报表文案 |
concat_ws() |
带分隔符拼接 | concat_ws(',', 'a', 'b', 'c') |
生成 CSV 数据 |
length() |
计算字节数 | length('MySQL函数') |
校验字段长度 |
char_length() |
计算字符数 | char_length('MySQL函数') |
校验字符个数 |
replace() |
替换字符串 | replace('abc', 'a', 'A') |
数据清洗 |
substring() |
截取字符串 | substring('abcde', 2, 2) |
手机号脱敏 |
left()/right() |
截取左 / 右字符 | left('abcde', 2) |
提取省份 |
lower()/upper() |
大小写转换 | lower('MySQL') |
统一查询格式 |
trim() |
去除首尾空格 | trim(' abc ') |
数据清洗 |
instr() |
查找子串位置 | instr('abcde', 'c') |
内容过滤 |
四、第三核心类:数学函数(处理 "数字" 的超级计算器)
数学函数用于处理数值类型的数据:金额、分数、数量、距离...... 就像你的手机计算器 ------ 能算绝对值、取整、四舍五入、生成随机数,满足各种数值计算需求。

这一系列的函数,还是很简单的,大家在之前的学习中,也没少使用过。
4.1 基础功能:绝对值计算("去掉负号的魔法")
abs()函数用于计算数值的绝对值------ 不管输入是正数还是负数,都返回正数,就像计算器上的 "|x|" 按钮。
核心语法
abs(数值/字段名)
代码示例 + 场景化解读
sql
-- 示例1:正数的绝对值
select abs(100.2) as 绝对值; -- 输出:100.2
-- 示例2:负数的绝对值
select abs(-100.2) as 绝对值; -- 输出:100.2
-- 示例3:零的绝对值
select abs(0) as 绝对值; -- 输出:0
-- 示例4:实战场景------计算销量差值(不管多卖还是少卖,都显示正数)
-- 假设product表有target_sales(目标销量)和actual_sales(实际销量)字段
-- select product_name, abs(actual_sales - target_sales) as 销量差值 from product;
典型应用场景
- 计算差值:计算实际值与目标值的差,不管正负,都显示正数(如销量差值、温度差值);
- 数据清洗:把负数的金额转正(如退款金额存储为负数,展示时转成正数);
- 数值校正:确保计算结果为正数(如距离、长度、面积)。
4.2 核心功能:取整函数("数字的整形师")
取整是数值处理的高频需求,MySQL 提供了三个取整函数:ceiling()(向上取整)、floor()(向下取整)、round()(四舍五入取整)------ 就像整形师,把 "23.1" 变成 "24""23" 或 "23"。
4.2.1 ceiling()/ceil():向上取整(进一法)
核心逻辑:不管小数部分是多少,都向整数部分进 1------ 比如 23.01→24,23.99→24,-23.01→-23。
sql
select ceiling(23.01) as 向上取整; -- 输出:24
select ceiling(23.99) as 向上取整; -- 输出:24
select ceiling(-23.01) as 向上取整; -- 输出:-23(负数向上取整是往0的方向)
4.2.2 floor():向下取整(去尾法)
核心逻辑 :不管小数部分是多少,都舍弃小数部分------ 比如 23.99→23,23.01→23,-23.99→-24。
sql
select floor(23.99) as 向下取整; -- 输出:23
select floor(23.01) as 向下取整; -- 输出:23
select floor(-23.99) as 向下取整; -- 输出:-24(负数向下取整是远离0的方向)
4.2.3 round():四舍五入取整(常用)
核心逻辑:根据小数部分的大小,四舍五入到指定的小数位数 ------ 比如 round (23.4,0)→23,round (23.5,0)→24。
注意第二个参数是要我们传入想要最后的结果有几位小数。
round函数算是我们最常用的了,毕竟四舍五入这一块,公认的好使。
sql
-- 语法:round(数值, 小数位数)
select round(23.456, 2) as 四舍五入; -- 输出:23.46(保留2位小数)
select round(23.456, 0) as 四舍五入; -- 输出:23(保留0位小数)
select round(23.5) as 四舍五入; -- 输出:24(默认保留0位小数)
代码示例 + 场景化解读
sql
-- 示例1:计算运费(向上取整到元)
-- 假设运费是12.3元,向上取整到元→13元
select ceiling(12.3) as 运费; -- 输出:13
-- 示例2:计算箱子数量(向下取整)
-- 23件商品,每个箱子装5件→需要4个箱子(23/5=4.6→向下取整4)
select floor(23/5) as 箱子数量; -- 输出:4
-- 示例3:计算商品价格(四舍五入到分)
select round(99.995, 2) as 商品价格; -- 输出:100.00
典型应用场景
ceiling():计算运费、箱子数量(只要有小数,就多算一个);floor():计算可分配的数量(舍弃小数部分,避免不够分);round():计算商品价格、平均分(四舍五入到指定精度)。
4.3 实用功能:小数格式化("数字的化妆师")
format()函数用于把数值格式化为指定小数位数(采用四舍五入的形式进行舍去对应的小数),并添加千分位分隔符(当数值大于千的时候才会哦)------ 就像把 1234.5678 变成 1,234.57,让数字更美观、更易读。
核心语法
format(数值, 小数位数)
代码示例 + 场景化解读
sql
-- 示例1:保留2位小数(四舍五入)
select format(12.3456, 2) as 格式化结果; -- 输出:12.35
-- 示例2:保留0位小数(取整)
select format(12.3456, 0) as 格式化结果; -- 输出:12
-- 示例3:添加千分位分隔符
select format(12345.6789, 2) as 格式化结果; -- 输出:12,345.68
-- 示例4:实战场景------格式化订单金额(财务展示)
-- select order_id, format(amount, 2) as 订单金额 from order;
关键注意点
-
返回值是字符串类型 :
format()返回的是字符串,不是数值 ------ 如果需要进行计算,必须先用cast()转成数值类型:sql-- 错误:字符串不能直接计算 select format(12.34, 2) + 10; -- 正确:先转成decimal类型 select cast(format(12.34, 2) as decimal(10,2)) + 10; -- 输出:22.34 -
千分位分隔符:默认用逗号(,)作为千分位分隔符 ------ 符合财务数据的展示规范。
4.4 趣味功能:生成随机数("数字的骰子")
rand()函数用于生成 0 到 1 之间的随机浮点数(不包含 0 和 1)------ 就像掷骰子,每次执行结果都不同,常用于随机抽样、生成验证码等场景。
我们想要生成大一点的,就只能在函数后面进行*,当然,也可以+-/
核心语法
sql
-- 写法1:rand()(无参数,每次结果不同)
-- 写法2:rand(种子)(固定种子,每次结果相同,用于测试)
意思是说,给rand函数里面传入一个参数了,每次生成的结果就是一样的
聪明的你很快就能想到,这不就是之前c语言所有的rand函数吗,然后种子不就是srand函数吗,哈哈,救赎之道,就在其中。
代码示例 + 场景化解读
sql
-- 示例1:生成0-1之间的随机数
select rand() as 随机数; -- 输出:0.87654321(每次不同)
-- 示例2:生成1-100之间的随机整数
select floor(rand()*100)+1 as 随机整数;
-- 解析:rand()*100→0-99.999... → floor()→0-99 → +1→1-100
-- 示例3:生成6位数字验证码
select floor(rand()*900000)+100000 as 验证码;
-- 解析:rand()*900000→0-899999.999... → floor()→0-899999 → +100000→100000-999999
-- 示例4:固定种子生成随机数(测试用)
select rand(10) as 随机数; -- 输出:0.6570515219653505(每次相同)

典型应用场景
- 随机抽样 :查询 "随机 5 条商品数据"------
select * from product order by rand() limit 5;; - 生成验证码:生成 6 位数字验证码,用于用户登录、注册;
- 测试数据生成:生成随机的测试数据(如随机价格、随机数量)。
避坑要点
-
order by rand()效率低 :如果表数据量大(比如 10 万行),order by rand()会全表扫描并排序,效率极低 ------ 生产环境中尽量避免,改用 "随机 ID" 的方式:-- 高效随机抽样:先查最大ID,再生成随机ID查询 select * from product where id = floor(rand()*(select max(id) from product)) + 1; -
随机数是伪随机 :
rand()生成的是伪随机数,基于种子计算 ------ 固定种子会生成固定的随机数序列,适合测试。
4.5 拓展功能:其他常用数学函数
| 函数 | 作用 | 示例 | 应用场景 |
|---|---|---|---|
mod() |
取模(求余数) | mod(10,3)→1 |
判断奇偶性(mod (id,2)=0 是偶数) |
pow() |
幂运算 | pow(2,3)→8 |
计算面积(边长 ²)、体积(边长 ³) |
sqrt() |
平方根 | sqrt(16)→4 |
计算距离(勾股定理) |
pi() |
获取圆周率 π | pi()→3.141593 |
计算圆的周长、面积 |
sign() |
判断数值符号 | sign(10)→1,sign(-10)→-1 |
判断数值正负 |
一样的,聪明的你c++会的话,那么mysql也一定会
代码示例
sql
-- 判断奇偶性
select id, name, if(mod(id,2)=0, '偶数', '奇数') as 奇偶性 from user;
-- 计算圆的面积(面积=πr²)
select pi() * pow(5,2) as 圆面积; -- 半径5→输出:78.539816
4.6 实战案例:数学函数的综合应用(订单金额计算)
需求 :有一张订单表order,字段如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| order_id | int | 订单 ID |
| product_price | decimal(10,2) | 商品单价 |
| quantity | int | 购买数量 |
| discount | decimal(5,2) | 折扣率(0.8=8 折) |
| freight | decimal(10,2) | 运费 |
目标:
- 计算订单的商品总价(单价 × 数量);
- 计算折扣后总价(商品总价 × 折扣率);
- 计算订单实付金额(折扣后总价 + 运费,四舍五入到分);
- 判断实付金额的正负(正常订单为正,退款订单为负);
- 生成订单金额描述文案。
实现 SQL
sql
-- 步骤1:创建测试表并插入数据
create table `order` (
order_id int primary key auto_increment comment '订单ID',
product_price decimal(10,2) not null comment '商品单价',
quantity int not null comment '购买数量',
discount decimal(5,2) default 1.00 comment '折扣率(1.00=无折扣)',
freight decimal(10,2) default 0.00 comment '运费'
) comment '订单表';
-- 插入测试数据(包含正常订单、退款订单)
insert into `order` (product_price, quantity, discount, freight) values
(99.99, 2, 0.9, 10.00), -- 正常订单:折扣后总价=99.99*2*0.9=179.982,实付=179.982+10=189.982→四舍五入189.98
(199.99, 1, 1.00, 0.00), -- 正常订单:无折扣,无运费
(-299.99, 1, 1.00, 0.00); -- 退款订单:金额为负
-- 步骤2:计算订单金额相关字段
select
order_id,
product_price * quantity as 商品总价,
quantity as 购买数量,
discount as 折扣率,
freight as 运费,
product_price * quantity * discount as 折扣后总价,
round(product_price * quantity * discount + freight, 2) as 实付金额,
sign(round(product_price * quantity * discount + freight, 2)) as 金额符号, -- 1=正,-1=负,0=零
concat(
'订单', order_id, ':商品总价', product_price * quantity, '元,折扣后',
product_price * quantity * discount, '元,运费', freight, '元,实付',
round(product_price * quantity * discount + freight, 2), '元'
) as 金额描述
from `order`;
执行结果
sql
+----------+---------------+----------+----------+---------+------------+--------------+------------+------------+-------------------------------------------------------------+
| order_id | 商品总价 | 购买数量 | 折扣率 | 运费 | 折扣后总价 | 实付金额 | 金额符号 | 金额描述 |
+----------+---------------+----------+----------+---------+------------+--------------+------------+------------+-------------------------------------------------------------+
| 1 | 199.98 | 2 | 0.90 | 10.00 | 179.98 | 189.98 | 1 | 订单1:商品总价199.98元,折扣后179.98元,运费10.00元,实付189.98元 |
| 2 | 199.99 | 1 | 1.00 | 0.00 | 199.99 | 199.99 | 1 | 订单2:商品总价199.99元,折扣后199.99元,运费0.00元,实付199.99元 |
| 3 | -299.99 | 1 | 1.00 | 0.00 | -299.99 | -299.99 | -1 | 订单3:商品总价-299.99元,折扣后-299.99元,运费0.00元,实付-299.99元 |
+----------+---------------+----------+----------+---------+------------+--------------+------------+------------+-------------------------------------------------------------+
核心解析
- 用
product_price * quantity计算商品总价,这是最基础的数值运算; - 用
product_price * quantity * discount计算折扣后总价,实现 "打折" 逻辑; - 用
round(..., 2)把实付金额四舍五入到分,符合财务结算的精度要求; - 用
sign()判断金额正负,区分正常订单(正)和退款订单(负); - 用
concat()拼接所有金额信息,生成可读性高的金额描述文案。
其他函数:
一、user() 函数:查 "当前操作 MySQL 的人 + 登录地址"
1. 核心定义
user() 函数的作用就是查看 "现在是谁在操作这个 MySQL 服务器",以及 "这个人是从哪台电脑 / 服务器登录的" ------ 就像你去银行办理业务,柜员会先看你的身份证 + 登记你是从哪个网点来的,user() 就是 MySQL 的 "身份登记器"。
2. 语法规则(零门槛)
sql
select user(); -- 无参数!无参数!无参数!传任何参数都会报错
- 核心特点:不需要传任何参数,直接调用即可;
- 返回值格式:
用户名@主机名(固定格式,缺一不可)。

3. 底层运行逻辑
MySQL 会记录每个 "数据库连接会话" 的核心信息:
- 你登录时输入的「用户名」(比如
root、test_user); - 你的「登录主机」(即你的电脑 / 服务器的 IP / 主机名,MySQL 用它限制 "这个用户能从哪些地址登录");
- 调用
user()时,MySQL 会从当前会话的连接信息中提取这两个值,拼接成用户名@主机名返回。
4. 完整示例 + 结果解读
示例 1:本地登录 MySQL(最常见场景)
select user();
典型输出 :root@localhost
- 解读:
root:你登录用的是 MySQL 的最高权限用户(超级管理员);localhost:表示你是从 MySQL 服务器所在的本机登录(不是远程电脑)。
示例 2:远程登录 MySQL(办公 / 生产场景)
select user();
典型输出 :test_user@192.168.1.100
- 解读:
test_user:你登录用的是普通业务用户;192.168.1.100:表示你是从 IP 为 192.168.1.100 的电脑远程连接 MySQL 服务器。
示例 3:无权限用户登录(测试场景)
select user();
典型输出 :anonymous@%
- 解读:
anonymous:匿名用户(没输入用户名密码);%:通配符,表示 "任何主机都能以匿名用户登录"(生产环境必须禁用)。
5. 典型使用场景
场景 1:权限校验(开发 / 运维核心)
比如你想判断当前用户是否有删除表的权限(只有root能删表),可以结合 if() 函数:
sql
select if(user() = 'root@localhost', '有删除权限', '无删除权限') as 权限判断;
输出:有删除权限(如果是 root 本地登录)。
场景 2:操作日志记录(生产必备)
记录 "谁在什么时间执行了敏感操作",比如修改用户密码时:
sql
insert into operate_log (operator, operate_time, action)
values (user(), now(), '修改了订单表数据');
日志里会清晰显示:root@localhost | 2024-01-20 16:00:00 | 修改了订单表数据。
场景 3:脚本适配(跨环境兼容)
比如备份脚本中,判断当前用户是否是root,不是则提示:
sql
-- 伪代码(结合shell脚本)
if [ $(mysql -e "select user();") != "root@localhost" ]; then
echo "请用root用户执行备份脚本!"
exit 1
fi
6. 避坑要点
坑 1:给user()传参数
新手可能误写:select user('root'); → 直接报错(FUNCTION user does not exist),记住user()永远无参数。
坑 2:误解localhost和%的区别
localhost:仅允许本机登录(安全性高);%:允许任何远程主机登录(生产环境给业务用户授权时,要限制为具体 IP,比如192.168.1.%,禁止直接用%)。
坑 3:混淆user()和current_user()
-
user():返回 "实际登录的用户 + 主机"(你输入的用户名); -
current_user():返回 "MySQL 实际赋予权限的用户 + 主机"(可能因为权限映射,和user()不一致);示例:select user(), current_user();
-- 输出:test_user@192.168.1.100 | test_user@%
解读:你用test_user@192.168.1.100登录,但 MySQL 给test_user的权限是test_user@%(任何主机)。
7. 拓展补充
- 查看所有已授权的用户:
select user, host from mysql.user;(需要 root 权限); - 限制用户登录主机:
grant all on test_db.* to 'test_user'@'192.168.1.%' identified by '123456';(仅允许 192.168.1 段的 IP 登录)。
二、md5(str) 函数:给字符串生成 "唯一指纹"(不可逆加密)
1. 心定义
md5() 是把任意长度的字符串(比如admin、123456、甚至长篇文章)转换成固定 32 位的十六进制字符串(由 0-9、a-f 组成)------ 相当于给字符串做了一个 "唯一指纹":
- 相同的输入 → 绝对相同的 32 位指纹;
- 不同的输入 → 几乎不可能出现相同指纹(碰撞概率极低,可忽略);
- 无法从 32 位指纹还原回原字符串(不可逆,这是加密的核心特点)。
2. 语法规则
select md5(待加密的字符串/字段名);
- 参数:可以是字符串字面量(如
'admin')、表字段(如user.password)、拼接后的字符串(如concat('admin', 'salt123')); - 返回值:32 位十六进制字符串(固定长度,哪怕输入是空字符串,也会返回 32 位)。

3. 底层运行逻辑
MD5(Message-Digest Algorithm 5)是经典的哈希算法,MySQL 执行md5(str)时会做这些事:
- 把输入的字符串转换成 "字节流"(比如
admin的 UTF-8 字节流是:0x61 0x64 0x6D 0x69 0x6E); - 对字节流进行 MD5 哈希计算,生成 128 位的二进制数(比如
admin对应的二进制数是:00100001 00100011 ... 共 128 位); - 把 128 位二进制数转换成 32 位十六进制字符串(每 4 位二进制对应 1 位十六进制),这就是最终返回的结果。
4. 完整示例 + 结果解读
示例 1:加密固定字符串
select md5('admin');
输出 :21232f297a57a5a743894a0e4a801fc3
- 解读:这是
admin的 MD5 "指纹",是固定值(全网通用,任何工具算admin的 MD5 都是这个结果),可以用在线 MD5 工具验证。
示例 2:加密带特殊字符的字符串
select md5('admin@123');
输出 :e10adc3949ba59abbe56e057f20f883e
- 解读:哪怕只多一个
@123,指纹就完全不同,体现了 MD5 的 "唯一性"。
示例 3:实战核心 ------ 密码加盐加密(防破解)
单纯的 MD5 加密(如md5('123456'))有风险:黑客有 "彩虹表"(提前算好所有简单密码的 MD5),能快速破解。解决方法是 "加盐"(在密码后拼接一个随机字符串):
-- 盐值可以是固定字符串、用户ID、随机数(推荐存在数据库中)
select md5(concat('123456', 'salt_888')) as 加盐加密结果;
输出 :8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918(这个结果无法通过彩虹表破解)。
示例 4:加密表字段(用户密码存储)
sql
-- 1. 创建用户表
create table user (
id int primary key auto_increment,
username varchar(20),
password varchar(32) -- 存32位MD5结果
);
-- 2. 注册用户:加密密码后存储
insert into user (username, password) values ('zhangsan', md5(concat('123456', 'salt_888')));
-- 3. 登录验证:加密用户输入的密码,和数据库对比(无需解密)
select * from user
where username='zhangsan' and password=md5(concat('123456', 'salt_888'));
- 解读:这是生产环境的标准用法 ------ 永远不存储明文密码,只存 MD5 摘要;登录时只需要加密用户输入的密码,对比摘要即可,无需还原明文。
5. 典型使用场景
场景 1:用户密码存储(核心场景)
这是md5()最常用的场景,替代明文存储(明文存储的风险:数据库泄露后,用户密码直接被黑客获取)。
场景 2:数据完整性校验(运维 / 测试)
比如你下载了一个文件,官方提供了该文件的 MD5 值,你可以计算本地文件的 MD5,对比是否一致:
- 如果一致:文件没被篡改;
- 如果不一致:文件被修改过(可能有病毒)。在 MySQL 中,可用于校验字段是否被修改:
sql
-- 给订单表加一个md5字段,记录关键信息的摘要
alter table `order` add column data_md5 varchar(32);
-- 更新摘要:拼接订单金额、用户ID、创建时间
update `order` set data_md5 = md5(concat(amount, user_id, create_time));
-- 校验:如果摘要变了,说明数据被篡改
select * from `order` where data_md5 != md5(concat(amount, user_id, create_time));
场景 3:生成唯一标识(临时场景)
比如生成临时的订单编号、验证码(不推荐长期用,优先用 UUID):
select concat('order_', md5(now())) as 临时订单号;
输出:order_9e8b42c08d7e4f9a8c7b6a5d4f3e2a1b。
6. 避坑要点
坑 1:认为 MD5 是 "可逆加密"
新手会问:"怎么把 32 位的 MD5 结果还原成原字符串?" ------ 答案是:无法还原!MD5 是哈希算法,不是加密算法(加密可逆,哈希不可逆)。如果需要还原(比如找回密码),不要用 MD5,改用对称加密(如 AES)。
坑 2:单纯用 MD5 存储密码(无加盐)
比如直接md5('123456'),黑客用彩虹表 1 秒就能破解,必须加盐(盐值要足够复杂,且每个用户的盐值最好不同)。
坑 3:忽略大小写
MD5 的结果是小写的,但 MySQL 不区分大小写,比如md5('Admin')和md5('admin')结果不同:
select md5('Admin'), md5('admin');
-- 输出:0192023a7bbd73250516f069df18b500 | 21232f297a57a5a743894a0e4a801fc3
- 解决:存储和验证时,统一把输入转小写(
md5(lower(password)))。
坑 4:MD5 结果用于数值计算
MD5 返回的是字符串,不是数值,直接计算会报错:
select md5('123') + 10; -- 错误!字符串无法和数值相加
7. 拓展补充
替代方案:更安全的sha2()函数
MD5 的安全性已不足以应对现代破解技术,MySQL 推荐用sha2()(SHA-256 算法):
select sha2('admin', 256); -- 256表示哈希长度,返回64位字符串
输出:8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918(比 MD5 更安全)。

三、database() 函数:查 "当前正在操作的数据库"
1. 核心定义
database() 函数的作用是查看当前数据库会话中,你 "选中" 的数据库名 ------ 就像你打开 Excel 后,切换到了 "销售数据.xlsx" 这个文件,database() 就是告诉你 "现在操作的是哪个 Excel 文件"。
这个我们之前也有讲过,大家应该还记得。
2. 语法规则
select database(); -- 无参数!传参报错
- 核心特点:无参数,直接调用;
- 返回值:
- 如果执行过
use 数据库名;,返回该数据库名(字符串); - 如果没执行过
use,返回NULL(表示你还没选中任何数据库)。
- 如果执行过
3. 底层运行逻辑
MySQL 会为每个数据库连接会话维护一个 "当前数据库" 的标记:
- 当你执行
use test_db;时,MySQL 会把这个标记设置为test_db; - 当你调用
database()时,MySQL 会读取这个标记并返回; - 如果你没执行过
use,标记为空,返回NULL。
4. 完整示例 + 结果解读
示例 1:未选中数据库时调用
select database();
输出 :NULL
- 解读:你刚登录 MySQL,还没执行
use 数据库名;,所以没有 "当前数据库"。
示例 2:选中数据库后调用
use test_db; -- 选中test_db数据库
select database();
输出 :test_db
- 解读:现在你的所有操作(比如
select * from user;)都会默认在test_db库中执行。
示例 3:切换数据库后调用
use order_db; -- 切换到订单库
select database();
输出 :order_db
- 解读:
database()的结果会随use命令实时更新,始终指向 "当前选中的库"。
5. 典型使用场景
场景 1:备份脚本(动态生成备份文件名)
运维中最常用的场景:备份当前数据库,文件名包含库名 + 日期,避免覆盖:
-- 伪代码(结合shell脚本)
DB_NAME=$(mysql -e "select database();")
BACKUP_FILE="${DB_NAME}_$(date +%Y%m%d).sql"
mysqldump -uroot -p123456 $DB_NAME > $BACKUP_FILE
执行后会生成:test_db_20240120.sql(清晰标识备份的是哪个库、哪天的备份)。
场景 2:动态 SQL(跨库兼容)
比如写通用脚本,需要根据当前数据库动态拼接表名:
select concat(database(), '.user') as 完整表名;
输出:test_db.user(完整表名格式:库名。表名,跨库操作时必须写)。
场景 3:验证数据库切换是否成功
新手执行use 数据库名;后,可能手滑输错库名,用database()验证:
use test_db;
select if(database()='test_db', '切换成功', '切换失败') as 验证结果;
输出:切换成功。
6. 避坑要点
坑 1:给database()传参数
新手误写:select database('test_db'); → 报错(FUNCTION database does not exist),记住database()永远无参数。
坑 2:混淆database()和show databases;
-
database():查 "当前选中的 1 个库"; -
show databases;:查 "MySQL 中所有的库";示例:show databases; -- 输出:information_schema、mysql、test_db、order_db(所有库)
select database(); -- 输出:test_db(当前库)
坑 3:认为database()返回 NULL 是错误
看到database()返回NULL会慌,其实这只是 "没选中库",执行use 库名;后就会返回正常值,不是错误。
7. 拓展补充
schema()函数:和database()完全等价(MySQL 中,schema 和 database 是同一个概念),select schema();=select database();;- 查看当前库的所有表:
show tables;(依赖database()的结果,选中哪个库就显示哪个库的表); - 跨库操作表:如果没选中库,操作表时必须写 "库名。表名",比如
select * from test_db.user;。
四、password() 函数:MySQL "过时的系统密码加密工具"
1. 大白话核心定义
password() 是 MySQL早期版本(5.7 及以前) 用于加密 "MySQL 系统用户密码" 的函数(比如root用户的密码),MySQL 8.0 已完全废弃 ------ 简单说:"这是个'老古董'函数,现在千万别用了"。
2. 语法规则(仅老版本可用)
select password(待加密的字符串); -- 仅MySQL 5.7及以前支持
- 参数:字符串(通常是 MySQL 用户的密码);
- 返回值:41 位的哈希字符串(以
*开头,区别于 MD5 的 32 位)。
3. 底层运行逻辑
password() 是 MySQL 专属的哈希算法(非 MD5、非 SHA),仅用于加密mysql.user表中的系统用户密码:
- 在 MySQL 5.6/5.7 中,执行
set password for 'root'@'localhost' = password('123456');时,MySQL 会用password()函数生成 41 位哈希串; - 该哈希串会存储在
mysql.user表的password字段(MySQL 5.7 后改名为authentication_string); - MySQL 8.0 后,官方认为该算法不安全,废弃了
password(),改用caching_sha2_password作为默认认证插件。
4. 完整示例 + 结果解读
示例 1:基础示例(MySQL 5.7 环境)
select password('root');
输出 :*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
- 解读:
- 这是
root的password()加密结果,41 位,以*开头(MD5 是 32 位,无*); - 该结果会存储在
mysql.user表的authentication_string字段中。
- 这是
示例 2:MySQL 8.0 中调用password()(核心坑点)
select password('root');
输出 :ERROR 1305 (42000): FUNCTION password does not exist
- 解读:MySQL 8.0 彻底删除了
password()函数,调用直接报错,这是新手最容易踩的坑。
5. 典型使用场景(仅老版本)
场景 1:MySQL 5.7 中修改系统用户密码
-- 5.7中设置root密码
set password for 'root'@'localhost' = password('new_root_pwd');
- 解读:这是
password()唯一的合法场景(系统用户密码管理),绝对不能用于业务密码存储(比如用户表的密码)。
6. 避坑要点
坑 1:MySQL 8.0 中使用password()
新手在 8.0 环境中写password('123456'),直接报错,记住:8.0 及以后版本,password()函数已消失。
坑 2:用password()存储业务密码
比如把用户表的密码存为password('123456') ------ 这是错误的!password()是为 MySQL 系统用户设计的,不是为业务用户设计的,且算法不公开,安全性无法保证。
坑 3:混淆password()和md5()
| 函数 | 长度 | 前缀 | 适用场景 | 版本支持 |
|---|---|---|---|---|
password() |
41 位 | * | MySQL 系统用户密码(老版) | 5.7 及以前 |
md5() |
32 位 | 无 | 业务密码 / 数据校验 | 所有版本 |
7. 拓展补充(8.0 替代方案)
MySQL 8.0 中,修改系统用户密码的正确方式(替代password()):
-- 8.0中设置root密码
alter user 'root'@'localhost' identified by 'new_root_pwd';
-- 或
set password for 'root'@'localhost' = 'new_root_pwd'; -- 直接写明文,MySQL自动加密
- 解读:8.0 会自动用
caching_sha2_password算法加密密码,无需手动调用任何加密函数。
五、ifnull(val1, val2) 函数:给 NULL 值找 "替补"
1. 核心定义
ifnull() 是 MySQL 中处理NULL值的 "救星函数"------ 它会先检查第一个值(val1)是不是NULL:
- 如果
val1是NULL(未知 / 不存在),就返回第二个值(val2,替补值); - 如果
val1不是NULL,就返回val1本身;简单说:"NULL就用替补,非NULL就用自己"。
就相当于我们之前学习的三目运算符,result=val1==NULL?val2:val1;,这样子大家就很好理解了
2. 语法规则
sql
select ifnull(待判断值, 替补值);
- 核心要求:两个参数必须都传,缺一不可(传 1 个或 3 个都会报错);
- 参数类型:
val1和val2可以是任意类型(数值、字符串、字段、表达式),MySQL 会自动做隐式类型转换。
3. 底层运行逻辑
MySQL 中NULL是一个特殊值(表示 "未知""不存在"),不是 0,也不是空字符串('')。ifnull()的运行逻辑:
- 执行
val1 IS NULL判断(注意:不是val1 = NULL,NULL不能用=判断); - 如果判断结果为
TRUE(val1是NULL),返回val2; - 如果判断结果为
FALSE(val1非NULL),返回val1; - 整个过程是 "短路判断":只要确定
val1非NULL,就不会计算val2(提升性能)。
4. 完整示例 + 结果解读
示例 1:你提供的示例 1(val1 非 NULL)
select ifnull('abc', '123');
输出 :abc
- 解读:
val1是'abc'(非 NULL),所以直接返回val1,忽略替补值'123';- 这是最基础的场景:非 NULL 值直接返回。
示例 2:val1 是 NULL
select ifnull(null, '123');
输出 :123
- 解读:
val1是NULL,所以返回替补值'123';- 这是
ifnull()的核心场景:把 NULL 替换成友好的数值 / 字符串。
示例 3:实战核心 ------ 避免数值计算中出现 NULL
坑:NULL参与数值计算,结果一定是NULL:
-- 错误示例:英语成绩为NULL,总分变成NULL
select 90(语文) + 85(数学) + NULL(英语) as 总分; -- 输出:NULL
-- 正确示例:用ifnull把NULL转0
select 90 + 85 + ifnull(NULL, 0) as 总分; -- 输出:175
- 解读:这是
ifnull()最常用的场景 ------ 保证数值计算结果不为 NULL。
示例 4:实战核心 ------ 避免字符串拼接中出现 NULL
concat()函数中只要有一个参数是 NULL,结果就是 NULL,用ifnull()解决:
-- 错误示例:手机号为NULL,拼接结果为NULL
select concat('用户张三的手机号是:', NULL); -- 输出:NULL
-- 正确示例:把NULL替换成"未绑定"
select concat('用户张三的手机号是:', ifnull(NULL, '未绑定')); -- 输出:用户张三的手机号是:未绑定
示例 5:处理表字段中的 NULL 值(核心实战)
sql
-- 创建学生表(英语成绩可能为NULL)
create table student (
id int primary key auto_increment,
name varchar(20),
chinese decimal(5,1),
math decimal(5,1),
english decimal(5,1) -- 可能为NULL
);
-- 插入测试数据
insert into student (name, chinese, math, english) values
('张三', 90.5, 85.0, 95.0),
('李四', 80.0, 92.5, NULL); -- 李四英语缺考,为NULL
-- 计算总分(英语NULL转0)
select
name,
chinese + math + ifnull(english, 0) as 总分,
ifnull(english, '缺考') as 英语成绩 -- 展示时转成"缺考"
from student;
输出:
| name | 总分 | 英语成绩 |
|---|---|---|
| 张三 | 270.5 | 95.0 |
| 李四 | 172.5 | 缺考 |
- 解读:
- 李四的英语成绩是 NULL,用
ifnull(english, 0)转成 0,保证总分计算正确; - 展示时用
ifnull(english, '缺考')转成友好的文字,提升用户体验。
- 李四的英语成绩是 NULL,用
5. 典型使用场景
场景 1:数值计算(避免 NULL 导致结果异常)
比如计算订单总价、学生总分、商品库存,只要有字段可能为 NULL,就用ifnull()转成 0。
场景 2:数据展示(把 NULL 转成友好文字)
比如:
- 手机号 NULL → "未绑定";
- 地址 NULL → "未填写";
- 积分 NULL → "0 分"。
场景 3:字符串拼接(避免拼接结果为 NULL)
select concat('姓名:', ifnull(name, '匿名'), ',年龄:', ifnull(age, '未知')) as 用户信息;
输出:姓名:匿名,年龄:未知(如果 name 和 age 都是 NULL)。
6. 避坑要点
坑 1:参数顺序反了(最常见)
会写:ifnull('替补值', 待判断值) → 完全颠倒逻辑!正确顺序:ifnull(待判断值, 替补值)(先判断,再替补)。示例:
-- 错误:想把NULL转成123,却写反了
select ifnull('123', null); -- 输出:123(逻辑错误,哪怕val1非NULL,也返回val1)
-- 正确
select ifnull(null, '123'); -- 输出:123
坑 2:混淆NULL和空字符串('')
NULL是 "未知",空字符串是 "已知的空值",ifnull对空字符串无效:
select ifnull('', '123'); -- 输出:''(空字符串非NULL,所以返回自身)
- 解决:如果要同时处理
NULL和空字符串,用if(val1 is null or val1='', '替补值', val1)。
坑 3:val1 和 val2 类型不一致(隐式转换风险)
sql
select ifnull(100, '未填写'); -- 输出:100(数值转字符串,没问题)
select ifnull(null, '未填写'); -- 输出:未填写(字符串)
select ifnull(100 + null, '未填写'); -- 输出:未填写(100+NULL=NULL,转字符串)
-- 风险场景:数值转字符串后无法计算
select ifnull(null, '0') + 10; -- 错误!字符串'0'无法和10相加
-- 解决:保证val2和val1类型一致
select ifnull(null, 0) + 10; -- 输出:10(正确)
7. 拓展补充
拓展函数 1:coalesce()(多值替补,更灵活)
ifnull()只能处理 1 个待判断值,coalesce()可以处理多个值,返回第一个非 NULL 的值:
sql
-- 语法:coalesce(值1, 值2, 值3, ...)
select coalesce(null, null, '最终替补值'); -- 输出:最终替补值
-- 实战:优先显示手机号,没有则显示邮箱,都没有则显示"未绑定"
select name, coalesce(phone, email, '未绑定') as 联系方式 from user;
拓展函数 2:isnull()(仅判断是否为 NULL)
isnull(val) 等价于 val is null,返回 1(是 NULL)或 0(非 NULL):
sql
select isnull(null); -- 输出:1
select isnull('abc'); -- 输出:0
总结(核心要点回顾)
user():查 "谁在操作 MySQL + 登录地址",返回用户名@主机名,无参数,用于权限校验和日志记录;md5():给字符串生成 32 位不可逆 "指纹",核心用于业务密码存储(必须加盐),替代方案是更安全的sha2();database():查 "当前选中的数据库",无参数,返回库名或 NULL,用于备份脚本和动态 SQL;password():MySQL 老版本(5.7 及以前)的系统用户密码加密函数,8.0 已废弃,绝对不能用于业务场景;ifnull():处理 NULL 值的核心函数,规则是 "NULL 用替补,非 NULL 用自己",重点避坑参数顺序反、NULL 和空字符串混淆的问题,拓展用coalesce()处理多值替补。
实用的其他函数:
一、TRIM (str) 函数:字符串的 "首尾空格橡皮擦"
1. 核心定义
TRIM 函数是专门清理字符串首尾两端空白字符的工具(包括普通空格、制表符 \t、换行符 \n 等),但不会修改字符串中间的空格------ 就像用橡皮只擦字的开头和结尾的脏痕,中间的内容原封不动。
2. 语法规则
sql
-- 基础版(默认去除首尾空格)
TRIM(待处理字符串/字段名)
-- 进阶版(指定去除字符/位置)
TRIM([{BOTH | LEADING | TRAILING} [要去除的字符] FROM] 字符串)
BOTH:去除首尾(默认值,可省略);LEADING:只去除开头;TRAILING:只去除结尾;要去除的字符:默认是空格,也可指定其他字符(如逗号、星号)。
3. 底层运行逻辑
MySQL 会从字符串的开头 和结尾同时扫描,逐个匹配要去除的字符(默认空格),直到遇到非目标字符为止;中间的字符无论是否匹配,都保持原样。
4. 完整示例 + 结果解读
sql
-- 示例1:基础用法------去除首尾空格
SELECT TRIM(' 张三 李四 ') AS 处理结果;
-- 输出:张三 李四(首尾空格没了,中间两个空格保留)
-- 示例2:指定去除开头的空格(等价于LTRIM)
SELECT TRIM(LEADING FROM ' MySQL函数 ') AS 处理结果;
-- 输出:MySQL函数 (开头空格没了,结尾保留)
-- 示例3:指定去除首尾的非空格字符(比如逗号)
SELECT TRIM(BOTH ',' FROM ',,,订单123,,,') AS 处理结果;
-- 输出:订单123(首尾逗号全去除,中间无影响)
-- 示例4:实战------清洗用户表姓名的首尾空格
CREATE TABLE user (id INT, name VARCHAR(20));
INSERT INTO user VALUES (1, ' 王五 '), (2, '赵六\t'); -- 包含空格、制表符
SELECT id, TRIM(name) AS 清洗后姓名 FROM user;
-- 输出:1 | 王五;2 | 赵六(制表符也被去除)
5. 典型使用场景
- 数据清洗:用户注册时输入的姓名 / 地址大概率带首尾空格,用 TRIM 统一清洗;
- 字符串匹配 :避免 "张三" 和 "张三" 因空格导致
name = '张三'匹配失败; - 数据导入:从 Excel/CSV 导入的数据常带首尾空白,TRIM 是预处理必用函数。
6. 避坑要点
- 坑 1:误以为 TRIM 会去除中间空格 → 中间空格需用
REPLACE(str, ' ', '')全删除; - 坑 2:指定去除字符时语法错误 → 正确写法是
TRIM(BOTH ',' FROM str),而非TRIM(str, ','); - 坑 3:TRIM 对 NULL 无效 →
TRIM(NULL)返回 NULL,需先通过IFNULL处理:TRIM(IFNULL(name, ''))。
7. 拓展补充
TRIM 的进阶用法本质是整合了 LTRIM/RTRIM 的功能:
TRIM(LEADING FROM str)=LTRIM(str);TRIM(TRAILING FROM str)=RTRIM(str)。
二、LTRIM (str) / RTRIM (str) 函数:字符串的 "单边空格橡皮擦"
1. 大白话核心定义
- LTRIM(str) :只擦除字符串开头(左侧) 的空白字符,结尾和中间的保留;
- RTRIM(str) :只擦除字符串结尾(右侧) 的空白字符,开头和中间的保留;是 TRIM 函数的 "单边拆分版",精准处理某一侧的空格。
2. 语法规则
LTRIM(待处理字符串/字段名):去除左侧空格;RTRIM(待处理字符串/字段名):去除右侧空格。
3. 底层运行逻辑
- LTRIM:从字符串第一个字符开始扫描,逐个删除空格 / 空白字符,直到遇到非空白字符为止;
- RTRIM:从字符串最后一个字符倒序扫描,逐个删除空格 / 空白字符,直到遇到非空白字符为止。
4. 完整示例 + 结果解读
sql
-- 示例1:LTRIM去除左侧空格
SELECT LTRIM(' 手机号:13800138000 ') AS 处理结果;
-- 输出:手机号:13800138000 (左侧空格没了,右侧保留)
-- 示例2:RTRIM去除右侧空格
SELECT RTRIM(' 手机号:13800138000 ') AS 处理结果;
-- 输出: 手机号:13800138000(右侧空格没了,左侧保留)
-- 示例3:LTRIM+RTRIM等价于TRIM
SELECT LTRIM(RTRIM(' MySQL ')) AS 处理结果;
-- 输出:MySQL(和TRIM(' MySQL ')结果一致)
-- 示例4:实战------处理用户输入的左侧多余空格
CREATE TABLE feedback (id INT, content VARCHAR(100));
INSERT INTO feedback VALUES (1, ' 产品体验很好 '), (2, ' 价格偏高');
SELECT id, LTRIM(content) AS 清洗后内容 FROM feedback;
-- 输出:1|产品体验很好 ;2|价格偏高(只清左侧,保留右侧/中间)
5. 典型使用场景
- 单边空格清洗:用户输入时只在开头多打空格(如 "我要反馈"),用 LTRIM 精准清理;
- 数据导入适配:某些系统导出的数据只在右侧带空白符,用 RTRIM 统一处理;
- 格式保留:需要保留一侧空格的场景(如 "标题" 需保留左侧缩进,只清右侧空格)。
6. 避坑要点
-
坑 1:混淆 LTRIM/RTRIM 和 TRIM → 记住 "L = 左,R = 右,TRIM = 左右都清";
-
坑 2:无法去除非空格空白符 → LTRIM/RTRIM 只处理空格,制表符
\t需用 TRIM 或 REPLACE:sql
-- 去除左侧制表符+空格 SELECT TRIM(LEADING FROM REPLACE(str, '\t', ' ')) AS 处理结果;
7. 拓展补充
LTRIM/RTRIM 是 MySQL 早期的基础函数,功能可被 TRIM 的进阶用法替代,但单边处理时写 LTRIM/RTRIM 更直观。
三、IF (expr, val1, val2) 函数:MySQL 的 "二选一开关"
1. 核心定义
IF 函数是 MySQL 最基础的条件判断工具,等价于编程里的 "三元运算符(expr ? val1 : val2)"------ 先判断表达式 expr 是否成立(TRUE),成立返回 val1,不成立返回 val2,像个 "二选一" 的开关。
2. 语法规则
sql
IF(判断表达式, 满足条件返回值, 不满足条件返回值)
- 判断表达式 :能返回 TRUE/FALSE/NULL 的任意表达式(如
score >= 60、phone IS NULL、id = 10); - 返回值:可以是字符串、数值、字段、甚至嵌套函数,类型无需完全一致(MySQL 会隐式转换)。
3. 底层运行逻辑
- 先执行 "判断表达式",得到布尔结果(MySQL 中 TRUE=1,FALSE=0,NULL 为特殊值);
- 若结果为 TRUE,直接返回
val1(不计算val2,短路执行); - 若结果为 FALSE/NULL,返回
val2。
4. 完整示例 + 结果解读
sql
-- 示例1:基础条件判断(成绩是否及格)
SELECT IF(85 >= 60, '及格', '不及格') AS 成绩判定;
-- 输出:及格
-- 示例2:判断NULL值(手机号是否绑定)
SELECT IF(NULL IS NULL, '未绑定', '已绑定') AS 手机号状态;
-- 输出:未绑定
-- 示例3:实战------订单状态转换(0→未支付,1→已支付)
CREATE TABLE `order` (id INT, status TINYINT);
INSERT INTO `order` VALUES (1, 0), (2, 1), (3, 2);
SELECT id, IF(status=0, '未支付', IF(status=1, '已支付', '已取消')) AS 订单状态 FROM `order`;
-- 输出:1|未支付;2|已支付;3|已取消(嵌套IF实现多条件)
-- 示例4:结合数值计算(是否达标)
SELECT IF(sales > 10000, sales * 0.05, sales * 0.02) AS 提成 FROM product;
-- 销售额>1万提5%,否则提2%
5. 典型使用场景
- 状态转换:把数字状态(0/1/2)转成易读文字(未支付 / 已支付 / 已取消);
- 数值分级:成绩≥90→优秀,≥60→及格,否则→不及格;
- NULL 友好展示 :
IF(phone IS NULL, '未绑定', phone); - 业务逻辑分支:根据条件动态计算提成、折扣等。
6. 避坑要点
-
坑 1:表达式返回 NULL →
IF(NULL, 'A', 'B')会返回B(NULL 视为 FALSE); -
坑 2:返回值类型不一致 →
IF(1>0, 100, '无')会把 100 转成字符串,可能影响后续计算; -
坑 3:嵌套层数过多 → 超过 3 层建议用
CASE WHEN(更易读):sql-- 替代嵌套IF的CASE WHEN写法(更清晰) SELECT id, CASE status WHEN 0 THEN '未支付' WHEN 1 THEN '已支付' ELSE '已取消' END AS 订单状态 FROM `order`;
7. 拓展补充
IF 函数是 "极简条件判断",复杂场景(多分支、范围判断)优先用 CASE WHEN,两者功能等价但 CASE WHEN 可读性更高。
四、CASE WHEN 函数:MySQL 的 "多分支条件判断器"
1. 核心定义
CASE WHEN 是 MySQL 中功能更强大的多条件判断工具,等价于编程里的 if-else if-else 语句 ------ 可以设置多个条件分支,按顺序依次判断,满足某个条件就返回对应值,所有条件都不满足则返回 ELSE 里的默认值(若没写 ELSE 则返回 NULL),完美解决 IF 函数嵌套层数多、可读性差的问题。
2. 语法规则
CASE WHEN 分两种常用形式,核心语法都需用代码块体现:
形式 1:通用形式(适配任意条件判断,最常用)
sql
CASE
WHEN 条件表达式1 THEN 返回值1
WHEN 条件表达式2 THEN 返回值2
-- 可添加任意多个WHEN分支
[ELSE 默认返回值] -- 可选,无则不满足条件时返回NULL
END
形式 2:简化形式(仅适配单个字段的等值判断)
sql
CASE 待判断的字段/表达式
WHEN 等值1 THEN 返回值1
WHEN 等值2 THEN 返回值2
-- 可添加任意多个WHEN分支
[ELSE 默认返回值]
END
语法关键说明:
条件表达式:能返回 TRUE/FALSE/NULL 的任意表达式(如score >= 90、age BETWEEN 18 AND 30);返回值:类型不限(字符串、数值、函数调用均可),建议保持类型统一;ELSE:可选子句,无 ELSE 且所有条件不满足时,整体返回 NULL;END:必须写,用于结束 CASE WHEN 逻辑。
3. 底层运行逻辑
- 按从上到下的顺序依次校验每个 WHEN 后的条件;
- 一旦某个条件返回 TRUE,立即返回对应的 THEN 值,后续 WHEN 不再判断(短路执行);
- 若所有 WHEN 条件都不满足:
- 有 ELSE → 返回 ELSE 后的默认值;
- 无 ELSE → 返回 NULL。
4. 完整示例 + 结果解读
sql
-- 示例1:通用形式------成绩多等级划分(核心场景)
SELECT
student_id,
score,
CASE
WHEN score >= 90 THEN '优秀'
WHEN score >= 80 THEN '良好' -- 能到这步说明score<90,无需写score>=80 AND score<90
WHEN score >= 60 THEN '及格'
ELSE '不及格' -- 兜底,避免返回NULL
END AS 成绩等级
FROM student;
-- 输入score:95 → 优秀;85 → 良好;70 → 及格;55 → 不及格
-- 示例2:简化形式------订单状态编码转文字
SELECT
order_id,
status,
CASE status
WHEN 0 THEN '未支付'
WHEN 1 THEN '已支付'
WHEN 2 THEN '已取消'
ELSE '未知状态'
END AS 订单状态
FROM `order`;
-- 输入status:1 → 已支付;3 → 未知状态
-- 示例3:实战组合------结合TRIM+CASE WHEN判断地址地区
SELECT
user_id,
TRIM(address) AS 清洗后地址,
CASE
WHEN TRIM(address) LIKE '%北京%' THEN '北京市'
WHEN TRIM(address) LIKE '%上海%' THEN '上海市'
WHEN TRIM(address) LIKE '%广州%' OR TRIM(address) LIKE '%深圳%' THEN '珠三角'
ELSE '其他地区'
END AS 所属地区
FROM user;
-- 输入address:' 北京市朝阳区 ' → 清洗后为'北京市朝阳区',所属地区为'北京市'
5. 典型使用场景
- 多条件分级 / 分类:成绩分级、用户消费等级划分(如消费≥1 万 = VIP,≥5 千 = 高级会员);
- 编码转文字:把数字状态(0/1/2)、编码(1 = 男 / 2 = 女)转换成易读文字,提升查询结果可读性;
- 动态计算:根据不同条件计算数值(如不同订单金额区间对应不同折扣、提成);
- 替代多层 IF 嵌套:IF 嵌套超过 2 层时,CASE WHEN 可读性远更高(如 3 级及以上条件判断)。
6. 避坑要点
-
坑 1:条件顺序错误 → 范围条件需按 "从大到小 / 从小到大" 排列,否则逻辑失效:
-- 错误写法(先判断score>=60,90分也会被判定为及格) CASE WHEN score >= 60 THEN '及格' WHEN score >= 90 THEN '优秀' ELSE '不及格' END -- 正确:先判断高阈值(score>=90),再判断低阈值 -
坑 2:省略 ELSE 导致返回 NULL → 业务不允许 NULL 时,务必加 ELSE(如
ELSE '未知'); -
坑 3:简化形式误用非等值条件 → 简化形式仅支持 "字段 = 等值",不能写范围 / 模糊查询:
-- 错误:简化形式不能用>= CASE score WHEN >=90 THEN '优秀' ELSE '其他' END -- 正确:改用通用形式 -
坑 4:返回值类型不一致 → 虽 MySQL 允许,但可能导致后续计算异常,尽量保持类型统一(如都返回字符串)。
7. 拓展补充
-
CASE WHEN 可嵌套使用,但不建议超过 3 层(可读性下降):
sql-- 嵌套示例:先判断是否有成绩,再分级 CASE WHEN score IS NULL THEN '无成绩' ELSE CASE WHEN score >= 90 THEN '优秀' ELSE '其他' END END AS 成绩状态 -
CASE WHEN 可用于 SELECT、WHERE、ORDER BY、GROUP BY 等子句:
sql-- ORDER BY中使用:按成绩等级排序(优秀>良好>及格>不及格) SELECT * FROM student ORDER BY CASE WHEN score >=90 THEN 1 WHEN score >=80 THEN 2 WHEN score >=60 THEN 3 ELSE 4 END;
五、CAST (expr AS type) 函数:MySQL 的 "数据类型转换器"
1. 大白话核心定义
CAST 函数用于将一个表达式(数值、字符串、日期等)强制转换为指定的数据类型 ------ 就像把 "字符串形式的数字 123" 变成 "能计算的数值 123",把 "数值 20240120" 变成 "日期类型 2024-01-20",解决类型不匹配导致的计算 / 查询失败问题。
2. 语法规则
CAST(待转换的表达式 AS 目标数据类型)常用目标类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| CHAR(n) | 固定长度字符串(n 为长度) | CAST(123 AS CHAR(3)) |
| INT/INTEGER | 整数 | CAST('123' AS INT) |
| DECIMAL(m,n) | 小数(m 总长度,n 小数位) | CAST('123.45' AS DECIMAL(5,2)) |
| DATE | 日期类型(YYYY-MM-DD) | CAST('20240120' AS DATE) |
| DATETIME | 日期时间类型(YYYY-MM-DD HH:MM:SS) | CAST('202401201530' AS DATETIME) |
3. 底层运行逻辑
MySQL 会按照 "目标类型的格式规则" 解析原表达式:
- 若表达式能适配目标类型,转换为对应类型并返回;
- 若无法适配(如文本转整数),返回 NULL(严格模式下会报错);
- 数值转字符串时,直接按字符形式输出;字符串转数值时,只保留开头的数字部分(如
CAST('123abc' AS INT)返回 123)。
4. 完整示例 + 结果解读
sql
-- 示例1:字符串转整数(解决计算问题)
SELECT CAST('123' AS INT) + 10 AS 计算结果;
-- 输出:133(字符串转整数后可正常相加,否则会报错)
-- 示例2:数值转固定长度字符串(格式化展示)
SELECT CAST(123 AS CHAR(5)) AS 处理结果;
-- 输出: 123(补空格到5位,CHAR(n)是固定长度)
-- 示例3:字符串转日期(适配日期函数)
SELECT CAST('20240120' AS DATE) AS 日期结果;
-- 输出:2024-01-20(可直接用于DATEDIFF等日期函数)
-- 示例4:实战------FORMAT返回字符串转数值计算
SELECT FORMAT(1234.56, 2) AS 格式化结果; -- 输出:1,234.56(字符串类型)
SELECT CAST(REPLACE(FORMAT(1234.56, 2), ',', '') AS DECIMAL(10,2)) + 100 AS 计算结果;
-- 输出:1334.56(先删千分位,转小数后计算)
-- 示例5:小数转整数(截断,非四舍五入)
SELECT CAST(123.99 AS INT) AS 处理结果;
-- 输出:123(直接舍弃小数部分,区别于ROUND的四舍五入)
5. 典型使用场景
- 数值计算:把前端传入的字符串数字(如 '99.99')转成 DECIMAL 后计算订单金额;
- 日期处理:把用户输入的 "20240120" 转成 DATE 类型,用于日期范围查询;
- 函数返回值适配:FORMAT 函数返回字符串,需用 CAST 转数值才能计算;
- 数据导出:把 INT 类型的 ID 转成 CHAR (10),统一导出格式。
6. 避坑要点
-
坑 1:转换失败返回 NULL →
CAST('abc' AS INT)返回 NULL,需提前校验数据格式; -
坑 2:小数转整数是 "截断" 而非 "四舍五入"→ 如需四舍五入,先 ROUND 再 CAST:
SELECT CAST(ROUND(123.99, 0) AS INT) AS 结果; -- 输出:124 -
坑 3:DATE 类型转换格式要求 → 字符串需符合 YYYY-MM-DD/YYYYMMDD,否则返回 NULL:
SELECT CAST('2024/01/20' AS DATE); -- 输出:NULL(需先替换/为-)
7. 拓展补充
- CAST 和
CONVERT(expr, type)功能完全等价,CONVERT 是 ODBC 标准语法,CAST 是 SQL 标准语法,日常使用无区别; - 严格模式下转换失败会报错:执行
SET sql_mode = 'STRICT_ALL_TABLES';后,CAST('abc' AS INT)直接报错,而非返回 NULL。
总结(核心要点回顾)
- TRIM/LTRIM/RTRIM:TRIM 支持指定去除位置 / 字符,LTRIM/RTRIM 仅单边去空格;核心用于清洗字符串首尾空白,不处理中间空格;
- IF 函数 :语法
IF(表达式, 满足值, 不满足值),适用于简单二选一判断,复杂场景替换为 CASE WHEN 更易读; - CAST 函数 :语法
CAST(表达式 AS 类型),强制类型转换,小数转整数为截断逻辑,转换失败返回 NULL(严格模式报错); - CASE WHEN:分通用(任意条件)和简化(等值判断)两种语法,按从上到下短路执行,需注意条件顺序,建议添加 ELSE 避免 NULL。
MySQL中常用函数总结:
| 函数类别 | 函数名称 | 核心作用 | 语法格式 | 使用示例(含执行结果) |
|---|---|---|---|---|
| 【字符串函数】 | TRIM | 清理字符串首尾指定字符(默认空格),不处理中间 | TRIM({BOTH/LEADING/TRAILING} \[char FROM] str) | TRIM (' 张三 李四 ') → 张三 李四;TRIM (BOTH ',' FROM ',,, 订单 123,,,') → 订单 123 |
| LTRIM | 仅清理字符串左侧(开头)空白字符 | LTRIM(str) | LTRIM (' MySQL 函数 ') → MySQL 函数 | |
| RTRIM | 仅清理字符串右侧(结尾)空白字符 | RTRIM(str) | RTRIM (' MySQL 函数 ') → MySQL 函数 | |
| CONCAT | 拼接多个字符串 / 字段,任意参数为 NULL 则返回 NULL | CONCAT(str1, str2, ..., strn) | CONCAT (' 姓名:', ' 张三 ', ',年龄:', 25) → 姓名:张三,年龄:25 | |
| CONCAT_WS | 按指定分隔符拼接字符串,自动忽略 NULL(比 CONCAT 更实用) | CONCAT_WS (分隔符,str1, str2, ..., strn) | CONCAT_WS('-', '2024', '01', '20') → 2024-01-20;CONCAT_WS('-', 'a', NULL, 'b') → a-b | |
| SUBSTRING/SUBSTR | 截取字符串(从指定位置开始,取指定长度),位置从 1 开始 | SUBSTRING(str, start, length) | SUBSTRING ('MySQL 函数 ', 1, 5) → MySQL;SUBSTRING ('20240120', 5, 2) → 01 | |
| LEFT | 截取字符串左侧指定长度的字符 | LEFT(str, length) | LEFT ('MySQL 函数 ', 3) → Mys;LEFT ('13800138000', 3) → 138 | |
| RIGHT | 截取字符串右侧指定长度的字符 | RIGHT(str, length) | RIGHT ('MySQL 函数 ', 2) → 函数;RIGHT ('13800138000', 4) → 8000 | |
| REPLACE | 替换字符串中所有指定子串 | REPLACE (str, 旧子串,新子串) | REPLACE (' 张三 - 123-456', '-', '') → 张三 123456;REPLACE ('abcabc', 'a', 'A') → AbcAbc | |
| UPPER/UCASE | 将字符串全部转为大写 | UPPER(str) / UCASE(str) | UPPER('mysql') → MYSQL | |
| LOWER/LCASE | 将字符串全部转为小写 | LOWER(str) / LCASE(str) | LOWER('MYSQL') → mysql | |
| LENGTH | 返回字符串的字节长度(中文占 3 字节,英文 / 数字占 1 字节) | LENGTH(str) | LENGTH (' 张三 ') → 6;LENGTH ('123') → 3 | |
| CHAR_LENGTH | 返回字符串的字符个数(中文 / 英文 / 数字都算 1 个) | CHAR_LENGTH(str) | CHAR_LENGTH (' 张三 ') → 2;CHAR_LENGTH ('123') → 3 | |
| LPAD | 左侧填充指定字符至指定长度(长度不足时填充,超出则截断) | LPAD(str, length, padstr) | LPAD('123', 5, '0') → 00123;LPAD('123456', 4, '0') → 1234 | |
| RPAD | 右侧填充指定字符至指定长度(长度不足时填充,超出则截断) | RPAD(str, length, padstr) | RPAD('123', 5, '0') → 12300;RPAD('123456', 4, '0') → 1234 | |
| INSTR | 返回子串在字符串中首次出现的位置(无则返回 0,位置从 1 开始) | INSTR(str, substr) | INSTR ('MySQL 函数 ', ' 函数 ') → 6;INSTR ('abcabc', 'd') → 0 | |
| LOCATE | 同 INSTR,参数顺序相反(更易读) | LOCATE(substr, str, start) | LOCATE (' 函数 ', 'MySQL 函数 ') → 6;LOCATE ('a', 'abcabc', 2) → 4 | |
| REVERSE | 反转字符串 | REVERSE(str) | REVERSE ('12345') → 54321;REVERSE (' 张三 ') → 三张 | |
| SPACE | 返回指定个数的空格字符串 | SPACE(n) | SPACE (3) → ' ';CONCAT (' 姓名 ', SPACE (2), ' 张三 ') → 姓名 张三 | |
| SUBSTRING_INDEX | 按分隔符截取字符串(取第 n 个分隔符前 / 后的内容) | SUBSTRING_INDEX(str, delim, count) | SUBSTRING_INDEX('a,b,c,d', ',', 2) → a,b;SUBSTRING_INDEX('a,b,c,d', ',', -2) → c,d | |
| 【数值函数】 | ROUND | 按指定小数位四舍五入数值 | ROUND (num , 小数位数) | ROUND(123.456, 2) → 123.46;ROUND(123.9) → 124 |
| FLOOR | 向下取整(取不大于当前数的最大整数) | FLOOR(num) | FLOOR(123.99) → 123;FLOOR(-123.1) → -124 | |
| CEIL/CEILING | 向上取整(取不小于当前数的最小整数) | CEIL(num) / CEILING(num) | CEIL(123.01) → 124;CEIL(-123.9) → -123 | |
| ABS | 返回数值的绝对值 | ABS(num) | ABS(-123) → 123;ABS(0) → 0 | |
| MOD | 取两个数的余数(等价于 % 运算符) | MOD(num1, num2) | MOD(10, 3) → 1;MOD(10, 2) → 0 | |
| FORMAT | 按千分位格式化数值,返回字符串类型 | FORMAT (num, 小数位数) | FORMAT(123456.789, 2) → 123,456.79 | |
| TRUNCATE | 按指定小数位截断数值(仅保留指定位数,不四舍五入) | TRUNCATE (num, 小数位数) | TRUNCATE(123.456, 2) → 123.45;TRUNCATE(123.999, 0) → 123 | |
| POWER/POW | 计算数值的 n 次方 | POWER(num, n) / POW(num, n) | POWER(2, 3) → 8;POW(10, 2) → 100 | |
| SQRT | 计算数值的平方根(仅支持非负数) | SQRT(num) | SQRT(16) → 4;SQRT(2) → 1.41421356 | |
| RAND | 返回 0~1 之间的随机浮点数(指定种子可生成固定随机数) | RAND(seed) | RAND () → 0.857321(随机);RAND (10) → 固定值 0.6570515219653505 | |
| SIGN | 返回数值的符号(正数 = 1,负数 =-1,0=0) | SIGN(num) | SIGN(123) → 1;SIGN(-123) → -1;SIGN(0) → 0 | |
| PI | 返回圆周率 π(默认保留 6 位小数) | PI() | PI() → 3.141593 | |
| CEILING | 同 CEIL,向上取整 | CEILING(num) | CEILING(123.01) → 124 | |
| 【日期时间函数】 | NOW() | 返回当前系统的日期 + 时间(格式:YYYY-MM-DD HH:MM:SS) | NOW() | NOW () → 2024-01-20 15:30:20(随当前时间变化) |
| CURDATE() | 返回当前系统的日期(格式:YYYY-MM-DD) | CURDATE() | CURDATE() → 2024-01-20 | |
| CURTIME() | 返回当前系统的时间(格式:HH:MM:SS) | CURTIME() | CURTIME() → 15:30:20 | |
| DATEDIFF | 计算两个日期的差值(date1 - date2,单位:天) | DATEDIFF(date1, date2) | DATEDIFF ('2024-01-20', '2024-01-10') → 10;DATEDIFF (NOW (), '2024-01-01') → 间隔天数 | |
| TIMESTAMPDIFF | 计算两个日期时间的差值(指定单位:年 / 月 / 日 / 时 / 分 / 秒) | TIMESTAMPDIFF(unit, start, end) | TIMESTAMPDIFF (MONTH, '2024-01-20', '2024-03-20') → 2;TIMESTAMPDIFF (HOUR, NOW (), '2024-01-21 00:00:00') → 剩余小时数 | |
| DATE_ADD | 给日期添加指定时间间隔(可加年 / 月 / 日 / 时 / 分 / 秒) | DATE_ADD (date, INTERVAL 数值 单位) | DATE_ADD ('2024-01-20', INTERVAL 7 DAY) → 2024-01-27;DATE_ADD (NOW (), INTERVAL 1 HOUR) → 加 1 小时 | |
| DATE_SUB | 给日期减去指定时间间隔(与 DATE_ADD 相反) | DATE_SUB (date, INTERVAL 数值 单位) | DATE_SUB('2024-01-20', INTERVAL 1 MONTH) → 2023-12-20 | |
| YEAR/MONTH/DAY | 分别提取日期中的年 / 月 / 日 | YEAR(date) / MONTH(date) / DAY(date) | YEAR('2024-01-20') → 2024;MONTH(NOW()) → 1;DAY(CURDATE()) → 20 | |
| HOUR/MINUTE/SECOND | 分别提取时间中的时 / 分 / 秒 | HOUR(time) / MINUTE(time) / SECOND(time) | HOUR('15:30:20') → 15;MINUTE(NOW()) → 30;SECOND(CURTIME()) → 20 | |
| DATE_FORMAT | 按指定格式格式化日期 / 时间(常用:% Y-% m-% d % H:% i:% s) | DATE_FORMAT(date, format) | DATE_FORMAT (NOW (), '% Y 年 % m 月 % d 日 ') → 2024 年 01 月 20 日;DATE_FORMAT ('2024-01-20', '% Y% m% d') → 20240120 | |
| STR_TO_DATE | 将字符串按指定格式转为日期 / 时间类型(DATE_FORMAT 反向操作) | STR_TO_DATE(str, format) | STR_TO_DATE ('2024 年 01 月 20 日 ', '% Y 年 % m 月 % d 日 ') → 2024-01-20;STR_TO_DATE ('202401201530', '% Y% m% d% H% i') → 2024-01-20 15:30:00 | |
| CAST(日期转换) | 将字符串 / 数值转为日期类型 | CAST(expr AS DATE/DATETIME) | CAST('20240120' AS DATE) → 2024-01-20;CAST('202401201530' AS DATETIME) → 2024-01-20 15:30:00 | |
| SYSDATE() | 返回函数执行时的系统日期时间(区别 NOW ():NOW () 是语句开始时的时间) | SYSDATE() | SYSDATE () → 2024-01-20 15:35:10(随执行时间变化) | |
| LAST_DAY | 返回指定日期所在月份的最后一天 | LAST_DAY(date) | LAST_DAY ('2024-02-01') → 2024-02-29;LAST_DAY (NOW ()) → 当月最后一天 | |
| 【条件判断函数】 | IF | 二选一条件判断(等价于编程三元运算符) | IF (判断表达式,满足值,不满足值) | IF (85>=60, ' 及格 ', ' 不及格 ') → 及格;IF (phone IS NULL, ' 未绑定 ', ' 已绑定 ') → 对应结果 |
| CASE WHEN(通用) | 多分支条件判断(适配任意条件,替代多层 IF) | CASE WHEN 条件 1 THEN 值 1 WHEN 条件 2 THEN 值 2 ELSE 默认值 END | CASE WHEN score>=90 THEN ' 优秀 ' WHEN score>=60 THEN ' 及格 ' ELSE ' 不及格 ' END → 95→优秀,55→不及格 | |
| CASE WHEN(简化) | 多分支等值判断(仅适配单个字段的等值匹配) | CASE 字段 WHEN 等值 1 THEN 值 1 WHEN 等值 2 THEN 值 2 ELSE 默认值 END | CASE status WHEN 0 THEN ' 未支付 ' WHEN 1 THEN ' 已支付 ' ELSE ' 未知 ' END → 1→已支付,3→未知 | |
| IFNULL | 替换 NULL 值为指定默认值(仅处理 NULL,非 NULL 返回原值) | IFNULL (expr, 默认值) | IFNULL (phone, ' 未绑定 ') → 有手机号返回手机号,无则返回未绑定;IFNULL (NULL, 0) → 0 | |
| 【NULL 处理函数】 | ISNULL | 判断值是否为 NULL,是返回 1,否返回 0 | ISNULL(expr) | ISNULL (NULL) → 1;ISNULL (' 张三 ') → 0 |
| COALESCE | 返回参数列表中第一个非 NULL 值(比 IFNULL 更灵活,支持多参数) | COALESCE(expr1, expr2, ..., exprn) | COALESCE (NULL, ' 未知 ', ' 默认 ') → 未知;COALESCE (phone, email, ' 无联系方式 ') → 第一个非空值 | |
| NULLIF | 若两个参数相等返回 NULL,否则返回第一个参数 | NULLIF(expr1, expr2) | NULLIF (123, 123) → NULL;NULLIF (' 张三 ', ' 李四 ') → 张三 | |
| 【聚合函数】 | COUNT | 统计行数(COUNT (*) 统计所有行,COUNT (字段) 排除 NULL 值) | COUNT (*) / COUNT (字段) / COUNT (DISTINCT 字段) | COUNT (*) → 表总行数;COUNT (DISTINCT user_id) → 去重后的用户数 |
| SUM | 计算数值字段的总和(自动忽略 NULL 值) | SUM (数值字段) | SUM (score) → 所有成绩的总和;SUM (IF (status=1, amount, 0)) → 已支付订单金额总和 | |
| AVG | 计算数值字段的平均值(自动忽略 NULL 值) | AVG (数值字段) | AVG (score) → 成绩平均值;AVG (DISTINCT score) → 去重后成绩平均值 | |
| MAX/MIN | 分别返回数值 / 日期 / 字符串字段的最大值 / 最小值 | MAX (字段) / MIN (字段) | MAX (score) → 最高成绩;MIN (CURDATE ()) → 表中最早日期;MAX (' 张三 ') → 张三(字符串按编码排序) | |
| GROUP_CONCAT | 按分组拼接字符串(默认逗号分隔,可指定分隔符) | GROUP_CONCAT (DISTINCT 字段 ORDER BY 字段 SEPARATOR 分隔符) | GROUP_CONCAT (name SEPARATOR ';') → 张三;李四;王五;GROUP_CONCAT (DISTINCT score ORDER BY score DESC) → 95;85;70 | |
| STDDEV/STD | 计算数值字段的标准差 | STDDEV (数值字段) / STD (数值字段) | STDDEV (score) → 成绩标准差 | |
| VARIANCE | 计算数值字段的方差 | VARIANCE (数值字段) | VARIANCE (score) → 成绩方差 | |
| 【加密函数】 | MD5 | 对字符串进行 MD5 加密(返回 32 位十六进制字符串) | MD5(str) | MD5('123456') → e10adc3949ba59abbe56e057f20f883e |
| SHA1/SHA | 对字符串进行 SHA1 加密(返回 40 位十六进制字符串) | SHA1(str) / SHA(str) | SHA1('123456') → 7c4a8d09ca3762af61e59520943dc26494f8941b | |
| SHA2 | 对字符串进行 SHA2 加密(支持 224/256/384/512 位) | SHA2 (str, 位数) | SHA2('123456', 256) → 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 | |
| 【系统函数】 | USER()/CURRENT_USER() | 返回当前登录 MySQL 的用户(格式:用户名 @主机) | USER() / CURRENT_USER() | USER() → root@localhost |
| DATABASE() | 返回当前使用的数据库名 | DATABASE() | DATABASE () → test_db(当前选中的数据库) | |
| VERSION() | 返回 MySQL 服务器的版本号 | VERSION() | VERSION () → 8.0.36(MySQL 版本) | |
| CONNECTION_ID() | 返回当前数据库连接的 ID | CONNECTION_ID() | CONNECTION_ID () → 123(当前连接 ID) | |
| 【类型转换函数】 | CAST | 通用类型转换(支持字符串 / 数值 / 日期互转) | CAST (expr AS 目标类型) | CAST('123' AS INT) → 123;CAST(123.45 AS CHAR) → '123.45';CAST('2024-01-20' AS DATETIME) → 2024-01-20 00:00:00 |
| CONVERT | 同 CAST,语法格式不同(ODBC 标准) | CONVERT (expr, 目标类型) / CONVERT (expr USING 字符集) | CONVERT ('123', INT) → 123;CONVERT (' 张三 ' USING utf8mb4) → 张三(字符集转换) |
结语:让 MySQL 函数成为你开发路上的 "效率利刃"
当你耐心看完这份关于 MySQL 内置函数的全部内容时,我想先对你说一句:恭喜你,又为自己的数据库技能库添上了沉甸甸的一笔。我们从 "为什么要学函数" 的核心问题出发,走过了日期时间、字符串、数学函数的核心应用,也拆解了条件判断、类型转换、系统工具类函数的实用场景,最后还梳理了聚合、加密等拓展函数的用法 ------ 这些内容看似繁杂,实则都围绕着一个核心:让你摆脱 "重复造轮子" 的低效,用 MySQL 原生的 "最优解" 解决业务问题。
回头看,我们一开始就用 "计算日期差" 的例子点破了函数的价值:50 行手动逻辑不如 1 行datediff(),这不是 "偷懒",而是专业。MySQL 的内置函数,是无数数据库工程师沉淀了数十年的最佳实践 ------ 它们用底层 C 语言实现,经过了海量场景的验证,既规避了手动写逻辑时的边界漏洞(比如日期计算的闰年、字符串截取的下标越界),又能让你的 SQL 代码更简洁、更易读、更高效。你不必再为 "如何给手机号脱敏" 绞尽脑汁写多层截取,不必再为 "如何判断订单状态" 嵌套无数个 IF,不必再为 "如何计算用户注册天数" 手动拆解年月日 ------ 这些问题,函数都已经给出了标准答案。
我知道,初学这些函数时,你可能会觉得 "记不住":date_format的格式符、substring的起始位置、case when的条件顺序...... 这些细节确实容易混淆,但请你记住:学习 MySQL 函数,从来不是 "死记硬背",而是 "理解场景 + 刻意练习"。你不需要把所有函数的语法背得滚瓜烂熟,却一定要清楚 "什么场景该用什么函数":
当你需要处理时间相关的业务(订单过期、生日提醒、近 7 天数据筛选),第一时间想到now()、date_add、datediff;当你需要清洗用户输入的脏数据(姓名首尾空格、手机号脱敏、地址格式化),立刻调用trim、substring、replace;当你需要做条件判断(成绩分级、订单状态转换、NULL 值友好展示),熟练使用if、case when、ifnull;当你需要做数值计算(金额四舍五入、随机验证码、运费取整),随手写出round、rand、ceiling------ 这才是掌握函数的核心。
我见过很多初学 MySQL 的同学,总觉得 "写原生 SQL 就行,函数可有可无",直到遇到生产环境的问题才追悔莫及:比如手动计算日期差时漏掉了闰年,导致订单过期时间计算错误;比如拼接字符串时没处理 NULL 值,导致报表展示全是 NULL;比如用多层 IF 嵌套替代case when,让同事接手代码时一头雾水...... 这些问题,本质上都是对函数的价值认知不足。MySQL 函数不是 "锦上添花" 的炫技,而是 "雪中送炭" 的基础能力 ------ 它能帮你避开 90% 的手动逻辑漏洞,也能让你的 SQL 代码从 "能跑" 变成 "好用"。
当然,掌握函数也不是终点。真正的高手,是能把不同类别的函数组合起来解决复杂业务问题:比如用trim清洗姓名,用ifnull处理 NULL 值,用concat拼接用户标签,用case when划分用户等级 ------ 就像我们在实战案例中做的那样,把零散的函数变成解决问题的 "组合拳"。你要记住,函数是工具,工具的价值在于 "用":今天学了md5,就可以试着给用户表的密码字段做加密;学了date_format,就可以优化前端展示的时间格式;学了group_concat,就可以实现 "按用户分组拼接订单号" 的需求 ------ 只有在实战中反复使用,这些函数才会真正成为你自己的技能。
我也想提醒你,学习 MySQL 函数的过程中,"避坑" 和 "学习" 同样重要:比如datediff的参数顺序(结束在前、开始在后),substring的起始位置(从 1 开始而非 0),ifnull和空字符串的区别,password函数在 8.0 版本的废弃...... 这些细节看似微小,却是新手最容易踩的坑。我们在文中反复强调这些 "坑点",不是为了增加学习难度,而是希望你少走弯路 ------ 记住这些避坑要点,能让你在实际开发中节省大量排查问题的时间。
最后,我想对你说:数据库能力的提升,从来不是一蹴而就的。今天你掌握的这些函数,只是 MySQL 庞大功能体系中的一小部分,但却是最基础、最常用的核心。未来你还会接触到存储过程、触发器、窗口函数等更进阶的内容,但请相信,今天打下的 "函数基础",会成为你进阶路上最坚实的台阶。
不要害怕犯错,也不要畏惧复杂的业务场景。当你遇到 "不知道用什么函数" 的问题时,回头看看这份总结,或者打开 MySQL 的官方文档 ------ 优秀的工程师从来不是 "什么都记得",而是 "知道去哪里找答案"。把这些函数当成你的 "工具箱",当业务问题来临时,先想 "工具箱里有没有现成的工具",再考虑 "要不要手动造工具"。
愿你能把这些 MySQL 函数真正用起来,让它们帮你简化逻辑、提升效率、规避错误,让你的每一行 SQL 都既简洁又可靠。数据库的世界看似枯燥,却藏着无数提升开发效率的 "小技巧",而内置函数,就是其中最容易掌握、也最能立刻见效的那一个。
学习的路上,慢一点没关系,只要一直在往前走就好。当你某天用date_add算出了精准的订单过期时间,用case when清晰地划分了用户等级,用md5保障了用户数据的安全 ------ 你会发现,这些看似琐碎的函数,早已成为你解决业务问题的 "效率利刃"。而这,就是我们学习的意义:不是掌握多少知识点,而是能用这些知识点,从容地解决工作中遇到的每一个问题。
继续加油吧!MySQL 的世界还有更多精彩的内容等着你探索,而这些内置函数,会是你探索路上最忠实的伙伴。