简介:本资源是一份面向高校数据库课程初学者的嵌套查询专项实验报告,聚焦SQL高级查询能力训练,适用于《数据库原理与应用》课程实验、课后巩固及期末备考。文档完整覆盖统计查询、聚合函数(SUM/COUNT/MAX/MIN)、多表连接(INNER/LEFT/RIGHT JOIN)及核心嵌套查询技术(子查询、相关子查询、派生表),含14道典型实操题及标准SQL语句示例,如上海客户订单筛选、跨表价格比较、未订购产品查询等。资源为单文件Word文档(.doc),大小642KB,结构清晰、即开即用,便于对照学习与代码复现。已有931人学习下载,内容紧扣CPXS教学数据库环境,涵盖实验目的、操作步骤、语法解析与思考延伸,是掌握SQL复杂查询逻辑与提升数据库实战能力的实用参考资料。
1. 这不是“写SQL考试题”,而是让你第一次真正看懂数据库怎么“自己动脑子找答案”
你有没有试过:打开一个表格,想找出“所有住在杭州、买过三样以上东西的人”,结果对着Excel筛选栏发呆——先筛城市,再筛订单数,再合并去重,最后还漏了人?或者更糟:老板微信甩来一句“把上季度复购率超30%的客户名单导出来”,你点开数据库软件,光是看到“SELECT”就手抖?别慌。这份《数据库实验5嵌套查询.doc》根本不是什么枯燥的实验报告,它是一份带注释的“数据库思维训练手册”:教你怎么让数据库自己动脑子,一层层推理出你要的答案。它解决的不是“怎么敲命令”,而是“怎么把人话翻译成数据库能听懂的逻辑链”。比如,“查和‘美美’公司在同一城市的客户”,数据库不会自动联想“同一城市=地址字段相同”,你需要明确告诉它:“先去CUSTOMER表里找到‘美美’的SITE值,再拿这个值去筛其他客户”——这就是嵌套查询的本质:用一次查询的结果,当另一次查询的条件。它适合所有刚学完基础增删改查、但一碰到“既要…又要…还得…”就卡壳的新手;也适合被业务需求追着跑、总在临时拼SQL却反复出错的初级数据使用者。这不是考你背语法,是带你练一种“分步拆解问题”的肌肉记忆。
2. 先搞懂一个概念:嵌套查询不是“套娃”,而是“问完A再问B”的自然逻辑
2.1 为什么非得用嵌套?单条SELECT真的不够用吗?
想象你负责整理客户资料。领导说:“把所有没下过单的客户名字列出来。”你第一反应可能是:
SELECT CNAME FROM CUSTOMER;然后手动比对SALE表里的CNO……这显然不现实。
再试一次:
SELECT CNAME FROM CUSTOMER, SALE WHERE CUSTOMER.CNO != SALE.CNO;——错了!这会产生笛卡尔积的混乱结果(每个客户都和每个非匹配订单配对),数据量爆炸且毫无意义。
问题根源在于:单条SELECT只能描述“同一张表内”或“多表直接关联”的关系,但无法表达“先确认某类记录存在/不存在,再据此筛选另一类记录”这种因果逻辑。
嵌套查询就是为了解决这类“条件依赖”:它把问题拆成两步——
- 第一步(内层):查出“A01号产品多少钱?” →
SELECT PRICE FROM PRODUCT WHERE PCODE='A01' - 第二步(外层):查出“所有比A01贵的产品订单” →
SELECT ... FROM SALE,PRODUCT WHERE ... AND PRICE > (第一步结果)
数据库执行时,会先算完括号里的子查询,把结果当作一个“已知数字”塞进外层条件。这就像你查快递,先问客服“我的单号是多少?”,再拿这个单号去物流网站查进度——两步缺一不可,但第二步必须等第一步的答案。
2.2 嵌套查询的三种典型场景,对应你每天遇到的真实问题
| 场景类型 | 人话描述 | 实验中对应例题 | 数据库怎么理解 |
|---|---|---|---|
| “存在性判断” | “找出所有没被订购过的产品” | 第14题:SELECT ... FROM PRODUCT WHERE PCODE != (SELECT PCODE FROM SALE) | 外层逐行检查PRODUCT.PCODE,是否不等于内层查出的所有PCODE集合(注意:这里用!=是简化写法,实际应使用NOT IN,后文避坑会详解) |
| “范围限定” | “查出单价比最便宜产品还高的所有订单” | 第13题:PRICE > (SELECT PRICE FROM PRODUCT WHERE PCODE='A01') | 内层返回单个值(标量子查询),外层直接用>比较 |
| “同组筛选” | “查出和‘美美’公司在同一城市的客户” | 第12题:SITE = (SELECT SITE FROM CUSTOMER WHERE CNAME='美美') | 内层返回单个值(‘美美’的SITE),外层用=匹配 |
提示:初学者最容易混淆的是“返回单个值”和“返回多行”的区别。只要内层查询结果只有一行一列(如
MAX(PRICE)、SELECT SITE FROM ... WHERE CNAME='美美'),就叫标量子查询,可直接用于=、>等比较符;如果返回多行(如SELECT CNO FROM SALE WHERE OQUANTITY>100),就必须配合IN、EXISTS等谓词,否则会报错。
2.3 从实验题反推:嵌套查询的语法骨架长什么样?
所有嵌套查询都遵循同一个结构模板:
SELECT [外层要查的字段] FROM [外层主表] WHERE [外层条件] [比较符] (SELECT [内层要查的字段] FROM [内层表] WHERE [内层筛选条件]);以第12题为例,我们把它“剥洋葱”式拆解:
-- 外层:我要查哪些客户? SELECT TELE, CNAME FROM CUSTOMER -- 外层条件:他们的城市SITE必须等于... WHERE SITE = -- 内层:先找出‘美美’公司的城市是哪 (SELECT SITE FROM CUSTOMER WHERE CNAME = '美美');关键观察:
- 内层查询独立可运行:你单独复制
(SELECT SITE FROM CUSTOMER WHERE CNAME='美美')粘贴到数据库里执行,会得到一行结果(比如'上海'); - 外层查询依赖内层结果:
WHERE SITE = '上海'这个条件,是内层执行完才生成的; - 括号
()是强制语法:没有括号,数据库会认为WHERE SITE = SELECT ...是语法错误。
注意:内层查询可以引用外层表的字段(称为相关子查询),比如“查每个客户订购的最高单价产品”,这时内层会写成
SELECT MAX(PRICE) FROM PRODUCT P, SALE S WHERE P.PCODE=S.PCODE AND S.CNO=CUSTOMER.CNO。但本实验所有题目都是非相关子查询(内外层表完全独立),对新手更友好,建议先彻底掌握这种。
3. 别急着敲代码:先用“三步验证法”确保你的嵌套逻辑没翻车
3.1 第一步:单独运行内层查询,确认它真的能跑通
这是新手最常跳过的步骤,却导致80%的报错。比如第14题:
SELECT PRODUCT.PCODE,PNAME FROM PRODUCT WHERE PRODUCT.PCODE !=(SELECT SALE.PCODE FROM SALE)你直接执行,大概率报错:Subquery returns more than 1 row(子查询返回多行)。为什么?因为SELECT SALE.PCODE FROM SALE会查出所有订单的产品编号,少则几十行,多则成千上万行。而!=只能和单个值比较。
正确做法:先单独执行内层
SELECT SALE.PCODE FROM SALE;看到结果是多行后,立刻意识到:这里不能用!=,得换成NOT IN:
SELECT PRODUCT.PCODE,PNAME FROM PRODUCT WHERE PRODUCT.PCODE NOT IN (SELECT SALE.PCODE FROM SALE);提示:
NOT IN会自动处理多行结果,但要注意NULL陷阱(后文避坑详述)。这一步花30秒,能省你半小时调试。
3.2 第二步:检查内外层字段类型和逻辑是否匹配
第6题原题有严重逻辑错误:
SELECT COUNT(PCODE) FROM PRODUCT GROUP BY STOCKS HAVING STOCKS>500;表面看是“查库存超500的产品个数”,但GROUP BY STOCKS会让结果按库存量分组(比如库存501、502、503各算一组),COUNT(PCODE)统计的是每组的产品数,而非总数。
验证方法:把内层逻辑单独拎出来想
- 如果目标是“总数”,就不该
GROUP BY——直接SELECT COUNT(*) FROM PRODUCT WHERE STOCKS>500; - 如果目标是“各库存段的产品数”,才用
GROUP BY,但HAVING后面应该是聚合函数条件(如HAVING COUNT(*)>1),而非原始字段条件。
结论:原题意图是前者,GROUP BY STOCKS属于冗余且错误的写法。
3.3 第三步:用最小数据集模拟,肉眼核对结果是否合理
假设CUSTOMER表只有3条数据:
| CNO | CNAME | SITE |
|---|---|---|
| C01 | 美美 | 上海 |
| C02 | 东东 | 北京 |
| C03 | 西西 | 上海 |
执行第12题:
SELECT TELE, CNAME FROM CUSTOMER WHERE SITE=(SELECT SITE FROM CUSTOMER WHERE CNAME='美美');内层返回'上海',外层筛选出C01和C03。结果应为2条记录。
如果你得到0条或3条,说明逻辑有误(比如内层查不到‘美美’,或外层条件写错)。
实操技巧:在真实数据库中,先用SELECT * FROM CUSTOMER LIMIT 5;看几行样本,再代入你的查询,比空想高效十倍。
4. 新手必踩的5个坑:我当年调通第一个嵌套查询时的血泪经验
4.1 坑:用!=或=匹配多行子查询,报错“Subquery returns more than 1 row”
- 现象:执行类似
WHERE PCODE != (SELECT PCODE FROM SALE)时,数据库直接报错,拒绝执行。 - 原因:
!=、=、>等比较运算符设计初衷就是处理单个值。当子查询返回100行PCODE时,数据库不知道该和哪一行比。 - 解决:根据语义换用正确的谓词:
- 需要“不在某个集合中” → 用
NOT IN(例题14修正版); - 需要“在某个集合中” → 用
IN(如WHERE SITE IN (SELECT SITE FROM ...)); - 需要“存在至少一条匹配记录” → 用
EXISTS(更高效,但本实验未涉及)。
- 需要“不在某个集合中” → 用
4.2 坑:NOT IN遇到NULL值,整个查询返回空结果
- 现象:第14题用
NOT IN后,明明有未订购产品,查询结果却是空。 - 原因:如果SALE表的PCODE列有NULL值(比如某订单产品编号缺失),
NOT IN (1,2,NULL)在SQL标准中会返回UNKNOWN(既不是TRUE也不是FALSE),导致外层WHERE条件永远不成立。 - 解决:在子查询中显式过滤NULL:
SELECT PRODUCT.PCODE,PNAME FROM PRODUCT WHERE PRODUCT.PCODE NOT IN ( SELECT SALE.PCODE FROM SALE WHERE SALE.PCODE IS NOT NULL -- 关键!排除NULL );
4.3 坑:GROUP BY和HAVING混用错位,结果完全不对
- 现象:第4题
SELECT SITE ,COUNT(CNO) FROM CUSTOMER GROUP BY SITE HAVING SITE='上海';返回空或错误数据。 - 原因:
HAVING是对分组后的结果进行筛选,但SITE='上海'是原始字段条件,应该放在WHERE子句。GROUP BY SITE后,HAVING SITE='上海'逻辑上可行(因SITE是分组键),但效率极低——数据库会先按所有城市分组,再丢弃非上海组,白白计算。 - 解决:把单行条件放
WHERE,聚合条件放HAVING:-- 正确:先筛出上海客户,再统计数量 SELECT SITE, COUNT(CNO) FROM CUSTOMER WHERE SITE = '上海' -- 放这里! GROUP BY SITE;
4.4 坑:连接查询写成逗号分隔,漏写WHERE关联条件,产生笛卡尔积
- 现象:第11题原写法
SELECT ... FROM SALE,CUSTOMER WHERE CUSTOMER.CNO=SALE.CNO AND ...看似正确,但若漏掉CUSTOMER.CNO=SALE.CNO,结果行数会是SALE行数 × CUSTOMER行数(比如100×1000=10万行),卡死数据库。 - 原因:逗号分隔的多表查询(隐式连接)必须用
WHERE指定关联字段,否则默认做全组合。 - 解决:养成用显式
JOIN的习惯,关联条件写在ON后,一目了然:SELECT SALE.CNO, PCODE, CNAME, OQUANTITY FROM SALE INNER JOIN CUSTOMER ON SALE.CNO = CUSTOMER.CNO -- 关联条件在这里! WHERE CUSTOMER.SITE = '上海' AND SALE.OQUANTITY > 200;
4.5 坑:字符串比较忽略大小写或空格,导致“明明存在却查不到”
- 现象:第12题查
CNAME='美美'返回空,但表里确实有这个名字。 - 原因:数据库默认可能区分大小写(如MySQL的utf8mb4_bin校对规则),或姓名前后有不可见空格。
- 解决:
- 用
TRIM()清理空格:WHERE TRIM(CNAME) = '美美'; - 用
LOWER()统一转小写:WHERE LOWER(CNAME) = LOWER('美美'); - 更稳妥:先查
SELECT CNAME, LENGTH(CNAME) FROM CUSTOMER WHERE CNAME LIKE '%美美%';确认实际存储格式。
- 用
5. 从零开始跑通第一个嵌套查询:手把手带你执行第12题
5.1 准备工作:确认你有环境,不需要装任何新软件
这份实验文档基于标准SQL语法,无需安装数据库软件也能学习。你只需要:
- 一个文本编辑器(记事本、VS Code均可);
- 或访问任意在线SQL练习平台(如SQL Fiddle、DB Fiddle),选择MySQL或SQL Server;
- 实验中提到的CPXS数据库是虚构教学库,所有表结构(CUSTOMER、PRODUCT、SALE)已在文档中隐含定义(字段名如CNO、SITE、PCODE等)。
提示:如果你本地有MySQL,可用
CREATE DATABASE cpvs; USE cpvs;建库,再按文档字段名建三张表(建表语句非本实验重点,可后续补充)。
5.2 第一步:输入并执行内层查询,拿到“上海”这个答案
打开你的SQL执行界面,输入以下语句(注意单引号):
SELECT SITE FROM CUSTOMER WHERE CNAME = '美美';点击执行。预期结果:
| SITE |
|---|
| 上海 |
如果返回空,说明CUSTOMER表中没有CNAME为‘美美’的记录——此时需检查数据或修改条件(如用LIKE '%美美%'模糊匹配)。 |
| 这一步成功,证明内层逻辑正确,且数据存在。 |
5.3 第二步:把内层结果“代入”外层,写出完整查询
既然内层返回'上海',外层条件WHERE SITE = '上海'就明确了。完整语句如下:
SELECT TELE, CNAME FROM CUSTOMER WHERE SITE = ( SELECT SITE FROM CUSTOMER WHERE CNAME = '美美' );注意:
TELE是联系电话字段,实验文档中虽未明确定义,但根据上下文(第12题要求“公司名称及联系电话”)可推断存在。若实际表中字段名为PHONE或CONTACT_TEL,请替换为实际名称。
5.4 第三步:执行并验证结果,理解每一行数据的来源
执行上述语句,预期结果类似:
| TELE | CNAME |
|---|---|
| 021-1234 | 美美 |
| 021-5678 | 东东 |
| 021-9012 | 西西 |
| (假设这三家公司都在上海) | |
| 关键理解: |
- 外层
FROM CUSTOMER扫描所有客户; - 对每一行,数据库执行内层查询(固定返回
'上海'); WHERE SITE = '上海'判断当前客户的城市是否匹配;- 只有匹配的行(上海客户)才被选中输出。
这印证了嵌套查询的核心:内层提供动态条件,外层用此条件筛选。
5.5 第四步:尝试微调,巩固“条件即变量”的思维
现在,把'美美'换成另一个名字,比如'东东':
SELECT TELE, CNAME FROM CUSTOMER WHERE SITE = ( SELECT SITE FROM CUSTOMER WHERE CNAME = '东东' );如果‘东东’也在上海,结果应与之前相同;如果‘东东’在北京,则只返回北京客户。
这个小实验让你直观看到:内层查询不是固定值,而是随外层参数变化的“活条件”——这才是嵌套查询的威力所在。
6. 下一步学什么?给你一条不绕弯的进阶路径
6.1 先补牢:把实验里所有“统计函数”和“连接查询”练到肌肉记忆
嵌套查询的根基是扎实的单表查询和多表关联。实验中出现的COUNT、SUM、MAX、MIN、GROUP BY、HAVING,以及INNER JOIN、LEFT JOIN,绝不是点缀,而是你未来写复杂查询的“零件库”。建议按此顺序刻意练习:
- 单表统计:用
COUNT(*)查总行数,COUNT(字段)查非空行数,对比差异; - 分组聚合:对CUSTOMER按SITE分组,用
COUNT(CNO)统计各城市客户数,再用HAVING COUNT(CNO)>1筛出客户多的城市; - 连接实战:用
INNER JOIN重写第11题(上海客户的大额订单),对比逗号写法,体会ON条件的清晰性; - 混合应用:尝试“查每个城市的平均订单金额”,需
JOIN SALE和CUSTOMER,再GROUP BY SITE,最后AVG(OQUANTITY*PRICE)(需关联PRODUCT表获取单价)。
提示:每次练习后,用
EXPLAIN(MySQL)或EXECUTION PLAN(SQL Server)查看查询计划,理解数据库如何执行你的SQL——这比死记语法重要十倍。
6.2 再突破:从“非相关子查询”升级到“相关子查询”,解锁动态过滤
目前实验全是“内层不依赖外层”的静态嵌套(如查‘美美’的城市)。下一步挑战“内层需要外层数据”的动态场景:
- 场景:“查每个客户订购的最高单价产品名称”;
- 关键:内层需用外层的
CNO作为条件:
这里SELECT CNAME, (SELECT PNAME FROM PRODUCT P, SALE S WHERE P.PCODE = S.PCODE AND S.CNO = CUSTOMER.CNO ORDER BY PRICE DESC LIMIT 1) AS MAX_PRICE_PRODUCT FROM CUSTOMER;S.CNO = CUSTOMER.CNO就是相关性——内层每次执行,CUSTOMER.CNO的值都不同。
练习建议:从实验第3题“每个客户订购产品数量总数”出发,改为“每个客户订购的最高数量”,用MAX(OQUANTITY)替代SUM,再包装成子查询。
6.3 终极目标:用嵌套查询解决一个真实小问题,建立信心
别停留在实验题。找一个你工作中真实的、不超过3步的业务问题:
- 比如:“我们上周新增了5个客户,他们买了哪些产品?”
- 拆解:
- 内层:
SELECT CNO FROM CUSTOMER WHERE CDATE >= '2023-10-01'(假设CDATE是注册日期); - 外层:
SELECT DISTINCT PCODE FROM SALE WHERE CNO IN (内层);
- 内层:
- 执行、验证、截图存档。
从那以后我每次写嵌套查询,都强制走一遍“内层先跑、结果代入、外层验证”三步流程——哪怕只是心里默念。这习惯让我在团队里成了那个“SQL写得慢但几乎不返工”的人。希望帮到你。
本文还有配套的精品资源,点击获取