news 2026/10/23 7:14:20

MySQL字符串函数实战:从拼接截取到数据清洗的工程指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL字符串函数实战:从拼接截取到数据清洗的工程指南

这两年我接手的数据清洗和报表需求里,十个有七个都在跟字符串纠缠。要么是从旧系统导出来的字段带着一堆前后空格和换行,要么是手机号、身份证、订单号混在一个字段里需要拆开,要么是编码规则不统一导致排序结果怎么看怎么别扭。每次遇到这种活儿,我就翻出MySQL那一组字符串函数,三下五除二拼一条SQL搞定。字符串函数在MySQL里属于那种平时不太显眼、但真要处理数据时能救命的东西,它解决的核心问题就是:怎么对文本字段做裁剪、拼接、查找、替换、格式化,以及怎么让这些操作在SQL里直接完成,而不是把数据捞回应用程序里用Java或Python慢慢磨。这篇文章不打算挨个罗列函数文档,而是按照实际使用场景把这些函数串起来讲,适合刚接触MySQL没多久、被各种字符串字段搞到头大的同学,也适合写了好几年SQL但从没系统整理过这类函数的老手。

1. 先搞清楚你到底需要哪类字符串函数

字符串函数在MySQL里的数量接近五十个,看着一大堆,其实真正高频用得上的没有那么多。我自己的习惯是先把它们按“想对字符串做什么”分成四类,用的时候直接到对应分类里找,比对着文档从头翻到尾高效得多。

1.1 按操作目的把函数分类

第一类是拼接与拆分,典型代表是CONCAT、CONCAT_WS、SUBSTRING、SUBSTRING_INDEX。这类函数解决的是“把字段拼起来”和“把字段拆开”的问题。比如客户表里姓和名分两列存,报表要显示全名;或者地址字段里同时包含了省市区,需要单独把省拎出来。

第二类是查找与定位,典型代表是LOCATE、INSTR、POSITION、FIELD。这类函数解决的是“某个子串到底在哪个位置”的问题。平时做数据分析时,经常需要判断某个字段里是否包含某个关键词,或者根据关键词出现的位置去截取后面的内容。

第三类是替换与格式化,典型代表是REPLACE、INSERT、TRIM、LPAD、RPAD、UPPER、LOWER。这类函数解决的是“文本长得不好看,需要调整一下”的问题。比如导出的手机号有的带横杠、有的不带,统一去掉横杠;或者编码长度不齐,左边补零。

第四类是编码与比较,典型代表是CHAR_LENGTH、LENGTH、STRCMP、REGEXP。这类函数解决的是“这个字段到底有多长、两个字符串怎么比较、是否符合某种模式”的问题。它们经常出现在WHERE条件里做筛选,或者用于判断数据质量。

1.2 为什么组合使用比单函数更有价值

单独看每个函数都挺简单,真正拉开效率差距的是组合使用。举个例子,客户地址字段存的是“广东省深圳市南山区科技园路1号”,你想把省份单独取出来,光用一个SUBSTRING搞不定,得先LOCATE找出“省”的位置,再配合SUBSTRING才能截出来。类似这种多函数嵌套的写法,才是处理真实数据时的常态。

我自己写SQL有个习惯:先判断需要哪几步——“先找到目标位置,再截取,最后去空格”,然后把每一步对应的函数写出来,嵌套在一起。这个思路比背函数文档管用得多。下面几章就按这个思路,把实战中最高频的几组函数逐一拆开讲。

2. 拼接与截取:处理字段组合的看家本领

2.1 CONCAT和CONCAT_WS拼接的技巧与NULL陷阱

CONCAT的作用是把多个字符串首尾相连。用法非常直白:

SELECT CONCAT('Hello', ' ', 'World'); -- 结果:Hello World

实际业务里最常见的场景是把姓名、地址、订单号等字段拼成一个完整描述。我遇到过最典型的案例是把订单表和用户表联查后,需要生成一个“用户名-订单号-商品名”的合并字段,直接CONCAT三个字段就行。

但CONCAT有一个特别容易踩的坑:只要参与拼接的任何一个字段是NULL,整个结果就变成NULL。这跟很多人的直觉不一样,也是实际开发中线上数据报错、报表显示空白的常见元凶。解决办法有两个:一是用IFNULL先把NULL转成空字符串,二是改用CONCAT_WS。

SELECT CONCAT_WS('-', 'A', NULL, 'C'); -- 结果:A-C

CONCAT_WS里的WS是WITH SEPARATOR的意思,第一个参数指定分隔符,后面的参数是要拼接的内容。它的好处有两个:NULL参数会被自动忽略,而不是让整个结果变NULL;分隔符只需要写一次,代码更简洁。比如拼“2024-12-01”这种日期格式,用CONCAT_WS('-', year_col, month_col, day_col)就很方便。

2.2 SUBSTRING系截取:从正数、负数到按分隔符截取

SUBSTRING是字符串截取的核心函数,基本语法是SUBSTRING(str, pos, len),表示从第pos个位置开始取len个字符。注意MySQL的字符串位置是从1开始数的,不是从0开始,这个跟很多编程语言不一样,容易搞混。

SELECT SUBSTRING('abcdef', 2, 3); -- 结果:bcd

pos还可以传负数,表示从字符串末尾往前数位置。SUBSTRING('abcdef', -3, 2)的结果是de,因为从右边数第3个字符是d,往后取2个字符。

真正处理业务数据时,SUBSTRING_INDEX比SUBSTRING更常用。它的语法是SUBSTRING_INDEX(str, delim, count),按照分隔符把字符串拆成若干段,然后返回第count段之前的内容。count为正数时从左往右数,为负数时从右往左数。

SELECT SUBSTRING_INDEX('www.example.com', '.', 2); -- 结果:www.example SELECT SUBSTRING_INDEX('www.example.com', '.', -1); -- 结果:com

这个函数在处理带分隔符的字段时极其好用。比如在一列里存了“品牌-型号-容量”,想提取容量,可以嵌套使用SUBSTRING_INDEX取最后一段;想提取品牌型号,先取前两段,再取第一段。

2.3 CHAR_LENGTH和LENGTH:字符数和字节数的区别必须分清

这两个函数看起来都跟长度有关,实际上差很多。LENGTH返回的是字符串的字节数,CHAR_LENGTH返回的是字符数。对于英文字母和数字,两者结果一致,因为每个字符占1个字节;但对于中文、表情符号这类多字节字符,LENGTH的值会比CHAR_LENGTH大,具体大多少取决于字符集。

SELECT LENGTH('数据库'), CHAR_LENGTH('数据库'); -- 在utf8下:LENGTH=9,CHAR_LENGTH=3

utf8字符集下一个汉字占3个字节,所以“数据库”三个字的LENGTH是9,CHAR_LENGTH是3。如果在utf8mb4下存了emoji表情,一个表情占4个字节,差距更明显。

这个区别在写校验逻辑时很关键。比如限制用户昵称最长20个字符,应该用CHAR_LENGTH而不是LENGTH,否则中文用户还没输入几个字就被拦下来了。判断字段是否有脏数据时,也可以用LENGTH和CHAR_LENGTH不一致来判断字段里是否含中文或特殊字符。

3. 查找与替换:处理脏数据的核心手段

3.1 LOCATE、INSTR和POSITION的定位用法

这类函数解决的是“我的字符串里有没有某个子串,如果有,它在哪里”。LOCATE是最常用的一个,语法是LOCATE(substr, str),返回子串第一次出现的位置,找不到时返回0。

SELECT LOCATE('world', 'hello world hello'); -- 结果:7

INSTR(str, substr)和LOCATE的功能基本一致,只是参数顺序相反。POSITION(substr IN str)也是同样的作用,写法更接近英文习惯。三者选一个用就行,我习惯用LOCATE,因为参数顺序跟直觉一致,先写要找的子串,再写从哪里找。

LOCATE在WHERE条件里经常配合判断使用:WHERE LOCATE('关键字', content) > 0。不过需要注意,用一个简单的LIKE '%关键字%'也能实现,但如果要基于子串位置做进一步的截取或判断,LOCATE就是不可替代的了。

3.2 REPLACE做批量替换:UPDATE场景下的威力

REPLACE的语法是REPLACE(str, from_str, to_str),把字符串里的from_str全部替换成to_str。它是处理历史脏数据的一把好手。

我处理过一个经典订单:手机号字段里既有“138-1234-5678”这种带横杠的,又有“138 1234 5678”这种带空格的,还有不带任何格式的。要统一格式,一条UPDATE就行:

UPDATE users SET phone = REPLACE(REPLACE(phone, '-', ''), ' ', '') WHERE phone LIKE '%-%' OR phone LIKE '% %';

这种操作比先把数据导出来、用程序处理完再导回去高效得多。注意REPLACE是替换所有匹配项,不是只替换第一个,这点在业务上要清楚。另一个容易忽略的点:REPLACE也可以用于修正历史数据中的错别字或规则变更,比如把旧编码“BJ”统一替换成“BEIJING”,一秒钟完成全表更新。

有一点要提醒:如果REPLACE的目标字段上有索引,大规模UPDATE会触发索引更新,锁表时间可能较长。生产环境大表做这种操作,最好避开业务高峰期,或者分批执行。

3.3 TRIM系列:去除空格之外的额外能力

TRIM最常见的用法是去掉字符串前后的空格。LTRIM只去左边,RTRIM只去右边。实际业务里导出的Excel字段经常带着换行符和回车符,直接用TRIM去不掉,需要配合REPLACE把换行符替换掉。

SELECT TRIM(' abc '); -- 结果:abc

TRIM还有一个比较少有人用的扩展语法:TRIM(removed_str FROM str),可以去掉指定字符。比如去掉字符串首尾的“#”号:

SELECT TRIM('#' FROM '###abc###'); -- 结果:abc

这在处理被特定符号包裹的字段时很实用。还有平时做报表时经常遇到字符串左右长短不齐,用LPAD和RPAD可以补齐到指定长度。LPAD的意思是LEFT PAD,在字符串左边填充指定字符;RPAD在右边填充。比如编号字段统一补成8位:

SELECT LPAD('123', 8, '0'); -- 结果:00000123

4. 排序、UPDATE和存储过程里的字符串函数实战

4.1 ORDER BY配合字符串函数解决排序错乱

字符串函数不只是用来SELECT查询,在ORDER BY里也经常要做手脚。MySQL默认排序是按照字符集规则排的,数字字符串排序会按字典序排,所以“10”会排在“9”前面,这是很多人遇到过的坑。

比如版本号字段存的是“1.0.9”、“1.0.10”,直接ORDER BY版本号升序,结果是1.0.10排在1.0.9前面。要按真实数字语义排,可以配合REPLACE去掉小数点后转成数值,或者用CAST转成DECIMAL。更通用的办法是把版本号拆成多段再排,但实现会比较绕。

实际业务里最常见的需求是“从字符串中提取数字后再排序”,比如门牌号“A-101”、“A-102”。低版本MySQL没有REGEXP_REPLACE,要提取数字稍微费劲;MySQL 8.0开始可以用REGEXP_REPLACE把非数字字符替换掉,然后转成数值排序:

SELECT * FROM rooms ORDER BY CAST(REGEXP_REPLACE(room_code, '[^0-9]', '') AS UNSIGNED);

4.2 UPDATE结合REPLACE修正全表数据

前面提过用REPLACE清洗手机号,这里补充一个更复杂一点的场景:把地址字段里的“XX市”统一改成“XX省XX市”。比如一批数据里存的是“深圳市南山区”,需要变成“广东省深圳市南山区”,但省名跟市名不是一个字段,就得根据固定的市名列表逐条处理。

这种场景下用UPDATE加REPLACE依然高效,但要注意匹配的准确性,防止把“深圳市”错误匹配到“深州市”这类相近地名。如果规则比较复杂,我的建议是在UPDATE之前先SELECT统计一下受影响的行数,确认无误后再执行UPDATE。

还有一个实际操作中的经验:UPDATE之前先备份原表,或者把原值复制到一个新的临时列,便于出错时回滚。特别是影响的数据量比较大时,这种回退手段是必须的。

4.3 存储过程和GROUP BY里的字符串函数组合

存储过程是另一个字符串函数大量出没的地方。我之前写过一个批量清理日志数据的存储过程,流程就是先用SUBSTRING_INDEX解析日志文件名里的日期,再判断日期是否在保留范围内,最后拼接DELETE语句删除对应表的数据。中间用了至少五六个字符串函数,包括CONCAT、SUBSTRING_INDEX、DATE_FORMAT配合使用。

GROUP BY里用字符串函数的场景也很常见。比如按地址字段里的省份分组统计用户数,就需要先截取出省份字段再用它分组。由于MySQL的GROUP BY可以在SELECT后面使用别名,写法可以很简洁:

SELECT SUBSTRING_INDEX(address, '省', 1) AS province, COUNT(*) AS user_count FROM users GROUP BY province;

这里先截取省份,再用province作为别名分组。需要注意GROUP BY中使用别名是MySQL允许的扩展功能,其他数据库不一定支持。

5. 正则匹配、大小写与编码:进阶场景不能乱来

5.1 REGEXP做灵活匹配:LIKE的加强版

LIKE查询只能做简单的通配符匹配,正则表达式则能处理更复杂的模式。MySQL里用REGEXP关键字(同义词是RLIKE)做正则匹配,比如判断手机号是否合法:

SELECT * FROM users WHERE phone REGEXP '^1[3-9][0-9]{9}$';

这条语句能筛选出符合大陆手机号基本格式的记录。再比如找到所有以数字开头、以字母结尾的编码字段。正则的灵活性远高于LIKE,但要注意性能问题。正则匹配没法利用普通索引,每行都要全表扫描判断,数据量大时查询会明显变慢。

5.2 大小写转换与排序规则(COLLATE)的关系

UPPER和LOWER函数可以对字符串做大小写转换,这在做不区分大小写的匹配时很有用。比如用户输入的用户名可能是大写或小写,统一转成小写再比较可以避免漏查:

SELECT * FROM users WHERE LOWER(username) = LOWER('Admin');

但这里有个性能隐患:左边用了函数后,索引失效。更推荐的做法是把查询条件也转成小写后跟字段比较,或者应用“字段函数”改为“条件函数”。另外MySQL的排序规则本身也影响大小写比较,例如utf8mb4_general_ci和utf8mb4_bin对字母大小写是否敏感就不同。前者大小写不敏感,后者大小写敏感。平常开发时要注意表的COLLATE设置,否则明明两个值看起来一样,WHERE却匹配不到。

5.3 字符串函数在WHERE条件里的索引失效问题

这是字符串函数使用中最值得注意的性能问题。只要WHERE里对字段用了函数,MySQL就难以利用该字段上的索引,因为它需要先对每一行做函数运算,再比较结果。这在数据量小的表上感觉不明显,在千万级大表上就是灾难。

-- 索引失效写法 SELECT * FROM t WHERE LEFT(name, 3) = '张'; -- 可以考虑的替代写法(范围条件,索引可用) SELECT * FROM t WHERE name LIKE '张%';

我遇到过不少案例是把这种写法写进了线上接口,结果分页查询越来越慢,直到超时才排查出来。所以写SQL时有一个习惯:尽量避免在WHERE子句中对索引列应用函数,如果实在需要用,优先考虑改写为范围查询或者等值查询;或者建立函数索引(MySQL 8.0支持在索引中使用表达式)来缓解。

6. 高频问题排查速查:从报错到结果异常

6.1 字符串函数常见坑对照表

用字符串函数时遇到的问题,大多不是函数本身报错,而是结果跟预期不一致。我把实战中遇到的高频问题整理成了下面这张表。

问题现象常见原因解决方案
CONCAT结果变成NULL某个参数为NULL用IFNULL包裹参数,或改用CONCAT_WS
中文字符串长度不对用了LENGTH而不是CHAR_LENGTH长度校验用CHAR_LENGTH
排序结果数字错乱字符串按字典序排列用CAST/REGEXP_REPLACE转为数值排序
两端空格去不掉存在换行符或制表符先用REPLACE去掉\r\n再TRIM
SUBSTRING截取结果少一位位置从1开始而不是0检查pos参数是否从1开始数
大小写匹配不到排序规则大小写不敏感改用utf8mb4_bin或使用COLLATE指定规则
WHERE用函数后变慢索引失效改写为范围条件或函数索引
版本号排序不对点分字符串字典序问题拆段转数值再排序

6.2 我的排查习惯与两个保底技巧

遇到字符串函数结果不对时,我的第一步永远是单独SELECT出来看,不直接套UPDATE。比如怀疑某个字段里有没有看不见的字符,就把字段转成十六进制看它的二进制内容:

SELECT HEX(column_name) FROM table_name WHERE id = 1;

这个办法能直接看到不可见字符的编码,例如空格是20,换行是0A,回车是0D。很多时候数据“看似干净”实际不干净,用HEX一查就现原形。

第二个保底技巧是在做复杂字符串处理之前,先用SELECT把处理后的结果查出来检查,确认无误后再用同样的表达式放进UPDATE。这种做法虽然多一步操作,但能避免错误UPDATE写坏整张表。

还有一次排查乱了半天,发现是字符集问题:表的字符集是latin1,英文字符正常,中文全变成了乱码,当时用的LENGTH、SUBSTRING这些函数都基于当前字符集解析,结果自然全错。后来统一改成utf8mb4后问题消失。这里给各位提个醒:遇到莫名其妙的中文截断或长度不对,先用下面这条语句看一下表的字符集:

SHOW CREATE TABLE table_name;

6.3 实际走一遍:从需求到SQL的过程

最后用一个完整的例子串一遍今天讲的这些函数。假设有个需求:从订单表里提取出客户地址的省份,按省份统计订单数量,并生成“省份-数量”的拼接字段,同时只统计数量大于100的省份,按数量降序排列。

地址字段存的是“XX省XX市XX区XX路XX号”这类格式。

先写统计SQL:

SELECT SUBSTRING_INDEX(address, '省', 1) AS province, COUNT(*) AS order_count FROM orders GROUP BY province HAVING order_count > 100 ORDER BY order_count DESC;

如果要生成拼接字段,就再套一层CONCAT或CONCAT_WS:

SELECT CONCAT(SUBSTRING_INDEX(address, '省', 1), '-', COUNT(*)) AS province_order FROM orders GROUP BY province HAVING COUNT(*) > 100 ORDER BY COUNT(*) DESC;

这个例子看着不复杂,但它把SUBSTRING_INDEX、CONCAT、GROUP BY、HAVING、ORDER BY全部串起来了,而且每一步背后都有明确的业务意图,不是生搬硬套。

就我个人的经验而言,MySQL字符串函数的学习节奏应该是“先掌握最核心的七八个,再到具体业务场景里补充”,不需要一开始就把文档啃完。真正让我熟练起来的,不是看文档,而是不断处理类似“地址解析”“手机号清洗”“编码转换”这种需求。每处理一次,对函数组合的理解就深一层。下次再遇到字符串相关的需求,你可以先别急着写代码,停下来想一想:这个操作用哪几个函数能实现?组合顺序是什么?NULL怎么处理?字符集有没有影响?想清楚这几步,SQL基本就写对了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 7:14:18

增长停滞诊断框架:5步定位用户流失与激活问题

一位做增长的朋友跟我说过一句话:做产品最难受的时刻,不是没量,而是“不知道为什么不涨了”。数据上周还在涨,这周突然停下来,团队已经开始做实验,但每个人对原因的判断都不一样。你问产品,他说…

作者头像 李华
网站建设 2026/10/19 15:43:31

反向传播与梯度下降:大模型训练底层原理与工程实践

1. 从一次训练损失不降的排查说起很多人第一次接触大模型,注意力都被参数量、上下文长度、微调数据量这些"看得见"的东西吸引,反而把反向传播和梯度下降当成两个背下来的名词。我带过几个刚入行的同学,他们能说清楚Transformer的注…

作者头像 李华
网站建设 2026/10/19 15:43:32

基于GPT-6的企业级AI工作伙伴OpenWorkMate:RAG架构与权限控制实战

1. 从“只会聊天”到“真能干活”:企业AI工作伙伴到底缺了什么公司里那套AI助手,十有八九你都用过——问它“年假怎么算”,它给你背一遍员工手册;问它“上季度华东区退货率”,它开始一本正经地编数字。问题不在于模型不…

作者头像 李华
网站建设 2026/10/19 15:43:33

Windows 上安装配置 Claude Code 全流程:环境搭建、VSCode 集成与避坑指南

1. 为什么要在 Windows 上认真折腾 Claude Code如果你平时主力开发环境是 Windows,又恰好对命令行 AI 编程助手这类工具感兴趣,那 Claude Code 这个名字大概率已经在你视野里晃过好几轮了。它本质上是一个跑在终端里的 AI 编程代理,能直接读写…

作者头像 李华
网站建设 2026/10/19 15:43:31

text-to-cad实战:从自然语言到可编辑CAD模型的完整工作流

凌晨两点,我对着电脑屏幕把一段微信消息来回看了三遍——"轴承座外侧再加两个M6的安装孔,孔距40,别碰到加强筋"。一台已经出图的设备要做设计变更,落到图纸上意味着改三个视图、更新两处标注、再检查一遍干涉和装配关系…

作者头像 李华
网站建设 2026/10/19 17:52:44

printspooler binder超时导致焦点切换阻塞的排查与修复

printspooler这个组件,平时很少有人注意它,但一旦它出问题,用户感受到的就是“整个系统界面像冻住了一样”。最近我处理了一个典型的案例:com.android.printspooler主线程binder通信超时,直接导致焦点切换阻塞。这台设…

作者头像 李华