SQL嵌套查询入门:SELECT、子查询、连接查询与报错排查 简介面向数据库课程学习者与实验教学场景的《数据库实验5嵌套查询》实验报告文档聚焦SELECT统计查询、聚合函数与嵌套查询的实操训练适合课后复盘、实验提交和语法查漏补缺并配有实验目的、内容与思考模块。压缩包内共1个doc文件约642KB围绕CPXS数据库组织内容涵盖COUNT、SUM、MAX、MIN等统计函数、连接查询以及子查询、派生表和相关子查询等写法。报告结合上海客户订购数量大于200套、与“美美”同城客户、单价高于A01产品、未被订购产品等典型题目展示分组聚合、HAVING过滤与多层条件嵌套的查询示例。已有931人学习可作为数据库实验课配套参考帮助梳理从统计查询到复杂嵌套查询的解题思路。1. 数据库实验里那串 SELECT到底在干什么第一次翻开「数据库实验5嵌套查询」这份文档大多数人的反应是满屏大写的 SELECT、FROM、WHERE像一堆没背下来的咒语。其实数据库查询没那么玄它就是在跟几张表“提问”——你手上有客户表、产品表、销售表你想知道“一共几个客户”“哪些产品没人买”SQL 就是把这句话翻译成机器听得懂的句式。这份实验围绕三件事展开统计查询、连接查询、嵌套查询。其中嵌套查询是明显的分水岭学会它你就能把一个大问题拆成两个小问题分别去问。它适合刚接触 SQL、被 GROUP BY 和多层括号劝退的零基础同学学完能独立写出实验里那十几条查询。2. 打地基SELECT 和五个统计函数怎么写先别急着看嵌套。嵌套查询是“查询里面套查询”如果外面那一层你都没写顺套起来只会更乱。这一章的目标很朴素让你能独立写出单表查询并且看懂实验文档里前八条题在干什么。等你把统计函数和分组玩熟了后面两章才有落脚点。2.1 一条 SELECT 其实只有三段话新手最容易犯的错是把 SELECT 当成一个整体去背。换个角度它永远在回答三个问题查什么、从哪查、什么条件下查。写成句式就是SELECT 列 FROM 表 WHERE 条件三段话拼起来中间用空格隔开最后加分号。SELECT CNAME, SITE -- 查什么客户名称、所在城市两列 FROM CUSTOMER -- 从哪查客户表 CUSTOMER WHERE SITE 南城; -- 什么条件只要城市是南城的行这段的意思直白到不用翻译从客户表里把南城那些客户的名称和城市挑出来。三个参数你要会改SELECT后面换成你想要的列名多个列用逗号隔开、写*表示全部列FROM后面换成你的表名WHERE后面换成筛选条件文本值要用英文单引号包起来数字不用。提示SQL 里的引号必须是英文半角单引号中文引号会让语句直接报语法错误这是新手第一个晚上最常见的翻车点。顺带说一句SQL 不区分大小写select和SELECT效果一样但习惯上把关键字写成大写、表名列名写成小写读起来一眼就能分清哪是语法、哪是你自己的东西。2.2 五个统计函数数数、求和、找最大最小实验文档里反复出现的 COUNT、SUM、MAX、MIN、AVG统称聚合函数。它们的共同点是吃进去一堆行吐出来一个数。你可以把它们想成 Excel 表格底部的那个状态栏。函数干什么实验里的典型用法COUNT(*)数一共有多少行统计客户的数目COUNT(列名)数这一列有多少个非空值数有多少个客户编了号SUM(列名)把这一列的数字加起来求库存量的总和MAX / MIN取这一列的最大 / 最小值求所有产品的最高单价AVG求平均值算平均订单量SELECT COUNT(*) AS 客户总数 FROM CUSTOMER; -- 数行数结果是 3 SELECT SUM(STOCKS) AS 库存合计 FROM PRODUCT; -- 四行库存相加 SELECT MAX(PRICE) AS 最高单价, MIN(PRICE) AS 最低单价 FROM PRODUCT; -- 一条语句取两个极值AS是起别名纯粹为了结果好看不起也不影响正确性。这里真正要留意的是 COUNT 的两种写法COUNT(*)数的是行数哪怕整行都是空值也算一行COUNT(列名)只数这一列非空的值遇到 NULL 直接跳过。实验里“统计客户数目”用COUNT(*)最稳别为了显得高级去改。2.3 GROUP BY 和 HAVING先分堆再筛堆单表查询里最难绕的一段就是分组。GROUP BY CNO的意思是把销售表按客户编号分成一堆一堆同一个客户的记录摞在一起然后对每一堆分别算一次聚合函数。SELECT CNO, COUNT(PCODE) AS 产品种类数 -- 每个客户数他买了几种产品 FROM SALE GROUP BY CNO -- 按客户编号分成若干堆 HAVING COUNT(PCODE) 2; -- 只留下种类数达到 2 的堆这段对应的正是实验里“求至少订购两种以上产品的客户编号和产品种类数”。注意HAVING和WHERE的分工WHERE 在分堆之前过滤单行HAVING 在分堆之后过滤整堆。写错位置就会报错因为 WHERE 那一层还没有“堆”的概念聚合函数根本没法算。提示一条完整 SELECT 的执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。你按这个顺序去读语句比从第一个单词往后读要顺得多。2.4 第一个能跑起来的例子把实验库搭出来光看不练没用。下面这段脚本把实验里的三张表缩成一个最小可跑的版本字段名沿用原文档值全部是虚构的占位数据。在 MySQL 或 SQLite 里整段执行你就有了一个能反复折腾的小库。-- 客户表一个客户一行 CREATE TABLE CUSTOMER ( CNO VARCHAR(10), -- 客户编号如 C01 CNAME VARCHAR(30), -- 客户名称 SITE VARCHAR(20), -- 所在城市 TELE VARCHAR(20) -- 联系电话 ); -- 产品表一个产品一行 CREATE TABLE PRODUCT ( PCODE VARCHAR(10), -- 产品编号如 A01 PNAME VARCHAR(30), -- 产品名称 PRICE DECIMAL(10,2), -- 单价 STOCKS INT -- 库存量 ); -- 销售表一条订购记录一行 CREATE TABLE SALE ( CNO VARCHAR(10), -- 哪个客户买的 PCODE VARCHAR(10), -- 买了哪个产品 OQUANTITY INT -- 订购数量 );建完表再灌几行数据进去注意插入时列的顺序要和建表时一致INSERT INTO CUSTOMER VALUES (C01,云帆,南城,T-0001), (C02,星野,南城,T-0002), (C03,北岭,北港,T-0003); INSERT INTO PRODUCT VALUES (A01,台灯, 15.00, 800), (A02,水杯, 12.50, 300), (B01,背包, 88.00, 120), (B02,雨伞, 30.00, 60); INSERT INTO SALE VALUES (C01,A01, 300), (C01,B01, 150), (C02,A01, 120), (C03,A02, 50);现在跑第一条真正的查询SELECT COUNT(*) FROM CUSTOMER;结果应该是 3。再跑SELECT SUM(STOCKS) FROM PRODUCT;80030012060结果是 1280。如果这两条都对上了说明你的库搭好了后面所有例子都可以在这个库里验证。跑不对就回头看两件事表建成功了吗数据插进去了吗——用SELECT * FROM CUSTOMER;看一眼比盯着报错猜快得多。3. 两张表拼起来才有答案连接查询入门3.1 为什么单表问不出“南城客户买了多少”实验文档里有一道很典型的题查询由南城客户订购的、且订购数量大于 200 的记录要输出客户编号、产品编号、联系人和订购数量。你一看就知道这四个字段根本不在同一张表里——客户编号、联系电话在 CUSTOMER订购数量在 SALE。单表查询再怎么写也拼不出这四个字段因为一张表根本不知道另一张表的存在。这就是连接查询存在的理由把两张表按某个共同的列“对上”拼成一张宽表然后在这张宽表上做筛选。对新手来说先记住一句话就够了——连接的本质是“配对”配对的依据是两张表里含义相同的那个列在实验库里就是 CNO 和 PCODE。3.2 最朴素的写法FROM 写两张表WHERE 负责牵线SELECT SALE.CNO, PCODE, CNAME, OQUANTITY FROM SALE, CUSTOMER -- 两张表都写进 FROM逗号隔开 WHERE CUSTOMER.CNO SALE.CNO -- 牵线客户编号相等才算同一个人 AND CUSTOMER.SITE 南城 -- 再筛只要南城的客户 AND SALE.OQUANTITY 200; -- 再筛只要订购量超过 200参数上要注意两点一是列名前加了表名做前缀因为 CNO 这张表有、那张表也有不加前缀数据库不知道该用哪个二是配对条件CUSTOMER.CNO SALE.CNO必须写漏掉它就变成了两张表的笛卡尔积3 个客户 × 4 条销售记录 12 行全部冒出来看着像答案其实是错的。对照我们的示例数据跑一遍结果只有一行C01、A01、云帆、300。C02 的 120 不满足大于 200C03 不在南城都被过滤掉了。这一步能跑通你就已经掌握了实验里大半的连接题。3.3 INNER JOIN 只是换了个写法别被吓到同一件事现代一点写成JOIN ... ON ...配对条件从 WHERE 挪到 ON 后面筛选条件还留在 WHERE读起来层次更清楚。功能上两者完全等价实验文档用的是前一种你两种都得看得懂。SELECT s.CNO, s.PCODE, c.CNAME, s.OQUANTITY FROM SALE AS s -- 给表起短别名 s JOIN CUSTOMER AS c ON c.CNO s.CNO -- 配对条件写在 ON 后面 WHERE c.SITE 南城 AND s.OQUANTITY 200;别名的作用是少打字s.CNO比SALE.CNO短表多的时候尤其明显。这里最容易踩的坑是把筛选条件写进 ONON只负责怎么配对WHERE才负责配对完之后留哪些行两者混在一起写结果集可能悄悄变样你还以为是自己数据错了。还有一个概念要知道但先别深挖INNER JOIN只保留两边都能配上的行LEFT JOIN会保留左表全部行右边配不上的位置填 NULL。实验里基本用不到 LEFT JOIN等你遇到“想列出所有产品、包括没卖出去的那些”这类需求时再回头看它那时候你自然就懂了。3.4 换表名是新手最常犯的错示例里用的是 CUSTOMER、PRODUCT、SALE 这套名字但很多同学自己建库时写成了客户表、产品表或者大小写混着来。写查询之前先用一条SELECT * FROM 表名;确认表真的存在、列名拼写真的对。这一步花十秒能省掉半小时的“语法没错为什么报错”。另外实验文档里第 11 题写的是TELE CNAME中间少了个逗号属于文档笔误你照抄会直接报错。看到这类地方别慌把它当文档 bug 修掉就行——真实工作里别人给的示例 SQL 有笔误是常态。4. 嵌套查询把大问题拆成两个小问题4.1 嵌套查询就是“先算括号里的那一句”到正题了。所谓嵌套查询字面意思就是一条 SELECT 里面又套了一条 SELECT里面那条叫子查询外面那条叫父查询。它的写法很像数学里的括号先算里面的把结果当成一个值或者一串值交给外面那条用。举个人话例子。你要找“和云帆公司在同一个城市的客户”你不知道云帆在哪得先查一次知道它在南城之后再去查南城有哪些客户。这两步合成一句话就是嵌套查询。新手觉得它难往往不是语法难而是习惯了一步问完所有事。改成“先问一个小问题拿答案再问下一个”思路立刻就通了。提示写嵌套查询的时候永远先把子查询单独拎出来跑一遍看看它到底返回一个值、一列值、还是一整张表。这个习惯能帮你避开后面一半的坑。4.2 子查询只吐一个值时直接拿来比大小最安全的一种嵌套子查询只返回单个值这种叫标量子查询。因为它就是一个数或者一个字符串所以可以直接放在、、后面。-- 先查 A01 的单价15.00再找单价比它高的产品被谁买过 SELECT s.PCODE, s.CNO, s.OQUANTITY FROM SALE AS s JOIN PRODUCT AS p ON p.PCODE s.PCODE WHERE p.PRICE (SELECT PRICE FROM PRODUCT WHERE PCODE A01);拆开看更清楚括号里那句SELECT PRICE FROM PRODUCT WHERE PCODEA01单独跑结果就是一行一列、值 15.00。外面那句等价于WHERE p.PRICE 15.00。这里之所以能直接比大小前提是 A01 在产品表里唯一子查询保证只返回一行。如果 PCODE 有重复这句立刻报错“子查询返回多于一行”那就是第 5 章要讲的坑。对上我们的示例数据A02 单价 12.50 不够高B01 是 88.00所以最终结果只有一行B01、C01、150。4.3 子查询吐一列多行时得用 IN 接住第二种情况更常见子查询返回的是一列、好几行比如“所有南城客户的编号”。这时候你没法用等号因为等号只能对上一个值得换成IN意思是“在这个集合里”。-- 查出所有南城客户的编号再拿这串编号去销售表里捞订单 SELECT CNO, PCODE, OQUANTITY FROM SALE WHERE CNO IN (SELECT CNO FROM CUSTOMER WHERE SITE 南城);括号里单独跑返回的是 C01、C02 两个值父查询就等价于WHERE CNO IN (C01,C02)。跑出来的结果是 C01 的两条订单加 C02 的一条共三行。IN的兄弟是NOT IN意思反过来但NOT IN有个著名的 NULL 陷阱第 5 章会专门拆。还有一对操作符ANY、ALL实验文档里提到了谓词这个概念。你可以先记个大概 ANY (子查询)相当于“比最小的那个大” ALL (子查询)相当于“比最大的那个还大”。日常写查询用MAX、MIN更直观ANY、ALL看懂就行不必强求自己用。4.4 完整走一遍找出从来没被订购过的产品这是实验文档最后一道题也是最能体现嵌套价值的一道。原文档用的是WHERE PRODUCT.PCODE ! (SELECT SALE.PCODE FROM SALE)思路对但写法埋着雷只要销售表里有不止一条记录子查询就返回多行!直接报错。这正是新手最容易照抄翻车的地方。稳妥的写法有两种先看NOT IN版本-- 子查询列出所有出现过的产品编号父查询要那些不在名单里的 SELECT PCODE, PNAME FROM PRODUCT WHERE PCODE NOT IN (SELECT PCODE FROM SALE WHERE PCODE IS NOT NULL);WHERE PCODE IS NOT NULL这句是保险丝如果销售表里存在产品编号为 NULL 的记录NOT IN会因为拿不准“不在名单里”而整体返回空结果。加上它问题就绕过去了。对照示例数据SALE 里出现过 A01、A02、B01PRODUCT 里有 A01、A02、B01、B02结果自然是 B02 雨伞——进了货但一单没卖出去。另一种写法用NOT EXISTS语义是“不存在这样一条匹配记录”它对 NULL 天然免疫是更推荐的形式SELECT p.PCODE, p.PNAME FROM PRODUCT AS p WHERE NOT EXISTS ( SELECT 1 FROM SALE AS s WHERE s.PCODE p.PCODE -- 内层能不能找到匹配 );SELECT 1在这里只是个占位EXISTS只关心“有没有行”不关心取出来的是什么列。这条语句的执行方式是产品表每一行都拿到内层去试一次试不到匹配就留下。两种写法结果一样NOT EXISTS更耐操实际写实验报告时用哪种都行但要知道它们的区别在哪。4.5 三层嵌套也别慌从最里面往外读嵌套到三层的时候人眼容易看花。办法只有一个把最内层单独复制出来跑确认结果再往外包一层再跑一次。嵌套查询是能一层层验证的别指望一口气写对。-- 找订购了南城客户买过的产品的那些订单 SELECT CNO, PCODE, OQUANTITY FROM SALE WHERE PCODE IN ( -- 第三层拿产品编号去捞订单 SELECT PCODE FROM SALE WHERE CNO IN (SELECT CNO FROM CUSTOMER WHERE SITE 南城) -- 第一、二层 );从最里面那句开始先查出南城客户编号 C01、C02往外一层查出这两个客户买过的产品编号 A01、B01最外层把买过这两个产品的订单全捞出来。三层拆成三次单独运行每次都验证一遍比一口气写完再调试轻松十倍。5. 报错与空结果排查新手最容易踩的五个坑写完查询点执行出来的要么是红字报错要么是一片空白。这两种情况处理方式完全不同但新手往往分不清是哪一步出的问题。下面五条按“现象—原因—解决”写几乎覆盖了这份实验里所有会卡住人的地方。5.1 坑一GROUP BY 之后 SELECT 里塞了没分组的列现象语句报错提示某列不在 GROUP BY 里或者不报错但结果里那一列的值看着莫名其妙。原因你按客户编号分了组一堆记录被压成一行这时候再去 SELECT 一个没参与分组的列比如产品名称数据库不知道该从这一堆里挑哪一个显示。在开启了严格模式的 MySQL 上会直接拒绝执行在宽松模式下则会随便挑一个结果不可信。解决遵守一条规矩——SELECT 后面出现的列要么在 GROUP BY 里要么被聚合函数包着。想让结果里带客户名称就先把 CUSTOMER 连进来按客户编号分组时把名称也放进 GROUP BY或者干脆分组查完编号再拿编号去关联一次。5.2 坑二该用 HAVING 的地方写成了 WHERE现象报错说聚合函数不能用在 WHERE 里整条语句执行不了。原因WHERE COUNT(PCODE) 2这种写法很符合直觉但执行顺序上 WHERE 在分组之前就跑完了那时候还没有“堆”COUNT 无从算起。解决凡是条件里出现聚合函数的一律搬到 HAVING。条件里只有原始列比如SITE 南城的留在 WHERE 里性能更好因为可以先过滤再分组白算的堆少一些。判断方法很简单扫一眼条件里有没有 COUNT、SUM、MAX 这些词。5.3 坑三子查询返回多行却用等号去比现象报错“Subquery returns more than 1 row”或者结果只有一行但你预期好几行。原因WHERE PCODE ! (SELECT PCODE FROM SALE)这种写法只有当子查询恰好返回一行时才对。销售表一多几条记录子查询就返回一列多行等号和不等号都接不住。实验文档最后那道题就是这么写的照抄必翻车。解决先单独跑子查询看它返回几行。一行就用、多行就用IN、NOT IN或者改写成EXISTS、NOT EXISTS。养成这个检查习惯能省下大量调试时间。5.4 坑四NOT IN 撞上 NULL结果一片空白现象语句没报错但一行数据都不返回而你确信库里明明有该出现的数据。原因NOT IN的语义是“不等于名单里的每一个值”一旦名单里混进一个 NULL比较结果就变成“未知”整个条件对任何行都不成立于是全部被过滤掉。这是 SQL 里最典型的黑匣子行为之一光看语句根本看不出来。解决子查询里加WHERE 列名 IS NOT NULL把 NULL 排除掉或者直接改用NOT EXISTS。排查的时候先把子查询单独跑看结果里有没有空白格一眼就能确认。5.5 坑五LIKE B% 写成 LIKE B现象你明明知道有 B 开头的产品WHERE PCODE LIKE B却返回空。原因%是通配符代表任意多个字符。少了它LIKE 就退化成精确匹配只认编号刚好等于一个字母 B 的记录。类似的还有大小写问题部分数据库默认区分大小写like b%和LIKE B%结果不一样。解决前缀匹配统一写LIKE B%包含匹配写LIKE %B%。再顺手检查一下值里有没有首尾空格从别处复制粘贴的数据经常带不可见空格看着一样实际不一样。查不出来的时候先SELECT PCODE FROM PRODUCT;把整列打出来肉眼扫一遍比猜快。6. 下一步学什么把这三类查询练成肌肉记忆这份实验覆盖的其实是 SQL 的三块地基聚合统计、连接、嵌套。练熟之后你会发现工作中百分之七八十的取数需求都在这三块里打转。往下的路怎么走我给一个可以照着排的顺序。6.1 两周练习路线阶段练什么检验标准第 13 天单表查询 五个聚合函数不看文档写出实验前 8 题第 46 天GROUP BY 与 HAVING 混用能说清 WHERE 和 HAVING 的区别第 710 天两表、三表连接能自己判断该写几个配对条件第 1114 天嵌套查询与 EXISTS写完能一眼看出子查询返回几行每天的练法别贪多一天三五条就够但每条都要做到两件事先自己写再拆开验证。写完之后把子查询单独跑一遍看看返回的东西是不是你预期的形状这个动作重复二十次就变成条件反射了。6.2 一个具体的小技巧如果你只能带走一个习惯那就是从最内层往外写写一层跑一层。新手最常见的姿势是从外往里憋脑子里同时装着三层逻辑憋到一半自己都忘了外面在查什么。改成先把最里面那句跑出结果再包一层再跑一次你会发现嵌套查询突然变得没什么可怕的。再往下可以碰的东西有索引、视图、窗口函数但都别急。你现在这份实验文档里的十几条查询就是最好的练手材料——把每一条都改一改条件重跑一次比看十篇教程管用。我第一次带人做这个实验的时候自己也栽过一条三层嵌套查了半小时没结果最后发现是子查询里少写了个IS NOT NULL数据全被 NULL 吃掉了。从那以后我每次写嵌套查询都强制先把子查询单独拎出来跑一遍确认它返回的是一行还是一列多行再决定外面用等号还是 IN。这个动作看着笨救过我无数回。希望帮到你。本文还有配套的精品资源点击获取