mysql 8.0+ 支持 count(distinct col1, col2) 直接统计列组合唯一值对,忽略含 null 的行;旧版及 postgresql、sql server 等需用子查询 select count(*) from (select distinct col1, col2 from t) as _。

MySQL 8.0+ 直接支持多列 COUNT(DISTINCT col1, col2)
MySQL 8.0 及以上版本原生支持 COUNT(DISTINCT col1, col2),它统计的是「列组合的唯一值对」数量。比如 (1,'a') 和 (1,'b') 算两个不同组合,而 (1,'a') 出现两次只算一次。
常见错误是误以为它等价于 COUNT(DISTINCT col1) + COUNT(DISTINCT col2) —— 完全不是一回事,后者是两个独立去重计数之和,语义完全不同。
- 适用场景:分析联合主键、用户-行为对、时间-设备组合等复合唯一性
- 注意 NULL 处理:
(NULL, 'a')和('a', NULL)均被视为有效组合,但(NULL, NULL)在 MySQL 中会被当作一个唯一值(不是忽略) - 性能影响:多列
DISTINCT会触发临时表 + 排序,大数据量时比单列慢不少,建议在(col1, col2)上建联合索引
PostgreSQL 和 SQL Server 不支持多列 COUNT(DISTINCT)
PostgreSQL 报错 ERROR: DISTINCT is not supported for multiple columns in aggregate functions;SQL Server 则直接语法不通过。它们只接受单列或表达式(如 COUNT(DISTINCT col1 || col2)),但拼接有风险。
安全做法是用子查询去重再计数:
SELECT COUNT(*) FROM (SELECT DISTINCT col1, col2 FROM t) AS _
这个写法通用、语义清晰,且各数据库都支持。
- 别名
AS _是惯用写法,避免无意义命名 - 不能省略子查询别名(PostgreSQL/SQL Server 强制要求)
- 如果只查一列去重后计数,
COUNT(DISTINCT col1)仍比子查询快,别为了一致性强行统一成子查询
用字符串拼接模拟多列 DISTINCT 要小心边界
有人用 COUNT(DISTINCT col1 || '|' || col2)(PostgreSQL)或 COUNT(DISTINCT CONCAT(col1, '|', col2))(MySQL)来绕过限制,但极易出错:
- 若
col1='a|b'且col2='c',拼成'a|b|c',和col1='a',col2='b|c'结果一样 → 假合并 - NULL 参与拼接时整个结果变 NULL(多数数据库中),导致该行被丢弃,计数偏少
- 类型隐式转换可能截断或报错,比如
INT和TEXT拼接时长度溢出
除非你 100% 控制数据内容且加了严格校验,否则别碰拼接方案。
需要同时算多个 DISTINCT 组合?别嵌套 COUNT(DISTINCT ...)
想在同一 SELECT 中计算 COUNT(DISTINCT a,b)、COUNT(DISTINCT a,c)、COUNT(DISTINCT b,c)?别试图写成一行——MySQL 8.0 允许,但其他数据库不行;而且可读性差、优化器难生效。
更可靠的做法是用 CTE 或多次子查询并联:
WITH d1 AS (SELECT COUNT(*) c FROM (SELECT DISTINCT a,b FROM t) _),<br>d2 AS (SELECT COUNT(*) c FROM (SELECT DISTINCT a,c FROM t) _),<br>d3 AS (SELECT COUNT(*) c FROM (SELECT DISTINCT b,c FROM t) _) <br>SELECT d1.c AS ab_cnt, d2.c AS ac_cnt, d3.c AS bc_cnt FROM d1, d2, d3
虽然多扫几遍表,但逻辑分层清楚,各数据库兼容,也方便加 WHERE 条件做条件去重统计。
真正容易被忽略的是:当表很大且多个组合涉及不同过滤条件时,应该优先考虑物化中间结果(比如建临时表存 DISTINCT 后的结果),而不是反复全表 DISTINCT 扫描。











