前言
聊聊SQL里的正则表达式。这东西,说穿了就是个模式匹配工具,但用好了真的能帮你解决很多头疼的文本处理问题。比如验证用户输入的邮箱格式是否合法、从一堆杂乱日志里把电话号码扒出来、或者批量清洗数据里的特殊符号。虽然标准SQL规范里没有给正则一个统一的“正统地位”,但好在主流的数据库厂商们都很识趣,纷纷通过扩展函数把这块功能补齐了。下面,我们从基础概念、语法规则、主流数据库实现、常见场景以及注意事项这几个维度,掰开了揉碎了讲清楚。

一、正则表达式的核心概念
正则表达式本质上是由特殊字符和普通字符组合成的一种模式字符串,用来描述你希望匹配的文本规则。在SQL的世界里,它主要有三大用武之地:
- 模式匹配:判断一个字符串是否符合某个特定格式,比如手机号、URL。这是最常用的场景。
- 内容提取:从一个长字符串里,把符合规则的子串给揪出来,比如从商品描述里提取所有数字价格。
- 数据清洗:替换或删除那些不符合规则的字符,比如去掉用户昵称里的表情符号。
为啥要用正则呢?因为传统的LIKE操作符实在太“笨”了,翻来覆去就只会用%和_这两个通配符。正则能实现的复杂逻辑——比如“至少包含3个数字”、“必须是以字母开头”——对LIKE来说,基本是mission impossible。
二、正则表达式的基础语法规则
SQL里常用的正则元字符,跟你在其他编程语言里看到的几乎是一个模子刻出来的。核心规则都在下面这张表里,建议大家收藏起来当字典用:
| 元字符 | 含义 | 示例 |
| . | 匹配任意单个字符(除换行符) | a.b 匹配 "aab"、"acb"(中间任意字符) |
| * | 匹配前一个字符 0 次或多次 | ab* 匹配 "a"、"ab"、"abb" |
| + | 匹配前一个字符 1 次或多次 | ab+ 匹配 "ab"、"abb"(至少 1 个 b) |
| ? | 匹配前一个字符 0 次或 1 次 | ab? 匹配 "a"、"ab"(最多 1 个 b) |
| ^ | 匹配字符串开头 | ^abc 匹配 "abc123"(以 abc 开头) |
| $ | 匹配字符串结尾 | xyz$ 匹配 "123xyz"(以 xyz 结尾 |
| [] | 匹配括号内的任意一个字符 | [0-9] 匹配任意数字;[a-zA-Z]匹配字母 |
| [^] | 匹配不在括号内的任意一个字符 | [^0-9] 匹配非数字字符 |
| {n} | 匹配前一个字符恰好 n 次 | a{3} 匹配 "aaa" |
| {n,} | 匹配前一个字符至少 n 次 | a{2,} 匹配 "aa"、"aaa" |
| {n,m} | 匹配前一个字符 n 到 m 次 | a{1,3} 匹配 "a"、"aa"、"aaa" |
| | | 逻辑 "或",匹配两边任意一个模式 | ab|cd 匹配 "ab" 或 "cd" |
| () | 分组,将多个字符视为一个整体 | (ab)+ 匹配 "ab"、"abab" |
| d | 匹配数字(等价于[0-9]) | d{3} 匹配 3 位数字 |
| D | 匹配非数字(等价于[^0-9]) | D+ 匹配连续非数字字符 |
| w | 匹配字母、数字、下划线(等价于[a-zA-Z0-9_]) | w+ 匹配单词或标识符 |
| W | 匹配非字母、数字、下划线 | W 匹配特殊符号(如@、#) |
三、主流数据库的正则表达式函数
因为标准SQL没规定统一的写法,所以各家数据库都是自己玩自己的。但好在万变不离其宗,核心功能就三大类:“匹配判断”、“替换”和“提取”。咱们一个一个来看。
1. 模式匹配判断(最常用)
这个最基础,就是判断字符串“是”或“不是”符合某个模式,返回TRUE/FALSE或者1/0。
| 数据库 | 函数语法 | 说明 |
| MySQL | column REGEXP 'pattern' | 匹配返回 1,不匹配返回 0;忽略大小写需加i(如REGEXP BINARY 'pattern'区分大小写) |
| PostgreSQL | column ~ 'pattern' | ~ 区分大小写,~* 不区分大小写 |
| SQL Server | PATINDEX('%pattern%', column) > 0 | 非严格正则(支持_、%和[],不支持*、+等,需用LIKE增强或 CLR 扩展) |
| Oracle | REGEXP_LIKE(column, 'pattern', 'match_param') | match_param可指定i(忽略大小写)、c(区分大小写) |
示例 1:匹配手机号(11 位数字,以 1 开头)
-- MySQL
SELECT phone FROM users WHERE phone REGEXP '^1\d{10}$'; -- 注意转义:d需写成\d
-- PostgreSQL
SELECT phone FROM users WHERE phone ~ '^1d{10}$'; -- PostgreSQL无需额外转义
-- Oracle
SELECT phone FROM users WHERE REGEXP_LIKE(phone, '^1d{10}$');
示例 2:匹配邮箱格式(包含 @和.)
-- 通用逻辑:包含@,@后有.,且.不在末尾
SELECT email FROM users WHERE email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$';
2. 字符串替换(按正则规则替换)
这个功能在数据清洗时特别好用,能把符合正则模式的子串一键替换成你想要的内容。
| 数据库 | 函数语法 | 说明 |
| MySQL | REGEXP_REPLACE(column, 'pattern', 'replacement') | 替换所有匹配的子串;加i忽略大小写(如REGEXP_REPLACE(col, 'pattern', 'rep', 1, 0, 'i')) |
| PostgreSQL | REGEXP_REPLACE(column, 'pattern', 'replacement', 'flags') | flags可指定g(全局替换)、i(忽略大小写) |
| Oracle | REGEXP_REPLACE(column, 'pattern', 'replacement', position, occurrence, match_param) | 支持指定起始位置、替换次数 |
示例:去除字符串中的所有特殊符号(保留字母、数字、下划线)
-- MySQL:将非单词字符(W)替换为空 SELECT REGEXP_REPLACE(desc, '[^a-zA-Z0-9_]', '') AS clean_desc FROM products; -- PostgreSQL:全局替换非单词字符 SELECT REGEXP_REPLACE(desc, 'W', '', 'g') AS clean_desc FROM products;
3. 子串提取(提取符合正则的内容)
从一堆混乱的文本里,把你要的结构化信息精准地“挖”出来。
| 数据库 | 函数语法 | 说明 |
| MySQL | REGEXP_SUBSTR(column, 'pattern') | 提取第一个匹配的子串;加i忽略大小写 |
| PostgreSQL | REGEXP_MATCHES(column, 'pattern', 'flags') | 返回所有匹配的子串(数组形式),flags指定g全局提取 |
| Oracle | REGEXP_SUBSTR(column, 'pattern', position, occurrence, match_param) | 提取第 N 个匹配的子串 |
示例:从文本中提取所有数字
-- MySQL:提取第一个数字 SELECT REGEXP_SUBSTR(desc, '\d+') AS first_number FROM logs; -- PostgreSQL:提取所有数字(返回数组) SELECT REGEXP_MATCHES(desc, '\d+', 'g') AS all_numbers FROM logs;
四、常见应用场景
1. 数据验证(确保格式正确)
- 验证手机号、邮箱、身份证号等格式是否合法,把那些“浑水摸鱼”的脏数据揪出来。
- 示例:筛选出格式错误的邮箱(无 @或无.):
SELECT email FROM users WHERE email NOT REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$';
2. 内容清洗(去除无效字符)
- 去除文本中的特殊符号、多余空格,甚至是恼人的HTML标签。
- 示例:去除字符串中的 HTML 标签(如
、):
-- 匹配<...>格式的标签并替换为空 SELECT REGEXP_REPLACE(html_content, '<[^>]+>', '', 'g') AS plain_text FROM articles;
3. 信息提取(从文本中提取关键数据)
- 从日志、商品描述中提取日期、金额、ID、IP地址等结构化信息。
- 示例:从日志中提取 IP 地址(如
192.168.1.1):
-- IP地址格式:4组0-255的数字,用.分隔
SELECT REGEXP_SUBSTR(log, '\b(?:\d{1,3}\.){3}\d{1,3}\b') AS ip FROM system_logs;
五、性能问题与解决方案
正则虽好,可不能贪杯。在数据量大的时候,如果使用不当,很容易成为性能杀手。这里有几个非常现实的坑和对应的填坑办法。
1. 避免在大表上无索引使用正则
问题:直接在WHERE条件里用column REGEXP 'pattern',数据库只能老老实实地进行全表扫描,因为它没法利用B+树索引。一旦表里有几百万行数据,这条查询慢到让你怀疑人生。解决方案:
- 先用一个简单的条件(比如
LIKE)把数据范围大幅缩小,然后再对剩下的数据用正则做精确匹配:
-- 先过滤包含@的邮箱(减少正则处理的数据量),再验证完整格式
SELECT email FROM users WHERE email LIKE '%@%' -- 快速过滤无@的记录
AND email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$';
- 对于需要频繁进行正则匹配的字段,可以考虑预计算。比如新增一个
is_valid_email的布尔字段,在数据入库或定期任务里用正则更新好,查询时直接走索引查这个字段就行。
2. 简化正则模式,减少回溯
问题:正则的匹配逻辑非常复杂,尤其是嵌套分组、贪婪匹配这些,容易导致大量的回溯操作,计算耗时可能呈指数级增长。解决方案:
- 能用非贪婪匹配(
*?、+?)的地方,就别用贪婪匹配(*、+),能省下很多不必要的尝试; - 避免写过于复杂的单条正则。如果模式太复杂,不妨拆成多个简单的条件,用
AND/OR组合起来,可读性和性能往往都有提升。
3. 区分大小写匹配更高效
问题:忽略大小写的匹配(比如REGEXP 'pattern' i)需要在运行时额外处理字符的大小写转换,肯定比直接区分大小写慢。解决方案:如果业务逻辑允许,优先使用区分大小写的匹配。如果必须忽略大小写,确保你的数据量或者处理的数据范围是可控的。
六、注意事项
- 转义字符差异:这是个非常容易踩坑的地方。在MySQL里,
\是转义符,所以写\d才能表示数字;但在PostgreSQL和Oracle里,你直接写d就行。换了个数据库,正则就得跟着改。 - 兼容性有限:SQL Server对正则的支持简直是“后妈养的”,它那个
PATINDEX功能很简陋,复杂点的场景基本无能为力。真要玩转正则,要么用LIKE加[]凑合,要么就得通过CLR集成.NET的正则库,门槛高不少。 - 谨慎用于更新 / 删除:正则匹配的结果有时候会有意想不到的边缘case。在批量
UPDATE或DELETE之前,强烈建议先跑一遍SELECT把匹配结果看一眼,确认没问题了再动手。数据安全第一。
七、总结
正则表达式绝对是SQL里处理复杂字符串的一把“瑞士军刀”。核心价值就三个:模式匹配、内容清洗、信息提取。使用时需要时刻牢记三点:
- 不同数据库的函数语法各不相同,换了个环境就要调整写法;
- 在大数据量场景下,一定要有性能意识,通过“先过滤后正则”、“预计算”等方式优化,避免全表扫描的噩梦;
- 正则不是越复杂越好,平衡好匹配规则的精确度与SQL语句的可读性、执行效率,才是高手之道。
把这套东西掌握了,你会发现自己处理起文本数据来,真的能灵活很多。
