在SQL的实际查询工作中,经常会遇到需要先关联多张表获取数据,再对分组后的聚合结果进行筛选的需求,这种场景下WHERE子句无法直接使用聚合函数作为过滤条件,就需要借助HAVING子句配合聚合函数来完成过滤操作。

多表连接与聚合函数、HAVING子句的基础概念
多表连接是通过JOIN关键字将两张或多张表按照关联字段拼接成一张临时结果集的操作,常见的连接类型有INNER JOIN、LEFT JOIN、RIGHT JOIN等。聚合函数是用于对一组值进行计算并返回单一值的函数,常见的包括COUNT、SUM、AVG、MAX、MIN等。HAVING子句的作用是对GROUP BY分组后的结果集进行筛选,它可以直接使用聚合函数作为过滤条件,这是和WHERE子句最核心的区别。
多表连接后使用HAVING配合聚合函数的典型场景
假设存在两张业务表,一张是用户表user_info,存储用户的基础信息,另一张是订单表order_info,存储用户的订单数据,两张表通过user_id字段关联。现在需要查询出下单次数大于3次的用户信息以及对应的下单总数,这个需求就需要先完成多表连接,再分组聚合,最后用HAVING过滤聚合结果。
表结构说明
user_info表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| user_id | int | 用户ID,主键 |
| user_name | varchar | 用户姓名 |
| age | int | 用户年龄 |
order_info表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| order_id | int | 订单ID,主键 |
| user_id | int | 用户ID,关联user_info表 |
| order_amount | decimal | 订单金额 |
| create_time | datetime | 订单创建时间 |
具体实现SQL示例
首先通过INNER JOIN连接两张表,按照user_id分组统计每个用户的下单次数,再用HAVING筛选下单次数大于3的用户:
-- 查询下单次数大于3次的用户信息及下单总数
SELECT
u.user_id,
u.user_name,
u.age,
COUNT(o.order_id) AS order_count,
SUM(o.order_amount) AS total_amount
FROM
user_info u
INNER JOIN
order_info o ON u.user_id = o.user_id
GROUP BY
u.user_id, u.user_name, u.age
HAVING
COUNT(o.order_id) > 3;
上述代码中,INNER JOIN完成了用户表和订单表的关联,GROUP BY按照用户维度分组,COUNT(o.order_id)是统计每个用户的订单数量,HAVING子句中使用COUNT聚合函数作为过滤条件,最终只返回下单次数超过3次的用户数据。
注意事项
- WHERE子句是在分组前对原始数据进行过滤,HAVING子句是在分组后对聚合结果进行过滤,两者执行顺序不同,不要混淆使用场景。
- HAVING子句中使用的聚合函数必须和SELECT子句或GROUP BY子句中的聚合逻辑对应,避免出现逻辑错误。
- 如果多表连接使用LEFT JOIN,那么右表中没有匹配到的记录会显示为NULL,聚合函数统计时会忽略NULL值,需要在HAVING中考虑这种情况的处理。
复杂场景示例
如果需要查询出订单平均金额大于100,且下单次数大于等于2次的用户信息,SQL可以这样写:
-- 查询平均订单金额大于100且下单次数大于等于2次的用户
SELECT
u.user_id,
u.user_name,
AVG(o.order_amount) AS avg_order_amount,
COUNT(o.order_id) AS order_count
FROM
user_info u
INNER JOIN
order_info o ON u.user_id = o.user_id
GROUP BY
u.user_id, u.user_name
HAVING
AVG(o.order_amount) > 100
AND COUNT(o.order_id) >= 2;
这个示例中HAVING子句同时使用了AVG和COUNT两个聚合函数作为过滤条件,实现了更复杂的聚合结果筛选需求。