尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SQL嵌套查询与统计查询实战:从数据库实验5解析执行顺序与避坑指南

SQL嵌套查询与统计查询实战:从数据库实验5解析执行顺序与避坑指南 简介这份数据库实验报告面向高校计算机及相关专业学生聚焦数据统计查询与嵌套查询的实操训练帮助读者掌握SELECT语句、统计函数、连接查询及子查询的综合运用。资源包内含1个doc文档约642KB内容围绕CPXS数据库展开涵盖COUNT、SUM、MAX、MIN等统计函数的使用INNER JOIN、LEFT JOIN、RIGHT JOIN等连接查询的语法以及子查询、派生表等嵌套查询操作符与谓词的实践。文档以实验目的、实验内容、实践结论、相关知识点和实验思考为脉络收录了统计客户数目、求库存总和、查询上海客户订购记录、比较产品单价等十余道典型习题及SQL参考写法便于对照练习与复盘。目前已有930人学习下载适合正在学习数据库课程、需要完成实验报告或巩固查询语法的读者参考使用。1. 从一份“数据库实验5嵌套查询.doc”说起为什么统计查询和子查询总在实验课上翻车很多人第一次接触 SQL 嵌套查询都是在类似“数据库实验5嵌套查询.doc”这样的实验文档里。文档不长十几条 SELECT 语句覆盖 COUNT、SUM、MAX、MIN、GROUP BY、HAVING、多表连接和子查询。看起来只是课堂作业但真正把这些语句逐条跑通你会发现它其实是一份浓缩的 SQL 实战清单统计函数怎么配合分组、HAVING 和 WHERE 到底谁先执行、子查询返回多行时为什么直接报错、NOT IN 遇到 NULL 为什么会静默返回空结果。这份资源适合三类人正在做数据库实验、需要一份可复现脚本的学生工作中写报表 SQL、被分组统计绕晕的初级开发以及想系统梳理 SELECT 执行顺序的转行者。它不教你装数据库也不讲索引优化它解决的是一个更基础的问题——把“统计查询 嵌套查询”这条线彻底走通。下面我按实验文档里的真实语句拆开讲每一步怎么落地、参数怎么改、哪里最容易翻车。2. 统计查询落地COUNT、SUM、GROUP BY 与 HAVING 的执行顺序2.1 先建表再谈查询CPXS 数据库的最小可用结构实验文档里反复出现 CUSTOMER、PRODUCT、SALE 三张表但没给建表语句。要复现得先补上。常见做法是按实验语义反推字段CUSTOMER 存客户编号、公司名、城市、电话PRODUCT 存产品编号、名称、单价、库存量SALE 存客户编号、产品编号、订购数量。下面这段 SQL 在 MySQL 和 SQL Server 上都能跑字段类型按实验里出现的比较和运算来定。-- 客户表CNO 客户编号CNAME 公司名SITE 城市TELE 电话 CREATE TABLE CUSTOMER ( CNO VARCHAR(10) PRIMARY KEY, CNAME VARCHAR(50), SITE VARCHAR(30), TELE VARCHAR(20) ); -- 产品表PCODE 产品编号PNAME 名称PRICE 单价STOCKS 库存量 CREATE TABLE PRODUCT ( PCODE VARCHAR(10) PRIMARY KEY, PNAME VARCHAR(50), PRICE DECIMAL(10,2), STOCKS INT ); -- 销售表CNO PCODE 联合主键OQUANTITY 订购数量 CREATE TABLE SALE ( CNO VARCHAR(10), PCODE VARCHAR(10), OQUANTITY INT, PRIMARY KEY (CNO, PCODE), FOREIGN KEY (CNO) REFERENCES CUSTOMER(CNO), FOREIGN KEY (PCODE) REFERENCES PRODUCT(PCODE) );逻辑说明SALE 表用 (CNO, PCODE) 做联合主键是因为实验第 2 题要统计“至少订购两种以上产品的客户”同一个客户对同一个产品只应有一条订购记录。参数上OQUANTITY 用 INT 足够PRICE 用 DECIMAL(10,2) 避免浮点误差。注意外键约束会让插入顺序变成先 CUSTOMER、再 PRODUCT、最后 SALE否则直接报外键冲突。2.2 COUNT 和 SUM统计函数不是孤立的它们和分组绑在一起实验第 1 题SELECT COUNT(*) FROM CUSTOMER是最简单的行数统计。但第 2 题开始变味SELECT CNO, COUNT(PCODE) FROM SALE GROUP BY CNO HAVING COUNT(PCODE)2。这里有两个关键点COUNT(PCODE) 只统计 PCODE 非 NULL 的行如果某条销售记录的 PCODE 为空它不会被计入HAVING 是对分组后的结果过滤不能写成 WHERE COUNT(PCODE)2因为 WHERE 在分组前执行聚合函数还不存在。-- 统计客户总数 SELECT COUNT(*) AS customer_count FROM CUSTOMER; -- 求库存量总和 SELECT SUM(STOCKS) AS total_stocks FROM PRODUCT; -- 至少订购两种以上产品的客户编号和产品种类数 SELECT CNO, COUNT(PCODE) AS product_kinds FROM SALE GROUP BY CNO HAVING COUNT(PCODE) 2; -- 每个客户订购产品数量的总数 SELECT CNO, SUM(OQUANTITY) AS total_quantity FROM SALE GROUP BY CNO;参数说明COUNT(*)统计所有行包括 NULLCOUNT(PCODE)跳过 PCODE 为 NULL 的行。SUM(OQUANTITY)如果遇到 NULL 会忽略但整组全 NULL 时返回 NULL不是 0。常见做法是用COALESCE(SUM(OQUANTITY),0)兜底。执行顺序上FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY记住这条链HAVING 能写什么、不能写什么就清楚了。2.3 WHERE 和 HAVING 的分工一个过滤行一个过滤组实验第 4 题SELECT SITE, COUNT(CNO) FROM CUSTOMER GROUP BY SITE HAVING SITE上海其实暴露了一个写法问题SITE上海 是行级过滤放在 WHERE 里更高效HAVING 应该留给聚合条件。第 6 题SELECT COUNT(PCODE) FROM PRODUCT GROUP BY STOCKS HAVING STOCKS500同理STOCKS500 是行条件放 WHERE 能让分组前就减少数据量。-- 更优写法行条件放 WHERE聚合条件放 HAVING SELECT SITE, COUNT(CNO) AS company_count FROM CUSTOMER WHERE SITE 上海 GROUP BY SITE; -- 库存量超过 500 的产品个数 SELECT COUNT(PCODE) AS product_count FROM PRODUCT WHERE STOCKS 500; -- 单价在 10~20 元之间产品的个数 SELECT COUNT(PCODE) AS price_range_count FROM PRODUCT WHERE PRICE BETWEEN 10 AND 20;逻辑说明WHERE 在分组前过滤行能走索引HAVING 在分组后过滤组通常要等聚合算完。把行条件误放 HAVING结果一样但性能差数据量大时差距明显。BETWEEN 是闭区间包含 10 和 20如果实验要求开区间得改成PRICE 10 AND PRICE 20。第 9 题WHERE PCODE LIKE B%里B% 的百分号是通配符如果产品编号本身含下划线还要注意_在 LIKE 里也代表任意单字符需要转义。3. 嵌套查询落地子查询返回单行、多行和 NULL 的三种命运3.1 标量子查询返回单值的子查询能直接比较实验第 12 题和第 13 题是典型的标量子查询。第 12 题先查出“美美”所在城市再用这个城市查同城客户第 13 题先查出 A01 的单价再查单价高于它的产品。这类子查询必须保证返回单行单列否则数据库直接报错。-- 查询与“美美”公司在同一城市的客户公司名称及联系电话 SELECT CNAME, TELE FROM CUSTOMER WHERE SITE ( SELECT SITE FROM CUSTOMER WHERE CNAME 美美 ); -- 查询订购了单价比 A01 高的产品的产品编号、客户编号和订购数量 SELECT S.PCODE, S.CNO, S.OQUANTITY FROM SALE S JOIN PRODUCT P ON S.PCODE P.PCODE WHERE P.PRICE ( SELECT PRICE FROM PRODUCT WHERE PCODE A01 );参数说明第一个子查询如果“美美”有多条记录会报“子查询返回多于一行”。稳妥做法是加LIMIT 1或用IN。第二个查询用了表别名 S 和 P避免 SALE 和 PRODUCT 都有 PCODE 时的歧义。子查询里的PCODEA01如果拼错子查询返回空集外层比较结果全是 NULL最终返回空结果——这是最隐蔽的翻车点之一。3.2 IN 和 NOT IN多行子查询的甜区和 NULL 陷阱实验第 14 题SELECT PRODUCT.PCODE, PNAME FROM PRODUCT WHERE PRODUCT.PCODE ! (SELECT SALE.PCODE FROM SALE)写法有问题子查询返回多行时!直接报错。正确做法是用NOT IN或NOT EXISTS。但NOT IN遇到子查询结果含 NULL 时整个条件会变成 UNKNOWN返回空结果。-- 正确写法一NOT IN但要求子查询结果无 NULL SELECT PCODE, PNAME FROM PRODUCT WHERE PCODE NOT IN ( SELECT PCODE FROM SALE WHERE PCODE IS NOT NULL ); -- 正确写法二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 IN的语义是“不等于列表中的每一个值”只要列表里有 NULL任何值跟 NULL 比较都返回 UNKNOWNWHERE 只保留 TRUE所以结果为空。NOT EXISTS是相关子查询逐行判断是否存在匹配NULL 不影响布尔判断。常见做法是优先用NOT EXISTS尤其是子查询列可能为空时。如果坚持用NOT IN务必在子查询里加WHERE PCODE IS NOT NULL。3.3 相关子查询与派生表什么时候该换写法实验第 11 题用多表连接完成了“上海客户订购数量大于 200”的查询其实也可以用相关子查询。相关子查询的特点是内层引用外层的列逐行执行逻辑清晰但性能通常不如 JOIN。派生表则是把子查询放在 FROM 里当成临时表用。-- 相关子查询写法查询上海客户订购数量大于 200 的记录 SELECT S.CNO, S.PCODE, C.CNAME, S.OQUANTITY FROM SALE S JOIN CUSTOMER C ON S.CNO C.CNO WHERE C.SITE 上海 AND S.OQUANTITY 200; -- 派生表写法先统计每个客户的总订购量再筛大于 500 的 SELECT t.CNO, t.total_qty FROM ( SELECT CNO, SUM(OQUANTITY) AS total_qty FROM SALE GROUP BY CNO ) t WHERE t.total_qty 500;参数说明派生表必须起别名MySQL 里叫t否则报“Every derived table must have its own alias”。相关子查询在数据量大时可能被优化器改写成 JOIN但不要依赖这一点。如果实验环境是 SQL Server派生表里用TOP要小心如果是 MySQL 8.0可以用 CTEWITH替代派生表可读性更好。选型上多表连接适合表之间有关联键、结果集需要合并列的场景子查询适合“先算一个值/一组值再用它过滤”的场景。4. 避坑与排查嵌套查询实验里最容易翻车的五个点4.1 子查询返回多行直接报错现象执行WHERE SITE (SELECT SITE FROM CUSTOMER WHERE CNAME美美)时数据库报“Subquery returns more than 1 row”。原因CNAME 没有唯一约束“美美”可能对应多条客户记录。解决确认业务上是否允许重名如果允许改用IN如果只取一条子查询加LIMIT 1或TOP 1并明确排序规则。4.2 NOT IN 遇到 NULL结果静默为空现象WHERE PCODE NOT IN (SELECT PCODE FROM SALE)返回 0 行但明明有产品没被订购。原因SALE.PCODE 允许 NULL子查询结果含 NULLNOT IN整体变成 UNKNOWN。解决子查询加WHERE PCODE IS NOT NULL或改用NOT EXISTS。这个坑没有报错只能靠结果数量反查血泪经验是统计类查询跑完先看一眼行数是否符合预期。4.3 GROUP BY 后 SELECT 非聚合列MySQL 不报错但结果随机现象SELECT CNO, PCODE, COUNT(*) FROM SALE GROUP BY CNO在 MySQL 5.7 之前能跑但 PCODE 返回的是组内任意一行的值。原因SQL 标准要求 SELECT 列表里的非聚合列必须出现在 GROUP BY 里MySQL 旧版本放宽了。解决把 PCODE 放进 GROUP BY或用ANY_VALUE(PCODE)明确表示“任意值可接受”。实验环境如果是 SQL Server 或 PostgreSQL这条直接报错反而更安全。4.4 连接查询漏写连接条件变成笛卡尔积现象FROM SALE, CUSTOMER WHERE CUSTOMER.SITE上海忘了写CUSTOMER.CNOSALE.CNO结果行数爆炸。原因多表查询没有连接条件时数据库做笛卡尔积。解决用显式JOIN ... ON语法替代逗号连接连接条件写在 ON 里过滤条件写在 WHERE 里结构更清晰。跑之前先用SELECT COUNT(*)估算行数和单表行数乘积对比。4.5 LIKE 通配符和转义字符混淆现象WHERE PCODE LIKE B%想查 B 开头结果把B_01也查出来了因为_在 LIKE 里是任意单字符。原因LIKE 的通配符%和_没有转义。解决用ESCAPE子句指定转义符比如LIKE B\_% ESCAPE \或者改用LEFT(PCODE,1)B。不同数据库默认转义符不同MySQL 默认是反斜杠SQL Server 需要用ESCAPE显式声明。5. 进阶技巧把实验文档变成可复用的 SQL 验证脚本实验文档里的语句是零散的真正要验证自己写对了得有一套可重复执行的脚本。我一般会建一个lab5_check.sql按“建表 → 插数据 → 逐题查询 → 断言结果”的顺序组织。插数据时故意埋几个边界值一个客户订购两种产品、一个产品库存为 0、一个客户城市为 NULL、一个产品编号以 B 开头且含下划线。这样跑完所有查询能一次性暴露 NULL、多行子查询、LIKE 转义这几类问题。-- 边界测试数据覆盖 NULL、多产品、B 开头含下划线 INSERT INTO CUSTOMER VALUES (C01,美美,上海,021-1111), (C02,华联,上海,021-2222), (C03,北方,北京,010-3333), (C04,空城,NULL,000-0000); INSERT INTO PRODUCT VALUES (A01,螺丝,15.00,600), (B01,扳手,25.00,300), (B_02,钳子,18.00,800), (C01,胶带,5.00,0); INSERT INTO SALE VALUES (C01,A01,150), (C01,B01,80), (C02,A01,250), (C02,B_02,120), (C03,B01,90);逻辑说明C04 的 SITE 为 NULL用来验证第 12 题子查询遇到 NULL 时的行为B_02 含下划线用来验证 LIKE 转义C01 订购两种产品满足 HAVING COUNT(PCODE)2C01 的 A01 订购量 150C02 的 A01 订购量 250用来验证“订购数量大于 200”的过滤。跑完这些数据再逐条执行实验查询对比预期行数。验证方法上我习惯用SELECT包一层计数把实验查询作为子查询外面套SELECT COUNT(*) FROM (...)看返回行数是否和手算一致。比如第 14 题“没有被订购的产品”手算应该是 C01胶带因为 A01、B01、B_02 都被订购过。如果NOT IN写法返回 0 行说明踩了 NULL 坑如果返回多行说明子查询逻辑写错了。还有一个技巧是把实验里的!全部替换成NOT EXISTS然后对比两种写法的结果集差异。差异行就是 NULL 或重复值导致的边界情况。这个对比过程比单纯跑通实验更有价值因为它逼你理解每种写法的语义边界。从那以后我每次写嵌套查询都会先问自己三个问题子查询会不会返回多行子查询列有没有 NULL外层比较符是、IN还是NOT IN这三个问题过一遍基本不会再翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表