
简介这份文档面向数据库课程学习者聚焦数据统计查询与嵌套查询的实验训练可用于高校数据库原理课程实验复盘、SQL语句对照练习和查询思路梳理。内容围绕CPXS数据库展开先交代掌握SELECT统计查询、COUNT/SUM/MAX/MIN等主要函数、连接查询和嵌套查询谓词等实验目的再以统计客户数目、库存总量、至少订购两种产品的客户、上海客户公司数、库存超500的产品数、单价区间产品数、B开头产品最低单价等题目串联知识点并给出子查询比较、同城客户查询、未被订购产品查询等嵌套写法便于理解分组统计、HAVING过滤与多表关联条件筛选。资源包共1个doc文件约642KB轻量便于直接查阅。已有931人学习下载适合需要补齐数据库查询语法、巩固实验操作或准备课程作业的读者参考。1. 先弄明白一件事数据库实验里的“嵌套查询”到底在查什么很多新手打开数据库实验5的题目看到“查询与某公司在同一城市的客户”就卡住明明只认识一条 SELECT怎么题目里又套一条 SELECT你可以把嵌套查询想成“先算括号里的小答案再拿小答案去问外层”。比如先查出示例公司甲在哪个城市再拿这个城市去筛客户。它解决的是“条件本身也要查一次”的问题。数据库实验5里的统计、连接、嵌套是一条线先会数数再会对表最后才会套娃。适合刚学 SQL、想跑通实验又怕写错的人。别急着背语法先跟着把第一个计数查询跑出来手上有结果心里就不发怵。2. 为什么新手先学统计函数COUNT、SUM、MAX、MIN先把数算明白统计函数是嵌套查询的地基。你后面写“比平均单价高”“和某公司同城”“至少被两个客户订过”子查询经常返回一个数字或一组编号。这个数字怎么来就是 COUNT、SUM、MAX、MIN 这些函数算出来的。新手一上来就写三层 SELECT十有八九会翻车不是因为嵌套难而是因为连“这一组数据到底怎么数”都没想清楚。这一章的目标很朴素你能看着一张表说出“我要数行、求和、找最大最小、按客户分组”并且知道 WHERE 和 HAVING 分别卡在哪一步。下面用示例库里的 CUSTOMER、PRODUCT、SALE 三张表练手字段都来自数据库实验5客户表有 CNO、CNAME、SITE、TELE产品表有 PCODE、PNAME、PRICE、STOCKS销售表有 CNO、PCODE、OQUANTITY。你不需要先理解索引、事务这些词先把查询结果跑出来。2.1 从一张客户表开始SELECT 和 COUNT 的第一口先建一个最小练习环境。下面的字段只保留实验里会用到的部分城市和公司名都用虚拟值避免你对着真实数据分心。建表语句跑完再插几条示例数据后面每一节都能直接接上。-- 客户表CNO 是客户编号SITE 是所在城市 CREATE TABLE CUSTOMER ( CNO VARCHAR(10) PRIMARY KEY, CNAME VARCHAR(50), SITE VARCHAR(30), TELE VARCHAR(30) ); -- 产品表PCODE 是产品编号PRICE 是单价STOCKS 是库存量 CREATE TABLE PRODUCT ( PCODE VARCHAR(10) PRIMARY KEY, PNAME VARCHAR(50), PRICE DECIMAL(10,2), STOCKS INT ); -- 销售表记录哪个客户订了哪个产品、订了多少 CREATE TABLE SALE ( CNO VARCHAR(10), PCODE VARCHAR(10), OQUANTITY INT ); INSERT INTO CUSTOMER VALUES (C01, 示例公司甲, 城市A, 1000001), (C02, 示例公司乙, 城市A, 1000002), (C03, 示例公司丙, 城市B, 1000003); INSERT INTO PRODUCT VALUES (A01, 示例产品A, 12.50, 300), (B01, 示例产品B, 18.00, 800), (B02, 示例产品C, 25.00, 120); INSERT INTO SALE VALUES (C01, A01, 120), (C01, B01, 220), (C02, B01, 150), (C03, A01, 80);这段代码里PRIMARY KEY 表示主键CNO 和 PCODE 不能重复VARCHAR 用来放编号和名称DECIMAL 用来放金额INT 用来放数量和库存。INSERT 只是给练习准备几行数据方便你看到“数出来是多少”。真实实验库如果已经建好表就不用重复建直接从下面的查询开始。第一个统计查询只做一件事数客户有多少家。-- 数 CUSTOMER 表里一共有多少行也就是多少家客户 SELECT COUNT(*) AS 客户数 FROM CUSTOMER;COUNT() 数的是行数不管某一列是不是空。结果应该是 3。这里给 COUNT() 起了别名“客户数”只是让结果表头更好读不起别名也不影响查询。新手常问 COUNT() 和 COUNT(CNO) 有什么区别如果 CNO 是主键、每行都有值两者结果一样如果某一列允许为空COUNT(列名) 会跳过空值。统计客户数量用 COUNT() 更直接。接着看库存总和与最高单价。SUM 只对数字列有意义MAX 和 MIN 也是。你要是把 SUM 套在 CNAME 上数据库会直接报错这不是玄学是类型不对。-- 求所有产品库存量的总和 SELECT SUM(STOCKS) AS 库存总量 FROM PRODUCT; -- 求所有产品里的最高单价 SELECT MAX(PRICE) AS 最高单价 FROM PRODUCT;SUM(STOCKS) 把 PRODUCT 表里每一行的 STOCKS 加起来当前示例数据是 3008001201220。MAX(PRICE) 找的是 PRICE 列里最大的那个值当前是 25.00。参数说明就一句括号里写你要统计的列名列里必须是数字或可计算的类型。结果只有一行因为这里没有分组整张表被当成一组。2.2 GROUP BY 和 HAVING为什么“先分组再筛”是新手分水岭不分组时整张表是一大组一分组数据库会按某一列的值把行拆成小堆再对每一堆做统计。GROUP BY 就是“按谁分堆”。比如求每个客户订购产品数量的总数就要按 CNO 分堆再对每堆的 OQUANTITY 求和。-- 按客户编号分组算每个客户订购数量的总数 SELECT CNO, SUM(OQUANTITY) AS 订购总量 FROM SALE GROUP BY CNO;CNO 是分组列SUM(OQUANTITY) 是每组的汇总值。结果里 C01 是 120220340C02 是 150C03 是 80。SELECT 里出现 CNO 和聚合函数是因为 CNO 在 GROUP BY 里数据库知道每一行结果代表哪个客户。你要是再塞一个没有分组的 OQUANTITY 进去很多数据库会报错或者给出让你看不懂的结果。HAVING 是分组之后的筛选。WHERE 在分组前筛行HAVING 在分组后筛组这个顺序是新手最容易混的地方。看一个实验里常见的需求至少订购两种以上产品的客户编号和产品种类数。-- 按客户分组只留下产品种类数大于等于 2 的客户 SELECT CNO, COUNT(DISTINCT PCODE) AS 产品种类数 FROM SALE GROUP BY CNO HAVING COUNT(DISTINCT PCODE) 2;COUNT(DISTINCT PCODE) 里的 DISTINCT 表示同一个产品只算一次。实验原文里用的是 COUNT(PCODE)它数的是订单行数如果同一个客户对同一个产品有两行记录COUNT(PCODE) 会把重复产品也算进去。你要的是“产品种类数”就用 COUNT(DISTINCT PCODE)你要的是“订单行数”才用 COUNT(PCODE)。这不是抠字眼结果口径不同后面嵌套查询的答案就会跟着变。HAVING 后面可以直接写聚合函数也可以写别名具体看数据库支持初学阶段建议写完整表达式别依赖别名换一个环境就容易翻车。WHERE 里不能写 COUNT 这类聚合函数因为那时还没分组。记住一句话先 WHERE 筛行再 GROUP BY 分堆再 HAVING 筛堆最后 SELECT 出列。2.3 函数套条件BETWEEN、LIKE 与统计结果怎么读统计函数经常和条件一起用。求库存量超过 500 的产品个数可以写成 WHERE STOCKS 500 再 COUNT(*)也可以按库存分组后 HAVING但两种写法结果形态不一样。新手实验里容易把“个数”查成“每个库存值各有多少个”读结果时才发现不对。-- 直接筛出库存超过 500 的产品再数行数 SELECT COUNT(*) AS 库存超500的产品数 FROM PRODUCT WHERE STOCKS 500;WHERE 先留下 STOCKS 大于 500 的行COUNT(*) 再数这些行。当前示例里只有 B01 的 800 符合结果是 1。你要是写成 GROUP BY STOCKS HAVING STOCKS 500得到的是按库存值分组后的多行结果不是单一的“产品个数”。实验里两种写法都可能出现但读题时先问自己我要一行总数还是每个分组一行再看 BETWEEN 和 LIKE。求单价在 10 到 20 元之间的产品个数BETWEEN 的边界是包含的10 和 20 都算在内。求以字母 B 开头的产品最低单价LIKE B% 里的 % 表示后面任意字符。-- BETWEEN 包含两端边界 SELECT COUNT(*) AS 价格区间产品数 FROM PRODUCT WHERE PRICE BETWEEN 10 AND 20; -- LIKE B% 表示以 B 开头后面任意字符 SELECT MIN(PRICE) AS B开头产品最低价 FROM PRODUCT WHERE PCODE LIKE B%;第一条查询当前会数到 A01 的 12.50 和 B01 的 18.00共 2 个B02 的 25.00 不在区间内。第二条查询只看 PCODE 以 B 开头的产品也就是 B01 和 B02最低价是 18.00。LIKE 的大小写行为跟数据库的排序规则有关入门时先按你实验环境里的结果为准不要凭感觉认定 B 和 b 一定一样。这一章练到这里你应该能独立写出“数客户、求总和、找最大最小、按客户分组、分组后筛、带条件统计”这几类查询。它们看起来不花哨却是后面连接查询和嵌套查询的零件。子查询返回的那个数字往往就是这里算出来的你现在能读懂结果后面才不会把“子查询返回多行”误当成语法错误。3. 连接查询入门两张表怎么“对上号”再一起筛统计查询是在一张表里数数连接查询开始处理“客户在客户表订单在销售表产品价格在产品表”这种分开存的信息。新手第一次写连接最常见的困惑不是 JOIN 这个词而是“这两张表凭什么能连起来”。答案不复杂它们有共同的编号列。客户表的 CNO 和销售表的 CNO 对上产品表的 PCODE 和销售表的 PCODE 对上连接条件就是把这两列相等写出来。这一章先不讲花哨的 LEFT JOIN、RIGHT JOIN先把最常用的内连接写顺。你要能看懂“FROM SALE, CUSTOMER WHERE CUSTOMER.CNO SALE.CNO”这种旧式写法和“FROM SALE JOIN CUSTOMER ON ...”这种新式写法它们表达的是同一类事情。数据库实验5里的第 11 题就是典型查询由某城市客户订购的、且订购数量大于 200 套的客户编号、产品编号、联系人和订购数量。城市值在练习里换成你库里的虚拟值下面统一用“城市A”。3.1 先把表关系画在纸上主键和外键不是玄学先看三张表的最小关系。CUSTOMER 的 CNO 是客户编号PRODUCT 的 PCODE 是产品编号SALE 里同时有 CNO 和 PCODE表示“哪个客户订了哪个产品”。SALE 里的 CNO 指向 CUSTOMER 的 CNOSALE 里的 PCODE 指向 PRODUCT 的 PCODE。你不需要背“外键约束”的定义先把这个对应关系画出来一个客户可以有多条销售记录一个产品也可以出现在多条销售记录里。表关键列作用CUSTOMERCNO、CNAME、SITE、TELE存客户编号、名称、城市、电话PRODUCTPCODE、PNAME、PRICE、STOCKS存产品编号、名称、单价、库存SALECNO、PCODE、OQUANTITY存谁订了什么、订了多少连接查询的本质是从 SALE 里拿一条订单用它的 CNO 去 CUSTOMER 找对应客户用它的 PCODE 去 PRODUCT 找对应产品然后把需要的列拼成一行结果。要是连接条件写漏了SALE 的每一行会和 CUSTOMER 的每一行强行配对结果行数暴涨这就是新手常见的“重复行”来源。连接条件写在 ON 后面筛选条件写在 WHERE 后面。旧式写法把连接条件和筛选条件都塞进 WHERE读起来容易混新式 JOIN 把“怎么连”和“筛什么”分开了更推荐新手先用这种写法。下面两个查询结果一样你可以对照看。-- 旧式写法连接条件放在 WHERE 里 SELECT SALE.CNO, SALE.PCODE, CUSTOMER.CNAME, SALE.OQUANTITY FROM SALE, CUSTOMER WHERE SALE.CNO CUSTOMER.CNO AND CUSTOMER.SITE 城市A AND SALE.OQUANTITY 200; -- 新式写法JOIN 后面写连接条件WHERE 只筛结果 SELECT S.CNO, S.PCODE, C.CNAME, S.OQUANTITY FROM SALE AS S JOIN CUSTOMER AS C ON C.CNO S.CNO WHERE C.SITE 城市A AND S.OQUANTITY 200;旧式写法里SALE.CNO CUSTOMER.CNO 是连接条件CUSTOMER.SITE 城市A 和 SALE.OQUANTITY 200 是筛选条件。新式写法用 AS 给表起了短别名 S 和 CJOIN 后面紧跟 ON C.CNO S.CNOWHERE 只留下城市和数量条件。别名不是必须的但表名一长、列名重复时别名能让 SQL 短很多。ON 后面写两张表怎么对应WHERE 后面写你要的业务条件这个分工先养成习惯。3.2 用 WHERE 连接两张表城市客户大额订单的例子把上面的例子拆成步骤。第一步先看 SALE 表里有哪些订单第二步把 CUSTOMER 接进来看每条订单对应哪家客户第三步加上城市和数量条件。你不要一上来就写完整 SQL先在纸上列出“我要的列来自哪张表”客户编号来自 SALE 或 CUSTOMER 都行产品编号来自 SALE联系人来自 CUSTOMER订购数量来自 SALE。列找对了连接条件就不容易漏。-- 第一步先看订单原始数据 SELECT CNO, PCODE, OQUANTITY FROM SALE; -- 第二步接上客户表先不筛条件观察结果行数 SELECT S.CNO, S.PCODE, C.CNAME, C.TELE, S.OQUANTITY FROM SALE AS S JOIN CUSTOMER AS C ON C.CNO S.CNO; -- 第三步加城市和数量条件 SELECT S.CNO, S.PCODE, C.CNAME, C.TELE, S.OQUANTITY FROM SALE AS S JOIN CUSTOMER AS C ON C.CNO S.CNO WHERE C.SITE 城市A AND S.OQUANTITY 200;第一步只看 SALE结果有 4 行。第二步接上 CUSTOMER结果还是 4 行因为每个 CNO 都能在客户表里找到对应行如果某个 CNO 在客户表里不存在内连接会把它丢掉。第三步筛城市A且数量大于 200当前示例里 C01 的 B01 订单 220 符合C02 的 B01 订单 150 不符合。结果里出现 CNAME 和 TELE是因为它们来自 CUSTOMERS.CNO、S.PCODE、S.OQUANTITY 来自 SALE。每个列前面带表别名数据库就知道去哪个表取不会因为两张表都有 CNO 而报“列名不明确”。参数怎么改城市值换成你实验库里的实际值200 换成题目要求的阈值CNAME、TELE 换成题目要的联系人列。要是实验表里联系人列名不叫 CNAME就按你库里的列名改不要硬抄示例。连接条件 C.CNO S.CNO 不能省省了就会变成笛卡尔积行数等于客户数乘订单数结果看起来“多了一堆重复记录”。3.3 连接查询和嵌套查询的分工什么时候别硬写子查询同一个需求连接查询和嵌套查询都能写。比如“查询城市A客户的大额订单”可以用 JOIN 把客户表接进来筛城市也可以用子查询先查出城市A的客户编号再在外层筛 SALE。两种写法没有绝对高低入门阶段先看可读性需要同时显示客户名称、电话和订单数量时JOIN 更直接只需要拿一个条件去筛另一张表时子查询更贴近人的思路。-- 用子查询实现同一件事先找城市A的客户编号再筛订单 SELECT CNO, PCODE, OQUANTITY FROM SALE WHERE CNO IN ( SELECT CNO FROM CUSTOMER WHERE SITE 城市A ) AND OQUANTITY 200;这段 SQL 先跑括号里的子查询得到城市A的客户编号集合比如 C01、C02外层再用 CNO IN 这个集合去筛 SALE并加上数量大于 200。IN 适合子查询返回多个值的情况如果子查询只返回一个值可以用 但新手先养成“可能返回多个就用 IN”的习惯能少踩很多坑。连接查询把两张表并排拼起来子查询把内层结果当成外层的一个条件这就是它们最直观的分工。数据库实验5里第 11 题要求同时出现客户编号、产品编号、联系人和订购数量用 JOIN 写最顺第 13 题“查询单价比 A01 号产品高的产品订单”更适合先用子查询查出 A01 的单价再拿这个单价去比较。你不需要强迫自己只用一种写法先把题目拆成“内层要算出什么、外层要拿这个结果筛什么”再决定用连接还是嵌套。连接查询这一关过了嵌套查询才不会变成背模板。4. 嵌套查询第一步把子查询当成“括号里的小答案”嵌套查询的写法是外层一个 SELECT内层一个 SELECT。内层先执行算出一个值或一组值外层再拿这个结果去筛选。新手最容易犯的错是把内层当成“装饰”随便塞进去结果外层用 接一个返回多行的子查询数据库直接报错。你先别追求三层套三层先把单值子查询、比较运算符子查询、NOT IN 和 NOT EXISTS 这几种最常见的形态跑通。这一章对应数据库实验5里的第 12、13、14 题同城市客户、比 A01 单价高的订单、没有被订购的产品。题目里的公司名和城市值都换成虚拟值逻辑不变。每写一个嵌套查询先单独跑内层确认它返回一行还是一列多行再写外层。这个习惯比你背十条语法都管用。4.1 单值子查询找“和某公司在同一城市”的客户第 12 题的需求是查询与“示例公司甲”在同一城市的客户公司名称及联系电话。人的思路分两步先查示例公司甲在哪个城市再查这个城市里还有哪些客户。子查询正好对应第一步外层对应第二步。-- 内层先查示例公司甲所在的城市 SELECT SITE FROM CUSTOMER WHERE CNAME 示例公司甲; -- 外层用内层返回的城市去筛其他客户 SELECT CNAME, TELE FROM CUSTOMER WHERE SITE ( SELECT SITE FROM CUSTOMER WHERE CNAME 示例公司甲 );内层返回一个城市值比如“城市A”。外层 WHERE SITE 这个值就能找到同城的客户。这里用 的前提是内层只返回一行如果客户表里有两家都叫“示例公司甲”内层返回两行外层就会报“子查询返回多于一行”。真实实验里公司名一般不会重复但你要知道这个限制。想避开这个坑可以把 换成 IN或者在内层加一个能唯一确定客户的条件比如客户编号。结果里会包含示例公司甲自己。如果题目要求“其他客户”外层再加一个条件 CNAME 示例公司甲。这个条件写在外层不要写进内层内层只负责回答“城市是什么”。嵌套查询的每一层只做一件事读起来才不绕。参数说明CNAME 是客户名称列SITE 是城市列字符串要用单引号包住换成你库里的列名时内外层要对应同一张表的同一列。4.2 比较运算符配子查询比 A01 单价高的产品订单第 13 题是查询订购了单价比 A01 号产品单价高的那些产品的产品编号、客户编号和订购数量。这里的内层不是找城市而是找 A01 的单价外层拿 SALE 和 PRODUCT 连接后用 PRICE 内层单价来筛。-- 内层查 A01 号产品的单价 SELECT PRICE FROM PRODUCT WHERE PCODE A01; -- 外层连接销售表和产品表筛单价高于 A01 的订单 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 );内层返回 A01 的单价 12.50。外层先把 SALE 和 PRODUCT 按 PCODE 连起来得到每条订单对应的产品单价再筛单价大于 12.50 的记录。当前示例里 B01 单价 18.00、B02 单价 25.00符合条件A01 自己的订单不符合。外层用 JOIN 是因为结果要产品编号、客户编号和订购数量SALE 里有客户和数量PRODUCT 里有单价两张表都要用。这里的比较运算符 只能接单值子查询。内层 WHERE PCODE A01 如果匹配到两行外层就会报错。产品编号如果是主键通常不会重复你要是拿产品名称去查就可能返回多行这时要么改用 IN要么先确认名称是否唯一。参数怎么改A01 换成题目指定的产品编号 换成题目要求的比较方向SELECT 里要的列按题目增减。不要在外层 WHERE 里再写一遍 PCODE A01那会把 A01 自己的订单也筛进来和题意相反。4.3 NOT IN / NOT EXISTS 的入门用法没被订购的产品怎么查第 14 题是查询没有被订购的产品的编号和名称。人的思路是“产品表里有哪些产品销售表里没有出现过的就是没被订购”。用 NOT IN 写先查 SALE 里出现过的产品编号再让 PRODUCT 排除这些编号。-- NOT IN先查销售表里出现过的产品编号再排除它们 SELECT PCODE, PNAME FROM PRODUCT WHERE PCODE NOT IN ( SELECT PCODE FROM SALE WHERE PCODE IS NOT NULL );内层 SELECT PCODE FROM SALE 返回一列产品编号外层用 NOT IN 排除这些编号。当前示例里 SALE 出现过 A01 和 B01PRODUCT 里的 B02 没出现过所以结果是 B02。内层加 WHERE PCODE IS NOT NULL 是防一手如果 SALE 的 PCODE 里有空值NOT IN 可能让整个结果变成空这是 SQL 的三值逻辑不是你的眼睛花了。入门阶段先记住“NOT IN 遇到 NULL 容易出问题”要么过滤空值要么改用 NOT EXISTS。-- NOT EXISTS对每个产品检查销售表里有没有对应记录 SELECT P.PCODE, P.PNAME FROM PRODUCT AS P WHERE NOT EXISTS ( SELECT 1 FROM SALE AS S WHERE S.PCODE P.PCODE );NOT EXISTS 的写法是相关子查询内层用到了外层的 P.PCODE数据库会对 PRODUCT 的每一行跑一次内层检查 SALE 里有没有相同产品编号。SELECT 1 只是占位表示“有记录就行”不需要真的取某一列。这个写法对 NULL 更稳读起来也更贴近“不存在对应销售记录”的意思。新手先会 NOT IN再会 NOT EXISTS两者都能解决“没被订购”的问题但后者在数据有空值时更让人放心。嵌套查询练到这里你已经覆盖了单值子查询、比较运算符子查询、IN、NOT IN、NOT EXISTS。写的时候先单独跑内层看它返回一行还是一列外层用 还是 IN取决于内层结果的行数。数据库实验5里的嵌套题不会故意为难你难的是你在一张纸上把“内层回答什么、外层拿它筛什么”说清楚。说清楚了SQL 只是把这个思路翻译出来。5. 新手避坑排查数据库实验5嵌套查询最容易翻车的 5 个地方这一章不堆新语法专门处理你写完 SQL 后“结果不对、报错、查不到”的场景。数据库实验5的题目看起来短但连接条件、分组口径、子查询返回值、NULL、中文条件这几个地方任何一个没对齐结果就会让你怀疑人生。下面每条都按“现象、原因、解决”走你遇到问题时可以直接对号入座。5.1 现象结果多出一堆重复行原因连接条件漏了解决先数表再写 WHERE现象很典型你只想查城市A客户的大额订单结果每一条订单重复出现好几次客户名称还来回换。原因通常是 FROM 后面写了两张表却忘了写两张表的连接条件数据库把两张表做了笛卡尔积。SALE 有 4 行、CUSTOMER 有 3 行没连接条件就会出 12 行看起来像“数据重复”其实是强行配对。-- 错误漏了 C.CNO S.CNO行数会膨胀 SELECT S.CNO, S.PCODE, C.CNAME, S.OQUANTITY FROM SALE AS S JOIN CUSTOMER AS C WHERE C.SITE 城市A; -- 正确先写连接条件再写筛选条件 SELECT S.CNO, S.PCODE, C.CNAME, S.OQUANTITY FROM SALE AS S JOIN CUSTOMER AS C ON C.CNO S.CNO WHERE C.SITE 城市A AND S.OQUANTITY 200;解决步骤不复杂先单独 SELECT COUNT(*) 看 SALE 有多少行再接上 CUSTOMER 看行数有没有变化如果接上后行数暴涨先检查 ON 后面的连接条件。旧式写法就把连接条件放在 WHERE 的第一行别和业务筛选混在一起。你每写一个 JOIN都在纸上写一句“这两张表用哪个编号对应”连接条件就不会漏。5.2 现象子查询报“返回多行”原因用了 却返回多个值解决IN 或加条件现象是数据库直接报错提示子查询返回多于一行。原因是你用 接了一个可能返回多行的子查询。比如按客户名称查城市如果名称不唯一内层会返回多个城市外层 WHERE SITE (...) 就接不住。这不是嵌套查询本身的问题是你给 塞了它处理不了的结果。-- 有风险如果“示例公司甲”不唯一内层返回多行 会报错 SELECT CNAME, TELE FROM CUSTOMER WHERE SITE ( SELECT SITE FROM CUSTOMER WHERE CNAME 示例公司甲 ); -- 更稳用 IN 接一列多行 SELECT CNAME, TELE FROM CUSTOMER WHERE SITE IN ( SELECT SITE FROM CUSTOMER WHERE CNAME 示例公司甲 );解决时先单独跑内层看结果是一行还是一列多行。只返回一行 可以用可能返回多行就换 IN。另一种办法是让内层条件更唯一比如用 CNO 而不是 CNAME。IN 的语义是“等于这个集合里的任意一个值”读起来也比 更宽容。数据库实验5里很多嵌套题内层只返回一行但你最好养成先验证的习惯。5.3 现象GROUP BY 报错或结果怪原因SELECT 列不在分组里解决先分组再选列现象是 GROUP BY 查询报错或者结果里某个数量列的值对不上。原因通常是 SELECT 里出现了没有放进 GROUP BY、也没有被聚合函数包住的列。比如按产品编号分组却直接选 OQUANTITY数据库不知道你要取这一组里的哪一行数量。-- 有风险OQUANTITY 没有分组也没有被聚合 SELECT PCODE, OQUANTITY FROM SALE GROUP BY PCODE; -- 正确要每组总量就用 SUM 包住数量列 SELECT PCODE, SUM(OQUANTITY) AS 订购总量 FROM SALE GROUP BY PCODE;解决思路先问自己这一行结果代表什么。按 PCODE 分组一行代表一个产品那么数量列要么求和要么求平均要么求最大最小不能直接裸选。实验里“至少被两个以上客户订购且每个客户订购数量超过 100”的题目还要注意 COUNT(PCODE) 数的是订单行COUNT(DISTINCT CNO) 数的才是客户数。口径选错结果看起来像数据库在骗你其实是你把“行数”当成了“客户数”。5.4 现象NULL 让 NOT IN 失效原因三值逻辑解决NOT EXISTS 或过滤 NULL现象是明明有没被订购的产品NOT IN 却返回空结果。原因多半是子查询的那一列里出现了 NULL。NOT IN 的语义是“不等于集合里的每一个值”当集合里有 NULL 时比较结果既不是真也不是假整个条件可能不成立。新手看到空结果常以为数据不存在其实是 NULL 在捣乱。-- 防 NULL先过滤掉空的产品编号 SELECT PCODE, PNAME FROM PRODUCT WHERE PCODE NOT IN ( SELECT PCODE FROM SALE WHERE PCODE IS NOT NULL ); -- 更稳用 NOT EXISTS不依赖 NOT IN 的 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 PCODE FROM SALE看有没有空值有空值就加 WHERE PCODE IS NOT NULL或者直接换 NOT EXISTS。NOT EXISTS 只关心“有没有对应记录”空值不会把整个结果拖垮。数据库实验5第 14 题很适合用这个写法练手你写一次就能记住 NULL 这个坑。5.5 现象中文条件查不到原因空格、引号或值不一致解决先查原值再比对现象是 SQL 没报错但 WHERE CNAME 示例公司甲 查不到任何行。原因可能是你输入的值和表里的值不完全一致前后有空格、全角半角不同、名称里多了看不见的字符或者你用了双引号但环境把双引号当成了列名。中文条件最容易让人以为“数据库不支持中文”其实多数时候是值没对上。-- 先看表里到底存了什么包括城市和公司名的原值 SELECT CNO, CNAME, SITE, LENGTH(CNAME) AS 名称长度 FROM CUSTOMER; -- 确认值一致后再用单引号写条件 SELECT CNO, CNAME, SITE FROM CUSTOMER WHERE SITE 城市A;解决步骤先 SELECT 原值把表里的名称复制出来比对检查字符串是不是用单引号包住检查有没有多余空格。LENGTH 只是帮你看名称长度不同数据库函数名可能不同用你环境里能跑的长度函数就行。中文值本身没有问题问题是“你看到的值”和“表里存的值”是否完全一样。每次查不到先查原值再怀疑语法。6. 下一步学什么把实验5的查询练成自己的手感下一步不要急着学窗口函数、存储过程先把数据库实验5里的查询按“统计、连接、嵌套”三组重写一遍。第一组把 COUNT、SUM、MAX、MIN、GROUP BY、HAVING 各写两条每条都单独跑内层或分组结果第二组把第 11 题用 JOIN 写一遍再用子查询写一遍比较结果行数第三组把第 12、13、14 题各写两版一版用 IN 或 一版用 NOT EXISTS 或 JOIN。每组写完用 COUNT(*) 对一下行数别只看第一行数据。阶段练什么达标标准第 1 周统计函数加 GROUP BY能说清 WHERE 和 HAVING 的顺序第 2 周两表 JOIN 加筛选能独立写出连接条件不漏 ON第 3 周单值子查询和 IN会先单独跑内层判断返回行数第 4 周NOT IN 与 NOT EXISTS遇到 NULL 知道换写法具体技巧写嵌套查询时先把内层单独跑出来确认它返回一行还是一列外层要用 还是 IN看内层结果决定。你可以给内层加注释写清楚“这里查城市”“这里查单价”别让三层 SELECT 挤在一起。再进一步可以学 WITH 子句把子查询提到前面命名读起来像流水账。-- 用 WITH 给子查询起名适合下一步练习 WITH same_city AS ( SELECT SITE FROM CUSTOMER WHERE CNAME 示例公司甲 ) SELECT CNAME, TELE FROM CUSTOMER WHERE SITE IN (SELECT SITE FROM same_city);我第一次练嵌套查询时总想一口气写三层结果外层报错内层返回三行查了半小时才发现是 接不住多行。从那以后我每次写子查询都先单独跑内层再把结果接回外层这个习惯比背语法管用。希望帮到你。本文还有配套的精品资源点击获取