
备考计算机三级数据库技术很多人前面几章学得挺顺一翻到高级数据库查询就开始发毛。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。看到这种题不要犹豫答案基本就是它。也可以用这个规则来检查自己写出来的综合题 SQLSELECT 里凡是没套聚合函数的字段必须逐一对照 GROUP BY。3.3 COUNT(*) 与 COUNT(列) 的语义差别COUNT(*) 统计的是行数不管哪一列是不是 NULLCOUNT(列名) 统计的是这一列非 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 EXISTSSELECT 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 ALLINTERSECT 返回两个结果都有的行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. 综合设计题的固定拆解流程从题面文字到一段安全 SQL6.1 四步拆题法做题顺序比 SQL 本身更重要到了综合设计题很多考生的问题不是不会写而是拿到题目不知道从哪里下手。我给自己定的做题流程是这样的你可以直接拿去用通读题面圈出涉及的所有表名和字段名。判断是否需要多表联接。看看题目要求的输出字段和筛选条件是不是同一张表里都有。判断是否需要分组和聚合。题面出现“每个”“平均”“最多”“至少”“超过”等词基本跑不掉。写完之后逐项对照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.SnameSELECT 里才能带出 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 这东西是典型“眼睛会了手不会”只有真写一遍才知道自己卡在语法上还是卡在审题上。如果你也在准备计算机三级数据库技术希望这篇能把“高级查询”这层窗户纸捅破一点接下来刷题的时候能少掉几根头发。