结论:仅用count(distinct cheitcode) = 3无法保证“恰好拥有a、b、c三项”,必须同时校验目标集内去重计数与总去重计数均为3,否则会误包含含冗余项(如d)的任务;case when配合count(distinct)可精准统计命中目标的数量,确保全覆盖且无冗余。

直接说结论:用 HAVING 做多对多“完全匹配”(不多不少),必须同时校验两个数:目标集合内项数 == 总项数,且两者都等于预期值。只比一个 COUNT 是错的。
为什么 COUNT(DISTINCT cheitcode) = 3 不够用
这是最常踩的坑。比如查「恰好拥有 a、b、c 三项」的任务,写成:
SELECT taskid FROM CHECKITEM GROUP BY taskid HAVING COUNT(DISTINCT cheitcode) = 3
它会把 taskid = 0002(含 a/b/c/d)也捞出来——因为 COUNT(DISTINCT cheitcode) 只管“至少有3个”,不管有没有第4个。
- 中间表若存在重复记录(如同一
taskid+cheitcode插了两次),COUNT(*)会虚高,COUNT(DISTINCT)才可靠 -
WHERE cheitcode IN ('a','b','c')预过滤再 COUNT,会直接丢掉含 d 的行,但你根本看不到这些任务是否“多出冗余”,逻辑就断了
正确做法:HAVING 里塞两个聚合条件
核心是:对每个 taskid,分别统计「属于 {a,b,c} 的项数」和「总项数」,两者必须都等于 3。
SELECT taskid
FROM CHECKITEM
GROUP BY taskid
HAVING COUNT(DISTINCT CASE WHEN cheitcode IN ('a','b','c') THEN cheitcode END) = 3
AND COUNT(DISTINCT cheitcode) = 3;
-
CASE WHEN ... THEN ... END把非目标值转为 NULL,COUNT(DISTINCT ...)自动忽略 NULL,精准算出“命中目标集的数量” - MySQL 5.7+ / PostgreSQL / SQL Server 均支持;Oracle 需改用
SUM(CASE ...)配合去重逻辑 - 如果目标集合动态变化(比如来自参数),建议用临时表或 CTE 预存目标值,避免硬编码
性能与兼容性要注意什么
这个写法看似简洁,但实际执行时可能触发全表扫描,尤其当 CHECKITEM 很大且没索引时。
- 必须在
(taskid, cheitcode)上建联合索引,否则GROUP BY+COUNT(DISTINCT)开销极大 - MySQL 8.0+ 支持
JSON_CONTAINS配合聚合,但不如原生CASE稳定;别依赖GROUP_CONCAT比字符串——默认长度 1024,超长就截断,匹配必错 - 如果要查“至少含 a、b,不含 c”,就不能用这套逻辑,得换
NOT EXISTS或布尔聚合(如BOOL_OR(cheitcode='a'))
真正难的不是写出 HAVING 条件,而是想清楚:你要的到底是“全覆盖”“无冗余”,还是“子集包含”。少校验任何一个维度,结果就不可信。










