distinct on 适用于保留每组首条记录(需 order by),group by 用于统计不同组合数量;count(distinct col1, col2) 先去重再计数,等价于子查询去重;null 处理严格,需用 coalesce 统一;filter 比 case 更清晰高效;索引顺序须匹配 group by 字段,work_mem 设置常比加索引更有效。

用 DISTINCT ON 还是 GROUP BY?先看你要去重的逻辑
多字段去重计数,核心在于「按哪些字段组合视为重复」。如果只要保留每组中某一条(比如最新/最早的一条),DISTINCT ON 更直接;如果目标是统计「有多少个不同的字段组合」,必须用 GROUP BY 配合 COUNT(*)。
常见误用:SELECT COUNT(DISTINCT col1, col2) 在 PostgreSQL 中是合法语法,但它等价于 SELECT COUNT(*) FROM (SELECT DISTINCT col1, col2 FROM t) t2 —— 本质仍是先去重再计数,不是对行做“部分去重”。
-
DISTINCT ON必须配合ORDER BY,且排序字段要包含DISTINCT ON的列(或其前缀) -
GROUP BY col1, col2不要求排序,但若后续要取每组某条记录(如最新时间),得嵌套窗口函数或子查询 - 性能上,
GROUP BY通常比DISTINCT ON更易被优化器利用索引(尤其当col1, col2有联合索引时)
COUNT(DISTINCT (col1, col2)) 是什么?和 COUNT(DISTINCT col1, col2) 一样吗?
不一样。COUNT(DISTINCT (col1, col2)) 把两个字段打包成一个匿名行(row type),再整体去重;而 COUNT(DISTINCT col1, col2) 是 PostgreSQL 特有的简写,语义相同,但写法更简洁。
注意:这种写法对 NULL 处理严格——只要 col1 或 col2 任一为 NULL,整行就被视为不可比较,不会与其他 NULL 组合合并(即 (1, NULL) ≠ (1, NULL))。这和 GROUP BY 中 NULL = NULL 的行为不同。
- 若业务允许将
NULL视为相同值,改用GROUP BY COALESCE(col1, '>'), COALESCE(col2, '>') - 若字段类型不支持直接拼接(如 JSON、数组),不能用字符串连接模拟去重,会出错或漏判
- 该写法无法利用普通 B-tree 索引加速去重,大数据量时可能触发大量临时磁盘排序
想按多字段分组后,再统计每组里某个字段的非空值数量?别漏掉 FILTER
比如统计每个 (user_id, action_type) 组合下,成功日志(status = 'success')的数量,而不是只算总行数。
错误写法:COUNT(CASE WHEN status = 'success' THEN 1 END) 可行,但冗长;正确且清晰的方式是用标准 SQL 的 FILTER 子句:
SELECT user_id, action_type,
COUNT(*) FILTER (WHERE status = 'success') AS success_cnt,
COUNT(*) FILTER (WHERE status = 'failed') AS failed_cnt
FROM logs
GROUP BY user_id, action_type;
-
FILTER是 PostgreSQL 9.4+ 支持的特性,比CASE更语义明确,也更容易被优化器识别为可下推条件 - 不能在
DISTINCT ON场景中使用FILTER,它只作用于聚合函数 - 如果同时需要去重 + 条件计数(例如“每个用户每种操作类型的唯一设备数”),必须先
GROUP BY,再在聚合内套COUNT(DISTINCT device_id) FILTER (...)
大表上多字段去重统计慢?优先检查索引和数据分布
没有索引时,GROUP BY a, b 会强制排序或哈希,内存不足就落盘,速度骤降。但加索引不是无脑建联合索引。
- 索引列顺序必须匹配
GROUP BY的顺序(如GROUP BY region, category→ 索引应为(region, category),反过来无效) - 如果查询还带
WHERE条件(如WHERE created_at > '2024-01-01'),考虑把过滤字段前置:(created_at, region, category) - 用
EXPLAIN (ANALYZE, BUFFERS)看是否命中索引扫描(Index Scan using ...),以及是否出现Sort或HashAggregate落盘(Write/Read temp file) - 若字段基数极低(如只有 3 种
status),有时全表顺序扫描 + 哈希聚合反而比索引快,别迷信索引
最常被忽略的是 work_mem 设置——默认 4MB 对百万级分组不够用,临时调高(如 SET LOCAL work_mem = '256MB')往往比加索引见效更快。










