having必须配合group by使用,用于筛选分组后的聚合结果;where在分组前过滤原始行,不可用聚合函数,二者执行顺序为where→group by→having。

HAVING 不能直接写成 WHERE 条件,必须配合 GROUP BY
很多人一上来就写 HAVING product IN ('A', 'B'),这其实等价于“买过 A 或 B”,不是“同时买过 A 和 B”。HAVING 是对分组后的聚合结果做筛选,所以必须先按用户分组,再统计每个用户买了哪些商品。
关键思路是:对每个用户,统计其购买的特定商品种类数。只有当这个计数 ≥ 2(且限定在 A、B 中),才说明他俩都买过。
- 必须用
GROUP BY user_id,否则HAVING无意义 -
WHERE product IN ('A', 'B')要提前过滤,避免干扰计数(比如用户还买了 C,但不影响我们只关心 A/B) - 用
COUNT(DISTINCT product)防止同一用户重复买 A(比如买了两次 A)被误算成 2
正确写法:COUNT(DISTINCT ...) + HAVING = 2
假设表叫 orders,字段为 user_id、product:
SELECT user_id
FROM orders
WHERE product IN ('A', 'B')
GROUP BY user_id
HAVING COUNT(DISTINCT product) = 2;
这段语句的意思是:先筛出所有买过 A 或 B 的记录 → 按用户分组 → 看每组里不重复的 product 值是不是恰好两个(只能是 'A' 和 'B')。
- 如果某用户只买过 A(哪怕多次),
COUNT(DISTINCT product)是 1,被HAVING过滤掉 - 如果某用户买了 A、B、C,则
WHERE已经把 C 排除,分组后只剩 A 和 B,计数仍是 2,符合条件 - 注意:这里依赖
WHERE提前限定范围;如果去掉该条件,COUNT(DISTINCT product)就变成统计他所有商品种类数,逻辑就偏了
遇到 NULL 或空字符串时 COUNT(DISTINCT) 会忽略它们
如果 product 字段可能为 NULL 或空字符串 '',而你又没在 WHERE 里排除,它们不会参与 COUNT(DISTINCT product) 计数 —— 这通常是好事,但得确认业务是否允许这些脏数据混入。
- 稳妥做法是显式排除:
WHERE product IN ('A', 'B') AND product IS NOT NULL AND product != '' - 某些数据库(如 MySQL 5.7 严格模式)对
GROUP BY有额外限制,若SELECT列不在GROUP BY或聚合函数中,会报错,此时只需确保只选user_id即可 - 性能上,如果表很大,建议在
(user_id, product)上建联合索引,能加速分组和去重
替代方案:用 EXISTS 或自连接,但更啰嗦
有人会想到用两个 EXISTS 子查询嵌套,或者用自连接找交集,语法更长且可读性差。例如:
SELECT DISTINCT o1.user_id FROM orders o1 WHERE EXISTS (SELECT 1 FROM orders o2 WHERE o2.user_id = o1.user_id AND o2.product = 'A') AND EXISTS (SELECT 1 FROM orders o3 WHERE o3.user_id = o1.user_id AND o3.product = 'B');
这种写法逻辑清晰,但执行计划往往不如 GROUP BY + HAVING 高效,尤其当数据量大、缺少索引时。除非你明确需要返回其他字段(比如首次购买时间),否则没必要绕路。
真正容易被忽略的是:HAVING 的过滤对象永远是聚合结果,不是原始行;而很多人试图在 HAVING 里引用未聚合的列(比如 HAVING product = 'A'),这在绝大多数 SQL 引擎里会直接报错 —— 错误信息通常是 column "product" must appear in the GROUP BY clause or be used in an aggregate function。










