SQL详细语法教程(三)mysql的函数知识
以下对 MySQL 中这几类函数进行超详细拆解,从基础语法、原理到复杂场景应用,帮你吃透这些函数:
一、字符串函数(String Functions)
字符串函数用于处理文本数据,从简单拼接、截取到复杂正则操作都能覆盖,是处理业务文本(如用户姓名、地址、日志内容)的核心工具。
1. 拼接与拆分
CONCAT(str1, str2, ...)
- 作用:拼接多个字符串,任意参数为
NULL
则结果为NULL
。 - 深度用法:结合
COALESCE
处理NULL
,如CONCAT(COALESCE(name, '匿名'), '@', domain)
,避免拼接出NULL
。 - 示例:
SELECT CONCAT('MySQL', ' ', '8.4') AS full_version; -- 结果:MySQL 8.4
- 作用:拼接多个字符串,任意参数为
CONCAT_WS(separator, str1, str2, ...)
- 作用:用
separator
拼接字符串,自动忽略NULL
参数,适合拼接带分隔符的场景(如拼接地址)。 - 底层逻辑:遍历参数,跳过
NULL
后用分隔符连接。 - 示例:
SELECT CONCAT_WS(', ', '北京', NULL, '朝阳区') AS address; -- 结果:北京, 朝阳区
- 作用:用
2. 长度与位置
LENGTH(str)
vsCHAR_LENGTH(str)
LENGTH
:返回字符串字节长度,受字符集影响(如UTF-8
中 1 个汉字占 3 字节)。CHAR_LENGTH
:返回字符个数,与字符集无关(1 个汉字算 1 个字符)。- 示例:
SELECT 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
参数指定起始位置)。 - 示例:
SELECT POSITION('SQL' IN 'MySQL8.4') AS pos; -- 结果:3(M y SQL...)
- 作用:返回子串
3. 截取与替换
SUBSTRING(str, pos, len)
(SUBSTR
是别名)- 语法细节:
pos
为起始位置(1 开始),len
可选(默认截取到末尾);pos
为负数时从末尾倒数。 - 复杂场景:截取动态长度内容(如日志的前 100 字符)。
- 示例:
SELECT 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+)。 - 示例:
SELECT 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.')
:去除末尾的.
。
- 示例:
SELECT TRIM(' MySQL ') AS trimmed; -- 结果:MySQL SELECT TRIM(LEADING 'x' FROM 'xxTestxx') AS leading_trim; -- 结果:Testxx
- 细分用法:
UPPER/LOWER(str)
(UCASE/LCASE
是别名)- 注意点:对非 ASCII 字符(如
ß
→SS
)的转换依赖字符集规则,部分字符可能无法完美转换。 - 业务场景:统一用户名格式(如存储时转小写)。
- 示例:
SELECT UPPER('mysql') AS upper_case, LOWER('MySQL') AS lower_case; -- 结果:MYSQL, mysql
- 注意点:对非 ASCII 字符(如
二、数值函数(Numeric Functions)
数值函数用于数学计算、统计分析,从简单加减到复杂金融计算(如利息、汇率)都离不开它们。
1. 基础运算
ABS(x)
:返回绝对值,支持整数、小数。- 示例:
SELECT ABS(-12.3) AS abs_val; -- 结果:12.3
- 示例:
MOD(x, y)
(%
是别名)- 细节:
MOD
返回与x
同符号的余数,且y
不能为 0(否则报错)。 - 对比
x % y
:功能等价,但MOD
是函数语法,%
是运算符。 - 示例:
SELECT 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 的最大整数(向下取整)。- 示例:
SELECT 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
)。 - 示例:
SELECT 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
时每次结果不同。 - 业务场景:生成验证码、随机抽样。
- 示例:
SELECT 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
)。 - 示例:
SELECT 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()
:返回实际执行到该函数时的时间(语句内多次调用可能不同)。
- 示例:
SELECT 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
更简洁。 - 示例:
SELECT 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
(季度)等。 - 示例:
SELECT 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
的差值。 - 示例:
SELECT 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
)。 - 示例:
SELECT 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
格式)。 - 示例:
SELECT 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
)。 - 示例:
SELECT 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
,更清晰)。 - 示例:
SELECT 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 能优化条件顺序)。 - 示例:订单状态多条件处理
SELECT 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
与聚合函数联动(分组条件统计)
核心场景:按动态条件分组统计,比如 “统计不同分数段的人数”。
- 示例:成绩分段统计
SELECTCASE 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 标准函数,支持任意多参数,且在跨数据库迁移时更兼容。
- 示例:用户昵称兜底
SELECT -- 优先用nickname,无则用username,还无则用'匿名用户'COALESCE(nickname, username, '匿名用户') AS display_name FROM users;
5. NULLIF(expr1, expr2)
- 空值等价判断
- 作用:若
expr1 = expr2
,返回NULL
;否则返回expr1
。常用于 “消除无效值” 场景(如订单金额为 0 时置为NULL
)。 - 示例:清洗无效金额
SELECT 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
判断,如:SELECT 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
?哦,这里之前的示例错误!正确计算:SELECT 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
索引失效(函数包裹字段)。 - 好方案:
-- 改写为: 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
转换时区,如:SELECT 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
,结构如下:
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)
- 按订单创建日期分组,统计每天的总订单数、总销售金额
代码示例:
-- 电商订单数据统计分析解决方案
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 函数在实际业务场景中的协同应用,通过组合使用不同类型的函数,可以高效地完成复杂的数据处理和分析任务。