SQL中如何实现类似SPLIT的字符串分割功能?

来源:Python编程网作者:杨子江头衔:网络博主
导读:本期聚焦于杨子江创作的《SQL中如何实现类似SPLIT的字符串分割功能?》,敬请观看详情。拿到一串逗号分隔的编号列表,却要把它拆成多行才能参与关联查询,是数据清洗和报表开发中绕不开的场景。不同数据库对字符串分割的支持差异很大:MySQL 8.0之前没有原生SPLIT函数,通常依赖SUBSTRING_INDEX配合辅助数字表,8.0以后可用JSON_TABLE或递归CTE优雅展开;SQL Server从2016版开始提供STRING_SPLIT,但不保证顺序,且不支持多字符分隔符;PostgreSQL的string_to_array与unnest组合最灵活,还能保留下标;Oracle则常用REGEXP_SUBSTR配合CONNECT BY。本文从单条记录拆分、多行展开到性能对比,给出各主流数据库的可运行示例,并分析分隔符为空、拆分后需要排序、数据量大时的优化思路。掌握这些写法后,再遇到按逗号、竖线、分号拆字段的需求,可以快速选择适合当前数据库的实现方案,避免把所有压力交给应用层。

在报表开发或数据清洗任务中,经常遇到某个字段存放的是逗号分隔的多个值,例如订单表的标签列保存了 1,3,7 这样的内容。为了与标签表做关联查询,需要把 1,3,7 拆成三行记录。SQL 标准并没有定义通用的 SPLIT 函数,各个数据库的实现方式差异较大,有的提供原生函数,有的则需要借助递归或 JSON 能力完成拆分。理解这些方案的区别后,才能根据当前数据库版本和业务场景选择最合适的写法。

SQL中如何实现类似SPLIT的字符串分割功能?

下面分别从 MySQL、SQL Server、PostgreSQL、Oracle 和 SQLite 几个主流数据库出发,介绍字符串拆分的典型实现,并讨论性能与通用性之间的权衡。

一、MySQL:从 SUBSTRING_INDEX 到 JSON_TABLE 的多种拆法

MySQL 8.0 之前没有原生的 SPLIT 函数,最常用的技巧是借助 SUBSTRING_INDEX 和辅助数字表。SUBSTRING_INDEX 函数接收三个参数:原始字符串、分隔符和序号。当序号为正数时,返回从左侧开始第几个分隔符之前的子串;当序号为负数时,返回从右侧开始数第几个分隔符之后的子串。例如拆分 a,b,c 时,可以通过嵌套调用取出中间部分。

SELECT SUBSTRING_INDEX('a,b,c', ',', 1) AS part1,
       SUBSTRING_INDEX(SUBSTRING_INDEX('a,b,c', ',', 2), ',', -1) AS part2,
       SUBSTRING_INDEX('a,b,c', ',', -1) AS part3;

实际问题中,字段里分隔符的数量并不固定,因此需要动态生成序号。可以通过关联一个数字表或系统表来产生序号,再用序号控制 SUBSTRING_INDEX 的截取位置。下面的示例使用数字序列把订单中的标签字段拆成多行,其中分隔符个数通过字符串长度差计算得到。

SELECT 
  SUBSTRING_INDEX(SUBSTRING_INDEX(t.tags, ',', n.num), ',', -1) AS tag_id
FROM orders t
JOIN (
  SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) n
ON n.num <= LENGTH(t.tags) - LENGTH(REPLACE(t.tags, ',', '')) + 1
WHERE t.id = 100;

这种写法在拆分固定少量元素时比较直观,但如果元素数量超过数字表的行数,就需要扩大数字表范围。另一个问题是每次取子串都要重复扫描整个字符串,数据量较大时性能偏差。因此在实际项目中,它更适合小批量数据或临时查询。

MySQL 8.0 引入了 JSON_TABLE,可以先通过 REPLACE 把逗号分隔字符串包装成 JSON 数组,再使用 JSON_TABLE 展开为行。这种写法非常简洁,特别适合数字或简单标识符列表。

SELECT jt.*
FROM orders t,
JSON_TABLE(
  CONCAT('["', REPLACE(t.tags, ',', '","'), '"]'),
  '$[*]' COLUMNS (tag_id VARCHAR(20) PATH '$')
) AS jt;

如果拆分的值中可能包含双引号或反斜杠,需要先对内容做转义,否则构造出来的 JSON 可能不合法。除了 JSON_TABLE,MySQL 8 也支持递归公用表表达式 CTE 来逐个截取逗号分隔值。递归 CTE 的可读性较高,但在数据行数很多时容易触发递归深度限制,同样需要谨慎使用。

WITH RECURSIVE split_cte AS (
  SELECT id, tags,
         SUBSTRING_INDEX(tags, ',', 1) AS part,
         IF(LOCATE(',', tags) != 0, SUBSTRING(tags, LOCATE(',', tags) + 1), '') AS rest
  FROM orders
  UNION ALL
  SELECT id, tags,
         SUBSTRING_INDEX(rest, ',', 1),
         IF(LOCATE(',', rest) != 0, SUBSTRING(rest, LOCATE(',', rest) + 1), '')
  FROM split_cte
  WHERE rest != ''
)
SELECT id, part FROM split_cte;

二、SQL Server 与 PostgreSQL:原生函数与数组展开

SQL Server 从 2016 版本开始提供 STRING_SPLIT 函数,可以直接按分隔符把字符串拆成行,返回列名 value。例如拆分 a,b,c 只需要一个简单查询。

SELECT value
FROM STRING_SPLIT('a,b,c', ',');

不过 STRING_SPLIT 有两个明显限制:一是不保证返回顺序,即使输入是 a,b,c,输出顺序也可能是 c,a,b;二是分隔符只支持单个字符,无法直接按逗号加空格这种多字符规则拆分。如果业务必须保留原有顺序,可以考虑使用 OPENJSON,先把字符串构造成 JSON 数组再展开,并通过 key 列获取下标。

SELECT j.[key], j.value
FROM OPENJSON('["a","b","c"]') j;

SQL Server 2022 对 STRING_SPLIT 增加了 ordinal 参数,可以在拆分时返回序号。不过很多生产环境并未升级到该版本,因此旧库中仍然需要借助 JSON 或自定义函数。如果只是临时拆分少量记录,也可以在应用层把值传成表变量再关联,避免在 SQL 内处理顺序问题。

PostgreSQL 的字符串拆分能力则灵活得多。它没有单独的 split 函数,但可以通过 string_to_array 把字符串转成数组,再配合 unnest 展开为多行,一行代码即可完成拆分。

SELECT unnest(string_to_array('a,b,c', ',')) AS part;

如果需要知道每个拆分值在原字符串中的位置,可以使用 WITH ORDINALITY 语法把数组下标一并返回。PostgreSQL 还提供了 regexp_split_to_table 函数,支持正则表达式作为分隔规则,因此可以轻松处理多字符分隔符或者包含空格的复杂格式。

SELECT elem, ord
FROM unnest(string_to_array('a,b,c', ',')) WITH ORDINALITY AS t(elem, ord);

SELECT regexp_split_to_table('a||b||c', '\|\|');

三、Oracle 与 SQLite:正则和递归路线的代表

Oracle 没有内置的 SPLIT 函数,但可以利用 CONNECT BY 层级查询配合 REGEXP_SUBSTR 实现拆分。REGEXP_SUBSTR 可以按正则表达式提取第 n 个子串,CONNECT BY 则负责生成连续序号。下面的示例按逗号拆分 a,b,c,并从 DUAL 表返回三行记录。

SELECT REGEXP_SUBSTR('a,b,c', '[^,]+', 1, LEVEL) AS part
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT('a,b,c', ',') + 1;

这种写法在执行单条拆分时非常简洁,但当源表包含多行数据时,直接把 CONNECT BY 放在同一查询里容易产生笛卡尔积,导致行数爆炸。更稳健的做法是用横向派生表或自定义 PL/SQL 函数来控制层级条件。Oracle 12c 以后还可以结合 LATERAL 视图来展开每一行,把层级生成限制在对应行的范围内。

SQLite 同样没有原生 split 函数,通常使用递归 CTE 模拟拆分过程。其思路是先给原字符串末尾补一个分隔符,然后用 instr 定位分隔符位置,逐次用 substr 截取第一个元素,同时截掉已经处理过的部分,直到找不到分隔符为止。

WITH RECURSIVE split(id, part, rest) AS (
  SELECT id, '', tags || ','
  FROM orders
  UNION ALL
  SELECT id,
         substr(rest, 1, instr(rest, ',') - 1),
         substr(rest, instr(rest, ',') + 1)
  FROM split
  WHERE instr(rest, ',') != 0
)
SELECT id, part FROM split WHERE part != '';

SQLite 的递归 CTE 默认有递归深度限制,如果一条记录包含大量分隔符,可能会触发限制。对于移动端或嵌入式场景,SQLite 中的字符串拆分通常数量不大,这种写法已经足够。若数据量较大,建议先在应用层完成拆分,再批量写入临时表。

四、跨数据库通用思路与性能注意事项

如果项目需要兼容多种数据库,最好避免在 SQL 内做过于复杂的字符串拆分,而将拆分工作放在应用层完成。比如 Python、Java 等语言可以轻松把逗号分隔字符串转成集合,再通过临时表或参数化查询把结果传回数据库。这样做的优点是逻辑清晰、便于单元测试,也能减少数据库 CPU 压力,缺点是会增加应用代码量和网络传输成本。

对于必须在数据库内拆分的场景,优先选择原生能力:PostgreSQL 的 string_to_array + unnest 最灵活;SQL Server 2016 以上可以直接用 STRING_SPLIT,但要注意顺序和多字符分隔符限制;MySQL 8 使用 JSON_TABLE 时性能通常优于辅助数字表;Oracle 的 CONNECT BY 方案要防止层级查询扩散;SQLite 递归 CTE 适合小数据量。判断性能时,可以使用 EXPLAIN 或执行计划检查拆分函数是否被重复调用,以及中间结果是否被过度放大。

综合来看,字符串拆分没有一种方案能在所有数据库中既高效又通用。理解不同数据库的执行机制后,再结合数据量、版本和业务对顺序的要求,才能选择最稳妥的实现。如果只是临时处理少量记录,最简单的原生函数即可;如果是高频任务,则建议把拆分结果落成明细表,避免每次查询都重复计算。

SQL字符串分割SPLIT函数分隔符拆分行修改时间:2026-08-23 18:06:10

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。