MySQL函数全攻略:字符串、日期、聚合与条件控制实战指南

📅 发布时间:2026/10/20 14:54:04
MySQL函数全攻略:字符串、日期、聚合与条件控制实战指南
1. 开局先讲点实在的“【MySQL】第九节—函数”看到这个标题点进来多半是已经在照着教程敲到第九节的同学要么就是项目里写SQL写到怀疑人生回来补基础的开发者。不管是哪种这篇内容能帮你把MySQL里最常用的函数系统捋一遍省得每次用到都要现查手册、翻博客效率太低。函数这东西平时不觉得它重要真正写业务的时候离不开。字符串拆了拼、拼了拆日期算来算去金额四舍五入统计报表按月份分组汇总条件判断换个写法让SQL短一半——全是函数的活。更重要的是同样的效果用不用函数、用哪个函数代码的清晰度和执行效率差别很大。这篇就把常碰到的函数分门别类拆开讲每个都配案例直接复制改参数就能用。适合谁看零基础跟着教程学的可以当系统讲义读已经写过一阵子SQL的也能查漏补缺尤其是那些“我记得有这个函数但怎么拼来着”的情况这篇就是干这个用的。内容我尽量用大白话讲保留实操细节不急的话收藏了慢慢看急的话直接搜你要的函数名。2. MySQL函数到底是个啥2.1 函数在SQL里的位置MySQL函数说白了就是MySQL内置好的一批加工数据的“工具”。你给它输入一个或几个值它按固定规则处理完返回一个结果。SQL里有表达式的地方基本就能用函数。比如查一张员工表SELECT emp_name, UPPER(emp_name) AS name_up FROM employees;这里UPPER()就是把字符串转大写的函数输入的emp_name是原值返回的是处理后的值。它不会改表里的数据只是影响查询结果。函数大多用在SELECT后面做输出处理也能用在WHERE条件里做筛选还能用在ORDER BY里做排序规则一个函数用在哪、怎么用效果完全不同这点后面会结合案例讲。2.2 按用途分几大类MySQL的函数按干的事不同一般分成这几类类别干什么用典型函数举例字符串函数处理文本拼接、截取、替换、格式化CONCAT、SUBSTRING、REPLACE、LENGTH数值函数数学计算、四舍五入、取整、绝对值ROUND、FLOOR、CEILING、ABS、MOD日期时间函数取当前时间、格式化日期、加减日期NOW、DATE_FORMAT、DATE_ADD、DATEDIFF条件控制函数写条件逻辑类似代码里的 if elseIF、IFNULL、CASE WHEN聚合函数对一组行做统计汇总COUNT、SUM、AVG、MAX、MIN加密/系统函数哈希、用户、版本等辅助信息MD5、USER、DATABASE、VERSION这篇把前五类展开讲透最后补一点加密函数和系统函数的实用场景。每一类我都按“常用函数逐个讲 案例组合用”的方式来保证看完就能往自己SQL里套。3. 字符串函数SQL里最操心的部分字符串函数是日常写SQL时接触最多的一类因为业务数据里文本占大头。用户姓名、地址、商品名称、订单备注全是字符串处理起来花样也多。下面逐个介绍高频函数。3.1 拼接CONCAT 和 CONCAT_WSCONCAT把多个字符串接在一起注意 MySQL 里如果任何一个参数是NULL整个结果就是NULL这坑不少人都踩过。比如查客户表要拼“城市姓名”SELECT CONCAT(city, -, customer_name) AS location_customer FROM customers;如果有客户的城市是NULL那这条结果整个变成NULL后续程序里拿去显示就很奇怪。解决办法可以用CONCAT_WS它的写法是CONCAT_WS(分隔符, 字符串1, 字符串2, ...)好处是它会忽略NULL值只把非空的用分隔符拼起来SELECT CONCAT_WS(-, city, customer_name) AS location_customer FROM customers;city 为空时结果会直接变成 customer_name而不是整条变NULL。用哪个看业务对空值的容忍度但凡允许空值存在CONCAT_WS稳妥得多。3.2 截取和长度SUBSTRING、LEFT、RIGHT、LENGTHSUBSTRING(字符串, 从第几位开始, 截几位)是最常用的截取函数。注意 MySQL的位数从 1 开始数不是从 0。比如从订单编号ORD20250121里取出日期部分的“20250121”SELECT order_no, SUBSTRING(order_no, 4, 8) AS order_date_part FROM orders;LEFT(str, n)取左边 n 个字符RIGHT(str, n)取右边 n 个字符适合固定格式的前缀后缀提取。比如取手机号后四位SELECT phone, RIGHT(phone, 4) AS phone_tail FROM users;LENGTH()返回的是字节数不是字符数。这点特别容易踩坑因为一个中文在 UTF-8 编码下占 3 个字节LENGTH(你好)返回 6不是 2。想要字符串的字符个数要用CHAR_LENGTH()SELECT LENGTH(你好) AS byte_len, CHAR_LENGTH(你好) AS char_len;结果能清楚看到一个6、一个2。凡是做用户名长度校验、输入内容个数限制的记得用CHAR_LENGTH不然中文和英文判定对不上用户体验很别扭。3.3 替换和大小写REPLACE、UPPER、LOWERREPLACE(str, 旧串, 新串)直接做字符串替换常用于清洗脏数据、屏蔽敏感词。例如商品描述里统一把“包邮”改成“运费0元”SELECT product_name, REPLACE(product_desc, 包邮, 运费0元) AS new_desc FROM products;UPPER和LOWER用来转换大小写做不区分大小写的比较时两个都能配合用。比如邮箱登录场景下判断用户输入的邮箱和库里的一致时SELECT * FROM users WHERE UPPER(email) UPPER(TestUserExample.COM);两边都转成大写再比较避免用户输入时大小写不一致导致匹配不上。不过这属于一种牺牲索引效率的写法数据量大的时候得另行考虑这个后面在“常见问题”里再展开。3.4 去空格TRIM、LTRIM、RTRIM文本数据从外部导入时经常带着意外的空格有的是开头、有的是结尾。TRIM()去掉两侧空格LTRIM()只去左边RTRIM()只去右边。做精确匹配前批量清一遍很有必要SELECT TRIM(customer_name) AS cleaned_name FROM customers;还有一个细节TRIM不只是去空格它还能指定要去掉的字符比如去掉字符串两侧的#SELECT TRIM(BOTH # FROM ##AB#C##) AS cleaned; -- 结果是 AB#C这个写法日常用得少但遇到某批数据被统一用某个符号包裹、需要批量清洗时一条SQL就搞定了效率比拉出来在程序里处理高很多。3.5 字符串函数组合实战单个函数用起来简单真正考验人的是组合。比如用户表里存了证件号想打码显示中间四位为****可以这样写SELECT id_number, CONCAT( LEFT(id_number, 3), ****, RIGHT(id_number, 4) ) AS masked_id FROM users;先用LEFT拿前3位再用RIGHT拿后4位CONCAT中间补上****属于经典组合。要是证件号长度不固定就不能用固定的3和4得配合函数先算长度再动态取中间部分SELECT CONCAT( LEFT(id_number, 3), ****, SUBSTRING(id_number, CHAR_LENGTH(id_number) - 3, 4) ) AS masked_id FROM users;这里用CHAR_LENGTH先算总长度再通过SUBSTRING定位到倒数第4位开始往后截4位让代码能兼容不同长度的数据这在真实业务中挺常见。4. 数值函数和日期时间函数算数和时间都靠它4.1 数值函数逐个说数值函数处理跟数字打交道的场景最常见的四个是ROUND、CEILING、FLOOR和MOD。ROUND(数字, 保留小数位数)做四舍五入比如价格接口算完折扣后统一保留两位SELECT original_price, ROUND(original_price * 0.8, 2) AS discounted_price FROM products;CEILING向上取整就是只要有小数部分就进一位比如算分页的总页数经常用它。假设每页显示10条总数53条SELECT CEILING(53 / 10) AS total_pages; -- 结果是 6FLOOR向下取整抛弃小数部分适合算已经满了的整批数。MOD(a, b)返回 a 除以 b 的余数最典型的用途是做数据分片或者奇偶行判断SELECT user_id FROM users WHERE MOD(user_id, 2) 0;这条SQL能把偶数user_id的用户筛出来简单但实用。另外两个也值得认识ABS取绝对值POWER(x, y)求 x 的 y 次方。数值计算和报表统计中偶尔用得到。4.2 日期时间函数是大头日期时间函数可以说是实际开发里最绕不开的因为几乎每张业务表都有时间字段。先从最常用的几个入手。NOW()返回当前的日期和时间CURDATE()只返回日期CURTIME()只返回时间。在插入订单的时候记录当前时间直接INSERT INTO orders (order_no, created_at) VALUES (ORD20250201, NOW());DATE_FORMAT()是格式化日期的核心能把日期按任意格式输出。比如把标准日期转成“2025年02月01日”这样的中文格式SELECT DATE_FORMAT(NOW(), %Y年%m月%d日) AS formatted_date;常用的几个格式化符号记一下%Y四位年份、%y两位年份、%m两位月份、%d两位日期、%H24小时制、%i分钟、%s秒。数据导出来做报表时DATE_FORMAT能直接在后端SQL里把格式做好省得程序里再转一遍。DATE_ADD(日期, INTERVAL 数值 单位)和DATE_SUB做日期的加减单位可以是 DAY、MONTH、YEAR、HOUR 等。比如算30天之前的日期SELECT DATE_SUB(CURDATE(), INTERVAL 30 DAY) AS date_30_days_ago;会员到期日前一天发提醒这种场景也可以直接写进SQLSELECT user_id, expire_date FROM vip_users WHERE expire_date DATE_ADD(CURDATE(), INTERVAL 1 DAY);DATEDIFF(日期1, 日期2)返回两个日期相差的天数适合算剩余天数、工龄、账龄SELECT DATEDIFF(2025-06-01, 2025-01-15) AS days_diff;如果你要算的是实名认证用户里距离生日还有多少天或者计算读者借书到期日还剩几天DATEDIFF就是干这个的。4.3 日期函数的一个性能提醒在WHERE里对日期字段直接套函数存在索引失效的风险。比如SELECT * FROM orders WHERE DATE_FORMAT(created_at, %Y-%m-%d) 2025-01-20;这句逻辑没错但 MySQL 没法直接用created_at的索引因为每一行的数据都先被函数转换了一遍才能比。比较常见的优化写法是改成范围查询SELECT * FROM orders WHERE created_at 2025-01-20 00:00:00 AND created_at 2025-01-21 00:00:00;范围查询能直接走索引数据量一大差距非常明显。记住这个原则能不加函数就不加函数实在要按日期查优先用范围条件。4.4 数值和日期函数组合案例需求查每笔订单的结算金额结算规则是“金额大于100元部分打9折不足100元不参与折扣”订单表里记录了下单时间要输出“年-月-日”格式的日期。SELECT order_id, order_amount, ROUND( IF(order_amount 100, order_amount * 0.9, order_amount), 2 ) AS settle_amount, DATE_FORMAT(created_at, %Y-%m-%d) AS order_date FROM orders;这里把后面要讲的IF函数也提前用上了。你看一个查询里数值函数、日期函数、条件函数是混着用的不用割裂开记组合才能解决真实问题。5. 条件控制函数SQL里也能写 if else5.1 IF 和 IFNULL 的用法IF(条件, 值1, 值2)结构简单条件成立返回值1不成立返回值2。比如统计订单时把支付状态转成中文SELECT order_id, IF(pay_status 1, 已支付, 未支付) AS pay_status_text FROM orders;这个写法在报表输出、后端拼接口字段时很常用。IFNULL(值1, 值2)专门处理NULL值如果值1是NULL返回值2否则返回值1。比如客户地址字段可能为空展示时想给个默认值“暂无”SELECT customer_name, IFNULL(address, 暂无) AS address_text FROM customers;NULL和空字符串不是一回事IFNULL只处理数据库里的NULL。如果字段本身存的是它就原样返回。这点在判断是否要展示“暂无”时需要留意。5.2 CASE WHEN 才是真正的多分支IF适合二选一多个条件判断时就该用CASE WHEN了。它的基本结构是CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 ... ELSE 兜底结果 END。比如根据订单金额把订单分为大、中、小单SELECT order_id, order_amount, CASE WHEN order_amount 10000 THEN 大单 WHEN order_amount 1000 THEN 中单 ELSE 小单 END AS order_level FROM orders;注意CASE WHEN的条件是从上到下挨个判断的只要匹配到一个就停止不再往后判断。所以写条件的时候范围大的、数值高的要放在前面否则会被后面挡住永远匹配不到。上面这个例子里 10000必须放在 1000前面不然大单全被“中单”截走了。CASE WHEN还有个很有用的场景就是做行转列。比如一张学生成绩表有“姓名、科目、分数”三列想变成“姓名、语文成绩、数学成绩、英语成绩”这种横向展示SELECT student_name, MAX(CASE WHEN subject 语文 THEN score END) AS chinese_score, MAX(CASE WHEN subject 数学 THEN score END) AS math_score, MAX(CASE WHEN subject 英语 THEN score END) AS english_score FROM student_scores GROUP BY student_name;这里是CASE WHEN 聚合函数 GROUP BY的组合能把竖表变横表报表系统里经常用到这个套路。理解它的核心就是分组后每个学生一行MAX只是用来在组内把符合条件的分数取出来条件之外的行的CASE结果是NULL不影响结果。5.3 条件函数在统计中的扩展用法条件控制函数不只是用来显示还能在聚合统计里当过滤器。统计订单时只算特定类型SELECT pay_channel, COUNT(*) AS total_count, SUM(IF(pay_status 1, order_amount, 0)) AS paid_amount FROM orders GROUP BY pay_channel;这种方式叫“条件聚合”不用WHERE先过滤而是直接在聚合过程中按条件取数。好处是可以在同一次查询里同时拿到总额和已支付额比先查一遍总数、再查一遍已支付数要高效得多。写报表时这个写法利用率非常高。6. 聚合函数报表统计的主力军6.1 五种聚合函数是基本功COUNT()、SUM()、AVG()、MAX()、MIN()这五个聚合函数是做统计的基础。它们的特点都是对一组行进行运算返回单个结果。COUNT(*)统计行数COUNT(列名)统计该列非空值的个数SELECT COUNT(*) AS total_customers, COUNT(phone) AS has_phone_count FROM customers;一个表里如果某些行的phone是NULL两个结果会不同。这个差异在判断数据完整程度时可以做参考。SUM求和注意如果所有值都是NULLSUM返回NULL而不是 0。写报表时要小心。AVG求平均它也会忽略NULL行比如SELECT AVG(score) FROM exam_results;假设有三条成绩一个是NULL另外两个是 80 和 100AVG的结果是 90不是 60。这个坑在统计学生成绩时挺容易忽视的。MAX和MIN取最大最小值对文本类型也可以用按字典序排。6.2 GROUP BY 是聚合的灵魂聚合函数单独用是对全表统计跟GROUP BY配合才能按组统计。比如按商品分类统计每个分类的销量SELECT category_id, COUNT(*) AS product_count, AVG(price) AS avg_price, MAX(price) AS max_price FROM products GROUP BY category_id;这里有两条规则容易出错。第一条SELECT里出现的非聚合列必须出现在GROUP BY里否则 MySQL 的严格模式下会直接报错。例如上面要选 category_id那GROUP BY就得有 category_id。第二条WHERE在分组前过滤HAVING在分组后过滤。过滤分组后的统计结果要用HAVINGSELECT category_id, COUNT(*) AS product_count FROM products GROUP BY category_id HAVING product_count 5;举一个有点晕但很常见的例子统计各部门的员工人数只要人数大于10人的部门。这时候HAVING后面用的是统计出来的别名product_count而不是原始的列名这个要记住。6.3 聚合函数配合条件函数做报表做报表时最经典的组合是“按时间维度分组 条件聚合”。比如按月份统计订单总额和已支付金额SELECT DATE_FORMAT(created_at, %Y-%m) AS month, COUNT(*) AS total_orders, SUM(order_amount) AS total_amount, SUM(IF(pay_status 1, order_amount, 0)) AS paid_amount FROM orders GROUP BY month;月份的分组是靠DATE_FORMAT把时间戳格式化成YYYY-MM字符串来实现的。这里再次用到前面讲过的知识点日期格式化、条件判断、聚合函数、分组四个放一起一条SQL就把月度报表的数据全取出来了。GROUP_CONCAT这个函数也值得单独提一下它能将一个分组内的多个值拼成一个字符串。比如查每个分类下都有哪些商品名SELECT category_id, GROUP_CONCAT(product_name ORDER BY product_id SEPARATOR 、) AS product_names FROM products GROUP BY category_id;结果里每个分类一行的product_names列包含该分类所有商品名用分隔符拼好。有个限制是默认长度上限为 1024 字节超了会被截断需要改参数或者精简分组内容。真实的商品类目页、标签聚合展示常用这个函数直接把数据拼好返回给前端能省掉不少程序里循环拼接的笨拙操作。7. 加密函数与系统函数不出名但关键时刻能救命7.1 加密哈希函数MD5()、SHA1()、SHA2()这几个函数可以对字符串做哈希运算生成固定长度的哈希串适用于敏感信息脱敏或密码摘要存储。比如用户注册时把明文密码转成 MD5 摘要再落库INSERT INTO users (username, password_hash) VALUES (newuser, MD5(password123));有一点必须提醒MD5 和 SHA1 早已不适合存密码暴力碰撞成本太低现在密码存储至少用SHA2加盐或者干脆在程序里用专门的密码哈希算法。但在数据一致性校验、接口签名等场景里MD5还是很好用的轻量工具。比如校验两份数据是否一致SELECT COUNT(*) FROM temp_import_data WHERE MD5(CONCAT_WS(|, name, phone, address)) IN (...);用哈希函数快速做对比比逐字段比对效率高也避免明文数据直接暴露在日志里。7.2 系统函数小集合VERSION()返回 MySQL 版本DATABASE()返回当前库名USER()返回当前用户LAST_INSERT_ID()返回最近一次插入操作的自增ID。这些函数在写脚本、做监控、手动排查问题时很实用。比如做数据迁移脚本时想先确认库表环境SELECT VERSION() AS mysql_version, DATABASE() AS current_db, USER() AS current_user;LAST_INSERT_ID()经典用法是在 A 表插入一条记录后马上用它的自增主键去插入关联的 B 表。注意它跟连接会话绑定别的会话插入的数据不会影响当前会话返回的值这点设计得比较安全。7.3 实际应用中怎么选加密和系统函数虽然不起眼但用对场景能让整体方案干净很多。做数据导入校验时用哈希做指纹比对做多表关联时用LAST_INSERT_ID()写运维脚本时用DATABASE()防止连错库。掌握这些细节在排查问题时会大幅提速。8. 实战综合案例一张订单表串起所有函数8.1 模拟一个真实业务表既然讲了一堆函数不如放在一个需求里过一遍。假设有这样一张订单表 ordersCREATE TABLE orders ( order_id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32), customer_name VARCHAR(64), city VARCHAR(32), order_amount DECIMAL(10,2), pay_status TINYINT, -- 1 已支付 0 未支付 created_at DATETIME );业务需求是按城市统计每个城市的订单量、订单总额、实付总额已支付才算、客单价总额除以订单量、最新订单时间只显示订单量超过 10 的城市结果按订单总额降序排。8.2 写一条综合 SQLSELECT city, COUNT(*) AS order_count, ROUND(SUM(order_amount), 2) AS total_amount, SUM(CASE WHEN pay_status 1 THEN order_amount ELSE 0 END) AS paid_amount, ROUND(SUM(order_amount) / COUNT(*), 2) AS avg_order_value, MAX(created_at) AS last_order_time FROM orders GROUP BY city HAVING order_count 10 ORDER BY total_amount DESC;这条SQL里的函数不少COUNT、SUM、ROUND、CASE WHEN、MAX加上GROUP BY、HAVING、ORDER BY这些子句。拆开分析执行顺序先根据WHERE这里没写过滤出符合条件的行然后GROUP BY按城市分组接着对每组做聚合计算HAVING对分组后的结果过滤最后ORDER BY排序。理解这个顺序写复杂SQL会顺手很多。8.3 跑完结果看什么执行后重点检查三件事第一order_count 10这个过滤在分组完成后执行写错位置会导致结果不对第二avg_order_value算的是客单价如果直接用SUM(order_amount) / COUNT(*)未支付订单也算进去了可能不符合业务口径第三MAX(created_at)查最新订单时间用DATEDIFF(NOW(), MAX(created_at))还能算出这个城市多久没下单了这种动态指标做运营分析很常见。把8个函数串进一条 SQL比单独记函数更能锻炼实际编码能力。面试时候手写这种题也是展示基本功的机会。9. 常见问题与避坑9.1 LENGTH 和 CHAR_LENGTH 搞混中文场景较多时LENGTH返回字节数导致你以为字符串超长实际并没有。凡涉及界面输入长度限制稳妥的做法是CHAR_LENGTH。9.2 GROUP BY 的严格模式报错只勾选非聚合列、忘加进GROUP BY在 MySQL 默认开启ONLY_FULL_GROUP_BY的设置下会报错。提示里的那句英文看着吓人其实解决就是两个选择要么把列加到GROUP BY里要么用ANY_VALUE()包一下。9.3 日期列用函数导致索引失效WHERE DATE(created_at) 2025-01-01这种写法数据量大的时候会导致扫全表。改成created_at 2025-01-01 00:00:00 AND created_at 2025-01-02 00:00:00即可。9.4 HAVING 和 WHERE 用混WHERE在分组前过滤行HAVING在分组后过滤组两者能接受的写法也不同。HAVING可以用聚合函数和别名WHERE不行。9.5 CONCAT 遇到 NULL 全变 NULLCONCAT(a, b)里只要有一个NULL结果串就是NULL。要么提前用IFNULL包一层要么直接用CONCAT_WS忽略NULL。另外IFNULL只处理 NULL不处理空字符串注意区分。9.6 函数写太复杂影响性能一条SQL里堆五六个函数没问题但如果函数套函数套到三四层可读性和性能都开始变差。简单过滤能用BETWEEN就别用函数转换能在程序里算好的参数就直接传参进来。实际开发里“能用简单SQL就不用复杂函数”是一种克制但数据量上来之后确实能避免很多麻烦。9.7 数字转字符串的隐式转换CONCAT(123, 456)结果是123456字符串没有报错但如果你想的是数字相加这就是个陷阱。做类型判断和运算前想清楚字段类型必要时用CAST(列名 AS SIGNED)或者CONVERT显式转换。遇到“明明结果看着对但类型不对”的诡异问题第一个检查项就是隐式类型转换。10. 函数用的顺手靠的不只是记语法函数章节基本都是靠练出来的不是靠背出来的。这一路看下来真正的高频函数也就二三十个但组合起来的场景千变万化。我日常工作里的习惯是能用函数在 SQL 层解决的问题尽量不带到程序里再做一遍。数据从数据库里出来就已经加工好接口代码少写很多循环和判断出错的概率也低不少。比如分页统计、格式化时间、打码脱敏、行转列这些都能在 SQL 里一条语句完成清晰又高效。另一个心得是SQL 写复杂了不要怕拆。碰到一条超过五行的查询先把它拆成小段逐个验证确认结果对了再拼回去。函数本身不难难的是组合时边界条件没想清楚比如空值、边界值、条件顺序。最后分享一个小技巧MySQL 官方文档其实挺好查的每个函数都有详细说明、语法示例和返回值类型。遇到含糊的地方直接查官方文档比翻各种二手博客要快得多也不容易被过时或错误的内容误导。把常用函数过一遍之后再复杂的查询也只是这些基础块的组合罢了。