简介:本资源是一份面向高校数据库课程初学者的嵌套查询专项实验报告,聚焦SQL高级查询能力训练,适用于数据库原理、数据库系统实验等课程的课后巩固与考前复习。文档完整覆盖统计查询、聚合函数(SUM/COUNT/MAX/MIN)、多表连接(INNER/LEFT/RIGHT JOIN)及核心嵌套查询技术(子查询、相关子查询、派生表),包含14道典型实操题及其完整SQL语句与执行逻辑说明,如上海客户订单筛选、同城市客户关联查询、高价产品比对等真实业务场景。资源为单文件Word文档(.doc),大小642KB,结构清晰、注释详尽,便于直接运行验证与笔记批注。目前已有931人学习下载,是掌握SELECT进阶语法、理解WHERE/HAVING/GROUP BY嵌套关系及提升复杂查询建模能力的实用教学材料。
1. 这不是“写SQL题”,而是让你第一次真正看懂数据库在「替你思考」
你有没有过这种感觉:打开一个表格软件,想查“上海的客户买了多少东西”,结果要手动筛选城市、再复制粘贴数量、再加总——手忙脚乱还容易漏?
其实,数据库早就在干这件事:它不存死数据,而像一个会算账、能记事、还带记忆的智能小助手。你只要说清楚“我要什么”,它就自动翻表、比对、汇总、过滤,最后把答案干干净净交给你。
这份《数据库实验5:嵌套查询》就是带你亲手调教它的第一课——不是背命令,而是学会用“人话”指挥数据库干活。里面所有例子,都来自真实业务场景:查哪些客户最活跃、哪些产品卖得最猛、谁和谁在同一个城市、哪个产品还没被买过……它不教你“数据库是什么”,它直接让你看到:原来一行SELECT,就能代替我手动翻十页Excel;原来一个括号里的子查询,就是在让数据库先去查一遍“美美公司在哪”,再按这个结果去找其他公司。
适合谁?零基础但敢点开SQL编辑器的新手;刚学完建表、插数据,却卡在“怎么把数据用起来”的同学;或者被老板临时问“上个月上海客户下单超200的有哪些?”当场愣住的职场新人。别怕报错,这门课的全部意义,就是让你从“不敢敲”变成“敲完就知道错在哪、为什么错、怎么改”。
2. 先搞懂一个概念:嵌套查询不是“套娃”,而是“分步提问”
很多人一看到SELECT ... WHERE ... (SELECT ...)就头皮发紧,觉得是“SQL套娃”,越套越晕。其实完全不是。嵌套查询的本质,是把一个复杂问题,拆成两步(或三步)清晰的提问。就像你问助理:“帮我找找和‘美美公司’同城的客户”,助理不会凭空猜,而是先做第一步:查出“美美公司”在哪儿;再做第二步:拿着这个地址,去客户表里挨个比对。数据库也是这么工作的——外层查询负责“要什么结果”,内层查询(也就是括号里的那个)只干一件事:先算出一个确定的答案,交给外层用。
2.1 嵌套查询的三种典型长相:你一眼就能认出来
我们不用术语轰炸,直接看实验里最常出现的三种样子,每种都配一句大白话翻译:
WHERE 后面跟括号(最常见)
SELECT CNAME, TELE FROM CUSTOMER WHERE SITE = (SELECT SITE FROM CUSTOMER WHERE CNAME = '美美');大白话:“先去客户表里找‘美美公司’的地址(SITE),找到后,再把所有地址和这个结果一样的客户名字和电话给我。”
✅ 关键特征:=、!=、IN这些比较符后面直接跟(SELECT ...),内层必须返回单个值(比如一个城市名),否则会报错。FROM 后面跟括号(叫“派生表”)
SELECT CNO, SUM(OQUANTITY) AS TOTAL_QTY FROM (SELECT CNO, OQUANTITY FROM SALE WHERE OQUANTITY > 100) AS BIG_ORDERS GROUP BY CNO;大白话:“先从订单表里筛出所有大于100的单子,把这张临时小表起名叫 BIG_ORDERS;再对这张小表按客户编号汇总数量。”
✅ 关键特征:(SELECT ...)出现在FROM后面,后面必须跟AS 别名;内层可以返回多行多列,它就是一张“临时表”。SELECT 列表里直接写子查询(叫“标量子查询”)
SELECT PCODE, PNAME, (SELECT COUNT(*) FROM SALE WHERE SALE.PCODE = PRODUCT.PCODE) AS ORDER_TIMES FROM PRODUCT;大白话:“查每个产品编号和名称,同时,对每个产品,单独去订单表里数一数它被订了多少次,把这个数字作为一列显示出来。”
✅ 关键特征:(SELECT ...)出现在SELECT后面,和字段并列;内层必须返回单个值(一行一列),而且通常要和外层表有连接条件(比如这里的SALE.PCODE = PRODUCT.PCODE),不然会算错。
记住:所有嵌套,核心就一条——内层先跑完,得出一个明确结果;外层再拿这个结果去干活。它不是魔法,只是把“先查A,再用A的结果查B”这两步,压缩成了一条语句。
2.2 为什么非得用嵌套?三个你马上会遇到的真实理由
新手常问:“我用两个独立查询不行吗?干嘛非得套在一起?”——当然可以,但你会立刻掉进效率、准确性和可维护性的坑里。来看实验里这几个例子怎么暴露问题:
理由1:避免重复劳动,防止数据不一致
实验第12题:查和“美美公司”同城的客户。如果分两步:第一步查出“美美”在“上海”,第二步再查WHERE SITE='上海'——看起来没问题。但如果“美美公司”地址改了,你得手动改两次!而嵌套查询里,地址只在一个地方(内层)定义,改一次,全链路自动更新。理由2:解决“动态参照”问题,这是普通连接做不到的
实验第13题:查单价比“A01”高的产品。A01的单价是多少?你根本不知道,它可能今天是15,明天调成18。普通JOIN要求两张表有固定关联字段,但这里没有“产品A01的单价”这张现成的表。嵌套查询直接让它现场查:“A01多少钱?→ 得到15 → 再去找所有价格>15的产品”。这是动态的、基于值的参照,不是基于字段的关联。理由3:实现“存在性判断”,这是统计函数无法替代的
实验第14题:查“没被订购过”的产品。你不能用COUNT()算,因为COUNT()只能告诉你“被订了多少次”,但0次和NULL在聚合里处理很麻烦。嵌套用NOT IN或NOT EXISTS就非常直白:“找出那些产品编号,不在订单表的PCODE列表里”。这是一种逻辑判断,不是数值计算。
所以,嵌套不是炫技,它是数据库给你的一把“瑞士军刀”:当问题需要“先知道一个值,再用它筛选”“需要动态参照”“需要判断存在与否”时,它就是最自然、最可靠的解法。
2.3 常见问题:新手写嵌套查询最容易踩的5个坑
提示:这些坑,我第一次写嵌套时全踩过,重装三次数据库才搞明白。现在列出来,帮你省下三天调试时间。
现象:运行
SELECT ... WHERE SITE = (SELECT SITE FROM CUSTOMER WHERE CNAME='美美')报错 “Subquery returns more than 1 row”(子查询返回多于1行)
原因:内层查询SELECT SITE FROM CUSTOMER WHERE CNAME='美美'找到了两条记录(比如有两个“美美公司”)。=要求右边只能是一个值,但你给了它两个。
解决:加LIMIT 1强制取第一个,或用IN替代=(WHERE SITE IN (...)),但更治本的是检查数据——为什么会有重名?是否该用唯一编号(如CNO)而非名称做查找?现象:
SELECT ... FROM (SELECT ... ) AS T报错 “Every derived table must have its own alias”(每个派生表必须有自己的别名)
原因:MySQL强制要求,FROM后面的子查询必须用AS 别名命名,哪怕你只是临时用一下。漏写AS T就会报这个错。
解决:死记硬背:凡是在FROM后面出现的(...),后面必跟AS XXX。别名可以是任意合法名字,比如AS TEMP、AS FILTERED_SALES。现象:
SELECT PCODE, (SELECT COUNT(*) FROM SALE WHERE SALE.PCODE=PRODUCT.PCODE) ... FROM PRODUCT返回全是0或NULL
原因:外层PRODUCT表和内层SALE表的连接条件写错了。常见错误是写成SALE.PCODE=PCODE(没指定表名),数据库不知道PCODE是指哪个表的,可能默认为内层表,导致条件失效。
解决:永远给字段加上表名前缀!写成SALE.PCODE = PRODUCT.PCODE。这是血泪经验——不加前缀,90%的标量子查询都会逻辑错误。现象:
SELECT ... WHERE PCODE != (SELECT PCODE FROM SALE)查不到任何“没被订购的产品”
原因:!=和NULL在SQL里是“失联”关系。如果子查询返回NULL(比如SALE表为空),PCODE != NULL的结果永远是UNKNOWN,不是TRUE,所以整行被过滤掉。正确做法是用NOT IN或NOT EXISTS。
解决:查“不存在”的场景,无条件用NOT IN或NOT EXISTS。例如:WHERE PCODE NOT IN (SELECT PCODE FROM SALE)。注意:NOT IN如果子查询含NULL,结果也会全空,此时改用NOT EXISTS更安全(实验第14题原写法!=就是典型错误,应改为NOT IN)。现象:嵌套查询执行极慢,几秒没反应,甚至卡死
原因:内层查询没有索引,且在外层循环中被反复执行。比如标量子查询SELECT ..., (SELECT ...) FROM PRODUCT,如果PRODUCT有1万行,内层SELECT COUNT(*) FROM SALE...就会被执行1万次!
解决:优先用JOIN代替标量子查询(如用LEFT JOIN SALE ON ... GROUP BY替代SELECT (SELECT ...));如果必须用子查询,确保内层WHERE条件字段(如SALE.PCODE)已建索引。这是性能玄学,但新手起步就该建立意识。
3. 先跑通第一个嵌套:从“查同城客户”开始,手把手敲出第一行有效SQL
别急着背所有语法,我们只聚焦实验第12题——它最简单、最直观,且100%能跑通。跟着下面步骤,你将亲手打出人生第一个真正有用的嵌套查询,并理解每一部分在干什么。假设你已经安装好MySQL(或类似数据库),并成功导入了CPXS数据库(含CUSTOMER、SALE、PRODUCT三张表)。
3.1 第一步:确认数据,先看看“美美公司”到底在哪儿
在你的数据库客户端(如MySQL Workbench、DBeaver,或命令行)里,先执行这一句:
SELECT CNO, CNAME, SITE FROM CUSTOMER WHERE CNAME = '美美';✅ 这不是嵌套,是“探路”。目的是确认:
- 表里真有叫“美美”的客户(避免拼写错误,比如写成“美美公司”或“美美有限公司”);
- 它的
SITE字段值是什么(比如是“上海”还是“上海市”?空格、标点都要一致);- 它的
CNO是多少(后续可能用到)。
如果这句返回空,说明数据里没有“美美”,你需要换成实验里存在的客户名,比如“华美”或“宏达”(看CUSTOMER表实际数据)。
3.2 第二步:写出内层查询,单独验证它是否返回单个值
把上一步查到的SITE值(假设是“上海”)填进去,执行:
SELECT SITE FROM CUSTOMER WHERE CNAME = '美美';✅ 这句必须只返回一行一列(比如就一个“上海”)。如果返回多行,说明有重名,按2.3节第一个坑处理;如果返回空,说明名字不对。只有这句稳了,才能进下一步。
3.3 第三步:组装外层查询,用内层结果做筛选条件
现在,把第二步的结果,当作一个“已知答案”,放进外层查询的WHERE条件里:
SELECT CNAME, TELE FROM CUSTOMER WHERE SITE = '上海';✅ 这句是“模拟版”,先手动把‘上海’写死,确认逻辑对不对:它应该列出所有地址是上海的客户名和电话。如果这句能跑,说明外层结构没问题。
3.4 第四步:正式嵌套——把“上海”替换成子查询
把第三步里的'上海',替换成第二步的整个查询(记得加括号!):
SELECT CNAME, TELE FROM CUSTOMER WHERE SITE = (SELECT SITE FROM CUSTOMER WHERE CNAME = '美美');✅ 这就是最终的嵌套查询!执行它。你应该看到和第三步相同的结果(所有上海客户),但这次,数据库是自己查出来的“上海”,而不是你手动写的。
🔍 关键参数说明:
SELECT SITE FROM CUSTOMER WHERE CNAME = '美美':内层,负责“找答案”,必须返回单值;WHERE SITE = (...):外层,负责“用答案”,=表示严格匹配单个值;- 整个语句的逻辑链条:先查美美在哪 → 得到“上海” → 再查所有地址是“上海”的客户。
3.5 第五步:举一反三,改一个参数,立刻得到新结果
现在,把'美美'换成另一个客户名,比如'宏达':
SELECT CNAME, TELE FROM CUSTOMER WHERE SITE = (SELECT SITE FROM CUSTOMER WHERE CNAME = '宏达');✅ 不用改任何其他地方,只换名字,就能立刻查出和“宏达”同城的所有客户。这就是嵌套查询的威力——参数化。你封装了一个逻辑(“查同城客户”),只需要改一个输入(客户名),就能复用无数次。以后写报表、做后台接口,全靠这种思路。
提示:如果执行报错,请立刻回到2.3节,对照那5个坑逐一排查。90%的问题,都出在内层返回多行、没加表名前缀、或用了
!=而不是NOT IN上。
4. 统计函数和分组查询:嵌套的“搭档”,不是对手
很多新手以为嵌套查询是“高阶技巧”,得等学完所有基础才能碰。其实恰恰相反——它和COUNT、SUM、GROUP BY这些基础操作是天然搭档,经常一起出场。实验里大量题目(如第2、3、5题)表面看是分组统计,但它们正是嵌套查询的“前置训练”和“最佳拍档”。我们不把它当独立章节学,而是直接看它怎么和嵌套配合发力。
4.1 分组统计(GROUP BY)是“分堆”,嵌套是“挑堆”
先看实验第2题:
SELECT CNO, COUNT(PCODE) FROM SALE GROUP BY CNO HAVING COUNT(PCODE) >= 2;这句在做什么?它先把订单表
SALE按客户编号CNO“分堆”,每堆是一个客户的所有订单;然后对每堆数一数有多少个产品(COUNT(PCODE));最后用HAVING把“堆里产品数>=2”的那些客户挑出来。
✅GROUP BY解决的是“按什么维度分组”,HAVING是对分组后的结果做筛选(注意:不是WHERE,WHERE是分组前筛,HAVING是分组后筛)。
现在,如果需求升级:“查出这些‘至少订了2种产品’的客户中,谁的总订购量最大?”
这就需要嵌套了——外层要排序取最大,内层先算出每个客户的总订购量:
SELECT CNO, TOTAL_QTY FROM ( SELECT CNO, SUM(OQUANTITY) AS TOTAL_QTY FROM SALE GROUP BY CNO HAVING COUNT(PCODE) >= 2 ) AS ACTIVE_CUSTOMERS ORDER BY TOTAL_QTY DESC LIMIT 1;✅ 解析:
- 内层(括号里):就是第2题的逻辑,但加了
SUM(OQUANTITY)计算总数量,并起别名TOTAL_QTY;- 外层:把内层结果当一张新表
ACTIVE_CUSTOMERS,再对TOTAL_QTY降序排列,取第一行。
这就是典型的“分组+嵌套”:GROUP BY负责精细切分和聚合,嵌套负责把聚合结果当数据再加工。
4.2 统计函数(COUNT/SUM/MAX/MIN)是“计算器”,嵌套是“调度员”
实验第5题SELECT MAX(PRICE) FROM PRODUCT很简单,就是查最高价。但如果你要“查出最高价对应的产品名称和编号”,就不能只用MAX()了,因为MAX()只返回值,不返回整行。这时嵌套就来调度:
SELECT PCODE, PNAME, PRICE FROM PRODUCT WHERE PRICE = (SELECT MAX(PRICE) FROM PRODUCT);✅ 这里
MAX(PRICE)是内层的“计算器”,它算出一个数字(比如99.9);外层的WHERE PRICE = ...是“调度员”,拿着这个数字,去主表里找到所有价格等于它的产品(注意:可能有多个同价产品,所以用=而不是LIMIT 1)。
对比:如果用ORDER BY PRICE DESC LIMIT 1,只能取一个,且无法保证是“所有最高价产品”。
4.3 避坑:HAVING 和 WHERE 的混淆,是新手分组查询最大雷区
提示:这个坑,几乎每个初学者都踩过,而且很难自己发现。
错误写法(实验第4题原题):
SELECT SITE, COUNT(CNO) FROM CUSTOMER GROUP BY SITE HAVING SITE = '上海'; -- ❌ 错!现象:可能返回空,或报错。
原因:HAVING是对分组后的结果(即每个SITE对应的COUNT(CNO))做筛选,但它不能用来筛选分组依据字段本身。SITE = '上海'应该在分组前就确定范围,属于WHERE的职责。
正确写法:SELECT SITE, COUNT(CNO) FROM CUSTOMER WHERE SITE = '上海' -- ✅ 放这里,先筛出上海客户 GROUP BY SITE; -- 再按SITE分组(此时SITE只有'上海'一个值)✅ 记住口诀:“WHERE 筛行,GROUP BY 分堆,HAVING 筛堆”。你想筛的是原始数据行(如“只要上海的客户”),用
WHERE;你想筛的是分组后的聚合结果(如“只要客户数超过10的城市”),用HAVING。另一个经典错误(实验第6题原题):
SELECT COUNT(PCODE) FROM PRODUCT GROUP BY STOCKS HAVING STOCKS > 500; -- ❌ 错!现象:返回多行,每行一个
COUNT(PCODE),但你要的是“库存>500的产品总数”。
原因:GROUP BY STOCKS把产品按库存量分成了无数堆(比如库存100一堆、200一堆…),HAVING STOCKS>500是在筛“库存量字段值>500”的那些堆,但每堆里COUNT(PCODE)是1(因为每堆只有一种库存值),结果就是一堆1。
正确写法(不需要分组):SELECT COUNT(PCODE) FROM PRODUCT WHERE STOCKS > 500; -- ✅ 直接筛行,再统计总数✅ 总结:当你要的是“满足某条件的记录总数”,用
WHERE + COUNT;当你要的是“按某字段分组后,每组的统计值”,才用GROUP BY + COUNT。
5. 连接查询(JOIN)和嵌套查询:不是二选一,而是“何时用哪个”的决策树
新手常陷入一个误区:学了JOIN,就觉得嵌套是“过时写法”;学了嵌套,又觉得JOIN是“绕远路”。其实它们是数据库的左右手,各有不可替代的场景。实验里第11、13题明显是JOIN,第12、14题明显是嵌套,但第13题如果写成嵌套,也完全可行。关键不是哪个高级,而是哪个更清晰、更安全、更易维护。
5.1 一张表 vs 多张表:先看数据在不在一张表里
这是最根本的判断起点。
数据在同一张表→ 优先考虑嵌套查询
实验第12题:查和“美美公司”同城的客户。所有信息(客户名、地址)都在CUSTOMER表里。你不需要拉其他表,只需要“在本表里先查A,再用A查B”。嵌套最直接。数据在不同张表→ 优先考虑
JOIN
实验第11题:查由上海客户订购的订单(要客户名、产品号、数量)。客户信息在CUSTOMER表,订单信息在SALE表。必须把两张表连起来,JOIN是天选之子:SELECT SALE.CNO, SALE.PCODE, CUSTOMER.CNAME, SALE.OQUANTITY FROM SALE INNER JOIN CUSTOMER ON SALE.CNO = CUSTOMER.CNO WHERE CUSTOMER.SITE = '上海' AND SALE.OQUANTITY > 200;✅
JOIN清晰表达了“关联关系”(CNO是连接键),且性能通常优于等价的嵌套(因为数据库优化器对JOIN更友好)。
5.2 逻辑是“关联”还是“存在性判断”:决定用 JOIN 还是 NOT IN/EXISTS
这是第二个关键决策点。
需要“关联出详细信息”(如客户名、产品名、联系电话)→ 用
JOIN
实验第13题:查单价比A01高的产品,还要显示产品编号、客户编号、数量。这些字段分散在PRODUCT和SALE表,JOIN可以一次性拉出所有需要的列。只需要“判断是否存在”(如“这个产品有没有被订过?”“这个客户有没有下过单?”)→ 用
NOT IN或NOT EXISTS
实验第14题:查“没被订购的产品”。你只关心“PCODE 是否在 SALE 表里出现过”,不关心订单详情。用NOT IN最简洁:SELECT PCODE, PNAME FROM PRODUCT WHERE PCODE NOT IN (SELECT PCODE FROM SALE);✅ 比
LEFT JOIN ... WHERE SALE.PCODE IS NULL更易读,且在某些情况下性能更好(尤其当SALE表很大时,NOT IN可能提前终止)。
5.3 性能与可读性权衡:JOIN 更快,嵌套更“自解释”
性能角度:现代数据库(MySQL 8.0+, PostgreSQL)对
JOIN的优化极其成熟,同等逻辑下,JOIN通常比等价嵌套快10%-50%。因为优化器能更好地估算数据分布、选择连接算法(Hash Join, Merge Join)。可读性角度:嵌套查询像一篇短文,逻辑是线性的:“先做A,再用A做B”。
JOIN像一张关系图,需要你脑补连接路径。对于复杂多表关联,嵌套反而更清晰。例如,查“订购了上海客户所购产品的其他客户”,用嵌套可以分三步写:-- 步骤1:上海客户买了哪些产品 -- 步骤2:哪些客户买了这些产品(排除上海客户) -- 步骤3:查这些客户的详细信息而
JOIN版本可能需要三层JOIN加复杂ON条件,一眼看不出主干。
所以我的建议(血泪经验):
- 新手起步,优先用嵌套:它逻辑直、报错明、调试易,能让你100%理解自己在干什么;
- 项目上线,逐步替换为
JOIN:当逻辑稳定、数据量上来后,用EXPLAIN分析执行计划,把高频查询换成JOIN;- 永远把可读性放第一位:如果一个嵌套查询,你能在3秒内读懂它在做什么,那就别为了“显得高级”硬改成
JOIN。代码是写给人看的,顺便让机器执行。
6. 下一步学什么:从“会写”到“写得好”的三步进阶路径
你现在能跑通第一个嵌套查询,能区分WHERE和HAVING,能判断什么时候该用JOIN、什么时候该用NOT IN——这已经超过了80%刚入门的同学。但真正的数据库能力,不在于会写多少种语法,而在于面对一个新需求时,能快速拆解、精准选型、写出健壮可维护的SQL。接下来,我建议你按这个顺序,花一周时间,每天专注一个方向,把基础打牢。
6.1 第一步:用好“执行计划”,告别盲写(1天)
别再靠猜和试错。所有主流数据库都提供EXPLAIN命令,它能告诉你数据库打算怎么执行你的SQL。这是你和数据库对话的“翻译器”。
- 怎么做:在你写好的任何查询前,加上
EXPLAIN,比如:EXPLAIN SELECT CNAME, TELE FROM CUSTOMER WHERE SITE = (SELECT SITE FROM CUSTOMER WHERE CNAME = '美美'); - 怎么看关键信息(以MySQL为例):
type列:ALL表示全表扫描(慢!),ref或eq_ref表示走了索引(好!);rows列:预估扫描行数,越小越好;Extra列:出现Using filesort或Using temporary是性能红灯。
- 行动清单:
① 对实验里所有查询,逐个加EXPLAIN,记录type和rows;
② 给CUSTOMER.SITE和CUSTOMER.CNAME字段创建索引(CREATE INDEX idx_site ON CUSTOMER(SITE); CREATE INDEX idx_cname ON CUSTOMER(CNAME););
③ 再EXPLAIN,对比rows是否大幅下降。你会发现,加索引后,原本扫1000行的查询,可能只扫5行。这就是“让数据库飞起来”的第一步。
6.2 第二步:掌握窗口函数,解锁“动态排名”“移动平均”(2天)
COUNT、SUM是静态聚合,只能给你一个总数。但业务中更多是“每个客户自己的销售额排名”“最近3个月的平均销量”。这就是窗口函数(Window Function)的领域,它是SQL 2003标准,MySQL 8.0+、PostgreSQL、SQL Server 全支持。
- 一个救命例子:实验第3题是“每个客户订购数量总数”,但如果要“每个客户订购数量总数,并按金额从高到低排名”,用传统
GROUP BY+ 子查询会非常绕。用窗口函数,一行搞定:SELECT CNO, SUM(OQUANTITY) AS TOTAL_QTY, RANK() OVER (ORDER BY SUM(OQUANTITY) DESC) AS RANKING FROM SALE GROUP BY CNO; - 学习路径:
① 先掌握ROW_NUMBER(),RANK(),DENSE_RANK()三兄弟的区别(重点看并列时的编号逻辑);
② 再学SUM() OVER (PARTITION BY ... ORDER BY ...)做分组内累计求和;
③ 最后练AVG() OVER (ORDER BY ... ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)做移动平均。为什么推荐?因为窗口函数是“嵌套查询的进化版”——它把“先分组,再对每组排序/累计”的逻辑,压缩进一个函数里,代码更短,性能更好,且天然支持
ORDER BY,这是老式嵌套做不到的。
6.3 第三步:动手写一个“可复用的查询模板”,固化你的方法论(2天)
不要停留在“做题”。把你学到的模式,封装成一个随时能抄的模板。我当年做的第一个模板,就是“查同城客户”的增强版:
-- 【模板】查与指定客户同城的所有客户(含防错、注释、可配置) SELECT c1.CNO AS TARGET_CNO, -- 目标客户编号 c1.CNAME AS TARGET_NAME, -- 目标客户名称 c2.CNO AS SAME_CITY_CNO, -- 同城客户编号 c2.CNAME AS SAME_CITY_NAME, -- 同城客户名称 c2.TELE AS SAME_CITY_TELE -- 同城客户电话 FROM CUSTOMER c1 INNER JOIN CUSTOMER c2 ON c1.SITE = c2.SITE -- 核心:同城即SITE相等 WHERE c1.CNAME = '美美' -- ← 这里改成你要查的客户名 AND c2.CNAME != '美美' -- 排除自己 AND c1.SITE IS NOT NULL -- 防NULL导致结果异常 AND c2.SITE IS NOT NULL;- 这个模板的价值:
- 有表别名(
c1,c2),避免字段歧义; - 有完整注释,说明每行作用;
- 有防错逻辑(
IS NOT NULL); - 有明确的修改提示(
← 这里改成...); - 用
JOIN实现,比嵌套更高效,且能同时拿到目标客户和同城客户的所有字段。
- 有表别名(
- 你的任务:
① 把实验第13题(查高价产品)也写成一个带注释、可配置的模板(把'A01'设为变量);
② 把第14题(查未订购产品)用NOT EXISTS重写,并加入COALESCE处理可能的NULL;
③ 把这三个模板,存成.sql文件,命名为template_same_city.sql、template_high_price.sql、template_unordered.sql。
从那以后我每次接到新需求,第一反应不是打开编辑器狂敲,而是翻出这几个模板,复制、粘贴、改参数、加字段。真正的效率,不是写得快,而是不重复造轮子,把80%的通用逻辑,固化成肌肉记忆。希望帮到你。
本文还有配套的精品资源,点击获取