☰
计算机三级数据库备考:高级查询考点解析与SQL实战技巧
2026/10/5 13:50:54 网站建设 项目流程

备考计算机三级数据库技术,很多人前面几章学得挺顺,一翻到高级数据库查询就开始发毛。SELECT、WHERE、ORDER BY 这些基础语法不难,但题目一旦变成多表联接、嵌套子查询、分组统计,选择题的选项开始长得差不多,填空题也容易漏掉一两个关键子句。这篇文章是计算机三级备考系列的第七篇,专门拆高级查询这块的考试重点和失分细节。适合正在刷题的三级考生,也适合学完 SQL 基础之后想系统梳理一遍高级查询语法的人。下面我不会按教材目录平铺直叙,而是按考试里真正的出题方式来组织——联接、分组、子查询、集合运算,以及综合题该怎么读题、怎么写。

1. 先看清这章的考法:三级考试到底怎样考高级查询

1.1 五种高频考点,排好优先级

三级数据库技术里的 SQL 部分,向来不是让你背几条语法就完事。高级查询这一块,我翻了历年的真题和主流教材后,总结下来就是下面五类,基本覆盖了能出的所有花样:

  • 联接查询:重点是内连接、外连接的方向判断,以及 ON 条件与 WHERE 条件的界线。
  • 分组聚合:GROUP BY、HAVING、聚合函数 COUNT/SUM/AVG/MAX/MIN 的组合逻辑。
  • 子查询:IN、EXISTS、相关子查询、派生表,以及“选全部、选至少、选不存在”这类题的标准写法。
  • 集合运算与视图:UNION、INTERSECT、EXCEPT 的使用前提,视图对复杂查询的封装作用。
  • 综合应用题:给表结构、给查询要求,让你写完整 SQL,或者给一段 SQL 让你读结果。

为什么把顺序排成这样?因为联接、分组、子查询是综合题的骨架,集合运算和视图更像“补充工具”。复习时先啃前三类,再把后两类当成查漏补缺,性价比最高。

1.2 题型分布与分值印象

考试一般分选择题、填空题和设计应用题几种形式。高级查询在每种题型里都会出现:

题型常见考法给分重点
选择题判断哪种 SQL 写法语义正确、执行顺序、NULL 陷阱概念辨析,干扰项通常只改一个关键字
填空题给一段残缺 SQL,补 JOIN 类型、HAVING、EXISTS 等对子句位置的敏感度
设计/应用题根据文字描述写完整 SQL,或读 SQL 写含义多表联接与分组条件是否完整

从往年题目的印象来看,高级查询相关的分值能占到 SQL 部分的四成到一半。尤其设计题,几乎每年都有一道以上离不开多表查询或者分组统计。所以这章不是可学可不学的边角料,而是决定能不能拿“良好”以上的关键。

1.3 复习顺序建议

我个人建议的顺序是:先做 10 道基础的单表查询题热身,然后按“联接→分组→子查询→集合运算→视图”的顺序推进,最后再集中做 5 道综合设计题。

不要一开始就对着教材把五种 JOIN 的语法背完再看题,那样合上书就忘。正确的做法是先看一道典型例题,理解它为什么这样写,再去做变式题。三级考试对 SQL 的考查从来不偏不怪,但特别喜欢在“你是不是真的懂语义”上做文章。

2. 联接查询的两个深坑:ON 条件位置与外连接方向

2.1 四种联接类型先过一遍

联接查询是高级查询的地基。考试环境一般以 SQL Server 为参考,四种联接的基本语义必须滚瓜烂熟:

联接类型保留哪些行记忆提示
INNER JOIN只保留两边都匹配到的行没有匹配就隐身
LEFT JOIN左表所有行都保留,右表没匹配就补 NULL以左边的名单为准,右边对不上号也得出场
RIGHT JOIN右表所有行都保留,左表没匹配就补 NULLLEFT 反着穿
FULL JOIN两边的所有行都保留,没匹配的对方侧补 NULL两边都全须全尾

备考的时候有个很土但好用的类比:LEFT JOIN 就像公司开年会,左边是全体员工花名册,右边是签到表。年会照片里每个员工都得出现,没签到的右边就标一个“查无此人”(NULL),但人不能被裁掉。这个类比能帮你记住外连接的根本特征——不匹配的行不会消失,只是补 NULL。

2.2 审题时怎么判断该用外连接

做联接题第一步不是回忆语法,而是先判断“题目到底要不要保留不匹配的行”。题干里出现“所有学生都要显示”“无论是否选课”“没有也要列出来”这类字眼,基本就是外连接。

举个例子,题目说“查询所有课程及其选课人数,没被选的课程也要出现在结果里”。很多考生一激动就写:

SELECT C.Cname, COUNT(SC.Sno) FROM Course C INNER JOIN SC ON C.Cno = SC.Cno GROUP BY C.Cname;

结果没被选的课程直接消失。正确写法是把 INNER JOIN 换成 LEFT JOIN,以课程表为主表。因为题目要求“所有课程”,课程表就是花名册,选课记录是签到表。审题时把“所有”“无论”圈出来,比纠结语法更管用。

2.3 ON 和 WHERE 为什么不能随意互换

这个坑每年都有考生踩。内连接里 ON 和 WHERE 写条件经常可以互换,但一到外连接就翻天。

对比一下这两句:

SELECT C.Cname, COUNT(SC.Sno) FROM Course C LEFT JOIN SC ON C.Cno = SC.Cno AND SC.Grade >= 60; SELECT C.Cname, COUNT(SC.Sno) FROM Course C LEFT JOIN SC ON C.Cno = SC.Cno WHERE SC.Grade >= 60;

第一句的意思是:课程照常全部列出,选课记录里只拼接成绩及格的那些,没及格或没选课的都按无匹配处理,课程照样出现。第二句则是在 LEFT JOIN 完成之后,再用 WHERE 把 Grade 为 NULL 的行淘汰掉——没选课或没及格记录的课程整行没了,外连接退化成了内连接的效果。

理解起来其实就一条原则:ON 决定“怎么拼接两张表”,WHERE 决定“拼接完之后留下哪些行”。外连接里条件放在 WHERE 之后,NULL 行会被过滤掉,这和外连接的初衷完全相反。考试时遇到外连接相关的补充条件,先想清楚这个条件该在拼接前还是拼接后生效。

2.4 填空和选择题里的常见陷阱

填空和选择题碰到的联接题,最喜欢用“缺关键字”来迷惑人。我曾经遇到一道题,让你在同一个 SQL 里补 ON 后面的条件,选项里故意塞了一个写进 WHERE 的等值条件。应对方法就一条:把 ON 和 WHERE 当成两个阶段来处理。凡是涉及左表或右表“是否保留”的条件,优先放到 ON 里;凡是针对结果整体的过滤条件,再考虑 WHERE。

还有一个细节:多表联接时,SELECT 后的字段一定要带表别名前缀,比如 C.Cname、SC.Sno。考试中表名可能比较长,但写别名能让阅卷人一眼看出你清楚字段属于哪张表,也能避免两个表出现同名字段时产生的歧义。

3. 分组聚合的黄金组合:WHERE、GROUP BY、HAVING 的顺序和边界

3.1 执行顺序是分组的钥匙

分组统计是三级考试里出题频率最高的知识点之一,而破解它的钥匙是 SQL 的执行顺序。很多人背了“GROUP BY 和 HAVING 的语法”,却不知道为什么 WHERE 不能用来筛组,本质就是没理解顺序。

一条典型的分组查询,内部执行顺序是这样的:

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY

看这个例子:

SELECT Sdept, AVG(Sage) FROM Student WHERE Sname <> '张三' GROUP BY Sdept HAVING AVG(Sage) >= 20 ORDER BY Sdept;

执行时先把 Student 表里名字叫张三的行去掉,然后按 Sdept 分组,对每个组算平均年龄,再用 HAVING 筛掉平均年龄小于 20 的组,最后按系名排序。WHERE 执行的时候还没有分组,所以它只能筛“行”,不能筛“组”;HAVING 执行的时候分组已经完成,所以它可以用聚合函数。这个区别就是选择题和概念判断题里最经典的干扰点。

3.2 SELECT 里出现非聚合列,必须出现在 GROUP BY 里

这条规则看起来是语法限制,其实背后是语义问题。你想统计“每个系的平均年龄”,SELECT 里可以写 Sdept,因为每个组只有一个系名;但如果你还想写 Sname,数据库就懵了——一个组里有几十个学生,到底输出哪个名字?所以强制规定:SELECT 中的非聚合列必须出现在 GROUP BY 中。

考试时经常给一段错误 SQL 让你挑毛病,最常见的错误就是 SELECT 里的某个列既没有聚合,又没写进 GROUP BY。看到这种题,不要犹豫,答案基本就是它。也可以用这个规则来检查自己写出来的综合题 SQL:SELECT 里凡是没套聚合函数的字段,必须逐一对照 GROUP BY。

3.3 COUNT(*) 与 COUNT(列) 的语义差别

COUNT(*) 统计的是行数,不管哪一列是不是 NULL;COUNT(列名) 统计的是这一列非 NULL 的个数。这个差别在选课成绩这类表里很容易出题。

比如统计每个学生的选课门数,一般用 COUNT() 或 COUNT(Cno) 都行,因为选课表的 Cno 本来就不应该为空。但如果题目改成“统计每个学生有成绩记录的课程门数”,用 COUNT() 就会把没成绩但选课记录存在的行也算进去,这时候就该用 COUNT(Grade)。

一个小小的建议:做题时先看题目强调的条件。题目里如果出现“成绩非空”“成绩不为 NULL”这类限定,聚合函数里就得用对应的列,而不是无脑 COUNT(*)。

3.4 典型题:按组筛选的条件应该写在 HAVING

来看一道几乎每年都会换数据重出的经典题:

查询选修了三门及以上课程,且平均成绩高于 80 分的学生学号和平均成绩。

SELECT Sno, AVG(Grade) AS avg_grade FROM SC GROUP BY Sno HAVING COUNT(*) >= 3 AND AVG(Grade) > 80;

这道题的关键判断有两点:第一,“三门及以上”是对每个学生统计选课记录后筛组,必须用 HAVING,不能写进 WHERE;第二,如果题目没有额外要求,只输出学号和平均成绩,不需要联接 Student 表,因为 SC 表里已经有 Sno。

做这类题时还有一个细节,有的题目会要求“至少选修了 3 门不同的课程”,这时候如果担心同一门课出现多条记录,更稳妥的写法是 COUNT(DISTINCT Cno)。虽然规范的选课表里不会出现重复选课,但考试题里偶尔会有这种“文字陷阱”,用 DISTINCT 可以避免扣分。

4. 子查询的几种面孔:IN、EXISTS、相关子查询与派生表

4.1 子查询可以出现在三个位置

子查询本质上就是把一个 SELECT 的结果当作另一个查询的输入。三级考试里常见的位置有三个:

  • SELECT 子句:返回单个值的标量子查询,比如在查询学生时附带该生的平均成绩。
  • FROM 子句:把子查询结果当成一张临时表,通常叫派生表或内联视图。
  • WHERE 子句:用 IN、EXISTS 或比较运算符来接子查询。

同一个查询需求,往往有多种写法。比如“找出选课超过 3 门的学生”,既可以用 GROUP BY 加 HAVING,也可以先写一个派生表再从里面查。选择题里经常让你判断哪种写法语义等价,本质上考的就是这个灵活转换能力。

4.2 NOT IN 的 NULL 陷阱与 NOT EXISTS 的稳定写法

“NOT IN 遇到 NULL 会全表翻车”是三级考试里的高频概念题,也是实际开发里常见的问题。

假设要查“没有选任何课程的学生”:

SELECT Sno FROM Student WHERE Sno NOT IN (SELECT Sno FROM SC);

如果 SC 表的 Sno 列里有 NULL,这条 SQL 的查询结果会变成空集。因为 NOT IN 的语义是“不等于子查询结果里的任何一个值”,而 NULL 参与比较时结果既不是 TRUE 也不是 FALSE,而是“未知”,最终导致所有行都不满足条件。

更稳的写法是用 NOT EXISTS:

SELECT Sno FROM Student S WHERE NOT EXISTS ( SELECT 1 FROM SC WHERE SC.Sno = S.Sno );

EXISTS 只关心内层查询能不能查出记录来,完全不关心 SELECT 后面写的是 1、* 还是某列,所以天然不受 NULL 干扰。备考时我建议直接把 NOT EXISTS 当成“不存在”类题目的默认写法,遇到选择题问“哪个写法更安全”,答案基本也是它。

4.3 相关子查询的逐行执行逻辑

相关子查询是阅读题里最容易卡住的点。它和普通子查询最大的区别是:普通子查询先执行一次,得到固定结果;相关子查询会把外层每一行轮流带进内层,逐行执行一次。

看这道经典题:查询年龄大于本系平均年龄的学生姓名。

SELECT Sname FROM Student S1 WHERE Sage > ( SELECT AVG(Sage) FROM Student S2 WHERE S2.Sdept = S1.Sdept );

执行时,外层 Student 表一行一行地过,当前行的 Sdept 会传入内层子查询,算出这个系的平均年龄,再和外层这一行的 Sage 比较。换句话说,有多少个不同系,内层子查询就可能执行多少次,而不是只执行一次。

这个“逐行绑定”的思路在做读题分析时特别重要。看到 WHERE 子查询里出现了外层表.字段的引用,立刻意识到这是相关子查询,然后按“一行一算”的方式推理结果。

4.4 “全部”类题目的双重 NOT EXISTS 模板

“查询选修了全部课程的学生”是历年设计题里的常客,也是很多考生的心理阴影。它可以用双重 NOT EXISTS 来写,逻辑是“找不出任何一门课这个学生没选”。

SELECT Sname FROM Student S WHERE NOT EXISTS ( SELECT 1 FROM Course C WHERE NOT EXISTS ( SELECT 1 FROM SC WHERE SC.Sno = S.Sno AND SC.Cno = C.Cno ) );

读法:内层第二句是“该学生是否选了当前这门课”;NOT EXISTS 取反后变成“当前这门课他没选”;外层再套一个 NOT EXISTS,意思是“不存在任何一门他”没选“的课”。两层否定叠在一起,语义就是“每一门课他都选了”。

这个模板值得背到条件反射的程度。因为考场上从零推导双重否定很容易绕晕,但背熟模板后遇到类似变式题,比如“没有选修任何课程”“没有通过任何考试”,只需要替换表名和条件即可。

5. 集合运算与视图:查询结果也能“加减拼凑”

5.1 集合运算三兄弟的前提条件

有些查询需求用 JOIN 写会很绕,但用集合运算一写就清楚了。SQL Server 里常见的集合运算有三个:UNION(并集)、INTERSECT(交集)、EXCEPT(差集)。

使用它们有硬性前提:

  • 两个 SELECT 结果的列数必须相同。
  • 对应位置列的数据类型必须兼容。
  • 结果集中列的顺序决定对应关系,不能乱排。

UNION 默认会去重,想保留重复记录要用 UNION ALL;INTERSECT 返回两个结果都有的行;EXCEPT 返回第一个结果里有、第二个结果里没有的行。可以把它们类比成一个“上下拼接”“找共同点”“减掉重合部分”的过程。

比如查“选修了 1 号课程或者 2 号课程的学生”,有人纠结用 OR 怎么写,其实用 UNION 最直观:

SELECT Sno FROM SC WHERE Cno = '1' UNION SELECT Sno FROM SC WHERE Cno = '2';

如果题目要求“既修了 1 号课又修了 2 号课”,则:

SELECT Sno FROM SC WHERE Cno = '1' INTERSECT SELECT Sno FROM SC WHERE Cno = '2';

注意,不能写成 WHERE Cno = '1' AND Cno = '2',因为一行里不可能同时满足两个条件。这种时候集合运算比普通查询表达得更干净。

5.2 视图是给复杂查询套的“壳子”

视图在三级考试里经常和高级查询结合着考。它的本质是一条保存起来的 SELECT 语句,查视图等于执行那段 SQL。高级查询中为什么要讲视图?因为它能把一段长而复杂的联接和分组逻辑包装成一张“虚拟表”,后续查询就像查普通表一样简单。

看一个典型的视图题:

CREATE VIEW V_AvgGrade AS SELECT Sno, AVG(Grade) AS avg_grade FROM SC GROUP BY Sno;

之后你可以在视图上继续查询,比如查平均分大于 90 的学生:

SELECT Sno FROM V_AvgGrade WHERE avg_grade > 90;

这种题在考试里不难,但容易丢分的是对视图本身概念的理解。视图不存储数据,它查询到的永远是底层表的当前数据。底层表数据变了,视图的结果跟着变;底层表删了,视图也就失效了。

还有一个高频细节:SQL Server 中定义视图时不允许直接使用 ORDER BY,除非配合 TOP 之类的子句。视图里面的行本身没有固定顺序,排序应该在查询视图时用 ORDER BY 来做。这个点常以判断题或选择题形式出现,不要被“视图里可以排好序”这种说法带偏。

5.3 视图概念题里的易丢分考点

视图的可更新性是另一个常考概念。如果视图基于单张表、不包含聚合和分组,那么通过视图更新数据通常可行;但如果视图基于多表联接、分组、聚合或 DISTINCT,更新操作就受到严格限制。备考时不用研究每种情况的细节,记住结论即可:视图越“复杂”,可更新能力越弱。

另外,视图的创建权限和查询权限是两回事。用户能被授予查询视图的权限,但不一定能看到底层表的原始结构。这一点在安全相关的选择题里出现过,强调“视图是一种安全机制”的表述都是对的。

6. 综合设计题的固定拆解流程:从题面文字到一段安全 SQL

6.1 四步拆题法,做题顺序比 SQL 本身更重要

到了综合设计题,很多考生的问题不是不会写,而是拿到题目不知道从哪里下手。我给自己定的做题流程是这样的,你可以直接拿去用:

  1. 通读题面,圈出涉及的所有表名和字段名。
  2. 判断是否需要多表联接。看看题目要求的输出字段和筛选条件是不是同一张表里都有。
  3. 判断是否需要分组和聚合。题面出现“每个”“平均”“最多”“至少”“超过”等词,基本跑不掉。
  4. 写完之后逐项对照:SELECT 列合不合法?HAVING 条件是否符合题意?排序方向对不对?

这个顺序之所以重要,是因为大多数错误都出在“需求没读懂就动手写”。先花 30 秒做需求拆分,比写完再返工快得多。

6.2 真题实战:三表场景下的分组统计

下面是一道很典型的三表综合题,包含联接、分组、HAVING 和排序。

表结构如下:

  • S(Sno, Sname, Sdept, Sage)
  • C(Cno, Cname, Cpno, Credit)
  • SC(Sno, Cno, Grade)

题目要求:查询平均成绩大于 85 分且至少选修了 3 门课程的学生学号和平均成绩,按平均成绩降序排列。

参考答案:

SELECT Sno, AVG(Grade) AS avg_grade FROM SC GROUP BY Sno HAVING AVG(Grade) > 85 AND COUNT(*) >= 3 ORDER BY avg_grade DESC;

拆解一下思路:输出“学号和平均成绩”,SC 表里都有,所以暂时不需要联接 S 表;筛选条件中的“平均成绩”和“选课门数”都是对每个学生的行分组后的统计结果,所以必须用 HAVING;按平均成绩降序排序,写在最后的 ORDER BY。

如果题目改成“还要输出学生姓名”,那就得联接 S 表,并且 GROUP BY 要扩为 S.Sno, S.Sname,SELECT 里才能带出 Sname。这个“字段跨表”的判断,三级考试里反复出现。

6.3 阅读题和定时训练的经验之谈

综合题还有一种反着考的形式:给一段 SQL,让你写出查询结果或者描述查询含义。阅读顺序有个诀窍——先看最内层的子查询,再一层一层往外剥。

比如下面这段:

SELECT Sname FROM Student S WHERE NOT EXISTS ( SELECT 1 FROM SC WHERE SC.Sno = S.Sno AND SC.Cno = '1' );

先看内层:判断当前学生是否在 SC 表中有 1 号课程的记录;再看外层 NOT EXISTS:把选了 1 号课程的学生筛掉。合起来就是“查询没有选修 1 号课程的学生名单”。信息都在,只是顺序反着读而已。

复习时我建议给自己做一次限时训练,一道综合题从读题到写完控制在 8 分钟以内。平时不限时容易养成“慢慢想”的依赖,考场上每道题的时间窗口没那么宽裕。

备考高级查询这一周,我自己的土办法是:每晚睡前把当天做错的 SQL 题在脑子里过一遍,第二天一早先把那条 SQL 默写出来。SQL 这东西是典型“眼睛会了手不会”,只有真写一遍,才知道自己卡在语法上还是卡在审题上。如果你也在准备计算机三级数据库技术,希望这篇能把“高级查询”这层窗户纸捅破一点,接下来刷题的时候能少掉几根头发。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询