CodeGym /课程 /SQL SELF /SELECT 里用子查询

SELECT 里用子查询

SQL SELF
第 14 级 , 课程 0
可用

咱们再聊聊 SELECT 里的子查询吧。尤其是,内部查询其实可以引用外部查询的数据。看起来简单,其实有点门道。咱们再深入一点聊聊这个话题……

SELECT 里的子查询可以加上 额外的计算列,或者那些依赖其他记录或表的数据。比如,你可以查出学生列表,顺便带上他们的平均分、选了多少门课,或者小组里的当前最高分。这在你想“边查边分析”数据时特别有用,能直接生成汇总列,不用提前处理数据。

SELECT 里子查询的基础

在上例子前,先看看基本语法。SELECT 里的子查询长这样:

SELECT column1,
       column2,
       (SELECT 聚合_或_条件 FROM 另一个_表 WHERE 条件) AS 新列名
FROM 主表;

注意,子查询返回一个值,这个值会作为新列出现在结果集里。而且 条件 可以引用 主表 的列。

例子1:加上学生的平均分

先来个简单又实用的查询:我们有 students 表,还有 grades 表,里面存着学生的分数。

students 表:

id name
1 Alex Lin
2 Anna Song
3 Dan Seth

grades 表:

student_id grade
1 90
1 85
2 76
3 88
3 92

现在我们想查出学生的名字和他们的平均分。用 SELECT 里的子查询就行:

SELECT
    s.id,
    s.name,
    (SELECT AVG(g.grade) 
     FROM grades g 
     WHERE g.student_id = s.id) AS average_grade
FROM students s;

结果:

id name average_grade
1 Alex Lin 87.5
2 Anna Song 76.0
3 Dan Seth 90.0

这里的子查询 (SELECT AVG(g.grade) FROM grades g WHERE g.student_id = s.id) 就是算每个学生的平均分。它对 students 表的每一行都返回一个值,这样你不用 JOIN 或提前建视图也能搞定。

例子2:统计每个学生选了多少门课

现在我们再加点数据:看看每个学生选了多少门课。我们有下面这些表:

enrollments 表:

student_id course_id
1 101
1 102
2 101

查出学生和他们选的课程数:

SELECT
    s.id,
    s.name,
    (SELECT COUNT(*)
     FROM enrollments e
     WHERE e.student_id = s.id) AS course_count -- 这里引用了外部 students 表
FROM students s;

结果:

id name course_count
1 Alex Lin 2
2 Anna Song 1
3 Dan Seth 0

子查询 (SELECT COUNT(*) FROM enrollments e WHERE e.student_id = s.id) 就是统计 enrollments 表里每个学生的记录数。

子查询里的数据聚合

很多时候,SELECT 里的子查询就是用来算聚合数据的。像 AVGSUMCOUNTMAXMIN 这些函数,都能直接在别的查询里用。

例子3:学生的总分

我们再加一列,每个学生的总分。用子查询把 grades 表里所有分数加起来:

SELECT
    s.id,
    s.name,
    (SELECT SUM(g.grade)
     FROM grades g
     WHERE g.student_id = s.id) AS total_grade
FROM students s;

结果:

id name total_grade
1 Alex Lin 175
2 Anna Song 76
3 Dan Seth 180

这个子查询 (SELECT SUM(g.grade) FROM grades g WHERE g.student_id = s.id) 就是把每个学生的分数加起来。如果学生没分数,结果就是 NULL,因为 SUM 没有值时会返回 NULL。

限制和建议

  1. 性能问题。 SELECT 里的子查询会对主表的每一行都执行一次。数据量大时,可能会很慢。如果能用 JOIN 或提前聚合好的数据,尽量用。比如:
SELECT
    s.id,
    s.name,
    g.total_grade
FROM students s
LEFT JOIN (
    SELECT student_id, SUM(grade) AS total_grade
    FROM grades
    GROUP BY student_id
) g ON s.id = g.student_id;

这种 JOIN 的写法更高效,因为聚合和统计只做一次。

2. NULL 的坑。

如果子查询没查到数据,结果就是 NULL。有时候你可能没想到。比如:

SELECT
    s.id,
    s.name,
    (SELECT SUM(g.grade)
     FROM grades g
     WHERE g.student_id = s.id) AS total_grade
FROM students s;

如果某个学生在 grades 里没有记录,total_grade 就是 NULL。想把 NULL 变成 0,可以用 COALESCE

SELECT
    s.id,
    s.name,
    COALESCE((SELECT SUM(g.grade)
              FROM grades g
              WHERE g.student_id = s.id), 0) AS total_grade
FROM students s;

对,这里 COALESCE 的第一个参数就是

(
    SELECT SUM(g.grade)
    FROM grades g
    WHERE g.student_id = s.id
)

优化 SELECT 里的子查询

想让查询更快、少点重复计算,可以这样:

  1. 给子查询用到的列加索引。比如 grades 表的 student_id 加索引,过滤会快很多。
  2. 能用 JOIN 预先聚合好的数据就用,不要全靠子查询。
  3. WHERE 过滤,尽量减少子查询处理的数据量。

最后一个例子:子查询大集合

咱们把前面学的都用上,写个查询:查出学生名字、平均分、课程数和总分:

SELECT
    s.id,
    s.name,
    (SELECT AVG(g.grade)
     FROM grades g
     WHERE g.student_id = s.id) AS average_grade,
    (SELECT COUNT(*) 
     FROM enrollments e 
     WHERE e.student_id = s.id) AS course_count,
    (SELECT SUM(g.grade)
     FROM grades g
     WHERE g.student_id = s.id) AS total_grade
FROM students s;

这个查询直接查出学生的完整档案,全靠子查询的威力。你能看到平均分、总分,还有选了几门课。像这样写,能很快拿到聚合信息,不用建 VIEW,也不用 JOIN。

id name average_grade course_count total_grade
1 Alex Lin 87.5 2 175
2 Anna Song 76.0 1 76
3 Dan Seth 90.0 0 180
2
任务
SQL SELF, 第 14 级, 课程 0
已锁定
查找学生的平均分
查找学生的平均分
2
任务
SQL SELF, 第 14 级, 课程 0
已锁定
统计每个学生的课程数量
统计每个学生的课程数量
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION