必须先拆分再聚合:mysql 8.0+用json_table(需concat+replace转合法json数组),postgresql用string_to_array()+unnest(),并配合trim()、lower()清洗;含逗号的标签须重构为关联表。

标签字段是逗号分隔字符串,怎么拆开再聚合?
直接 GROUP BY tags 会把 "手机,5G,旗舰" 和 "5G,手机,旗舰" 当作不同值,根本没法按单个标签统计。必须先拆分——但 SQL 标准不提供原生 split 函数,得靠具体数据库的字符串函数或递归 CTE。
MySQL 8.0+ 可用 JSON_TABLE 模拟拆分(需先转 JSON 数组),PostgreSQL 推荐 string_to_array() + UNNEST(),SQL Server 2016+ 用 STRING_SPLIT()。别硬写循环或自定义函数,性能差还难维护。
示例(PostgreSQL):
SELECT tag, COUNT(*) FROM products, UNNEST(string_to_array(tags, ',')) AS tag GROUP BY tag;
标签含空格、大小写、前后空格,怎么统一处理?
用户录入随意,"旗舰 "、" 旗舰"、"FLAGSHIP" 都可能被当不同标签。聚合前必须清洗,否则统计失真。
- 用
TRIM()去首尾空格 - 用
LOWER()统一小写(除非业务要求区分大小写) - 如果标签本身允许逗号(比如“iPhone, 15 Pro”),就不能简单按逗号切——这种场景必须改存储结构,别硬拆
清洗后聚合示例(MySQL 8.0):
SELECT LOWER(TRIM(tag)) AS tag, COUNT(*)
FROM products, JSON_TABLE(
CONCAT('["', REPLACE(REPLACE(tags, '"', '\"'), ',', '","'), '"]'),
'$[*]' COLUMNS (tag TEXT PATH '$')
) AS jt
GROUP BY LOWER(TRIM(tag));
想查“同时有 A 和 B 标签”的商品,为什么不能用 WHERE tags LIKE '%A%' AND tags LIKE '%B%'?
这会误匹配 "A123,B456" 或 "AB" 这类非独立标签。真正要的是“完整标签项包含 A”且“完整标签项包含 B”,本质是集合交集判断。
正确做法是先拆标签,再按商品分组计数:
- 把每个商品的标签展开成行
GROUP BY product_id- 用
HAVING COUNT(DISTINCT tag) >= 2且MIN(CASE WHEN tag IN ('a','b') THEN 1 END) = 1控制必须同时存在
更简洁写法(PostgreSQL):
SELECT product_id
FROM products, UNNEST(string_to_array(tags, ',')) AS tag
WHERE TRIM(LOWER(tag)) IN ('a', 'b')
GROUP BY product_id
HAVING COUNT(DISTINCT TRIM(LOWER(tag))) = 2;
标签字段频繁查询,为什么加索引也没用?
tags 字段是文本类型,哪怕加了普通 B-Tree 索引,LIKE '%x%' 或 string_to_array() 拆分操作也无法走索引。本质上这是反范式设计带来的性能硬伤。
真正有效的解法只有两个:
- 建物化视图(如 PostgreSQL 的
MATERIALIZED VIEW)预计算标签展开结果,定期刷新 - 彻底重构:新增关联表
product_tags(product_id, tag),对tag列建索引,查询直接走JOIN
临时应急可以加生成列(MySQL 5.7+ / PostgreSQL 12+)并索引,但仅适用于固定标签集且更新不频繁的场景。
标签系统一旦数据量过万,字符串拆分就不再是语法问题,而是架构选择问题——这时候再优化 SQL 写法意义不大。











