用 HAVING 过滤分组
第 4.3 课 · 阅读时间:约 7 分钟 · 练习题 ↓
使用 GROUP BY 和聚合函数时,经常需要根据聚合结果过滤分组。HAVING 就是用来过滤分组的运算符,就像 WHERE 用来过滤单独的行一样。在本课中,你将学习 WHERE 与 HAVING 的区别、语法以及基于 Sakila 的实用示例。学完后,你将能够熟练地用 HAVING 进行深入的数据分析。
在前面的课程中,你学会了用 GROUP BY 对数据分组并应用聚合函数。下一步是:按聚合值上的条件过滤分组本身。
HAVING 允许在分组之后设置条件,从而做到这一点。
100 保留 599 名客户中的 395 名" width="100%">
WHERE 与 HAVING
- WHERE 在分组之前过滤行
- HAVING 在聚合之后过滤组
SELECT column1, COUNT(*) AS cnt
FROM table
WHERE column1 > 100 -- filter BEFORE grouping
GROUP BY column1
HAVING COUNT(*) > 10; -- filter AFTER grouping
HAVING 语法
基本结构:
SELECT column1, AGG_FUNCTION(column2)
FROM table
GROUP BY column1
HAVING condition;
HAVING 中的条件通常包含聚合函数。
单个条件的示例
付款总额超过 100 的客户
SELECT customer_id, SUM(amount) AS total_paid
FROM payment
GROUP BY customer_id
HAVING SUM(amount) > 100;
结果:只返回付款总额超过 100 的客户。
处理超过 50 笔付款的员工
SELECT staff_id, COUNT(*) AS payments_count
FROM payment
GROUP BY staff_id
HAVING COUNT(*) > 50;
结果:处理付款超过 50 笔的员工。
平均付款额 ≥ 5 的客户
SELECT customer_id, AVG(amount) AS avg_payment
FROM payment
GROUP BY customer_id
HAVING AVG(amount) >= 5;
结果:平均付款额至少为 5 的客户。
带多个条件的 HAVING
可以用 AND 和 OR 组合条件:
SELECT staff_id, COUNT(*) AS cnt, SUM(amount) AS total
FROM payment
GROUP BY staff_id
HAVING COUNT(*) > 50 AND SUM(amount) > 500;
结果:付款超过 50 笔并且总额超过 500 的员工。
使用 OR 运算符
SELECT customer_id, COUNT(*) AS rentals, SUM(amount) AS paid
FROM payment
GROUP BY customer_id
HAVING COUNT(*) > 100 OR SUM(amount) > 1000;
结果:付款超过 100 笔或总额超过 1000 的客户。
实用示例
销售额超过 2000 的电影类别
SELECT category_id, SUM(p.amount) AS total_sales
FROM payment p
JOIN rental r ON p.rental_id = r.rental_id
JOIN inventory i ON r.inventory_id = i.inventory_id
JOIN film_category fc ON i.film_id = fc.film_id
GROUP BY category_id
HAVING SUM(p.amount) > 2000;
客户超过 20 位的国家
SELECT country, COUNT(*) AS customers_count
FROM customer cu
JOIN address a ON cu.address_id = a.address_id
JOIN city ci ON a.city_id = ci.city_id
JOIN country co ON ci.country_id = co.country_id
GROUP BY country
HAVING COUNT(*) > 20;
日收入超过 500 的员工
SELECT staff_id, DATE(payment_date) AS pay_date, SUM(amount) AS daily_revenue
FROM payment
GROUP BY staff_id, DATE(payment_date)
HAVING SUM(amount) > 500;
结果:56 行,每行是某位员工在某一天收款超过 500 的组合。
常见问题
为什么不能用 WHERE 代替 HAVING?
WHERE 在分组之前起作用,因此无法检查聚合函数。HAVING 在分组之后起作用,可以分析聚合结果(COUNT、SUM、AVG 等)。
HAVING 中可以使用未聚合的列吗?
可以使用 GROUP BY 中的列,但通常没有必要。例如,HAVING customer_id > 100 可以运行,但更自然的写法是在分组前放进 WHERE。
没有 GROUP BY 时可以使用 HAVING 吗?
在某些 DBMS 中技术上可以,但并不实用,因为 HAVING 是为过滤分组而设计的。不分组时,用 WHERE 过滤单独的行。
面试问题
什么是 HAVING,它和 WHERE 有什么不同?
HAVING 在聚合之后过滤组,而 WHERE 在分组之前过滤行。WHERE 不能使用聚合函数,而 HAVING 恰恰是针对聚合函数的。
可以在一条查询中同时使用 WHERE 和 HAVING 吗?
可以,而且推荐这样做。WHERE 在分组前过滤行,HAVING 在分组后过滤组。例如,WHERE amount > 10 GROUP BY customer_id HAVING SUM(amount) > 100 先排除小额付款,再分组,最后过滤分组。
WHERE 和 HAVING 的执行顺序是怎样的?
先执行 WHERE(在 GROUP BY 之前),然后 GROUP BY 进行分组,接着 HAVING 过滤得到的分组,最后执行 ORDER BY 和 LIMIT。
本课要点:
HAVING在聚合之后过滤组,WHERE在分组之前过滤行。HAVING与聚合函数(COUNT、SUM、AVG、MIN、MAX)一起使用。- 可以在
HAVING中用AND/OR组合多个条件。 - WHERE 和 HAVING 经常配合使用:WHERE 排除不需要的行,HAVING 过滤分组。
HAVING让你能够在分组层面进行深入的分析查询。