SQL习题集深度拆解:从三级模式到存储过程实战
简介这份《数据库原理及应用SQL-习题集(含答案)》面向高校数据库课程学习者与备考学生用于系统巩固数据库原理与SQL语言的核心考点。内容覆盖ER模型、三级模式结构、关系代数、SQL语句、事务处理、并发控制与数据库安全性等模块题型以单项选择题为主并附有参考答案便于自测与查漏补缺。资源包共1个doc文件约2.21MB结构紧凑可直接打印或在线练习。目前已有229人学习下载适合课堂同步练习、期末复习及考研初试的数据库科目刷题使用。通过逐题训练读者可熟悉ER图向关系模型转换、封锁协议与两段锁、事务ACID特性、GRANT/REVOKE权限控制等高频考点并借助答案快速定位知识盲区提升解题速度与准确率。1. 一份 20 页的 SQL 习题集为什么值得你花时间拆一遍很多人看到「数据库原理及应用 SQL-习题集(含答案).doc」这种文件名第一反应是「学生复习资料」然后划走。但如果你正在准备数据库相关的面试、带新人、或者自己讲一门数据库课这份东西的价值恰恰在于它把「概念—设计—SQL 编程—PB 开发」四条线压进了同一份文档里。它覆盖了 ER 模型、三级模式、事务 ACID、封锁协议、关系代数、SQL 的 DDL/DML/DCL、存储过程、视图甚至还有 PowerBuilder 的窗口事件编程。换句话说它不是零散的选择题堆砌而是一条从理论到落地的完整链路。适合谁一是要快速自测数据库基础是否扎实的开发者二是需要现成题库和答案做教学参考的讲师三是准备课程设计、需要 ER 图和关系模式转换范例的人。下面我按「怎么用、怎么避坑、怎么进阶」拆开讲。2. 从选择题到 ER 图这份习题集的知识骨架怎么读2.1 先分清四类题型别一上来就刷这份文档的结构其实很清晰分五大块单项选择题 50 道、综合设计题 5 道、编程题 1SQL 语句5 道、编程题 2PB 编程5 道、简答题略。很多人拿到手直接从第 1 题开始做做完对答案然后就没有然后了。这种用法效率很低因为四类题型的训练目标完全不同。单项选择题考的是概念辨析比如「ER 模型属于概念模型」「三级模式中定义索引组织方式属于内模式」「GRANT/REVOKE 实现安全性控制」。这些题的价值不在于选对而在于你能不能说清每个选项为什么错。综合设计题考的是 ER 图到关系模式的转换能力包括主键、外键的标注。编程题 1 考的是 SQL 语句的实际编写涉及多表连接、子查询、聚合、UPDATE、DELETE、存储过程。编程题 2 考的是 PB 这种老牌开发工具的事件驱动编程属于特定技术栈的实操。我一般建议的阅读顺序是先做 50 道选择题把错题对应的知识点在文档里定位然后精读 5 道综合设计题因为它们是 ER 模型和关系模式转换的典型范例最后看编程题SQL 部分可以直接在数据库里跑PB 部分如果没接触过可以跳过但建议至少看懂事件逻辑。2.2 三级模式与两级映象选择题里反复出现的核心考点翻一遍选择题会发现「三级模式」「两级映象」「数据独立性」这几个词反复出现。第 2 题问「定义索引的组织方式属于哪个模式」答案是内模式。第 11 题问「对用户使用的数据视图的描述称为什么」答案是外模式。第 12 题问「三级模式之间的两级映象使数据库具有较高的什么」答案是独立性。第 41 题问「表达物理数据库的是哪个模式」答案是内模式。这些题不是孤立的它们共同指向一个核心数据库的三级模式结构。概念模式也叫逻辑模式描述全体数据的逻辑结构外模式描述用户看到的数据视图内模式描述物理存储结构。两级映象分别是外模式/概念模式映象和概念模式/内模式映象前者保证了逻辑数据独立性后者保证了物理数据独立性。提示如果你在做这些题时只是背答案建议换一种方式——把每个模式对应的「谁在用」「描述什么」「变了会影响谁」列成一张表比死记硬背有效得多。2.3 ER 模型到关系模式综合设计题的通用套路综合设计题 51 到 55 都是同一个套路给一段业务语义要求画 ER 图、标注联系类型、转换成关系模式、指出主键和外键。以第 51 题为例某公司定单管理系统涉及销售职工、产品、供应商、定货人四类实体其中销售职工与产品是多对多供应商与产品是多对多定货人与产品是多对多每次定货有定货日期和数量。转换规则其实很固定每个实体转一个关系模式多对多联系转一个独立的关系模式一对多联系把「一」方的主键放到「多」方作为外键一对一联系可以合并也可以独立。第 51 题的答案里供应商、产品、销售职工、定货人各转一个关系三个多对多联系分别转成供给、订购等关系主键是双方主键的组合外键分别指向对应的实体关系。这里有个容易翻车的点很多人在转换时会把一对多联系也单独建一个关系导致关系模式冗余。第 26 题专门考了这个——「每个联系类型转换成一个关系模式」是错误的只有多对多联系才需要独立的关系模式。2.4 SQL 编程题从单表查询到存储过程的递进编程题 1 的 5 道题覆盖了 SQL 的主要操作类型。第 56 题基于供应商-零件-供货三个关系要求写 6 个 SQL查询、更新、聚合、存储过程。第 57 题基于出版社-图书两个关系同样覆盖检索、统计、删除、存储过程。第 58 题基于学生-选课-课程三个关系涉及分组统计、多表连接、子查询、删除、存储过程。第 59 题和第 60 题也是类似结构但增加了视图的创建。这些题的答案里有一些值得注意的写法。比如第 56 题第 1 问「求供给红色零件的供应商名字」答案用了子查询加 IN 的方式先查出供给红色零件的供应商号再在外层查名字。这种写法逻辑清晰但在数据量大时性能不如 JOIN。第 56 题第 6 问创建存储过程用的是CREATE PROC P_LIST Id CHAR(4) AS SELECT ... WHERE PNOId这是 SQL Server 的语法风格。注意文档里的 SQL 答案存在一些拼写错误比如CTREATE、Selcet、form等实际执行前需要修正。另外第 58 题第 6 问的存储过程用了count(distinct .课程编号)这里的.课程编号应该是SC.C#或类似写法直接抄会报错。3. 把习题集跑起来SQL 答案的验证与修正实操3.1 搭建一个最小验证环境这份习题集里的 SQL 答案不能直接复制粘贴就完事因为文档里存在拼写错误、表名不一致、列名缺失等问题。要验证这些答案最省事的做法是建一个本地数据库把题目里描述的表结构建出来然后逐条跑答案看能不能出结果。我一般用 SQLite 做快速验证因为它零配置、单文件、支持大部分标准 SQL。但要注意文档里的存储过程用的是 SQL Server 的CREATE PROC语法SQLite 不支持。所以如果你要验证存储过程部分建议用 SQL Server Express 或者 MySQL。下面以第 56 题的供应商-零件-供货数据库为例给出建表和插入测试数据的脚本。-- 供应商表 CREATE TABLE S ( SNO CHAR(4) PRIMARY KEY, SNAME VARCHAR(20), CITY VARCHAR(20), STATUS INT ); -- 零件表 CREATE TABLE P ( PNO CHAR(4) PRIMARY KEY, PNAME VARCHAR(20), WEIGHT INT, COLOR VARCHAR(10), CITY VARCHAR(20) ); -- 供货表 CREATE TABLE SP ( SNO CHAR(4), PNO CHAR(4), QTY INT, PRIMARY KEY (SNO, PNO), FOREIGN KEY (SNO) REFERENCES S(SNO), FOREIGN KEY (PNO) REFERENCES P(PNO) ); -- 插入测试数据 INSERT INTO S VALUES (S1, 甲方供应商, 北京, 20); INSERT INTO S VALUES (S2, 乙方供应商, 上海, 30); INSERT INTO S VALUES (S3, 丙方供应商, 北京, 15); INSERT INTO P VALUES (P1, 螺母, 12, 红色, 北京); INSERT INTO P VALUES (P2, 螺栓, 8, 蓝色, 上海); INSERT INTO P VALUES (P3, 垫片, 5, 红色, 北京); INSERT INTO SP VALUES (S1, P1, 100); INSERT INTO SP VALUES (S1, P2, 200); INSERT INTO SP VALUES (S2, P2, 150); INSERT INTO SP VALUES (S3, P1, 50); INSERT INTO SP VALUES (S3, P3, 80);这段脚本做了三件事建三张表、定义主外键约束、插入能覆盖查询条件的测试数据。建表时把 SNO 和 PNO 设为主键SP 表用联合主键这样能保证参照完整性。插入的数据里S1 和 S3 都在北京P1 和 P3 都是红色P2 被 S1 和 S2 供给这样后面跑查询时能验证结果是否正确。3.2 逐条修正并验证 SQL 答案第 56 题第 1 问「求供给红色零件的供应商名字」文档答案是SELECT SNAME FROM S WHERE SNO IN ( SELECT SNO FROM P, SP WHERE P.COLOR红色 AND P.PNOSP.PNO );这条语句逻辑是对的但写法上用了隐式连接。在 SQLite 里跑没问题但更规范的写法是显式 JOIN。另外如果同一个供应商供了多种红色零件IN 子查询会去重不会重复返回供应商名字这是符合预期的。第 2 问「求北京供应商的号码、名字和状况」SELECT SNO, SNAME, STATUS FROM S WHERE CITY北京;这条直接跑就行注意文档里写的是S.CITY北京加了表别名前缀在单表查询里可加可不加。第 3 问「求零件 P2 的总供给量」SELECT SUM(QTY) FROM SP WHERE PNOP2;这条没问题但要注意如果 P2 没有任何供给记录SUM 会返回 NULL 而不是 0。如果需要返回 0得用COALESCE(SUM(QTY), 0)。第 4 问「把零件 P2 的重量增加 5 公斤颜色改为黄色」UPDATE P SET WEIGHTWEIGHT5, COLOR黄色 WHERE PNOP2;文档里写的是WEIGHT 十 5那个「十」是全角字符直接抄会报语法错误必须改成半角加号。第 5 问「统计每个供应商供给的项目总数」SELECT SNO, COUNT(DISTINCT PNO) FROM SP GROUP BY SNO;这里用COUNT(DISTINCT PNO)是对的因为一个供应商可能多次供给同一种零件去重后才是「项目总数」。如果不去重统计的是供货记录数。第 6 问「建立存储过程输入零件编号显示零件的 PNAME、WEIGHT、COLOR、CITY」这是 SQL Server 语法CREATE PROCEDURE P_LIST Id CHAR(4) AS BEGIN SELECT PNAME, WEIGHT, COLOR, CITY FROM P WHERE PNO Id; END;在 SQL Server 里执行这段就能创建存储过程然后EXEC P_LIST P1就能查到结果。如果用的是 MySQL语法要改成CREATE PROCEDURE P_LIST(IN p_id CHAR(4)) BEGIN SELECT ... END;。3.3 用 Python 批量验证答案的正确性如果你想把 50 道选择题的答案和 SQL 编程题的答案都验证一遍手动跑太慢。我一般会写一个 Python 脚本用 sqlite3 模块建库、插数据、跑查询然后对比预期结果。下面是一个简化版的验证框架import sqlite3 # 连接内存数据库 conn sqlite3.connect(:memory:) cur conn.cursor() # 建表和插入数据省略具体语句同上 cur.executescript( CREATE TABLE S (SNO TEXT PRIMARY KEY, SNAME TEXT, CITY TEXT, STATUS INT); CREATE TABLE P (PNO TEXT PRIMARY KEY, PNAME TEXT, WEIGHT INT, COLOR TEXT, CITY TEXT); CREATE TABLE SP (SNO TEXT, PNO TEXT, QTY INT, PRIMARY KEY (SNO, PNO)); INSERT INTO S VALUES (S1, 甲方供应商, 北京, 20); INSERT INTO S VALUES (S2, 乙方供应商, 上海, 30); INSERT INTO S VALUES (S3, 丙方供应商, 北京, 15); INSERT INTO P VALUES (P1, 螺母, 12, 红色, 北京); INSERT INTO P VALUES (P2, 螺栓, 8, 蓝色, 上海); INSERT INTO P VALUES (P3, 垫片, 5, 红色, 北京); INSERT INTO SP VALUES (S1, P1, 100); INSERT INTO SP VALUES (S1, P2, 200); INSERT INTO SP VALUES (S2, P2, 150); INSERT INTO SP VALUES (S3, P1, 50); INSERT INTO SP VALUES (S3, P3, 80); ) # 验证第 1 问供给红色零件的供应商名字 cur.execute( SELECT SNAME FROM S WHERE SNO IN ( SELECT SNO FROM P, SP WHERE P.COLOR红色 AND P.PNOSP.PNO ) ) result cur.fetchall() print(第1问结果:, result) # 预期 [(甲方供应商,), (丙方供应商,)] # 验证第 3 问零件 P2 的总供给量 cur.execute(SELECT SUM(QTY) FROM SP WHERE PNOP2) result cur.fetchone() print(第3问结果:, result) # 预期 (350,) # 验证第 5 问每个供应商供给的项目总数 cur.execute(SELECT SNO, COUNT(DISTINCT PNO) FROM SP GROUP BY SNO) result cur.fetchall() print(第5问结果:, result) # 预期 [(S1, 2), (S2, 1), (S3, 2)] conn.close()这个脚本的核心思路是用内存数据库避免污染本地环境用executescript一次性建表和插数据然后逐条跑查询并打印结果。参数说明方面COUNT(DISTINCT PNO)里的 DISTINCT 是关键去掉它统计的是供货记录数而不是项目数。SUM(QTY)在无匹配记录时返回 NULL如果需要 0 得用COALESCE。提示SQLite 不支持存储过程所以第 6 问没法在 SQLite 里验证。如果你要验证存储过程建议装一个 SQL Server Express 或者用 Docker 跑一个 MySQL 实例。4. 避坑与排查这份习题集里最容易翻车的五个地方4.1 现象直接复制 SQL 答案执行报语法错误原因文档里的 SQL 存在大量拼写错误和全角字符。比如CTREATE应该是CREATESelcet应该是SELECTform应该是FROMWEIGHT 十 5里的「十」是全角加号。另外第 58 题第 6 问的count(distinct .课程编号)里.课程编号这个写法本身就不合法应该是SC.C#或SC.课程编号。解决把答案复制到编辑器后先做一次全局替换CTREATE→CREATE、Selcet→SELECT、form→FROM、十→。然后逐条检查列名和表名是否和建表语句一致。如果用的是 MySQL还要把CREATE PROC改成CREATE PROCEDURE把Id改成IN p_id。4.2 现象ER 图转关系模式时多建了关系原因把一对多联系也单独转成了一个关系模式。比如第 52 题里工段和车间是一对多车间和产品是多对多。有人会把「工段-车间」也建一个关系导致工段号在车间表和这个新表里重复出现。解决记住转换规则——一对多联系不单独建关系把「一」方的主键放到「多」方作为外键即可。第 52 题的正确答案里车间关系直接包含工段号作为外键没有单独的「工段-车间」关系。只有多对多联系才需要独立的关系模式比如「生产」关系包含产品号和车间号。4.3 现象COUNT(*) 和 COUNT(列名) 混用导致统计结果不对原因第 10 题考了「以下聚集函数中不忽略空值的是哪个」答案是 COUNT()。COUNT() 统计所有行COUNT(列名) 跳过该列为 NULL 的行。在实际写 SQL 时如果列里有 NULL 值用 COUNT(列名) 会少算。解决统计行数用 COUNT()统计某列非空值个数用 COUNT(列名)。第 58 题第 1 问「统计男生和女生的人数」答案用的是SELECT SEX, COUNT(*) FROM S GROUP BY SEX这里用 COUNT() 是对的因为每行都有 SEX 值。如果 SEX 列允许 NULL那 NULL 的那行不会被统计到任何分组里。4.4 现象存储过程创建失败提示「附近有语法错误」原因不同数据库的存储过程语法差异很大。文档里的答案用的是 SQL Server 风格CREATE PROC、Id、AS在 MySQL 或 PostgreSQL 里直接跑会报错。另外SQL Server 的存储过程如果包含多条语句需要用BEGIN...END包裹。解决先确认你用的数据库类型。SQL Server 用CREATE PROCEDURE 名称 参数 类型 AS BEGIN ... ENDMySQL 用CREATE PROCEDURE 名称(IN 参数 类型) BEGIN ... ENDPostgreSQL 用CREATE OR REPLACE FUNCTION 名称(参数 类型) RETURNS ... AS $$ ... $$ LANGUAGE SQL。如果只是验证逻辑可以先把存储过程里的 SELECT 语句单独拿出来跑。4.5 现象视图创建后查询报「没有这样的表」原因第 59 题第 6 问创建视图bad_health时答案用了SELECT * FROM 职工, 保健 WHERE ...这是一个连接查询。如果视图定义里引用了不存在的表或列创建时可能不报错但查询时会报错。另外如果视图定义里用了SELECT *后续基表结构变化会导致视图列不匹配。解决创建视图时尽量避免SELECT *明确列出需要的列。第 59 题的视图应该写成CREATE VIEW bad_health AS SELECT 职工.职工号, 职工.姓名 FROM 职工, 保健 WHERE 保健.健康状况差 AND 保健.职工号职工.职工号。创建后用SELECT * FROM bad_health验证一下能不能查出数据。5. 进阶用法把习题集变成自己的数据库知识检查清单5.1 用选择题做知识点映射50 道选择题覆盖了数据库原理的主要章节但它们是乱序的。我一般会做一件事把每道题对应的知识点标出来然后按章节归类。比如第 1、6、19、23、37、46 题都考 ER 模型和联系类型第 2、11、12、31、41 题考三级模式第 3、15、32 题考 SQL 的 DCL 和安全性第 9、16、17、28、29、30、34、48 题考事务和并发控制。归类之后你会发现有些知识点反复出现说明它们是重点有些知识点只出现一次可能是冷门考点。这张映射表就是你复习或备课的优先级清单。5.2 用综合设计题练 ER 图到关系模式的转换5 道综合设计题的业务场景不同但转换套路一致。我建议的做法是先不看答案自己画 ER 图、标联系类型、转关系模式、标主外键然后再对照答案。对照时重点看三个地方多对多联系有没有漏掉、一对多联系有没有多建关系、外键有没有标对。下面这张表是我整理的第 51 到 55 题的转换要点对比题号实体数多对多联系一对多联系独立关系数514307523115533114543124553115这张表能帮你快速看出规律实体数加多对多联系数基本等于独立关系数一对多不单独建关系。第 51 题有 4 个实体和 3 个多对多联系所以是 7 个关系第 52 题有 3 个实体和 1 个多对多联系加上一对多不单独建所以是 5 个关系工段、车间、产品、生产加上车间里的工段外键不新增关系。5.3 用编程题练 SQL 的「一题多解」编程题 1 的 SQL 答案只给了一种写法但实际工作中同一个需求往往有多种写法。比如第 56 题第 1 问「求供给红色零件的供应商名字」答案用了子查询加 IN也可以写成 JOINSELECT DISTINCT S.SNAME FROM S JOIN SP ON S.SNO SP.SNO JOIN P ON SP.PNO P.PNO WHERE P.COLOR 红色;两种写法的结果一样但执行计划可能不同。子查询加 IN 在数据量小的时候更直观JOIN 在数据量大且索引合理时通常更快。我一般会两种都跑一遍用EXPLAIN看执行计划然后根据实际数据量选择。第 58 题第 4 问「选修数据库原理的学生名单」答案用了三表连接SELECT S.SNAME FROM S, SC, C WHERE C.C# SC.C# AND S.S# SC.S# AND C.CNAME 数据库原理;也可以写成子查询SELECT SNAME FROM S WHERE S# IN ( SELECT SC.S# FROM SC, C WHERE SC.C# C.C# AND C.CNAME 数据库原理 );两种写法都能出结果但三表连接在返回多列时更灵活子查询在只需要一列时更简洁。5.4 把 PB 编程题当作事件驱动编程的入门案例编程题 2 的 5 道题都是 PowerBuilder 的窗口和控件事件编程。虽然 PB 现在用的人不多但事件驱动编程的思路是通用的在什么事件里写什么逻辑、怎么和数据库交互、怎么处理用户输入验证。第 61 题是登录验证第 62 题是数据管理窗口的增删改查按钮第 63 题是列表视图加载数据第 64 题是注册新用户第 65 题是数据窗口的打开和关闭查询。这些题的答案里有一些值得注意的细节。比如第 61 题的登录验证用了SELECT ... INTO :id FROM teacher WHERE ...然后判断sqlca.sqlcode100来判断是否查到记录。第 65 题的 closequery 事件里用了dw_1.modifiedcount() dw_1.deletedcount() 0来判断数据是否被修改过然后弹消息框询问是否保存。这些模式在今天的 Web 开发或桌面开发里依然适用只是换了个框架而已。从那以后我每次拿到一份习题集或题库都会先做一遍知识点映射再挑几道有代表性的题实际跑一遍最后把踩过的坑记下来。这份 SQL 习题集我前后拆了三遍第一遍对答案第二遍修 SQL第三遍整理成检查清单。希望帮到你。本文还有配套的精品资源点击获取