count(*) over 不能直接对字符串字段排名,因为它只统计每组行数而不排序编号;正确做法是先group by聚合频次,再用row_number()、rank()或dense_rank()按频次降序编号。

为什么 COUNT(*) OVER 不能直接对字符串字段“排名”
因为 COUNT(*) OVER 只统计行数,不生成序号或排序位置。它返回的是每个分组内的重复次数(比如某城市出现 5 次),但不会告诉你“上海是出现第 2 多的城市”。真正需要的是先聚合频次,再按频次排序并编号——而 COUNT(*) OVER 本身不支持 ORDER BY 在窗口内做累计排名逻辑。
正确做法:先 GROUP BY 统计频次,再用 ROW_NUMBER() 或 RANK() 排名
必须拆成两步:第一步用 GROUP BY 算出每个非数值字段(如 category、status)的出现次数;第二步在结果集上套一层查询,用窗口函数按频次降序编号。
-
ROW_NUMBER():严格递增序号,频次相同时会强行分先后(如 1,2,3) -
RANK():同频次同排名,跳过后续名次(如 1,1,3) -
DENSE_RANK():同频次同排名,不跳号(如 1,1,2)
示例(统计订单状态频次并排名):
SELECT status, cnt, RANK() OVER (ORDER BY cnt DESC) AS rank_by_freq FROM ( SELECT status, COUNT(*) AS cnt FROM orders GROUP BY status ) t;
COUNT(*) OVER 配合 PARTITION BY 的真实用途
它适合“保留原始行粒度的同时附带频次信息”,比如你想标记每条记录所属值的全局出现次数,而不是汇总后排名。
- 可用于去重前快速识别高频值(如查出所有
email出现 ≥3 次的原始记录) - 不能替代
GROUP BY + RANK()做频率排名,否则会因未聚合导致重复计数和错误序号 - 若误写成
COUNT(*) OVER (PARTITION BY category ORDER BY created_at),得到的是时间累积计数,不是频率排名
错误示范(得不到频率排名):
SELECT category, COUNT(*) OVER (PARTITION BY category) AS freq, -- ✅ 各 category 出现次数 ROW_NUMBER() OVER (PARTITION BY category ORDER BY id) AS rn -- ❌ 这是 per-category 内行号,不是全量频次排名 FROM products;
性能与 NULL 处理要注意的点
非数值字段常含 NULL,而 GROUP BY 默认把所有 NULL 归为同一组,但不同数据库对 NULL 在 ORDER BY 中的排序位置处理不一致(PostgreSQL 默认排最前,MySQL 8.0+ 默认排最后)。如果业务要求 NULL 不参与排名,得显式过滤:
SELECT status, cnt, RANK() OVER (ORDER BY cnt DESC) FROM ( SELECT status, COUNT(*) AS cnt FROM orders WHERE status IS NOT NULL -- 关键:排除 NULL 干扰频次统计 GROUP BY status ) t;
另外,大表上做 GROUP BY + 窗口排序可能触发临时磁盘排序,记得给分组字段建索引。










