必须先分词再聚合,因COUNT等函数不拆分字符串;MySQL用SUBSTRING_INDEX或REGEXP_SUBSTR,PostgreSQL用string_to_array+unnest,需TRIM/LOWER及标点过滤,且SQL词频统计性能差、逻辑脆、维护难。

直接用 COUNT 或其他标准聚合函数无法统计文本字段中「词」的频次——因为它们只作用于整行或整列,不拆分字符串。必须先做分词(tokenization),再聚合。
SQL 本身不支持原生分词,得靠字符串函数模拟
多数数据库(如 MySQL、PostgreSQL、SQL Server)没有内置的「按空格/标点切词」函数,只能用递归 CTE、正则替换、自定义函数或外部预处理来逼近。实际中最可行的是用 SUBSTRING_INDEX(MySQL)或 STRING_SPLIT(SQL Server 2016+)配合 GROUP BY。
- MySQL 8.0+ 可用
REGEXP_SUBSTR配合递归 CTE 提取每个词,但性能差、代码长,仅适合小表 - PostgreSQL 有
string_to_array()+unnest(),相对简洁:SELECT word, COUNT(*) AS freq FROM (SELECT unnest(string_to_array(lower(content), ' ')) AS word FROM articles) t WHERE word !~ '^[[:punct:][:space:]]*$' -- 过滤纯标点/空格 GROUP BY word ORDER BY freq DESC LIMIT 10;
- SQLite 完全没原生支持,必须用应用层分词或加载扩展(如
fts5的辅助表)
COUNT 和 GROUP BY 是骨架,但输入必须是“已分好的词”
聚合函数本身只认「一行一词」结构。如果你把原始文本字段直接丢进 GROUP BY content,得到的是「整段文本」的重复次数,不是「词」的频次。
- 错误写法:
SELECT content, COUNT(*) FROM posts GROUP BY content→ 统计的是相同全文出现几次 - 正确前提:必须先将
content拆成多行,每行一个词(比如 “hello world” → 两行:“hello”、“world”) - 拆分后才能用
GROUP BY word+COUNT(*)得到词频 - 记得加
TRIM()、LOWER()、过滤空字符串和标点,否则 “Hello” 和 “hello” 会被算作两个词
真实项目里,90% 的场景不该在 SQL 层做词频统计
数据库不是文本分析引擎。在 SQL 中硬搞分词会暴露三个硬伤:
- 性能崩坏:单条文本含 100 个词,10 万行就生成千万级中间行,
GROUP BY压力陡增 - 逻辑脆弱:中文分词根本没法靠空格解决;英文连字符(e-mail)、缩写(don't)、括号内容都需定制规则
- 维护成本高:一个正则写错,整个词表偏移;换数据库就得重写逻辑
- 更稳的路径是:用 Python(
nltk/jieba)、Logstash 或专用搜索库(Elasticsearch 的 term vector)预处理,只把「词→频次」结果存回数据库供查
真正要动手写 SQL 词频,先确认你的数据库版本是否支持数组展开,再决定要不要在生产环境扛这个压力——很多时候,导出 CSV 用脚本跑一遍,比调通一条递归 CTE 快且准。

















