SQL HAVING 子句(一文讲透)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在数据库查询的进阶技巧中,SQL HAVING 子句是一个容易被误解但至关重要的工具。它与 WHERE 子句看似相似,实则扮演着完全不同的角色。对于编程初学者而言,理解 HAVING 子句的使用场景和逻辑原理,能显著提升数据筛选的效率。本文将通过循序渐进的讲解、形象的比喻和实际案例,帮助读者掌握这一核心语法,并在项目中灵活应用。


什么是 SQL HAVING 子句?

HAVING 子句用于对已分组的查询结果进行过滤。它与 WHERE 子句的差异在于:

  • WHERE 在分组前过滤原始数据;
  • HAVING 在分组后过滤聚合结果。

形象比喻
想象你正在整理一堆书,WHERE 相当于在分组前筛选出“所有红色封面的书”,而 HAVING 则是在将书按作者分组后,筛选出“作者拥有超过5本书的组”。


HAVING 子句的基本语法

SELECT 列名, 聚合函数(列名)  
FROM 表名  
WHERE 条件表达式  
GROUP BY 分组列  
HAVING 条件表达式  
ORDER BY 排序列;  

关键点:

  1. HAVING 必须与 GROUP BY 联合使用,除非查询中包含聚合函数且未分组;
  2. 可以直接引用聚合函数(如 SUM, COUNT)作为条件;
  3. 支持逻辑运算符(AND, OR)和比较运算符(>, <, =)。

HAVING 与 WHERE 的区别对比

特性WHERE 子句HAVING 子句
作用阶段数据分组前过滤原始数据数据分组后过滤聚合结果
支持条件可直接引用单行数据列可引用聚合函数或分组后的列
适用场景筛选单条记录筛选满足聚合条件的组

案例说明
假设有一个 employees 表,包含 department(部门)、salary(工资)和 hire_date(入职时间)字段。

目标:找出平均工资超过10000元的部门。

SELECT department, AVG(salary) AS avg_salary  
FROM employees  
GROUP BY department  
HAVING AVG(salary) > 10000;  

此查询中:

  • WHERE 无法直接使用 AVG(salary),因为它作用于原始行数据;
  • HAVING 可以直接引用聚合后的 AVG(salary) 条件。

HAVING 子句的典型应用场景

1. 过滤包含特定数量记录的组

案例:统计每个城市的订单数量,仅显示订单数大于10的城市。

SELECT city, COUNT(order_id) AS order_count  
FROM orders  
GROUP BY city  
HAVING COUNT(order_id) > 10;  

2. 结合多个聚合条件

案例:找出同时满足总销售额超过5000元且平均订单金额低于200元的客户。

SELECT customer_id,  
       SUM(amount) AS total_sales,  
       AVG(amount) AS avg_order  
FROM orders  
GROUP BY customer_id  
HAVING SUM(amount) > 5000  
   AND AVG(amount) < 200;  

3. 处理 NULL 值的特殊条件

案例:筛选出所有员工数量为0的部门(即 department 列存在但无员工)。

SELECT department, COUNT(employee_id) AS employee_count  
FROM employees  
GROUP BY department  
HAVING COUNT(employee_id) = 0;  

HAVING 子句的进阶用法

1. 结合子查询

当需要基于外部查询的结果进行过滤时,可以嵌套子查询:

SELECT department, AVG(salary)  
FROM employees  
GROUP BY department  
HAVING AVG(salary) > (SELECT AVG(salary) FROM employees);  

此查询会找出平均工资高于全公司平均水平的部门。

2. 使用别名简化条件

虽然 SQL 标准不强制要求,但许多数据库(如 MySQL)允许在 HAVING 中引用 SELECT 子句的别名:

SELECT department,  
       AVG(salary) AS avg_salary  
FROM employees  
GROUP BY department  
HAVING avg_salary > 12000;  

3. 结合窗口函数

在复杂分析场景中,可结合窗口函数实现动态分组过滤:

SELECT *,  
       AVG(salary) OVER (PARTITION BY department) AS dept_avg  
FROM employees  
HAVING salary > dept_avg;  

此查询筛选出工资高于本部门平均工资的员工。


常见错误与注意事项

1. 忽略 GROUP BY

若未使用 GROUP BY 且查询中包含聚合函数,某些数据库(如 PostgreSQL)会报错。例如:

-- 错误示例  
SELECT AVG(salary)  
FROM employees  
HAVING AVG(salary) > 10000;  

修正方式

SELECT AVG(salary)  
FROM employees  
HAVING AVG(salary) > 10000;  
-- 或添加 GROUP BY(若需分组)  

2. 混淆 WHERE 和 HAVING

以下两种写法效果不同:

-- 错误用法(应使用 WHERE)  
SELECT department, COUNT(*)  
FROM employees  
GROUP BY department  
HAVING department = 'Sales';  

-- 正确写法  
SELECT department, COUNT(*)  
FROM employees  
WHERE department = 'Sales'  
GROUP BY department;  

3. 性能优化

HAVING 的过滤发生在分组之后,若分组涉及大量数据,可能影响性能。建议优先在 WHERE 中缩小原始数据范围。


实战案例:电商销售分析

需求:分析各商品类别的销售情况,筛选出以下条件的类别:

  1. 总销售额超过50000元;
  2. 平均订单数量少于5件;
  3. 有至少3个不同客户购买。

解决方案

SELECT category,  
       SUM(total_price) AS total_sales,  
       AVG(quantity) AS avg_quantity_per_order,  
       COUNT(DISTINCT customer_id) AS unique_customers  
FROM orders  
GROUP BY category  
HAVING SUM(total_price) > 50000  
   AND AVG(quantity) < 5  
   AND COUNT(DISTINCT customer_id) >= 3;  

总结

SQL HAVING 子句是数据分析师和开发者必备的工具之一,它通过在分组后过滤数据,帮助用户精准定位目标群体。掌握其与 WHERE 的区别、应用场景及进阶技巧,能显著提升查询效率和结果的准确性。无论是统计部门绩效、分析用户行为,还是优化销售策略,HAVING 子句都能成为你数据处理的得力助手。

延伸思考

  • 如何结合 CASE WHENHAVING 实现复杂条件过滤?
  • 在分布式数据库中,HAVING 的执行效率如何优化?

通过持续实践与探索,你将逐渐解锁更多 SQL 的强大功能!

最新发布