CodeGym /课程 /SQL SELF /在 HAVING 里用子查询过滤聚合数据

在 HAVING 里用子查询过滤聚合数据

SQL SELF
第 14 级, 课程 2
可用

有时候我们不只是要分组数据然后过滤结果,还得加点额外的逻辑——比如,拿学生在小组里的平均分和某个外部标准比一比。这时候 HAVING 里的子查询就派上用场了——这是个很强大的工具,可以让你直接在 SQL 查询里做更聪明的决策。

回顾一下 HAVING

我们来专注聊聊怎么把子查询和 HAVING 结合起来用,来在聚合值的层面过滤数据。为啥要这样?因为 WHERE 只能过滤单行,而 HAVING 是针对分组后的数据——这是另一种分析方式,让你能玩出更多花样。

在我们深入聊子查询和 HAVING 的组合之前,先复习下 HAVING 是啥,它和 WHERE 有啥区别。

  • WHERE 在 分组前 过滤行(GROUP BY 之前)。
  • HAVING 在 聚合后 过滤数据,也就是数据已经分组完了。

想象你在分析学生和他们的分数。用 WHERE 你可以把分数低于某个值的学生排除掉,而 HAVING 则能让你根据平均分或最高分把整个小组都排除。

数据示例

下面是一个学生表:

表 students:

student_id student_name department grade
1 Alex Physics 80
2 Maria Physics 85
3 Dan Math 90
4 Lisa Math 60
5 John History 70

HAVING 用法示例(不带子查询)

SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > 75;

结果:

department avg_grade
Physics 82.5
Math 75.0

“History” 这个院系没被选出来,因为它的平均分低于 75。很简单对吧?现在我们加点子查询的魔法。比如下面这个例子,我们可以按所有院系的平均分来过滤。

HAVING 里的子查询

HAVING 里的子查询是让你过滤聚合数据更灵活的好办法。你可以把平均分、最大值这些聚合结果和数据库其它地方算出来的值比一比。简单说,就是可以问:“我们这个结果是不是比全校平均还牛?”

例子:按平均分过滤院系

假设我们想找出那些学生学得比别人好的院系——也就是院系的平均分高于全校平均。

数据如下:

表 students:

student_id student_name department grade
1 Alex Physics 80
2 Maria Physics 85
3 Dan Math 90
4 Lisa Math 60
5 John History 70

先查一下所有学生的平均分:

SELECT AVG(grade) AS university_avg
FROM students;

现在在 HAVING 里用子查询:

SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);

结果:

department avg_grade
Physics 82.5

这里发生了啥?

  1. 子查询(SELECT AVG(grade) FROM students)算出全校平均分——这里是 77。
  2. 主查询按院系分组,算每个院系的平均分。
  3. HAVING 拿院系平均分和全校平均比,只保留更高的那些。

对比 WHERE 和 HAVING 的用法

为了更清楚区别,假设你想选出分数高于平均分的学生。只能用 WHERE:

SELECT name, grade
FROM students
WHERE grade > (SELECT AVG(grade) FROM students);

结果(用上面表的数据):

name grade
Alex 80
Maria 85
Dan 90

但如果你想看哪些院系的平均分高于全校平均,就必须用 HAVING——因为你过滤的是分组,不是单行:

SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);

结果:

department avg_grade
Physics 82.5

总结一下:

  • WHERE 针对分组前的单行。
  • HAVING 针对聚合后的分组。

例子:多个聚合一起用

再来看一个场景。假设我们有 students 表,存着学生分数和院系:

表 students:

name grade department
Alex 80 Physics
Maria 85 Physics
Dan 90 Math
Olga 95 Math
Ivan 70 History
Nina 75 History

现在我们想找出那些院系:

  1. 学生平均分 高于 全校平均。
  2. 院系里最高分 大于 90。

写个这样的查询:

SELECT department, AVG(grade) AS avg_grade, MAX(grade) AS max_grade
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
   AND MAX(grade) > 90;

这个查询干了啥:

  • AVG(grade) > (SELECT AVG(grade) FROM students) —— 检查院系平均分是不是比全校高。
  • MAX(grade) > 90 —— 说明有学生考得特别牛。

结果:

department avg_grade max_grade
Math 92.5 95

只有 “Math” 院系同时满足平均分高于全校、还有牛人分数大于 90。

例子:选出分差最小的组

假如你想找出那些组,组内最高分和最低分的差,比全校的分差还小。

我们用的还是 students 表:

name grade department
Alex 80 Physics
Maria 85 Physics
Dan 90 Math
Olga 95 Math
Ivan 70 History
Nina 75 History

分两步来:

  1. 先算全校的最大-最小分差:
    SELECT MAX(grade) - MIN(grade) AS range_university
    FROM students;
    
  2. 然后写主查询,把它和子查询结合:
SELECT department, MAX(grade) - MIN(grade) AS range_department
FROM students
GROUP BY department
HAVING (MAX(grade) - MIN(grade)) < ( SELECT MAX(grade) - MIN(grade) FROM students );

查询结果:

department range_department
Physics 5
Math 5

“Physics” 和 “Math” 这两个组的分数更稳定——分差比全校还小。

用 HAVING 和子查询优化查询

要记住,嵌套子查询在大数据库里可能会拖慢性能。给你几个建议:

用索引。 如果子查询用到的列在 WHERE 或 JOIN 里,记得给这些列加索引。

避免数据量爆炸。 如果子查询返回太多中间结果,拆成几步或者用临时表。

用 EXPLAIN 分析查询。 总是看看 PostgreSQL 怎么执行你的查询。如果发现子查询被执行了很多次,考虑怎么优化。

和 CTE 比一比。 有时候用 WITH(Common Table Expressions)会更快,也更好读。这个以后讲 :P

组合子查询、HAVING 和 GROUP BY

在 HAVING 里用子查询可以写出更复杂的过滤,特别是你要同时考虑聚合、平均值和别的指标时。这些都能帮你从真实数据里挖出有意思的洞见。

例子:按平均分和学生数对比院系

假设你想选出那些院系:

  1. 平均分高于全校平均。
  2. 学生人数比平均分最低的院系还多。

原始表 students:

name grade department
Alex 80 Physics
Maria 85 Physics
Dan 90 Math
Olga 95 Math
Ivan 70 History
Nina 75 History
Oleg 60 History

查询:

SELECT department, AVG(grade) AS avg_grade, COUNT(*) AS student_count
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
   AND COUNT(*) > (
       SELECT COUNT(*)
       FROM students
       GROUP BY department
       ORDER BY AVG(grade)
       LIMIT 1
   );

这个查询展示了怎么在 HAVING 和 GROUP BY 里组合子查询来多条件分析。结果:

department avg_grade student_count
Physics 82.5 2
Math 92.5 2

History 院系没被选出来,因为它平均分最低,学生也最少。Physics 和 Math 都是分数和人数都高于平均。

常见错误和怎么避免

NULL 的坑。 如果数据里有 NULL,HAVING 里的子查询可能会出奇怪结果。用 COALESCE 处理下:

SELECT AVG(grade)
FROM students 
WHERE grade IS NOT NULL;

子查询返回太多数据。 子查询结果太多会拖慢性能。一定要加好条件。

搞错执行顺序。 记住 HAVING 是在分组后执行的,子查询可能在主查询前就跑了。

没加索引。 子查询用到的列没索引会让查询很慢。

HAVING 里的子查询让你能在聚合层面做各种分析。你可以按复杂条件过滤分组、对比组间结果,写出很牛的分析查询。恭喜你,现在可以把这些知识用到实际项目里啦!

2
任务
SQL SELF, 第 14 级, 课程 2
已锁定
按平均分过滤院系
按平均分过滤院系
2
任务
SQL SELF, 第 14 级, 课程 2
已锁定
院系中学生人数高于某个水平
院系中学生人数高于某个水平
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION