MySQL 8.0.22+ 支持 COUNT(DISTINCT col1, col2) 直接统计多列组合去重数,忽略含 NULL 的行;旧版需用子查询 SELECT COUNT(*) FROM (SELECT DISTINCT col1, col2 FROM t) AS _;注意避免 CONCAT 拼接导致误合并或 NULL 丢失。

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) 组合”
老版本 MySQL(如 5.7)或跨数据库兼容写法
用子查询先去重,再外层计数,最稳:
SELECT COUNT(*) FROM ( SELECT DISTINCT job, level FROM emp ) AS _;
这个写法在 PostgreSQL、SQL Server、SQLite 全都能跑通。关键细节:
- 子查询里必须只写你要去重的列,别多带无关字段(比如
id),否则去重失效 -
AS _别名不能省,MySQL 强制要求子查询有别名 - 如果表很大,确保
(job, level)上有联合索引,否则执行计划里会出现Using temporary; Using filesort
CONCAT 拼接模拟多列去重的坑
有人写 COUNT(DISTINCT CONCAT(job, '|', level)),短期能跑,但隐患明显:
- 当
job = 'senior|lead'且level = 'L2',和job = 'senior'、level = 'lead|L2'拼出来都是'senior|lead|L2',误合并 -
CONCAT遇到任意一列为NULL,结果全为NULL,整行丢失;得改成CONCAT(COALESCE(job, 'NULL'), '|', COALESCE(level, 'NULL')) - TEXT 类型字段拼接可能被截断(MySQL 默认
CONCAT返回VARCHAR(1024)),不如直接走SELECT DISTINCT子查询可靠
PostgreSQL 和 SQL Server 的原生替代
它们不认 COUNT(DISTINCT col1, col2),但各有更规范的写法:
- PostgreSQL:用行构造器,必须加外层括号 ——
COUNT(DISTINCT (job, level)) - SQL Server:标准子查询即可 ——
SELECT COUNT(*) FROM (SELECT DISTINCT job, level FROM emp) t - 两者都支持
DISTINCT在窗口函数或STRING_AGG里直接用,但COUNT聚合里不接受裸多列
真正容易被忽略的是:无论用哪种写法,只要组合列基数高(比如用户 ID + 时间戳),去重过程大概率触发磁盘临时表——这时候索引和数据范围过滤比语法选择更重要。











