mysql 8.0+ 用 json_table 拆分逗号字符串需先用 replace 和 concat 转为合法 json 数组格式,再通过 json_table 提取并聚合;postgresql 用 string_to_array + unnest 更直接,但需 trim 并过滤空值;sql server 用 string_split 须注意无序性和空值处理;通用建议是避免 sql 层拆分,改用应用层预处理或反范式化设计。

MySQL 8.0+ 用 JSON_TABLE 拆分逗号字符串再聚合
MySQL 原生不支持直接拆分字符串,但 8.0+ 可借助 JSON_TABLE 把逗号分隔的字符串转成行集。前提是字符串格式规整(无嵌套引号、无转义逗号),且需先用 REPLACE 和 CONCAT 包装成 JSON 数组格式。
常见错误是漏掉外层方括号或双引号,导致 JSON_TABLE 解析失败,报错 Invalid JSON text。
- 原始字段如
tags VARCHAR(255)存值"java,python,web" - 先转成 JSON 字符串:
CONCAT('["', REPLACE(tags, ',', '","'), '"]') - 再传给
JSON_TABLE提取:SELECT jt.tag, COUNT(*) FROM t CROSS JOIN JSON_TABLE( CONCAT('["', REPLACE(tags, ',', '","'), '"]'), '$[*]' COLUMNS (tag TEXT PATH '$') ) AS jt GROUP BY jt.tag;
PostgreSQL 用 string_to_array + UNNEST
PostgreSQL 更直接:先用 string_to_array 转数组,再用 UNNEST 展开为行。注意空字符串和前后空格——UNNEST 不会自动过滤,会导致统计出空项。
- 若原始值含空格如
"java, python , web",得先TRIM每个元素 - 推荐写法:
SELECT TRIM(tag) AS tag, COUNT(*) FROM t, UNNEST(string_to_array(tags, ',')) AS tag WHERE TRIM(tag) != '' GROUP BY TRIM(tag);
- 性能上,
UNNEST在大数据量时比子查询略快,但若tags字段常为空或只含一个值,没必要强求展开
SQL Server 用 STRING_SPLIT(兼容性陷阱)
STRING_SPLIT 是 SQL Server 2016+ 内置函数,但返回结果**无序且无索引列**,对 GROUP BY 本身没影响,但若后续要按原顺序取第一个标签就不可靠。
- 基本用法安全:
SELECT value AS tag, COUNT(*) FROM t CROSS APPLY STRING_SPLIT(tags, ',') WHERE LTRIM(RTRIM(value)) != '' GROUP BY value;
- 容易踩的坑:
STRING_SPLIT不处理空字符串,'a,,b'会返回'a'、''、'b'三行;必须显式过滤空值 - SQL Server 2012/2014 用户得用递归 CTE 或自定义函数,性能差很多,不建议在高频统计场景用
通用方案:避免在 SQL 层拆分,改用应用层预处理
所有数据库在字符串拆分 + GROUP BY 场景下,执行计划都难优化,尤其当 tags 字段没索引、数据量超 10 万行时,响应可能从毫秒级升到秒级。
- 真正稳定的解法是写入时就「反范式化」:新增关联表
item_tags(item_id, tag),每次插入/更新主表时同步维护 - 如果只能读旧结构,且查询频次高,建议用应用代码(Python/Java)先
split(',')再用Counter或GROUP BY聚合,把计算压力从 DB 移走 - 临时 SQL 查询可以接受慢,但千万别在 WHERE 条件里对拆分后字段加索引——根本建不了
最麻烦的不是语法怎么写,而是原始数据里混着全角逗号、换行符、多余空格,或者标签本身含逗号(比如 "C++,JavaScript")。这种时候,正则或应用层解析几乎不可避免。










