资讯动态

数据库实验5嵌套查询:聚合函数与子查询的避坑指南

发布时间:2026/10/9 22:44:52 来源:尧图企业网站定制
简介面向数据库初学者的嵌套查询实验报告适用于正在学习SQL查询与数据库原理的高校学生。报告覆盖数据库查询语言基础、统计函数、连接查询与嵌套查询四大模块包含SELECT语句统计、SUM/COUNT/MAX/MIN函数使用以及子查询、派生表等嵌套查询的具体操作方法。内容提供多个贴近业务场景的SQL示例如统计客户数目、查询上海客户订购量大于200套的订单、检索与“美美”公司同城市的客户等有助于读者理解各种连接与嵌套查询的语法及执行逻辑。资源为doc文档共1个文件压缩包大小642KB章节安排从实验目的、实验内容到实践结论与思考逐步递进结构清晰完整。该资源已有930人学习下载可作为数据库实验课预习材料或实验报告写作参考对快速掌握统计查询与嵌套查询技巧很有帮助。1. 数据库实验5嵌套查询一道统计查询题暴露的六个SQL习惯拿到一份数据库实验5嵌套查询文档别急着复制SQL。我按同一份题目在本地跑了三遍得到三种“正确”结果一次COUNT多算一次子查询直接报错另一次因为漏掉连接条件把结果集撑到几万行。原因都出在聚合函数、GROUP BY分组、嵌套子查询的边界处理上。这份实验适合两类人刚学SELECT语句、想搞清楚统计函数怎么用的学生以及天天写报表SQL但总在分组与子查询上翻车的数据分析新人。把它完整跑通等于把计数、求和、分组过滤、子查询谓词串成一条可复用的SQL自查路线。2. 统计查询基础聚合函数与GROUP BY的配合边界2.1 COUNT、SUM、MAX、MIN空值和DISTINCT决定统计结果实验前几题看着简单但聚合函数的空值语义最容易埋雷。第一题“统计客户的数目”可以直接写SELECT COUNT(*) FROM CUSTOMER;可一旦把COUNT(*)换成COUNT(CNO)结果可能就会少几行——只要CNO列里有NULLCOUNT(列名)就会自动跳过。-- 实验第1题统计客户数目 SELECT COUNT(*) FROM CUSTOMER; -- 实验第2题求库存量总和 SELECT SUM(STOCKS) FROM PRODUCT;逻辑说明COUNT(*)按物理行计数行存在就计入不管这一行是不是全为NULLSUM(STOCKS)则跳过STOCKS为NULL的行只对非空库存值求和。两者对NULL的容忍度不同直接决定了统计口径。参数说明上COUNT(*)不需要指定列名语义是“总行数”SUM(列)要求列是数值类型否则多数数据库会在执行时报类型错误。为空值行为做个速查表写实验结论时能直接抄聚合函数空值处理最容易被误解的地方COUNT(*)NULL行也计入以为会跳过实际不会COUNT(列名)跳过NULL值结果比COUNT(*)少不是查错了SUM(列名)跳过NULL值全列NULL时返回NULL不是0MAX / MIN跳过NULL值对字符串列也能取首尾值AVG(列名)跳过NULL值分母是“非NULL行数”不是总行数如果想在库存全为NULL时也返回0用SELECT COALESCE(SUM(STOCKS), 0) FROM PRODUCT;这是聚合查询的底线写法。2.2 GROUP BY分组键实验第3题的分组逻辑拆解“求每个客户订购产品数量的总数”这类需求关键词是“每个客户”翻译成SQL就是GROUP BY CNO。按客户编号分组后同一客户的多条订单会被合成一组SUM只对组内数据求和-- 实验第3题每个客户订购产品数量的总数 SELECT CNO, SUM(OQUANTITY) AS total_quantity FROM SALE GROUP BY CNO;逻辑说明GROUP BY CNO把SALE表按客户编号切分成多个小组SUM(OQUANTITY)分别对每个小组的订购数量求和。参数说明里最关键的一条是SELECT子句中的非聚合列必须出现在GROUP BY中否则在数据库的严格模式下直接报错“列不在分组中”。再看“求至少订购两种以上产品的客户编号和产品种类数”-- 实验第3题的变体至少订购两种以上产品 SELECT CNO, COUNT(DISTINCT PCODE) AS product_kinds FROM SALE GROUP BY CNO HAVING COUNT(DISTINCT PCODE) 2;这里我特意把COUNT(PCODE)换成了COUNT(DISTINCT PCODE)如果同一客户分三次订购了同一种产品普通COUNT会数出3但产品种类数仍然只有1。这种重复订单在练习数据里不常见真实业务表里却是常态。2.3 执行顺序思维WHERE为什么必须写在GROUP BY之前实验第4题原给的写法是HAVING SITE上海在部分数据库里能跑通但严谨的分析应该这样写-- 实验第4题所在城市为“上海”的客户公司数 SELECT SITE, COUNT(CNO) AS customer_count FROM CUSTOMER WHERE SITE 上海 GROUP BY SITE;逻辑说明WHERE的作用是先筛行、后分组HAVING的作用是先分组、后筛组。把“上海”这个过滤条件放进WHERE让数据库在分组前就把非上海客户扔掉既减少分组开销又避免HAVING对每一组做无谓的城市判断。SQL各子句的逻辑执行顺序是FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。记住这个顺序能解释很多现象WHERE里不能写COUNT(CNO) 10因为执行到WHERE时聚合还没发生而HAVING里可以写聚合条件是因为它执行在分组之后。3. 嵌套查询实战子查询、连接与谓词的组合方式3.1 标量子查询实战单价比较与同城公司查询实验第12题“查询与美美公司在同一城市的客户公司名称及联系电话”是最典型的标量子查询-- 实验第12题与“美美”公司在同一城市的客户 SELECT TELE, CNAME FROM CUSTOMER WHERE SITE ( SELECT SITE FROM CUSTOMER WHERE CNAME 美美 );逻辑说明内层子查询先查出“美美”所在城市返回一个单值外层再用等号比较。标量子查询成立的前提是内层结果必须是“一行一列”。参数说明上如果CNAME美美在客户表里重复出现内层会返回多行此时查询直接报错后面避坑章节会展开。实验第13题是标量子查询配合连接查询的混合体-- 实验第13题单价比A01产品高的订购记录 SELECT SALE.PCODE, SALE.CNO, OQUANTITY FROM SALE, PRODUCT WHERE SALE.PCODE PRODUCT.PCODE AND PRICE ( SELECT PRICE FROM PRODUCT WHERE PCODE A01 );逻辑说明先执行内层SELECT PRICE FROM PRODUCT WHERE PCODEA01拿到A01的单价再对SALE和PRODUCT做连接后的每一行用PRICE与该值比较。这里的执行顺序是“先子查询后外层连接过滤”。参数说明上PCODEA01是子查询的定位参数如果产品表里不存在A01子查询返回NULL外层比较结果全部为“未知”查询返回空集。3.2 集合子查询与谓词用NOT IN改写“没有被订购的产品”实验第14题原始写法存在明显问题WHERE PRODUCT.PCODE ! (SELECT SALE.PCODE FROM SALE)。当SALE表有多行时不等号无法与一个结果集直接比较。正确做法是用集合谓词-- 实验第14题修正版没有被订购的产品 SELECT PCODE, PNAME FROM PRODUCT WHERE PCODE NOT IN ( SELECT PCODE FROM SALE );逻辑说明NOT IN会先把内层查询的PCODE集合取出来再判断外层PCODE是否不在此集合中逻辑语义与题目完全吻合。参数说明上内层集合允许返回多行这是IN与最本质的区别要求标量IN接受集合。不过NOT IN还有一个更稳健的替代方案就是相关子查询配合NOT EXISTS-- 推荐写法NOT EXISTS抗NULL SELECT P.PCODE, P.PNAME FROM PRODUCT P WHERE NOT EXISTS ( SELECT 1 FROM SALE S WHERE S.PCODE P.PCODE );逻辑说明NOT EXISTS对每个产品逐行判断“是否在SALE中存在匹配记录”不存在则保留该产品。参数说明上内层SELECT 1只是为了满足子查询“有返回即真”的语义具体SELECT什么值不影响结果。NOT EXISTS在子查询集合中出现NULL时不会像NOT IN那样直接“抽空”这是它更稳的原因。3.3 连接与子查询的取舍数据量变大之后谁的效率更稳实验第11题“查询由上海客户订购且订购数量大于200套的客户编号、产品编号、联系人和订购数量”用隐式连接可以写用显式JOIN更清楚-- 实验第11题显式JOIN写法 SELECT S.CNO, S.PCODE, C.CNAME, S.OQUANTITY FROM SALE S JOIN CUSTOMER C ON C.CNO S.CNO WHERE C.SITE 上海 AND S.OQUANTITY 200;逻辑说明JOIN ON把SALE和CUSTOMER按客户编号关联WHERE再执行城市和订购数量过滤。参数说明上S和C是表别名多表查询里用别名能显著减少字段前缀的书写量ON C.CNO S.CNO是连接键连接键选错会导致数据错位。连接和子查询不是互斥方案。子查询擅长表达“先算出一个参照值再比较”连接擅长表达“多表横向拼接后过滤”。现代数据库优化器经常会把能改写的子查询转换成连接执行所以小数据量上两者差距不明显但可读性和维护性上显式JOIN通常优于逗号隐式连接。我的习惯是能明确写出关联关系时优先JOIN只有“先算参照物再过滤”这种语义时才保留子查询。4. 避坑与排查五个SQL实验结果异常的真实原因4.1 排查套路现象、原因、解决三段式这一章所有坑都按“现象 → 原因 → 解决”展开这套三段式同样适用于真实报表排查。先确认现象是报错还是结果数量不对再定位是语法级别还是数据级别的问题最后再改写法验证。以下五个问题都来自我复跑实验5时实际踩过的。4.2 坑一HAVING里直接写列名部分数据库运行报错现象原样执行实验第4题HAVING SITE上海在某个数据库环境完美通过换到另一套数据库直接报错列SITE在HAVING子句中无效因为它既不在聚合函数中也不在GROUP BY子句中。原因HAVING的执行语义是“对分组后的结果做过滤”非聚合列SITE不在分组键的合法范围内不同数据库对HAVING的列校验宽严程度不同宽松模式下能跑严格模式直接拒绝。解决把城市过滤条件前移到WHEREWHERE SITE上海在分组前过滤语义和性能都更合理。4.3 坑二COUNT(PCODE)用错位置“至少两个客户”算成“两笔订单”现象复跑实验第8题手工统计明明只有两个客户查询结果却把同一客户的三笔重复订单也算成“满足条件”结果集比预期多。原因HAVING COUNT(PCODE)2统计的是分组内的记录行数而不是“不同客户数”。同一个客户订购三次PCODE行数就是3COUNT(PCODE)大于2被误判为多个客户。解决按客户去重计数改用HAVING COUNT(DISTINCT CNO) 1同时把外层SELECT中的OQUANTITY包进SUM聚合-- 实验第8题修正版至少被两个客户订购且数量超过100 SELECT PCODE, SUM(OQUANTITY) AS total_quantity FROM SALE WHERE OQUANTITY 100 GROUP BY PCODE HAVING COUNT(DISTINCT CNO) 1;逻辑说明WHERE先过滤掉订购数量不大于100的行GROUP BY按产品分组HAVING判断“有多少不同客户订购过”SUM对每个产品的有效订购数量求和。参数说明上COUNT(DISTINCT CNO)是这一题的核心口径改成COUNT(CNO)或COUNT(*)都会得到偏大的假数据。4.4 坑三多行子查询用!执行时报错现象实验第14题原始写法WHERE PRODUCT.PCODE !(SELECT SALE.PCODE FROM SALE)运行时报错子查询返回了多行记录无法与!比较。原因不等号!属于标量比较运算符要求右侧必须是单值SALE表里只要存在两条以上销售记录右侧子查询结果就不是标量。解决将!改成NOT IN或NOT EXISTS使用集合判断语义。建议优先使用NOT EXISTS原因在下一条坑里。4.5 坑四漏掉连接条件结果集直接爆炸现象写实验第11题时把FROM SALE, CUSTOMER后面的WHERE CUSTOMER.CNOSALE.CNO漏了查询跑了很久才出结果返回行数从几条膨胀到几千上万条。原因逗号连接没有连接条件时数据库会对两个表做笛卡尔积每一行和另一张表的每一行两两组合。SALE有200行、CUSTOMER有300行时临时结果集就是6万行。解决连接条件第一时间写在WHERE或ON里。我的经验是使用显式JOIN语法JOIN CUSTOMER C ON C.CNO S.CNOON子句强制你填写关联键漏写的概率比逗号隐式连接低得多。4.6 坑五NOT IN子查询混入NULL结果被抽空现象实验第14题改成NOT IN写法后偶尔出现明明存在“从未被订购的产品”查询结果却为空集的情况。原因SQL三值逻辑在作怪。SALE.PCODE存在NULL值时PCODE NOT IN (...)的判断结果不是TRUE而是UNKNOWN所有行的条件都不成立整个查询返回空集。解决用NOT EXISTS替代NOT IN。NOT EXISTS是逐行相关判断只要SALE中不存在匹配PCODE的行就返回TRUE不受NULL值干扰。这也是我在生产环境排查数据报表时发现“结果莫名缩水”最常出现的元凶。5. 嵌套查询验证技巧拆三步再组装用临时表自证5.1 拆三步内层子查询、外层主查询、过滤条件嵌套查询写完后直接跑结果对不上很难判断问题出在内层还是外层。我的做法是拆成三步验证以实验第13题为例-- 第一步单独跑内层子查询确认参照值是唯一的 SELECT PRICE FROM PRODUCT WHERE PCODE A01; -- 第二步把外层连接单独跑先忽略价格对比 SELECT SALE.PCODE, SALE.CNO, OQUANTITY FROM SALE JOIN PRODUCT ON PRODUCT.PCODE SALE.PCODE; -- 第三步把第一步的已知结果代入第二步加过滤条件 SELECT SALE.PCODE, SALE.CNO, OQUANTITY FROM SALE JOIN PRODUCT ON PRODUCT.PCODE SALE.PCODE WHERE PRICE 68;三步跑完哪一步出错就很清楚第一步拿不到值时问题在产品表数据第二步行数膨胀问题在连接条件只有第三步才可能涉及比较逻辑。大多数嵌套查询翻车翻在“子查询本身没跑通”而不是外层写错。5.2 用CTE临时表构造小样本手工验证聚合口径在验证“至少被两个以上客户订购”这类口径时我会用WITH语句构造几行假数据把业务逻辑先跑通再去碰真实表-- 用CTE模拟重复订单场景 WITH TEST_SALE(cno, pcode, oquantity) AS ( SELECT C001,P01,150 UNION ALL SELECT C001,P01,120 UNION ALL SELECT C002,P01,200 ) SELECT PCODE, COUNT(PCODE) AS row_count, COUNT(DISTINCT CNO) AS customer_count, SUM(OQUANTITY) AS total_quantity FROM TEST_SALE WHERE OQUANTITY 100 GROUP BY PCODE HAVING COUNT(DISTINCT CNO) 1;逻辑说明CTE在内存中模拟了三行销售数据第一行和第二行是同一客户C001对P01的两次订购第三行是C002的订购。运行结果里row_count等于3customer_count等于2能直观看到两种口径的差异。参数说明上UNION ALL用于拼接多行临时数据比INSERT临时表更轻量适合在查询窗口里快速验证。从那以后我每次写完嵌套查询都强制走一遍“拆三步、造样本、对口径”的流程先让子查询单独出结果再用CTE临时数据验证聚合逻辑最后才去碰全量数据。这套方法帮我拦截过不少看着正确、实则在边界条件下出错的SQL希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑