CodeGym /课程 /SQL SELF /复杂嵌套查询示例:EXISTS、IN、HAVING 的组合

复杂嵌套查询示例:EXISTS、IN、HAVING 的组合

SQL SELF
第 14 级 , 课程 3
可用

恭喜你,终于来到真正有趣的部分啦!今天我们要看看怎么把不同类型的子查询组合起来,解决更难的任务。EXISTSINHAVING —— 这三兄弟能让你在数据库里玩出魔法。我们会从一张表里查数据,再用另一张表的数据来筛选、分组,然后再过滤分组。最后还会聊聊怎么让查询更高效的小技巧。

咱们先来定个总目标,这节课会一步步把它搞定。

任务描述

假设我们有一个大学数据库,里面有三张表:

students

id name group_id
1 Otto 101
2 Maria 101
3 Alex 102
4 Anna 103

courses

id name
1 数学
2 编程
3 哲学

enrollments

student_id course_id grade
1 1 90
1 2 NULL
2 1 85
3 3 70

我们要选出所有满足以下条件的学生:

  1. 至少选了一门课 EXISTS
  2. 至少有一门课没有成绩 IN
  3. 属于平均分大于 80 的小组 HAVING

EXISTSIN 的解法

第1步:检查学生是否选课(EXISTS)。 先来最简单的条件。我们要知道哪些学生至少选了一门课。用 EXISTS 就行。

SELECT name
FROM students s
WHERE EXISTS (
  SELECT 1
  FROM enrollments e
  WHERE e.student_id = s.id
);
  1. 外层查询从 students 表里选出名字。
  2. 子查询检查 enrollments 表里有没有和外层学生对应的记录(WHERE e.student_id = s.id)。
  3. SELECT 1 只是说明我们只关心有没有记录,不在乎内容。

结果:

name
Otto
Maria
Alex

现在我们知道谁选了课。但我们还想进一步筛选——只要那些有课没成绩的学生。

第2步:检查有没有成绩(IN + NULL)。 现在加个过滤:只要那些至少有一门课没成绩的学生。这里用 INNULL

SELECT name
FROM students s
WHERE id IN (
  SELECT e.student_id
  FROM enrollments e
  WHERE e.grade IS NULL
);
  1. 外层查询选出学生名字。
  2. 子查询从 enrollments 表里选出 grade IS NULLstudent_id

结果:

name
Otto

所以 Otto 是唯一有课没成绩的学生。是不是有点戏剧性!不过还没完,我们还得只看那些平均分大于 80 的小组。

HAVING 的解法

第3步:分组和用 HAVING 过滤。

现在要把所有条件合起来。我们要:

  1. 算出每个小组的平均分。
  2. 只要平均分大于 80 的小组。
  3. 输出这些小组里、同时满足前面条件的学生。
SELECT name
FROM students s
WHERE s.group_id IN (
  SELECT group_id
  FROM students
  JOIN enrollments ON students.id = enrollments.student_id
  WHERE grade IS NOT NULL
  GROUP BY group_id
  HAVING AVG(grade) > 80
)
AND id IN (
  SELECT e.student_id
  FROM enrollments e
  WHERE e.grade IS NULL
);
  1. 外层查询选出所有满足条件的学生名字。
  2. WHERE 里的第一个子查询返回平均分大于 80 的 group_id
    • 我们把 studentsenrollments 联表,拿到成绩。
    • 只要 grade IS NOT NULL 的记录。
    • group_id 分组。
    • HAVING 过滤分组。
  3. 第二个子查询检查学生有没有课没成绩。
  4. 两个条件用 AND 组合。

结果:

name
Otto

所以 Otto 不仅是唯一没成绩的学生,还属于一个平均分很高的小组,厉害吧!

方法对比:EXISTS vs IN

EXISTS 最适合用来快速判断有没有记录。它很高效,因为一旦找到一条就停了。大表里尤其有用。

IN 更适合你关注数据内容的时候。比如你要拿一堆 id 去做后续筛选。但要注意,如果子查询返回太多值,IN 可能会变慢。

什么时候用 HAVING

聚合数据、需要按结果过滤时,HAVING 是首选。但如果能把条件提前到 WHERE(比如按列过滤),那样会更简单也更快。

完整例子

再来一个例子巩固下:选出那些有学生成绩低于 75,但没人选“哲学”课的小组。

再提醒下我们的表:

students

id name group_id
1 Otto 101
2 Maria 101
3 Alex 102
4 Anna 103

courses

id name
1 数学
2 编程
3 哲学

enrollments

student_id course_id grade
1 1 90
1 2 NULL
2 1 85
3 3 70
SELECT DISTINCT group_id
FROM students s
WHERE group_id IN (
  SELECT s.group_id
  FROM students s
  JOIN enrollments e ON s.id = e.student_id
  WHERE e.grade < 75
)
AND group_id NOT IN (
  SELECT s.group_id                                 -- 一级嵌套查询
  FROM students s
  JOIN enrollments e ON s.id = e.student_id
  WHERE e.course_id = (
    SELECT id FROM courses WHERE name = '哲学' -- 二级嵌套查询 :P
  )
);
  1. 第一个子查询选出有学生成绩低于 75 的小组。
  2. 第二个子查询排除和“哲学”课有关的小组。
  3. 我们用 INNOT IN 组合条件,得到最终结果。

结果:

group_id
101

这些技巧有啥用?

实际工作中,这些方法能帮你分析复杂的数据关系。比如:

  • 做数据分析时,找出“特殊”客户群(VIP、问题用户啥的)。
  • 做推荐系统开发时,按一堆条件筛用户。
  • 面试时,让你优化复杂 SQL 查询。

多练练吧!这就是你成为高手的路。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION