🎯 本月只剩几天支持 SQLTest.online,帮助我们让 SQL 练习对每个人都免费。 今天捐赠 →
SQL 代码已复制到剪贴板

用 HAVING 过滤分组

第 4.3 课 · 阅读时间:约 7 分钟 · 练习题 ↓

使用 GROUP BY 和聚合函数时,经常需要根据聚合结果过滤分组。HAVING 就是用来过滤分组的运算符,就像 WHERE 用来过滤单独的行一样。在本课中,你将学习 WHERE 与 HAVING 的区别、语法以及基于 Sakila 的实用示例。学完后,你将能够熟练地用 HAVING 进行深入的数据分析。

在前面的课程中,你学会了用 GROUP BY 对数据分组并应用聚合函数。下一步是:按聚合值上的条件过滤分组本身。

HAVING 允许在分组之后设置条件,从而做到这一点。

SQL HAVING 与 WHERE:WHERE 在分组前过滤行,HAVING 在分组后过滤组;HAVING SUM(amount) width= 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 让你能够在分组层面进行深入的分析查询。

尝试解决以下任务,以巩固您在本课中学到的内容。

  1. 重复的演员名字
  2. 影片类别与较长的平均时长
  3. HAVING 和 WHERE 之间有什么区别?
  4. 重复的演员姓氏
  5. 最延迟的电影