以下对 MySQL 中这几类函数进行超详细拆解,从基础语法、原理到复杂场景应用,帮你吃透这些函数:
一、字符串函数(String Functions)
字符串函数用于处理文本数据,从简单拼接、截取到复杂正则操作都能覆盖,是处理业务文本(如用户姓名、地址、日志内容)的核心工具。
1. 拼接与拆分
-
CONCAT(str1, str2, ...)
-
作用:拼接多个字符串,任意参数为
NULL
则结果为NULL
。 -
深度用法:结合
COALESCE
处理NULL
,如CONCAT(COALESCE(name, '匿名'), '@', domain)
,避免拼接出NULL
。 -
示例:
sqlSELECT CONCAT('MySQL', ' ', '8.4') AS full_version; -- 结果:MySQL 8.4
-
-
CONCAT_WS(separator, str1, str2, ...)
-
作用:用
separator
拼接字符串,自动忽略NULL
参数,适合拼接带分隔符的场景(如拼接地址)。 -
底层逻辑:遍历参数,跳过
NULL
后用分隔符连接。 -
示例:
sqlSELECT CONCAT_WS(', ', '北京', NULL, '朝阳区') AS address; -- 结果:北京, 朝阳区
-
2. 长度与位置
-
LENGTH(str)
vsCHAR_LENGTH(str)
-
LENGTH
:返回字符串字节长度 ,受字符集影响(如UTF-8
中 1 个汉字占 3 字节)。 -
CHAR_LENGTH
:返回字符个数,与字符集无关(1 个汉字算 1 个字符)。 -
示例:
sqlSELECT LENGTH('Hello世界') AS byte_len, -- 结果:9(H e l l o + 世界(3*2)) CHAR_LENGTH('Hello世界') AS char_len; -- 结果:7
-
-
POSITION(substr IN str)
-
作用:返回子串
substr
在str
中首次出现的位置(从 1 开始计数),无匹配则返回 0。 -
对比
LOCATE(substr, str)
:语法不同(LOCATE
更灵活,支持pos
参数指定起始位置)。 -
示例:
sqlSELECT POSITION('SQL' IN 'MySQL8.4') AS pos; -- 结果:3(M y SQL...)
-
3. 截取与替换
-
SUBSTRING(str, pos, len)
(SUBSTR
是别名)-
语法细节:
pos
为起始位置 (1 开始),len
可选(默认截取到末尾);pos
为负数时从末尾倒数。 -
复杂场景:截取动态长度内容(如日志的前 100 字符)。
-
示例:
sqlSELECT SUBSTRING('2025-08-14 12:00:00', 1, 10) AS date_part; -- 结果:2025-08-14 SELECT SUBSTRING('HelloWorld', -5) AS last_five; -- 结果:World(从倒数第5位开始)
-
-
REPLACE(str, old_sub, new_sub)
-
深度用法:批量替换文本(如清洗脏数据中的非法字符)。
-
对比正则替换(
REGEXP_REPLACE
):REPLACE
是精确替换,正则支持模糊匹配(需 MySQL 8.0+)。 -
示例:
sqlSELECT REPLACE('MySQL5.7', '5.7', '8.4') AS updated; -- 结果:MySQL8.4
-
4. 格式化与转换
-
TRIM([direction [remstr] FROM] str)
-
细分用法:
TRIM(str)
:默认去除两端空格。TRIM(LEADING '0' FROM '00123')
:去除开头的0
。TRIM(TRAILING '.' FROM '123.')
:去除末尾的.
。
-
示例:
sqlSELECT TRIM(' MySQL ') AS trimmed; -- 结果:MySQL SELECT TRIM(LEADING 'x' FROM 'xxTestxx') AS leading_trim; -- 结果:Testxx
-
-
UPPER/LOWER(str)
(UCASE/LCASE
是别名)-
注意点:对非 ASCII 字符(如
ß
→SS
)的转换依赖字符集规则,部分字符可能无法完美转换。 -
业务场景:统一用户名格式(如存储时转小写)。
-
示例:
sqlSELECT UPPER('mysql') AS upper_case, LOWER('MySQL') AS lower_case; -- 结果:MYSQL, mysql
-
二、数值函数(Numeric Functions)
数值函数用于数学计算、统计分析,从简单加减到复杂金融计算(如利息、汇率)都离不开它们。
1. 基础运算
-
ABS(x)
:返回绝对值,支持整数、小数。- 示例:
SELECT ABS(-12.3) AS abs_val; -- 结果:12.3
- 示例:
-
MOD(x, y)
(%
是别名)-
细节:
MOD
返回与x
同符号的余数,且y
不能为 0(否则报错)。 -
对比
x % y
:功能等价,但MOD
是函数语法,%
是运算符。 -
示例:
sqlSELECT MOD(10, 3) AS mod_val, 10 % 3 AS mod_op; -- 结果:1, 1 SELECT MOD(-10, 3) AS neg_mod; -- 结果:-1(因-10 = (-4)*3 + 2?不,实际计算:-10 = (-3)*3 + (-1),所以余数-1)
-
2. 取整与舍入
-
CEILING(x)
/FLOOR(x)
-
CEILING
:返回不小于 x 的最小整数(向上取整)。 -
FLOOR
:返回不大于 x 的最大整数(向下取整)。 -
示例:
sqlSELECT CEILING(3.14) AS ceil_val, FLOOR(3.99) AS floor_val; -- 结果:4, 3 SELECT CEILING(-3.14) AS neg_ceil; -- 结果:-3(因-3 ≥ -3.14)
-
-
ROUND(x, d)
-
规则:
d
是小数位数(默认 0,即取整);d>0
时保留d
位小数,d<0
时对整数部分舍入(如ROUND(1234.56, -2)
→1200
)。 -
对比
TRUNCATE(x, d)
:ROUND
是四舍五入,TRUNCATE
是直接截断(如TRUNCATE(3.99, 1)
→3.9
)。 -
示例:
sqlSELECT ROUND(3.14159, 2) AS round_2, ROUND(1234.567, -2) AS round_neg; -- 结果:3.14, 1200
-
3. 幂与开方
-
POW(x, y)
/POWER(x, y)
:返回x
的y
次幂(y
为小数时计算开方,如POW(4, 0.5)
→2
)。- 示例:
SELECT POW(2, 3) AS pow_val; -- 结果:8
- 示例:
-
SQRT(x)
:返回x
的平方根(x≥0
,否则返回NULL
)。- 扩展:
SQRT
等价于POW(x, 0.5)
,但更直观。 - 示例:
SELECT SQRT(16) AS sqrt_val; -- 结果:4
- 扩展:
4. 随机数与进制转换
-
RAND([seed])
-
细节:
seed
为可选参数(整数),相同seed
生成相同随机序列;无seed
时每次结果不同。 -
业务场景:生成验证码、随机抽样。
-
示例:
sqlSELECT RAND() AS rand_val, RAND(123) AS fixed_rand; -- 固定seed=123时,每次执行fixed_rand结果相同
-
-
CONV(num, from_base, to_base)
-
功能:将
num
从from_base
进制转为to_base
进制(支持 2~36 进制,超过 36 需自定义映射)。 -
限制:
num
为字符串时,字符需符合from_base
规则(如二进制只能是0/1
)。 -
示例:
sqlSELECT CONV('1010', 2, 10) AS bin_to_dec; -- 结果:10(二进制1010→十进制10) SELECT CONV('A', 16, 2) AS hex_to_bin; -- 结果:1010(十六进制A→二进制1010)
-
三、日期函数(Date/Time Functions)
日期函数是处理时间数据的核心,涉及业务逻辑(如订单时效、报表统计)、索引优化(合理用DATE
类型)等。
1. 获取当前时间
-
NOW()
/SYSDATE()
-
区别:
NOW()
:执行 SQL 语句时开始的时间点 (语句内所有NOW()
值相同)。SYSDATE()
:返回实际执行到该函数时的时间(语句内多次调用可能不同)。
-
示例:
sqlSELECT NOW() AS now_time, SYSDATE() AS sys_time, SLEEP(2), -- 休眠2秒 NOW() AS now_time_after, SYSDATE() AS sys_time_after; -- now_time和now_time_after相同,sys_time和sys_time_after差约2秒
-
-
CURDATE()
/CURTIME()
-
简化版:
CURDATE()
返回YYYY-MM-DD
,CURTIME()
返回HH:MM:SS
(带秒精度)。 -
对比
DATE(NOW())
:功能等价,但CURDATE
更简洁。 -
示例:
sqlSELECT CURDATE() AS today, CURTIME(6) AS now_time; -- 结果:2025-08-14, 12:34:56.789012(假设当前时间)
-
2. 日期计算与差值
-
DATE_ADD(date, INTERVAL expr unit)
(ADDDATE
是别名)-
灵活用法:支持复杂时间运算(如
+1 YEAR 2 MONTHS 3 DAYS
)。 -
常用单位:
YEAR/MONTH/DAY/HOUR/MINUTE/SECOND
,还支持WEEK
(周)、QUARTER
(季度)等。 -
示例:
sqlSELECT DATE_ADD('2025-08-14', INTERVAL 1 DAY) AS tomorrow; -- 结果:2025-08-15 SELECT ADDDATE('2025-08-14', INTERVAL 30 MINUTE) AS later; -- 结果:2025-08-14 00:30:00(假设当前是0点)
-
-
DATEDIFF(date1, date2)
-
规则:返回
date1 - date2
的天数差(仅算日期部分,忽略时间)。 -
对比
TIMESTAMPDIFF(unit, start, end)
:支持更细粒度(如小时、分钟差),且结果是end - start
的差值。 -
示例:
sqlSELECT DATEDIFF('2025-08-20', '2025-08-14') AS days_diff; -- 结果:6 SELECT TIMESTAMPDIFF(HOUR, '2025-08-14 10:00', '2025-08-15 12:00') AS hour_diff; -- 结果:26(24+2)
-
3. 日期格式化
DATE_FORMAT(date, format)
-
格式符:
%Y
(4 位年)、%y
(2 位年)、%m
(月份 01-12)、%c
(月份 1-12)、%d
(日 01-31)。%H
(24 小时制 00-23)、%h
(12 小时制 01-12)、%i
(分钟 00-59)、%s
(秒 00-59)。
-
复杂场景:生成带时区的时间字符串(需结合
CONVERT_TZ
)。 -
示例:
sqlSELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s') AS formatted; -- 结果:2025-08-14 12:34:56(假设当前时间) SELECT DATE_FORMAT(NOW(), '%W, %M %e, %Y') AS verbose; -- 结果:Thursday, August 14, 2025(英文星期、月份)
-
4. 时间提取与转换
-
EXTRACT(unit FROM date)
-
功能:提取日期的部分字段(如年、月、日、小时等)。
-
对比
YEAR(date)
/MONTH(date)
:EXTRACT
更灵活,支持组合单位(如EXTRACT(YEAR_MONTH FROM date)
返回YYYYMM
格式)。 -
示例:
sqlSELECT EXTRACT(YEAR FROM '2025-08-14 12:00:00') AS year, EXTRACT(HOUR_MINUTE FROM '2025-08-14 12:34:56') AS hour_min; -- 结果:2025, 1234(小时+分钟,无分隔)
-
-
STR_TO_DATE(str, format)
-
反向操作:将字符串按
format
转为日期类型(需严格匹配格式,否则返回NULL
)。 -
业务场景:处理用户输入的非标准日期(如
'25/08/14'
转DATE
)。 -
示例:
sqlSELECT STR_TO_DATE('14-08-2025', '%d-%m-%Y') AS converted_date; -- 结果:2025-08-14(注意格式符要和字符串匹配)
-
四、流程函数(Control Flow Functions)
流程函数用于条件判断、分支逻辑,类似编程语言中的if-else
、switch-case
,是实现业务逻辑(如权限控制、数据分组)的关键。
1. 简单条件判断
IF(expr, true_val, false_val)
-
底层逻辑:
expr
为真(非NULL
、非 0)则返回true_val
,否则返回false_val
。 -
嵌套用法:
IF
可多层嵌套(但建议复杂逻辑用CASE
,更清晰)。 -
示例:
sqlSELECT score, IF(score >= 60, '及格', '不及格') AS pass_status FROM students; -- 根据score判断是否及格
-
2. 多分支条件(CASE
语句)- 搜索CASE
高级用法
CASE WHEN condition1 THEN result1 [WHEN condition2 THEN result2 ...] ELSE default END
(搜索CASE
)
-
灵活条件判断 :支持任意布尔条件(非
=
比较),如范围判断、NULL
检查。 -
性能与可读性 :复杂逻辑下,搜索
CASE
比嵌套IF
更易维护,且执行计划更清晰(MySQL 能优化条件顺序)。 -
示例:订单状态多条件处理
sqlSELECT order_status, CASE WHEN order_status = 0 THEN '未支付' WHEN order_status = 1 AND pay_time > NOW() - INTERVAL 1 HOUR THEN '支付中(1小时内)' WHEN order_status = 1 THEN '支付中(超时)' WHEN order_status = 2 AND refund_time IS NULL THEN '已完成(未退款)' ELSE '其他状态' END AS status_desc FROM orders;
这里通过
CASE
处理了状态值 + 时间范围 + 关联字段(refund_time
) 的多层逻辑,比嵌套IF
简洁数倍。
3. CASE
与聚合函数联动(分组条件统计)
核心场景:按动态条件分组统计,比如 "统计不同分数段的人数"。
-
示例:成绩分段统计
sqlSELECT CASE WHEN score >= 90 THEN 'A' WHEN score >= 80 THEN 'B' WHEN score >= 60 THEN 'C' ELSE 'D' END AS grade_level, COUNT(*) AS student_count FROM exam_scores GROUP BY grade_level;
原理:
CASE
先为每行数据打标签(A/B/C/D
),再通过GROUP BY
按标签聚合,实现动态分组统计。
4. IFNULL
/COALESCE
- 空值兜底逻辑
-
IFNULL(expr, replacement)
:若expr
为NULL
,返回replacement
;否则返回expr
。 -
COALESCE(expr1, expr2, ...)
:返回第一个非NULL
的参数 ,支持多参数(比IFNULL
更灵活)。 -
底层区别 :
IFNULL
是 MySQL 特有函数,仅支持 2 个参数。COALESCE
是 SQL 标准函数,支持任意多参数,且在跨数据库迁移时更兼容。
-
示例:用户昵称兜底
sqlSELECT -- 优先用nickname,无则用username,还无则用'匿名用户' COALESCE(nickname, username, '匿名用户') AS display_name FROM users;
5. NULLIF(expr1, expr2)
- 空值等价判断
-
作用 :若
expr1 = expr2
,返回NULL
;否则返回expr1
。常用于 "消除无效值" 场景(如订单金额为 0 时置为NULL
)。 -
示例:清洗无效金额
sqlSELECT order_id, NULLIF(total_amount, 0) AS valid_amount -- 金额为0时返回NULL FROM orders;
补充:前几类函数的 "底层原理 + 高级优化 + 异常场景"
一、字符串函数 - 底层与优化
-
字符集影响:
LENGTH
依赖字符集(如UTF-8
中CHAR_LENGTH('中文')=2
,但LENGTH('中文')=6
)。- 优化建议:若需 "字符个数",优先用
CHAR_LENGTH
;若需 "存储长度",用LENGTH
。
-
索引与字符串函数:
- 危险操作:
WHERE LOWER(username) = 'admin'
会破坏索引(函数包裹字段)。 - 优化方案:
- 存储时统一大小写(如入库转小写),用
WHERE username = 'admin'
。 - 或用函数索引 (MySQL 8.0 + 支持):
ALTER TABLE users ADD INDEX idx_username (LOWER(username));
- 存储时统一大小写(如入库转小写),用
- 危险操作:
-
超长字符串截断:
-
异常场景:
SUBSTRING(str, pos, len)
中pos
超过字符串长度,返回空字符串(而非报错)。 -
防御性写法:结合
CHAR_LENGTH
判断,如:sqlSELECT IF(CHAR_LENGTH(str) >= pos, SUBSTRING(str, pos, len), NULL) AS safe_substr;
-
二、数值函数 - 精度与性能
-
浮点型精度陷阱:
ROUND(0.1 + 0.2, 1)
结果是0.3
(符合预期),但ROUND(0.3 - 0.1, 1)
可能因二进制存储误差出现0.20000000000000001
。- 解决方案:对金融场景,用
DECIMAL
类型(精确存储)替代FLOAT/DOUBLE
,或用ROUND
+CAST
强制转换。
-
MOD
函数的负数逻辑:-
原理:
MOD(x, y)
公式为x - y * FLOOR(x / y)
,所以MOD(-10, 3)
结果是-1
(因-10 = (-4)*3 + 2
?不,实际计算:FLOOR(-10/3) = -4
,所以-10 - 3*(-4) = 2
?哦,这里之前的示例错误!正确计算:sqlSELECT MOD(-10, 3); -- 结果:2(因为 -10 = (-4)*3 + 2 → 余数2) SELECT MOD(10, -3); -- 结果:-2(10 = (-4)*(-3) + (-2) → 余数-2)
-
结论:
MOD
余数符号与x
无关,取决于y
的符号?不,实际是余数与x
同符号 (MySQL 官方文档定义:MOD(x, y)
返回x - y * FLOOR(x / y)
,所以符号由x
决定)。
-
三、日期函数 - 索引与时区
-
日期索引优化:
-
坏案例:
WHERE DATE(create_time) = '2025-08-14'
会使create_time
索引失效(函数包裹字段)。 -
好方案:
sql-- 改写为: WHERE create_time >= '2025-08-14 00:00:00' AND create_time < '2025-08-15 00:00:00'
利用左闭右开区间 ,让
create_time
索引直接生效。
-
-
时区问题:
-
NOW()
返回的是数据库服务器时区时间 (由system_time_zone
和time_zone
参数控制)。 -
跨时区业务:用
CONVERT_TZ
转换时区,如:sqlSELECT CONVERT_TZ(NOW(), 'SYSTEM', 'Asia/Shanghai') AS shanghai_time;
-
四、流程函数 - 执行顺序与短路逻辑
-
CASE
的短路特性:- MySQL 中,
CASE
一旦匹配到第一个WHEN
条件,会立即停止后续判断(短路执行)。 - 应用:优先判断 "高优先级条件"(如
WHEN order_status = -1 THEN '已删除'
放最前,避免无效计算)。
- MySQL 中,
-
IF
与CASE
的性能对比:- 简单条件(如
IF(score>60, '及格', '不及格')
):IF
略快(少一层语法解析)。 - 复杂条件(3 个分支以上):
CASE
更优(执行计划更清晰,且短路逻辑更彻底)。
- 简单条件(如
实战案例
题目:电商订单数据统计分析
假设你有一个电商订单表orders
,结构如下:
sql
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
user_name VARCHAR(50) NOT NULL,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
quantity INT NOT NULL,
create_time DATETIME NOT NULL,
pay_time DATETIME NULL,
status TINYINT NOT NULL COMMENT '0-未支付,1-已支付,2-已取消'
);
请编写一条 SQL 语句,实现以下分析需求:
- 计算每个订单的总金额(单价 × 数量),并四舍五入保留 2 位小数
- 提取订单创建日期(格式:YYYY 年 MM 月 DD 日)
- 判断订单支付状态文本描述(已支付 / 未支付 / 已取消)
- 计算订单从创建到支付的时长(分钟),未支付订单显示 "未支付"
- 生成用户订单标识(格式:用户 ID_用户名_订单 ID)
- 按订单创建日期分组,统计每天的总订单数、总销售金额
代码示例:
sql
-- 电商订单数据统计分析解决方案
SELECT
-- 5. 生成用户订单标识(字符串函数:CONCAT)
CONCAT(user_id, '_', user_name, '_', order_id) AS user_order_id,
-- 2. 提取订单创建日期(日期函数:DATE_FORMAT)
DATE_FORMAT(create_time, '%Y年%m月%d日') AS create_date,
-- 3. 判断订单支付状态文本描述(流程函数:CASE)
CASE
WHEN status = 1 THEN '已支付'
WHEN status = 2 THEN '已取消'
ELSE '未支付'
END AS status_desc,
-- 1. 计算订单总金额并四舍五入(数值函数:ROUND)
ROUND(price * quantity, 2) AS total_amount,
-- 4. 计算支付时长(日期函数:TIMESTAMPDIFF + 流程函数:IFNULL)
IFNULL(TIMESTAMPDIFF(MINUTE, create_time, pay_time), '未支付') AS pay_duration_minutes,
-- 6. 按日期分组统计(聚合函数)
COUNT(order_id) OVER (PARTITION BY DATE(create_time)) AS daily_order_count,
SUM(ROUND(price * quantity, 2)) OVER (PARTITION BY DATE(create_time)) AS daily_total_sales
FROM orders
ORDER BY create_time;
-- 补充说明:
-- 1. 使用CONCAT函数拼接用户ID、用户名和订单ID,生成唯一标识
-- 2. 利用DATE_FORMAT将日期转换为中文格式
-- 3. 通过CASE语句将数字状态转换为可读性更高的文本
-- 4. 结合TIMESTAMPDIFF计算时间差,并用IFNULL处理未支付订单的NULL值
-- 5. 使用窗口函数(OVER + PARTITION BY)实现按日期分组统计
-- 6. ROUND函数确保金额计算结果保留2位小数,符合财务数据要求
这个案例展示了各类 MySQL 函数在实际业务场景中的协同应用,通过组合使用不同类型的函数,可以高效地完成复杂的数据处理和分析任务。