ARTICLE DETAIL

资讯详情

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

MySQL常用函数详解:从字符串到日期,一条SQL搞定数据处理

MySQL常用函数详解:从字符串到日期,一条SQL搞定数据处理 很多刚接触 MySQL 的同学写 SQL 的时候总是习惯性地把数据一股脑查出来然后丢到程序里去循环、判断、拼字符串。其实 MySQL 本身内置了一大批非常成熟的常用函数能直接在数据库层面把数据加工好你只需要取结果就行。这个思路一旦打开你会发现原来要写几十行 Java 或者 Python 才能搞定的逻辑一条 SQL 就结束了。这篇内容就是给零基础的同学准备的我把日常开发里真正高频的 MySQL 函数按类别拆开讲每个函数都配了能直接跑的例子和使用场景看完你就能在项目里用起来。我的建议是别死记硬背语法而是先理解这个函数解决的是什么问题。你写 SQL 的时候能想起来“这里应该有个函数”比记住函数名更重要。下面每一类我都会讲这个函数在什么场景下非用不可以及实际开发中常见的坑。1. 先从整体上认识 MySQL 函数MySQL 的函数其实就是数据库内置的一批“加工工具”输入一个或者多个参数返回一个处理后的结果。你可以在SELECT后面直接用它处理查询结果也可以在WHERE条件里用函数过滤数据还可以在ORDER BY排序、GROUP BY分组的时候用。理解函数的基本使用姿势比记住函数本身更重要。1.1 函数的基本调用方式函数调用的通用写法是函数名(参数1, 参数2, ...)。参数可以是字段名、固定值甚至另一个函数的结果。比如SELECT CONCAT(hello, world);这段 SQL 返回的是hello world两个字符串拼成了一个。再比如SELECT NOW();返回的是当前系统的日期时间。这里有个细节NOW()虽然括号里是空的但这组括号不能省略。MySQL 里有很多函数带不带括号含义完全不同后面讲日期函数的时候会专门强调这一点。函数可以直接嵌在 SQL 语句里使用比如查询用户表时把姓和名拼成完整姓名SELECT id, CONCAT(last_name, first_name) AS full_name FROM user;这里AS full_name是给结果列起别名。别名这个动作在函数使用中特别重要因为函数计算出来的列默认列名是CONCAT(last_name, first_name)这一长串程序里取数据非常不方便起个别名就好多了。1.2 函数可以嵌套使用MySQL 的函数支持嵌套也就是一个函数的结果可以作为另一个函数的参数。比如把字符串里的空格去掉再截取前三个字符SELECT SUBSTRING(TRIM( abcdef ), 1, 3);TRIM先去掉两端空格得到abcdef然后SUBSTRING再截取前 3 位最终结果是abc。这种嵌套用熟了之后你会发现写 SQL 有点像搭积木每个函数负责一步加工。不过嵌套不要太深超过三层以后可读性会急剧下降。我自己踩过坑写过一条 SQL 里嵌套了五六个函数过两周自己回来看都费劲更别说同事维护了。如果逻辑确实复杂建议拆成多个步骤用临时表或者子查询过渡代码清晰比一时爽更重要。1.3 函数对性能的影响这也是一个容易被忽略的点。函数用在SELECT后面处理数据通常问题不大但如果用在WHERE条件里对字段本身做计算比如SELECT * FROM user WHERE DATE(create_time) 2024-01-01;这段 SQL 虽然能查出当天创建的用户但create_time字段上的索引会失效因为数据库需要先把每行的create_time都做一次DATE()计算才能和右边的字符串比较。数据量大一点这个查询就会明显变慢。正确的写法是改成范围查询SELECT * FROM user WHERE create_time 2024-01-01 00:00:00 AND create_time 2024-01-02 00:00:00;这样create_time字段本身没被加工索引就能正常工作。这个经验特别重要零基础同学一开始可能意识不到等你负责的表格数据到百万级别一条 SQL 慢个几秒就会被领导点名提前养成好习惯能少踩很多坑。2. 字符串函数处理文本数据的主力军业务系统里的数据很大一部分是字符串类型的。用户名、手机号、邮箱、商品名称、备注信息这些字段在数据库里存着的时候往往需要各种加工拼接、截取、替换、去空格、大小写转换。MySQL 提供的字符串函数足够覆盖绝大多数需求。2.1 字符串拼接CONCAT 与 CONCAT_WSCONCAT用来拼接多个字符串可以传两个或者更多参数SELECT CONCAT(MySQL, 教程, 实战);结果是MySQL教程实战。有一点需要注意如果任何一个参数是NULL整个拼接结果就是NULL。这个坑我碰到过不止一次。比如用户表里nickname字段是空的你用CONCAT(first_name, nickname)拼展示名结果整个字段都变成了NULL前端展示就出问题了。解决方法是配合IFNULL函数把NULL转成空字符串SELECT CONCAT(first_name, IFNULL(nickname, ));CONCAT_WS是带分隔符的拼接第一个参数是分隔符后面的参数是要拼接的内容SELECT CONCAT_WS(-, 2024, 01, 15);结果是2024-01-15。CONCAT_WS还有一个优势它会自动跳过NULL值不会像CONCAT那样整个结果变NULL。所以在拼接地址、拼接多段信息的时候我一般优先用CONCAT_WS。2.2 字符串截取SUBSTRING 与 LEFT / RIGHTSUBSTRING(字符串, 起始位置, 长度)是截取字符串最常用的函数。注意 MySQL 的起始位置从 1 开始不是从 0 开始SELECT SUBSTRING(abcdef, 2, 3);结果是bcd。如果不传长度就截取到末尾SELECT SUBSTRING(abcdef, 2);结果是bcdef。起始位置也可以传负数表示从右侧倒数SELECT SUBSTRING(abcdef, -2);结果是ef。LEFT和RIGHT更简单分别从左侧或右侧截取指定长度的字符SELECT LEFT(abcdef, 3); -- abc SELECT RIGHT(abcdef, 2); -- ef实际开发里常见的用法从身份证号里取出生日期、从手机号里取后四位、从订单号里取前缀等。这些场景用SUBSTRING都能轻松搞定。2.3 字符串替换REPLACEREPLACE(字符串, 被替换的内容, 替换成的内容)会把字符串里所有匹配到的内容全部替换掉SELECT REPLACE(apple banana apple, apple, orange);结果是orange banana orange。这个函数在处理历史数据的时候特别好用比如把文章内容里的旧域名批量换成新域名或者把用户输入的电话号码里的空格去掉SELECT REPLACE(138 1234 5678, , );结果是13812345678。注意REPLACE是区分大小写的Apple和apple不会被一起替换如果需要忽略大小写可以先转成统一的大小写格式再处理。2.4 去空格与填充TRIM、LTRIM、RTRIM、LPAD、RPADTRIM去掉字符串两端的空格LTRIM只去左侧RTRIM只去右侧SELECT TRIM( hello ); -- hello SELECT LTRIM( hello); -- hello SELECT RTRIM(hello ); -- hello注意TRIM只处理空格不处理换行符和制表符。如果要去掉换行需要配合REPLACE先处理。LPAD和RPAD是填充函数把字符串填充到指定长度SELECT LPAD(7, 3, 0); -- 007 SELECT RPAD(abc, 5, *); -- abc**这个在生成编号、格式化展示的时候非常实用。比如订单号需要统一为 8 位数字不足的前面补零用LPAD一行就搞定了。注意填充的是字符串形式如果原字符串本身已经超过指定长度填充函数不会截断而是直接返回原字符串。2.5 大小写转换与长度计算UPPER、LOWER、LENGTH、CHAR_LENGTHUPPER转大写LOWER转小写SELECT UPPER(mysql); -- MYSQL SELECT LOWER(MySQL); -- mysql这两个函数在登录验证、验证码等场景特别常用。程序里经常出现用户输入验证码时大小写不匹配的问题数据库层可以直接用UPPER统一处理。LENGTH返回字符串的字节长度CHAR_LENGTH返回字符长度。这个区别很关键因为一个中文汉字在 UTF-8 编码下占 3 个字节但在CHAR_LENGTH里只算 1 个字符SELECT LENGTH(你好); -- 6 SELECT CHAR_LENGTH(你好); -- 2如果你的字段存储的是中文名字要校验长度用CHAR_LENGTH更符合直觉。用LENGTH容易把中文算成 3 倍长度导致明明 20 个字的名字报长度超限。3. 数值函数让计算在数据库里完成很多同学习惯把数据查出来以后在 Java、Python 里做数值计算。这种做法不是不行但在数据量较大的时候数据库里直接算好返回能省下大量网络传输和程序计算的开销。数值函数是 SQL 里非常实用的一类工具。3.1 四舍五入与截断ROUND、TRUNCATEROUND(数值, 保留位数)做四舍五入SELECT ROUND(3.14159, 2); -- 3.14 SELECT ROUND(3.14559, 2); -- 3.15注意 MySQL 的ROUND四舍五入规则在边界值上有点特殊比如SELECT ROUND(2.5); -- 3 SELECT ROUND(3.5); -- 4这两种情况都符合预期。但如果你做财务计算涉及精度要求高的场景我更建议用DECIMAL类型配合ROUND尽量少用浮点类型做金额运算。TRUNCATE(数值, 保留位数)是直接截断不四舍五入SELECT TRUNCATE(3.14159, 2); -- 3.14ROUND(3.145, 2)的结果可能让你意外因为浮点数在计算机里的二进制表示导致精度问题。实际业务中做金额统计时如果要保证精确先把字段转成DECIMAL(10, 2)再计算。3.2 向上取整与向下取整CEIL、FLOORCEIL向上取整FLOOR向下取整SELECT CEIL(3.2); -- 4 SELECT FLOOR(3.8); -- 3在分页计算、库存分配、任务拆分的场景里经常用到。比如总共有 102 条数据每页显示 10 条总页数就是CEIL(102 / 10)结果是 11。3.3 绝对值与取余ABS、MODABS返回绝对值SELECT ABS(-10); -- 10MOD(被除数, 除数)返回余数SELECT MOD(10, 3); -- 1取余在分表分库的逻辑里特别常见比如根据用户 ID 的哈希值对 4 取余决定数据落在哪张表。MOD还可以用来实现奇偶判断MOD(id, 2) 1表示奇数。3.4 随机数与最值RAND、LEAST、GREATESTRAND()生成 0 到 1 之间的随机数SELECT RAND();要生成某个范围内的随机整数可以这样SELECT FLOOR(RAND() * 100); -- 0 到 99 的随机整数LEAST返回最小值GREATEST返回最大值SELECT LEAST(3, 1, 2); -- 1 SELECT GREATEST(3, 1, 2); -- 3这两个函数在做价格比较、分数上限封顶的时候很实用。比如用户积分超过 1000 按 1000 算低于 0 按 0 算SELECT GREATEST(0, LEAST(score, 1000));这段代码一行就把积分限制在 0 到 1000 之间了。4. 日期时间函数统计与筛选的利器日期时间处理是 MySQL 里最容易被搞乱的一部分。存的时候各存各的格式取的时候又各转各的格式明明是同一天的数据就是因为时区、格式、精度的问题对不上。MySQL 的日期时间函数能帮你在数据库层面统一解决这些问题。4.1 获取当前日期时间NOW、CURDATE、CURTIME这三个函数分别获取当前完整的日期时间、当前日期、当前时间SELECT NOW(); -- 2024-01-15 10:23:45 SELECT CURDATE(); -- 2024-01-15 SELECT CURTIME(); -- 10:23:45NOW()在一条 SQL 语句里多次调用返回的时间是一致的这个特性在某些需要记录操作时间的场景下很有用。另外还有SYSDATE()也能获取当前时间但它每次执行都获取实时时间和NOW()行为略有不同。日常开发优先用NOW()。4.2 日期格式化DATE_FORMATDATE_FORMAT(日期, 格式)是日期处理里最常用的函数。格式符有固定规则SELECT DATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s);结果类似2024-01-15 10:23:45。常用的格式符有这么几个%Y四位年份%m两位月份%d两位日期%H24 小时制小时%i分钟%s秒%W星期几的英文名比如要统计某个月的销售情况只取年月SELECT DATE_FORMAT(create_time, %Y-%m) AS month, SUM(amount) AS total_amount FROM orders GROUP BY month;这就是一个非常经典的月度汇总查询把create_time按月份格式化后分组统计每个月订单总额。这里month这个列别名在GROUP BY里可以直接用MySQL 允许这样写。4.3 日期解析STR_TO_DATESTR_TO_DATE(字符串, 格式)和DATE_FORMAT是互逆操作把字符串按指定格式转成日期SELECT STR_TO_DATE(2024-01-15, %Y-%m-%d);结果是日期类型的2024-01-15。导入数据的时候特别有用比如 Excel 里的日期是2024/01/15这种格式直接插进数据库之前先转换一下。注意字符串和格式必须严格匹配不然返回NULL而且这种方式在数据质量参差不齐时容易踩坑。4.4 日期计算DATE_ADD、DATE_SUB、DATEDIFFDATE_ADD(日期, INTERVAL 表达式)在日期上加一段时间DATE_SUB则相反SELECT DATE_ADD(2024-01-15, INTERVAL 7 DAY); -- 2024-01-22 SELECT DATE_SUB(2024-01-15, INTERVAL 1 MONTH); -- 2023-12-15INTERVAL后面可以跟DAY、MONTH、YEAR、HOUR、MINUTE等单位。计算两个日期之间相差多少天用DATEDIFFSELECT DATEDIFF(2024-01-15, 2024-01-10); -- 5注意DATEDIFF只比较日期部分不比较时间部分。如果需要精确到小时、分钟的差值用TIMESTAMPDIFF(单位, 开始时间, 结束时间)SELECT TIMESTAMPDIFF(HOUR, 2024-01-15 08:00:00, 2024-01-15 12:30:00);结果是 4。TIMESTAMPDIFF的单位参数可以是SECOND、MINUTE、HOUR、DAY、MONTH、YEAR比DATEDIFF灵活很多。4.5 月份与星期的辅助函数LAST_DAY(日期)返回当月的最后一天SELECT LAST_DAY(2024-02-01);结果是2024-02-29因为 2024 年是闰年。这个函数在算当月剩余天数、月末结算的场景里很好用。DAYOFWEEK(日期)返回星期几的索引注意 MySQL 里周日是 1周一是 2以此类推SELECT DAYOFWEEK(2024-01-15); -- 2表示周一5. 条件判断函数给 SQL 加上逻辑能力写程序的时候if else是基础逻辑。在 SQL 里同样可以实现条件判断而且不需要把所有数据取出来再在程序里判断。MySQL 提供了IF、IFNULL、NULLIF、CASE WHEN这一系列条件函数熟练使用之后很多报表统计的 SQL 会简洁很多。5.1 IF 函数简单二元判断IF(条件, 条件为真时的值, 条件为假时的值)语法非常直白SELECT IF(1 0, yes, no);结果自然是yes。实际场景里比如查询用户表要根据性别字段显示中文SELECT name, IF(gender 1, 男, 女) AS gender_text FROM user;一个IF只能处理二元判断如果只有两种情况用IF足够了。它可以直接嵌套但嵌套多了可读性很差这时候就该用CASE WHEN。5.2 CASE WHEN多条件分支判断CASE WHEN其实就是 SQL 里的else if。有两种写法。第一种是简单函数形式SELECT name, CASE gender WHEN 1 THEN 男 WHEN 2 THEN 女 ELSE 未知 END AS gender_text FROM user;第二种是搜索函数形式可以写任意条件SELECT name, CASE WHEN score 90 THEN 优秀 WHEN score 60 THEN 及格 ELSE 不及格 END AS level FROM student;搜索函数形式的每个WHEN后面都可以跟独立的判断条件不受等值比较限制实际开发里这种写法更通用。CASE WHEN在统计报表里特别常用比如统计每个订单属于哪个价格区间SELECT CASE WHEN amount 100 THEN 小额订单 WHEN amount 500 THEN 中额订单 ELSE 大额订单 END AS order_level, COUNT(*) AS cnt FROM orders GROUP BY order_level;一条 SQL 就把订单分档统计出来了换成程序里处理的话要写不少循环代码。5.3 IFNULL 与 NULLIF处理空值的两个好帮手IFNULL(表达式, 替换值)的作用是当第一个参数是NULL时返回第二个参数SELECT IFNULL(NULL, 默认值);在统计场景里特别实用。比如计算用户的平均消费有些用户没有订单记录AVG结果可能是NULL展示的时候不好看SELECT user_id, IFNULL(AVG(amount), 0) AS avg_amount FROM orders GROUP BY user_id;NULLIF(表达式1, 表达式2)是另一个方向的函数如果两个参数相等返回NULL如果不相等返回第一个参数SELECT NULLIF(5, 5); -- NULL SELECT NULLIF(5, 3); -- 5这个函数在防止除零错误的时候特别好用。比如计算转化率的时候分母可能是 0直接除会报错用NULLIF把 0 转成NULLSELECT clicks / NULLIF(views, 0) AS click_rate FROM ad_stats;当views为 0 时NULLIF返回NULL整个除法的结果也是NULL不会报错也不会出现Infinity这种荒谬的值。5.4 条件函数的实际应用场景条件函数最大的价值是把“数据清洗”和“逻辑判断”下沉到数据库层。我之前做过一个用户标签系统用户表里有last_login_time要根据最后登录时间给用户打上活跃度标签一条 SQL 直接搞定SELECT user_id, CASE WHEN last_login_time DATE_SUB(NOW(), INTERVAL 7 DAY) THEN 近7日活跃 WHEN last_login_time DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 近30日活跃 ELSE 沉睡用户 END AS active_level FROM user;这里的DATE_SUB(NOW(), INTERVAL 7 DAY)会自动计算 7 天前的时间点和last_login_time直接比较。你看函数之间的配合使用能写出非常简洁又高效的统计 SQL。6. 聚合函数不写代码也能看报表聚合函数是分组统计的基石。COUNT、SUM、AVG、MAX、MIN这几兄弟是面试必考、工作必用的高频函数。它们的作用是在一组数据上计算出一个汇总结果。6.1 COUNT统计行数COUNT(*)统计所有行数COUNT(字段名)统计该字段非NULL的行数SELECT COUNT(*) FROM user; -- 总用户数 SELECT COUNT(phone) FROM user; -- 有手机号的用户数COUNT(*)和COUNT(1)在 InnoDB 存储引擎下的执行结果是相同的日常使用不用刻意区分。但要注意COUNT(字段)会忽略NULL值如果核对数据的时候发现数量对不上优先检查这个字段是不是有空值。6.2 SUM 与 NULL 的微妙关系SUM(字段)对一组值求和如果组内所有值都是NULL结果是NULL而不是 0SELECT SUM(amount) FROM orders WHERE user_id 999;如果这个用户没有订单结果是NULL而不是 0。很多新手在这里翻车把结果取出去做加法直接报了NullPointerException。更稳妥的写法是用IFNULL包一层SELECT IFNULL(SUM(amount), 0) FROM orders WHERE user_id 999;另外SUM会自动忽略NULL值不参与求和所以不用担心某一行金额为空导致整列求和失败。6.3 AVG平均值与空值处理AVG求平均值同样会自动忽略NULL值。但这里有个容易误解的点比如一组数据是 80、90、NULLAVG计算的是(80 90) / 2 85而不是除以 3。如果你希望把NULL当成 0 参与平均需要先用IFNULL转换SELECT AVG(IFNULL(score, 0)) FROM student;这个细节在绩效统计、评分统计里会造成很大的差异千万注意。6.4 MAX 与 MIN找极值MAX和MIN分别返回最大、最小值SELECT MAX(price), MIN(price) FROM product;它们也忽略NULL值。在业务场景里MAX、MIN不光能查数字也能查字符串和日期。比如查最近一次登录时间SELECT MAX(last_login_time) FROM user;6.5 GROUP_CONCAT把分组数据拼成字符串这个函数在报表里特别能提升效率它能把同一个分组里的多个值拼成一个字符串返回SELECT department_id, GROUP_CONCAT(name) AS employee_names FROM employee GROUP BY department_id;结果里每个部门的所有员工姓名会拼成一个字符串默认用逗号分隔。也可以指定分隔符GROUP_CONCAT(name SEPARATOR 、)注意GROUP_CONCAT有一个默认的长度限制默认group_concat_max_len是 1024 字节。如果拼接的内容很多超过限制会被截断导致结果不完整。我遇到过导出报表的时候角色列表少了几个排查了半天发现是这个参数在作怪。可以通过SET SESSION group_concat_max_len 10240;临时调大或者在配置文件里全局调整。7. 综合实战把常用函数串起来用前面把各类函数分开讲了真实项目里往往需要多个函数配合使用。这里我用一个电商订单统计的案例把字符串函数、日期函数、条件函数、聚合函数串起来让大家感受一下实际面对需求时该怎么组合这些工具。7.1 需求描述假设有一个电商平台的订单表orders结构大致如下字段名类型说明idINT订单IDorder_noVARCHAR(32)订单编号user_idINT用户IDamountDECIMAL(10,2)订单金额statusTINYINT订单状态1待支付 2已支付 3已取消create_timeDATETIME下单时间现在需要统计这个数据每个用户最近三个月的月均消费金额以及累计消费总额并且要展示用户ID、消费等级和首单日期。7.2 编写 SQL先统计每个用户的消费情况SELECT user_id, IFNULL(SUM(amount), 0) AS total_amount, IFNULL(AVG(amount), 0) AS avg_amount, MIN(create_time) AS first_order_time FROM orders WHERE status 2 AND create_time DATE_SUB(CURDATE(), INTERVAL 3 MONTH) GROUP BY user_id;这里用到了SUM求和、AVG求平均、MIN找首次下单时间、IFNULL处理空值、DATE_SUB和CURDATE圈定最近三个月的数据范围一个查询把核心指标都算出来了。在此基础上加上CASE WHEN做消费等级划分用DATE_FORMAT格式化日期SELECT user_id, total_amount, avg_amount, DATE_FORMAT(first_order_time, %Y-%m-%d) AS first_order_date, CASE WHEN total_amount 10000 THEN VIP客户 WHEN total_amount 5000 THEN 重点客户 ELSE 普通客户 END AS customer_level FROM ( SELECT user_id, IFNULL(SUM(amount), 0) AS total_amount, IFNULL(AVG(amount), 0) AS avg_amount, MIN(create_time) AS first_order_time FROM orders WHERE status 2 AND create_time DATE_SUB(CURDATE(), INTERVAL 3 MONTH) GROUP BY user_id ) AS stats;这段 SQL 里子查询先算出每个用户的统计指标外层查询再做等级划分和日期格式化。整个统计需求没有写一行程序代码全部在数据库层完成了。这也是函数组合使用的价值所在像流水线一样数据一层层加工最后输出业务直接需要的结果。7.3 如果结果需要按消费总额排序再叠加一个ORDER BYORDER BY total_amount DESC这样数据分析人员可以直接看到最核心的头部客户名单。整个思路就是聚合函数负责汇总条件函数负责打标日期函数负责筛选和格式化字符串函数负责展示效果。分清楚每个函数的职责写复杂 SQL 的时候就不会乱。8. 常见问题与踩坑记录函数虽然好用但实际使用中很容易踩到一些隐含的坑。我把这些年开发中遇到的典型问题整理出来每个都是真实发生过的不是理论推演。8.1 日期格式化符号的大小写陷阱DATE_FORMAT的格式符是大小写敏感的而且含义完全不同。%Y是四位年份%y是两位年份%m是数字月份%M是英文月份名%H是 24 小时制%h是 12 小时制。我第一次用的时候把%m写成了%M结果界面上的月份变成了January、February看起来不算错但对不上预期格式。SELECT DATE_FORMAT(NOW(), %Y-%m-%d); -- 2024-01-15 SELECT DATE_FORMAT(NOW(), %Y-%M-%d); -- 2024-January-15建议写完之后先跑一条SELECT验证一下结果再嵌进业务 SQL。8.2 隐式类型转换导致的结果异常当字符串和数字比较时MySQL 会尝试把字符串转成数字。比如WHERE phone 13812345678如果phone字段是 VARCHAR 类型存储的是138-1234-5678这个比较就会出问题。还有一个经典场景是WHERE amount 100这里amount是数字字段MySQL 会把字符串100转成数字结果可能出乎意料特别是当字符串里有非法字符时转换规则不是报错而是截断比如100abc会被转成100。这种隐式转换造成的匹配错误在排查的时候特别隐蔽。8.3 聚合函数结果和明细对不上排查统计数据的时候经常发现聚合结果和自己手动算的对不上。最常见的两个原因一是COUNT(字段)会忽略NULL二是有重复数据。比如统计订单数量直接用COUNT(id)但如果同一个订单在表里有两条记录统计就翻倍了。这种情况需要先确认业务上订单号是否唯一必要时用COUNT(DISTINCT order_no)去重统计。8.4 GROUP_CONCAT 被截断前面提过GROUP_CONCAT的默认长度限制是 1024 字节。业务里如果要把用户的所有角色名拼起来角色一多很容易超长被截断而且结果不会报错只是末尾少一段。这个问题极难发现因为只有在数据量达到一定程度才会触发。排查思路是检查group_concat_max_len参数或者直接用LENGTH(GROUP_CONCAT(...))查一下拼接后的实际长度SELECT user_id, LENGTH(GROUP_CONCAT(role_name)) AS len FROM user_role GROUP BY user_id ORDER BY len DESC LIMIT 10;如果发现长度已经到了 1000 左右就要考虑调参了。8.5 NOW() 括号不能省略NOW()和NOW是两种完全不同的东西。MySQL 里NOW是一个“当前时间”的别名常量其实如果写成SELECT NOW;在部分版本里会直接报错或者返回一个特殊的结果。更常见的是很多新手写CURDATE、CURTIME不带括号结果 SQL 直接报错。记住函数调用必须带括号这是语法规则不是可选项。8.6 数据库函数和 ORM 的协作如果你用的是 MyBatis、Hibernate 这类 ORM 框架函数可以直接写在 SQL 里但要区分哪些函数是 MySQL 特有的、哪些是标准 SQL。比如IFNULL在 SQL Server 里对应ISNULL在 PostgreSQL 里是COALESCE。如果项目后期有换数据库的打算尽量少用数据库特有的函数或者把这些逻辑封装在 SQL 映射文件里方便切换时统一修改。8.7 安全提醒拼接查询参数时注意注入风险函数里经常要传参比如CONCAT(first_name, , last_name)这些参数如果是用户输入的内容拼接 SQL 的时候一定要用预处理语句占位符不能直接拼进 SQL 字符串。我在早期开发中犯过这个错把用户输入的搜索关键词直接拼进LIKE语句结果被传入一个% OR 11整张表的数据差点被拉出来。函数本身没问题但传参方式必须规范。8.8 遇到慢查询先看执行计划当包含函数的 SQL 跑得慢最有效的排查手段是EXPLAIN看一下执行计划。重点看key列如果显示NULL说明没有走索引再结合前面说到的“避免在 WHERE 条件中对字段做函数加工”这条原则去优化。优先改 SQL 写法实在不行再加索引不要一上来就动表结构。9. 给零基础同学的学习路径建议函数这部分内容不算难但它是 SQL 从“能查”到“会查”之间的一道坎。我的建议是不要试图一口气把 MySQL 文档里所有函数都背下来这不现实也没有必要。抓住下面几个核心思路去学习和记忆就好第一先掌握本文讲的这一批高频函数尤其是CONCAT、SUBSTRING、REPLACE、DATE_FORMAT、DATE_ADD、CASE WHEN、IFNULL、COUNT、SUM、GROUP_CONCAT。这些都是日常开发里出现频率最高的。第二把每个函数代入一个真实的业务场景去理解。不要死记ROUND(3.14159, 2)返回什么而是想“计算价格平均值后保留两位小数应该用什么函数”。场景驱动的记忆方式牢固得多。第三试着把函数组合起来用。可以先从简单需求开始比如“查用户表把手机号中间四位换成星号”这需要CONCAT配合SUBSTRING和LEFT、RIGHT来实现SELECT CONCAT(LEFT(phone, 3), ****, RIGHT(phone, 4)) AS masked_phone FROM user;这个需求很经典用三个字符串函数就搞定了。多做这种组合练习函数的熟练度提升非常快。第四每个函数都自己跑一遍SELECT验证结果。MySQL 的函数可以脱离表独立执行SELECT NOW();、SELECT ROUND(3.14, 1);这些都能直接看到结果不用建表不用插数据学习成本非常低。建议打开命令行或者 Navicat把本文出现的所有示例都亲手敲一遍动手比看十遍都管用。我在实际带新人的过程中发现函数这块最大的问题不是不会用而是不知道“原来这个需求可以用函数解决”。很多新同事拿到需求就开始写 Java 代码循环、判断、字符串处理堆了一大堆我一看这条 SQL 加两个函数就搞定了代码量能省掉 80%。所以学习函数的时候多问自己一句这个功能能不能在数据库里直接完成这个习惯一旦养成你的 SQL 水平会上一个台阶。MySQL 的常用函数说多不多说少不少但万变不离其宗掌握好这些核心函数和处理思路后续再遇到没见过的函数翻一眼文档也能很快上手。SQL 这门手艺练得越多用起来越顺手。
返回列表