MySQL 8.0.22+ 支持 COUNT(DISTINCT col1, col2) 直接统计多列组合去重数,忽略含 NULL 的行;旧版需用子查询 SELECT COUNT(*) FROM (SELECT DISTINCT col1, col2 FROM t) AS _;PostgreSQL 用 COUNT(DISTINCT (col1, col2)),SQL Server 2017+ 原生支持,跨库兼容推荐子查询写法。

MySQL 8.0.22+ 直接用 COUNT(DISTINCT col1, col2)
只要版本够新,就不用绕路。运行 SELECT VERSION(); 确认是 8.0.22 或更高,就能直接写:COUNT(DISTINCT job, level)。
它统计的是 (job, level) 这一对值的去重数量,不是 job 去重数 + level 去重数。
-
NULL只要出现在任意一列,整行就被跳过——('Dev', NULL)和(NULL, 'L3')都不计入 - 别把
GROUP BY dept, job, level和这个混用:那是按三列分组计数,不是“每个dept里有多少种(job, level)组合” - 如果后续要加
WHERE或关联其他表,确保过滤条件写在最外层,否则可能漏掉本该去重的组合
老版本 MySQL 或跨数据库兼容写法:子查询 + COUNT(*)
用 SELECT COUNT(*) FROM (SELECT DISTINCT job, level FROM emp) AS _; 最稳。
这个写法在 PostgreSQL、SQL Server、SQLite 全都能跑通。
- 子查询里必须只写你要去重的列,别多带无关字段(比如
id),否则去重失效 -
AS _别名不能省,MySQL 强制要求子查询有别名 - 如果表很大,确保
(job, level)上有联合索引,否则执行计划里会出现Using temporary; Using filesort
PostgreSQL 要用行构造器:COUNT(DISTINCT (col1, col2))
必须加外层括号,写成 COUNT(DISTINCT (job, level)) —— 漏掉括号就报错。
(job, level) 是一个匿名行(row value),不是字段列表。
-
NULL在行构造器中被视为普通值:(NULL, 'click')和(NULL, 'view')算两个不同组合 - 不能写成
COUNT(DISTINCT job, level),会被解析为两个独立参数,语法错误 - 大表时建议在
(job, level)上建联合索引,避免哈希去重溢出到磁盘
SQL Server 2017+ 支持原生多列,但旧版必须用子查询
SQL Server 2017 起支持 COUNT(DISTINCT user_id, action_type),2022 完全无限制。
2016 及更早版本会报错:Incorrect syntax near ','。
- 错误写法:
SELECT DISTINCT COUNT(user_id)—— 括号和顺序都不能错 -
COUNT(DISTINCT *)❌ 不合法,DISTINCT后必须明确列名或表达式 - 若字段含
NULL,所有NULL在组合中被视为同一值,只计 1 次(例如两个(1, NULL)算重复)
真正容易被忽略的是:无论用哪种写法,只要组合列基数高(比如用户 ID + 时间戳),去重过程大概率触发磁盘临时表——这时候索引和数据范围过滤比语法选择更重要。










