ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL常用函数实战:从字符串处理到聚合统计全解析

MySQL常用函数实战:从字符串处理到聚合统计全解析 1. 为什么我建议每一个刚接触MySQL的人先学透函数说实话很多零基础的朋友一上来就盯着增删改查SELECT、INSERT、UPDATE、DELETE用得很溜但一碰到“要把用户名统一转成大写”“要按月份统计订单量”“要处理空值显示缺省文案”这类需求就立刻卡壳然后跑去问同事或者翻搜索引擎翻半天抄来一段自己都看不懂的SQL。问题的根源不是笨而是缺了MySQL常用函数这一环。函数本质上就是MySQL帮你预封装好的一批“加工工具”你把原始数据喂进去它啪地一下返回加工后的结果。比如你想把 hello world 两边空格去掉不想自己写循环遍历一个TRIM()就完事。你想知道两个日期相差几天不想手算日历一个DATEDIFF()直接给答案。这些东西不涉及复杂算法纯粹是熟练度的问题但恰恰是熟练度决定了你写SQL的速度和代码的干净程度。这篇内容的目标很明确让零基础的人建立起对MySQL函数的整体认知把工作中最高频、最能提升效率的那批函数讲透。我不打算按官方文档的顺序罗列几百个函数让你背那样没有意义。我会按照“字符串处理、数值计算、日期时间、条件逻辑、聚合统计、综合实战”这条主线来拆每一步都结合真实业务场景给例子每个例子都保证你能直接复制到自己电脑上跑通。适合谁看准备入行数据分析师、后端开发、运维或者正在自学MySQL准备面试的朋友都合适。有一个基础前提你至少已经会把MySQL装好、能连上库、能执行最简单的SELECT语句。如果连安装都还没搞定建议先花半小时把环境搭好再回来看这篇不然光看不练看完就忘。2. 字符串函数数据清洗和格式化的大半壁江山2.1 CONCAT拼接与CONCAT_WS分隔符拼接真实业务里字符串拼接是最常见的需求。比如用户表里有first_name和last_name两个字段你要在前端显示完整姓名订单表里有年份和订单号两个字段你要生成一个带业务前缀的单号。这两种场景都用CONCAT。-- 基础拼接把两列合成一列 SELECT CONCAT(last_name, first_name) AS full_name FROM users; -- 拼接时带固定前缀 SELECT CONCAT(HN, -, order_year, -, order_no) AS biz_no FROM orders;这里有一个非常容易踩的坑CONCAT只要有一个参数为NULL整个结果就是NULL。比如上面第一个例子如果某个用户的first_name为空那一行返回的就是NULL前端拿到空值直接显示空白排查起来很费劲。我个人的习惯是凡是做拼接之前先用IFNULL或者COALESCE把可能为空的字段处理掉。SELECT CONCAT(IFNULL(last_name, ), IFNULL(first_name, )) AS full_name FROM users;另一个更省事的写法是CONCAT_WS它的第一个参数是分隔符之后的参数里会自动跳过NULL值这个特性在拼接带分隔符的字段串时非常方便SELECT CONCAT_WS(-, HN, order_year, order_no) AS biz_no FROM orders;如果你要把某个列的多行值拼成一行一个字符串那就要用到后面会讲到的聚合函数GROUP_CONCAT这里先留个印象。2.2 SUBSTRING截取与LEFT/RIGHT快速取边截取函数在解析编号、提取省份、处理日志字符串时高频出现。语法是SUBSTRING(str, pos, len)pos从1开始计数这是很多新手困惑的地方他们习惯性认为从0开始结果截出来的字符串总差一位。-- 从第4位开始截取3个字符 SELECT SUBSTRING(2024-ORD-001, 5, 3); -- 结果: ORD -- 如果只给两个参数表示从第pos位截到末尾 SELECT SUBSTRING(2024-ORD-001, 6); -- 结果: RD-001 -- 截取左边3个字符 SELECT LEFT(mysql函数, 5); -- 结果: mysql -- 截取右边4个字符 SELECT RIGHT(2024-ORD-001, 3); -- 结果: 001这里我特别推荐养成用LEFT和RIGHT的习惯因为它们在语义上更直观尤其当你在代码评审时别人一眼就能看出“你要取左边几位”而SUBSTRING在很多团队里需要多一点思考成本。2.3 大小写转换、去空格与替换数据录入不规范是常态用户注册时邮箱存了大小写混合的地址、导入的外部数据两边带着空格、文案里某段固定内容需要全局替换。这组函数基本能覆盖-- 统一转大写/小写 SELECT UPPER(mysql), LOWER(MYSQL); -- 结果: MYSQL, mysql -- 去掉字符串两边的空格中间的空格不影响 SELECT TRIM( hello mysql ); -- 结果: hello mysql -- 去掉左边空格 SELECT LTRIM( left space); -- 去掉右边空格 SELECT RTRIM(right space ); -- 替换字符串中的指定内容 SELECT REPLACE(前端展示区-未支付, 未支付, 待付款); -- 结果: 前端展示区-待付款顺带提一个细节TRIM默认去除的是空格但它也可以指定去除字符比如TRIM(LEADING 0 FROM 000123)可以把数字前面的零去掉这个在处理以字符串形式存储的数字时很有用。还有LENGTH和CHAR_LENGTH的区别很多面试官爱考。LENGTH返回的是字节数一个中文在UTF-8编码下占3个字节CHAR_LENGTH返回的是字符数一个中文算1个字符。判断“字符串是否超长”时要用CHAR_LENGTH否则统计结果会被中文字符数翻倍放大。SELECT LENGTH(你好), CHAR_LENGTH(你好); -- 结果: 6, 23. 数值函数与日期时间函数统计报表里离不开的两大支柱3.1 数值处理四舍五入、向上取整、向下取整、绝对值、取模数值函数是写统计类SQL的地基。你要算客单价、算折扣率、算库存周转几乎每步都离不开取整和精度控制。-- 四舍五入保留2位小数 SELECT ROUND(3.14159, 2); -- 结果: 3.14 SELECT ROUND(3.145, 2); -- 结果: 3.15注意这里不是直接截断 -- 向上取整只要有小数就进1 SELECT CEIL(3.01); -- 结果: 4 SELECT CEIL(3.00); -- 结果: 3 -- 向下取整直接丢掉小数部分 SELECT FLOOR(3.99); -- 结果: 3 -- 绝对值 SELECT ABS(-5); -- 结果: 5 -- 取模余数 SELECT MOD(10, 3); -- 结果: 1这里要专门提醒一下ROUND的精度问题。MySQL的ROUND使用的是“四舍五入”还是“四舍六入五成双”取决于版本和底层库的浮点实现比如在某些边界情况下ROUND(2.675, 2)可能返回2.67而不是2.68。如果你在做金额计算且对精度要求极高千万别只依赖ROUND建议结合DECIMAL类型存储金额字段从一开始就用DECIMAL(10,2)不要用FLOAT和DOUBLE。这一点在财务对账场景里是血的教训我见过不止一个人因为浮点误差导致对不上账最后排查半天才发现是字段类型的问题。3.2 日期时间获取NOW、CURDATE、CURTIME和它们的时区注意事项日期时间函数是报表查询的绝对高频。每天的统计任务、每月的结算任务、每隔半小时的增量拉取全部依赖日期函数。-- 当前日期时间 SELECT NOW(); -- 结果: 2025-01-20 14:33:21 SELECT SYSDATE(); -- 结果: 2025-01-20 14:33:21 -- 当前日期不含时间 SELECT CURDATE(); -- 结果: 2025-01-20 -- 当前时间不含日期 SELECT CURTIME(); -- 结果: 14:33:21NOW()和SYSDATE()在绝大多数情况下表现一致但有一个细微差别NOW()在一个SQL语句中返回的是语句开始执行的时间而SYSDATE()返回的是该函数被调用那一刻的时间点。在长时间运行的复杂SQL里多次调用SYSDATE()可能出现时间不一致因此我默认都写NOW()只有特殊需求才用SYSDATE()。务必要注意时区问题。如果你的MySQL连接串或者服务器时区设置不对NOW()返回的时间和业务期望的时间可能差好几个小时。这是配置层面的问题但会在函数使用中直接暴露。常见的处理方式是在数据库连接参数里显式指定serverTimezone或者在会话级别执行SET time_zone 08:00确保业务日志和统计口径一致。3.3 日期格式化与计算DATE_FORMAT、DATEDIFF、DATE_ADD这三兄弟是我个人认为整个日期函数体系里最值得花时间练熟的。DATE_FORMAT负责把日期转成任意你想要的展示格式SELECT DATE_FORMAT(NOW(), %Y-%m-%d); -- 结果: 2025-01-20 SELECT DATE_FORMAT(NOW(), %Y年%m月%d日); -- 结果: 2025年01月20日 SELECT DATE_FORMAT(NOW(), %H:%i:%s); -- 结果: 14:33:21 SELECT DATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s); -- 结果: 2025-01-20 14:33:21注意区分%Y和%y大写%Y返回四位年份小写%y返回两位年份%m是两位月份%c是不带前导零的月份%H是24小时制%h是12小时制。这些细节在拼报表字段时特别容易出错我就是靠反复查文档才彻底记住的。DATEDIFF计算两个日期相差的天数注意是“前一个减后一个”-- 计算从今天到本月底还有多少天 SELECT DATEDIFF(2025-01-31, CURDATE()); -- 结果: 11取决于当天日期 -- 计算两个订单日期间隔天数 SELECT DATEDIFF(2025-01-20, 2025-01-01); -- 结果: 19DATE_ADD用于给指定日期增加或减少时间间隔interval的单位可以是DAY、MONTH、YEAR、HOUR、MINUTE、SECOND也可以组合写-- 增加30天 SELECT DATE_ADD(2025-01-20, INTERVAL 30 DAY); -- 结果: 2025-02-19 -- 减少3个月用负数 SELECT DATE_ADD(2025-01-20, INTERVAL -3 MONTH); -- 结果: 2024-10-20 -- 增加1小时30分钟 SELECT DATE_ADD(2025-01-20 14:33:21, INTERVAL 1:30 HOUR_MINUTE);DATE_SUB是DATE_ADD的孪生函数语义上等于“加负数”。我个人习惯统一用DATE_ADD配合负值因为少记一个函数名字逻辑上也更统一。还有个常见的取年月日函数YEAR()、MONTH()、DAY()做按年按月分组统计时频率极高SELECT YEAR(2025-01-20), MONTH(2025-01-20), DAY(2025-01-20); -- 结果: 2025, 1, 204. 条件控制函数让SQL具备“如果...那么...”的逻辑能力4.1 IF与IFNULL处理空值和二分支场景SQL不是只能做“无脑的搬运”它会通过条件函数做出逻辑判断。最基础的是IF函数它的语法是IF(条件, 真值, 假值)适合二分支场景。-- 判断库存状态低于100显示“库存告急”否则显示“库存充足” SELECT product_name, stock, IF(stock 100, 库存告急, 库存充足) AS stock_status FROM products;另一个高频场景是IFNULL专门用来处理NULL值的兜底显示-- 如果备注为空显示“无备注” SELECT order_no, IFNULL(remark, 无备注) AS remark_display FROM orders;SQL里的NULL和空字符串是两个完全不同的概念。NULL表示“没有值”空字符串表示“有值这个值是空”。很多新手在WHERE条件里写WHERE remark 去过滤空备注结果NULL值的行一条都没出来必须写成WHERE remark IS NULL OR remark 才能两网打尽。4.2 CASE WHEN多条件分支的利器IF只能处理两分支如果要处理多条件分级CASE WHEN是标准答案。比如按订单金额分等级SELECT order_no, amount, CASE WHEN amount 10000 THEN 大客户 WHEN amount 5000 THEN 中客户 WHEN amount 1000 THEN 小客户 ELSE 零星客户 END AS customer_level FROM orders;CASE WHEN还有一个常见姿势配合聚合函数做“行转列”统计。比如你有一张订单表字段包括order_date和order_status你想统计“每个月的已支付订单数和未支付订单数”各是多少SELECT DATE_FORMAT(order_date, %Y-%m) AS month, SUM(CASE WHEN status paid THEN 1 ELSE 0 END) AS paid_cnt, SUM(CASE WHEN status unpaid THEN 1 ELSE 0 END) AS unpaid_cnt FROM orders GROUP BY DATE_FORMAT(order_date, %Y-%m);这种方式比写多个子查询干净得多也是面试中常考的“条件聚合”写法。核心思路CASE WHEN配合SUM把“行”里的分类信息转换成“列”上的计数值。5. 聚合函数从单行视野切换到整体统计的钥匙5.1 COUNT、SUM、AVG、MAX、MIN逐个说清聚合函数最大的特点是“把多行数据压缩成一行结果”。它们通常配合GROUP BY按分组统计来用。-- 订单总数 SELECT COUNT(*) FROM orders; -- 已支付订单数COUNT(column)会自动忽略NULL SELECT COUNT(order_id) FROM orders WHERE status paid; -- 订单总金额 SELECT SUM(amount) FROM orders; -- 客单价平均订单金额 SELECT AVG(amount) FROM orders; -- 最大/最小订单金额 SELECT MAX(amount), MIN(amount) FROM orders;这里我必须强调一个COUNT(*)和COUNT(1)和COUNT(column)的区别这是面试题里出现率极高的问题。COUNT(*)和COUNT(1)在MySQL InnoDB引擎下没有本质性能差异都会数行数。但COUNT(column)只会统计该列不为NULL的行数。所以在统计用户数量时如果你是查user_id这种有唯一约束的列三种写法没差别但如果你不小心count了一个可空的列结果会少排查半天才发现是NULL被忽略导致的。5.2 GROUP BY与聚合函数的配合统计报表的分组逻辑聚合函数单独用的频率其实不高它几乎总是和GROUP BY搭配先按某个维度分组再对各组做聚合。SELECT status, COUNT(*) AS order_cnt, SUM(amount) AS total_amount, AVG(amount) AS avg_amount FROM orders GROUP BY status;还有一个很多人会搞错的地方当你用了GROUP BYSELECT后面出现的普通列必须要么是分组列要么被聚合函数包裹。比如SELECT order_no, COUNT(*) FROM orders GROUP BY status这句话在MySQL默认配置下不报错但语义上是错误的——order_no在分组后有多个值取哪一个完全是MySQL随机选的。这个问题在ONLY_FULL_GROUP_BY模式下会直接报错而MySQL 8.0默认开启了这个模式。我的建议是永远不要写这种“侥幸”SQL分组查询就该严格把SELECT的列限定好。5.3 GROUP_CONCAT把多行值拼成一行字符串这是一个被低估的实用函数。它可以把分组内的多个值拼成一个字符串在生成汇总描述时特别好用。-- 把每个客户的所有订单号拼接成一列逗号分隔 SELECT customer_id, GROUP_CONCAT(order_no ORDER BY order_date SEPARATOR 、) AS all_order_nos FROM orders GROUP BY customer_id;GROUP_CONCAT默认用逗号分隔你可以通过SEPARATOR指定其他分隔符组内排序用ORDER BY控制如果拼接结果很长可以通过SET GROUP_CONCAT_MAX_LEN 10240调整最大长度。这些细节在实际工作中总会用到建议收藏一下。6. 一组从零到一能跑通的完整示例订单数据综合统计前面每类函数都是单独讲的这一节我们把这些函数串起来做一个接近真实业务的任务。假设你有一张电商订单表orders字段如下CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_name VARCHAR(50), order_date DATETIME, amount DECIMAL(10, 2), status VARCHAR(20) -- paid/unpaid/cancelled );先插入几条测试数据INSERT INTO orders VALUES (1, 张三, 2025-01-05 09:12:00, 1500.00, paid), (2, 李四, 2025-01-08 14:30:00, 800.00, unpaid), (3, 王五, 2025-01-12 18:45:00, 12000.00, paid), (4, 张三, 2025-01-15 21:00:00, 600.00, unpaid), (5, 李四, 2025-02-01 10:05:00, 2600.00, cancelled), (6, 王五, 2025-02-03 16:20:00, 4300.00, paid);需求生成一份“按月、按客户”的订单统计报表要求包含客户名称、下单月份、总订单数、总金额、平均单笔金额、最大单笔金额金额大于等于10000的标记为“大单客户”否则显示“普通客户”已取消的订单不计入统计。SELECT customer_name, DATE_FORMAT(order_date, %Y-%m) AS month, COUNT(*) AS total_orders, SUM(amount) AS total_amount, ROUND(AVG(amount), 2) AS avg_amount, MAX(amount) AS max_amount, CASE WHEN SUM(amount) 10000 THEN 大单客户 ELSE 普通客户 END AS customer_flag FROM orders WHERE status ! cancelled GROUP BY customer_name, DATE_FORMAT(order_date, %Y-%m) ORDER BY month, total_amount DESC;你看这一条SQL里串起了字符串函数DATE_FORMAT、聚合函数COUNT/SUM/AVG/MAX、数值函数ROUND、条件函数CASE WHEN、过滤条件WHERE、排序ORDER BY覆盖了本节前面讲的所有重点。能在脑子里直接写出这种SQL说明你对常用函数的基本功已经过关了。每步都可以单独测试比如先跑SELECT * FROM orders WHERE status ! cancelled看过滤结果再一层层加上GROUP BY和聚合最后加CASE WHEN。7. 函数使用中的常见误区和性能边界我说点文档里不写的7.1 在WHERE条件里用函数会导致索引失效这是所有MySQL性能问题里我最想强调的一点。你给某个字段建了索引但如果查询时在WHERE子句里对该字段使用了函数MySQL大概率会放弃索引扫描转而做全表扫描。-- 不建议对索引字段order_date使用函数 SELECT * FROM orders WHERE DATE(order_date) 2025-01-20; -- 建议直接使用范围查询走索引 SELECT * FROM orders WHERE order_date 2025-01-20 00:00:00 AND order_date 2025-01-21 00:00:00;两种写法结果一样但第二种能命中索引数据量大时性能差距是数量级的。写SQL时养成一个条件反射能用范围表达式解决的就不要把字段包进函数里。7.2 隐式类型转换会悄无声息地拖慢查询字符串函数和数值函数混用时MySQL会自动做隐式类型转换。比如你的订单编号order_no是VARCHAR类型存的全是数字字符串查询时写WHERE order_no 1234数字MySQL会把每一行的字符串都转成数字再比较索引同样可能失效。正确的做法是让类型匹配如果你的字段是字符串查询参数就写字符串比如WHERE order_no 1234。这个细节在初期不容易被察觉数据量一上来就会变成慢查询。7.3 NULL值的坑在函数里无处不在前面讲CONCAT和COUNT时都提到了NULL这里做一个集中总结用一张表说清楚场景结果说明CONCAT(a, NULL)NULL拼接时任何参数为NULL结果返回NULLIFNULL(NULL, x)x专门处理NULL的兜底函数COUNT(NULL列)0该列非NULL的行才会计数SUM(NULL列)NULL若该列全部为NULLSUM返回NULLWHERE 列 NULL查不到SQL中判断NULL必须用IS NULL或IS NOT NULLNULL NULL1是NULL安全等于运算符极少用但要知道牢记这组规律可以避免我在实际排错中见过的绝大多数SQL“奇怪结果”问题。7.4 函数嵌套可读性维护函数可以无限嵌套比如TRIM(REPLACE(LOWER(name), , ))但嵌套超过三层后面维护的人包括三个月后的自己读起来就会崩溃。我的习惯是超过两层嵌套就考虑用子查询把中间结果拆开或者用SQL注释标注每一步在干什么。-- 可读性更好的写法 SELECT TRIM(LOWER(customer_name)) AS clean_name, CHAR_LENGTH(TRIM(customer_name)) AS name_len FROM users;8. 最后分享一点我的实际使用心得MySQL常用函数这门功夫最大的特点就是“练一次记终生”。我见过很多人买了厚厚的SQL教程从头翻到尾合上书一条也写不出来。我自己学的时候就是拿公司真实的订单表、用户表反复折腾把昨天的取数需求全部用函数重写一遍能拆函数就拆函数能合并查询就合并查询。这样练两三天几乎所有常用函数都能在写SQL时不假思索地用出来。再补一个小技巧在你常用的客户端工具Navicat、DBeaver乃至命令行里可以用SELECT 函数名(参数)这种方式快速测试函数结果不用建表、不用插数据立刻就能看到返回值。比如SELECT DATE_FORMAT(NOW(), %Y-%m-%d);敲一下回车结果就出来了比翻文档快得多。另外一个容易被忽略的细节是MySQL的版本之间函数行为会有细微差异尤其是日期处理和字符集相关函数。如果你们的线上环境是5.7你本地用的是8.0那像是字符串的默认排序规则、GROUP BY的严格模式、某些日期函数的边界行为都可能不一样。写好的SQL上线前建议在目标版本环境里跑一遍避免“本地好好的线上就报错”的尴尬。如果你是把这里面的例子一个个亲手敲完、跑通的那MySQL常用函数这块的地基已经打得差不多了。之后不管是在数据分析岗位上写报表还是在后端开发里拼查询都会发现大部分让人头疼的取数需求翻来覆去用的其实就是这些函数的不同排列组合。
返回列表