GROUP BY 不能直接对未分词文本做词频统计,因split返回数组而非标量,须先用explode展开或预处理分词;中文需jieba等UDF,且须过滤停用词防倾斜。

GROUP BY 本身不能直接对未分词的文本字段做词频统计——它只按整行值分组,不是按词分组。硬写 SELECT word, COUNT(*) FROM (SELECT split(text, ' ') AS word FROM table) t GROUP BY word 这类语句在多数 SQL 引擎里会报错或语义错误,因为 split() 返回的是数组,不是标量列。
真正可行的路径只有两条:预处理分词(推荐) 或 用支持内联展开的引擎(有限场景)。下面分情况说明。
为什么不能直接在标准 SQL 中对 TEXT 字段做词频 GROUP BY?
核心限制有三个:
• 大多数 SQL 引擎(MySQL、PostgreSQL、Hive 旧版)不提供原生「将字符串炸开为多行」的函数;
• 即使有 string_to_array()(如 PostgreSQL)或 explode()(如 SparkSQL/Hive),也必须配合 LATERAL 或 explode_outer() 才能展开,否则 GROUP BY 无法识别新生成的词行;
• 中文无空格分隔,split(text, ' ') 对「人工智能发展迅速」完全失效,必须依赖外部分词器(如 HanLP、jieba)预处理。
SparkSQL 中用 explode + lateral view 实现词频统计
这是目前最稳定、可落地的方案,适用于日志、评论、文档等大文本场景。
关键点不是“怎么写 GROUP BY”,而是“怎么把一行变多行”:
- 先用
split(lower(trim(text)), '[\s\p{P}]+')做基础清洗和切分(正则匹配空白与标点) - 用
explode()展开数组,注意要过滤空字符串:WHERE word != '' - 中文必须提前用 UDF 接入 jieba 或 HanLP,不能靠正则硬切;否则「上海浦东机场」会被切成「上海」「浦东」「机场」还是「上海浦」「东机」「场」完全不可控
- 示例片段(SparkSQL):
SELECT word, COUNT(*) AS freq FROM ( SELECT explode(split(lower(trim(content)), '[\s\p{P}]+')) AS word FROM articles ) t WHERE word RLIKE '^[a-z0-9\u4e00-\u9fa5]+$' GROUP BY word ORDER BY freq DESC LIMIT 100
MySQL / PostgreSQL 等传统数据库怎么办?
它们没有 explode,也没有 LATERAL(MySQL 8.0.14+ 才支持 LATERAL,且不支持数组展开),所以必须放弃在 SQL 层实时分词:
- 把分词逻辑下沉到应用层或 ETL 流程:用 Python 调
jieba.cut()或hanlp.pipeline()预处理,输出「文章ID + 词」宽表,再导入数据库 - 建一张
article_words(article_id BIGINT, word VARCHAR(64), position INT)表,word字段加前缀索引,GROUP BY word就变成普通聚合 - 如果只是临时查,可用递归 CTE(PostgreSQL)或数字辅助表模拟展开,但超过 100 词/行就明显变慢,不建议用于生产
性能与倾斜问题比你想的更早出现
词频统计真正的瓶颈往往不是语法,而是数据倾斜:
• 「的」「了」「and」「the」这类停用词会占 20%+ 行数,导致单个 GROUP BY task 处理上千万行
• SparkSQL 中需提前 FILTER 停用词,或用 salting(加随机前缀再分组)缓解 reduce 端压力
• MySQL 中若用子查询展开,EXPLAIN 会显示全表扫描 + filesort,10 万行文本基本就卡死
别指望一条 SQL 解决所有问题——分词是 NLP 任务,不是 SQL 任务。把清洗、切分、过滤、去重拆到不同阶段,比堆砌一个“全能 SQL”更可靠。

















