MySQL无内置函数直接提取所有连续数字,8.0+的REGEXP_SUBSTR仅返回首个匹配项;需用自定义函数配合WHILE循环逐段提取,每次调用返回下一个数字,外部需维护起始位置状态。

MySQL 中没有内置函数能直接提取字符串里的连续数字
MySQL 原生不提供类似正则提取捕获组(如 REGEXP_SUBSTR)的功能,5.7 及更早版本连 REGEXP_SUBSTR 都不存在;8.0+ 虽支持,但仅返回**第一个匹配项**,无法循环提取所有连续数字段。所以想“把 'abc123def4567gh89' 拆成 123、4567、89”,必须靠自定义函数 + 循环逻辑。
用存储函数 + WHILE 实现逐段提取数字
核心思路是:用 REGEXP 定位数字起始位置,用 SUBSTRING 截取,再更新剩余字符串继续找。注意 MySQL 函数不能返回结果集,只能单次返回一个值,所以这个函数设计为「每次调用返回下一个连续数字」,需配合外部循环(比如在存储过程里反复调用)。
实操建议:
- 函数入参为原始字符串和当前搜索起始位置(初始传 1)
- 用
LOCATE或REGEXP判断是否存在数字:推荐str REGEXP '[0-9]',但注意它不返回位置 —— 所以实际要用REGEXP_REPLACE配合占位符间接定位,或改用LOCATE配合循环扫描(更稳) - 真正可靠的做法是:从
pos开始逐字符检查SUBSTRING(str, pos, 1)是否匹配'[0-9]',找到第一个数字后,向后扩展直到非数字为止 - 函数必须声明
DETERMINISTIC,否则创建失败
示例关键片段(简化版):
DELIMITER $$
CREATE FUNCTION extract_next_number(str TEXT, start_pos INT)
RETURNS VARCHAR(50)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE i INT DEFAULT start_pos;
DECLARE len INT DEFAULT CHAR_LENGTH(str);
DECLARE num_start INT DEFAULT 0;
DECLARE num_end INT DEFAULT 0;
<p>-- 找数字起点
WHILE i <= len DO
IF SUBSTRING(str, i, 1) REGEXP '[0-9]' THEN
SET num_start = i;
LEAVE;
END IF;
SET i = i + 1;
END WHILE;</p><p>IF num_start = 0 THEN RETURN NULL; END IF;</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill2334" title="MySQL"><img
src="https://img.php.cn/upload/skill/000/000/081/178900927846657.jpg" alt="MySQL" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill2334" title="MySQL">MySQL</a>
<p>编写正确的MySQL查询,避免字符集、索引和锁方面的常见陷阱。</p>
</div>
<a href="/xiazai/skill2334" title="MySQL" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><p>-- 找数字终点
SET i = num_start;
WHILE i <= len DO
IF SUBSTRING(str, i, 1) REGEXP '[0-9]' THEN
SET i = i + 1;
ELSE
SET num_end = i - 1;
LEAVE;
END IF;
END WHILE;
IF num_end = 0 THEN SET num_end = len; END IF;</p><p>RETURN SUBSTRING(str, num_start, num_end - num_start + 1);
END$$
DELIMITER ;
调用时必须自己维护状态,不能“一键全出”
这个 extract_next_number 函数本身不记录上次提取到哪,每次调用都是独立的。所以你不能写 SELECT extract_next_number('a1b22c333', 1) 就拿到全部 —— 它只返回 '1'。要拿到全部,得在外面套一层逻辑:
- 在存储过程中用
WHILE循环,每次传入新起始位置(比如上一次结束位置 + 1) - 起始位置不能硬写 1,而要动态计算:上一轮返回了
'123'且起始于 4,长度 3 → 下次从 7 开始 - 函数返回
NULL表示无更多数字,作为循环退出条件 - 别忘了处理空格、负号、小数点等边界情况 —— 当前函数只认纯阿拉伯数字连续段
8.0+ 用户可考虑 JSON + 正则临时方案(不推荐生产)
有人用 REGEXP_REPLACE 把非数字全替换成逗号,再用 JSON_TABLE 拆分,例如:
SELECT TRIM(val) AS num
FROM JSON_TABLE(
CONCAT('["', REGEXP_REPLACE('abc123def4567', '[^0-9]+', '","'), '"]'),
'$[*]' COLUMNS (val TEXT PATH '$')
) AS jt
WHERE val != '';
但这只是“看起来像提取”,实际依赖字符串拼接和 JSON 解析,对含引号、反斜杠的原始数据会崩,且性能差、不可读。真正需要稳定提取,请坚持用带状态管理的自定义函数。
最易被忽略的一点:MySQL 函数内不能执行 SELECT ... INTO 写变量(除非目标是用户变量),也不能调用会修改数据的语句。所有逻辑必须靠纯计算和字符串操作完成 —— 这限制了容错空间,比如超长数字可能被截断为科学计数法,得提前用 CAST(... AS CHAR) 控制格式。

















