资讯详情

关系模型与SQL:从关系代数、视图到安全与范式

📅 2026/10/22 17:02:35 | 华诺云谱 👁 阅读
关系模型与SQL:从关系代数、视图到安全与范式
简介数据库系统概论第五版王珊版课后习题答案文档适合正在学习关系数据库理论的高校学生、考研复习者及自学者使用。整份资源为单个doc文件共588KB内容集中便于直接查阅或打印练习。已有1723人学习下载是数据库入门阶段常用的参考答案之一。文档围绕教材第2章“关系数据库”与第3章“关系数据库语言SQL”展开对关系模型三大组成部分、关系数据语言分类、实体完整性与参照完整性规则、关系代数五种基本运算等核心概念给出了简明解答同时配有SPJ数据库实例的查询演算、SQL建表与多表查询语句可直接对照教材习题检验理解。整体而言答案既能帮助读者快速定位课本重点也能通过典型例题加深对SQL查询和关系代数的掌握适合在课后练习或考前集中梳理时使用。1. 关系模型三句话的分工决定了关系数据库的一切行为这份答案文档里有一段很容易被一带而过的表述关系模型由关系数据结构、关系操作集合和关系完整性约束三部分组成。很多人把这句话当作背诵条目但实际上关系数据库后来所有让人困惑的“玄学”——为什么有的视图不能更新、为什么外码可以取空值、为什么一条 DELETE 会被数据库拒绝——都能从这三个组成部分的分工推出来。《数据库系统概论第5版》的配套答案正好提供了大量这样的场景同样是“至少用了供应商S1的全部零件”关系代数用一条除运算就写完了SQL 却要拆成两步子查询。差异不是表达能力高下而是数据模型与查询语言的分工方式不同。搞懂这个层面的机制再回头看答案每一道题都是在验证同一个内核结构定义边界操作决定能力约束守住底线。2. 关系代数五种基本运算为什么“够用”除运算到底在算什么2.1 完备性并、差、笛卡尔积、投影、选择为什么恰好是五种答案第 2 章第 8 题给出了一个结论并、差、笛卡尔积、投影和选择是五种基本运算交、连接和除都可由它们表达。这个结论的真正分量在于“基本”二字——它不是“常用的五种”而是“最小完备集”。任何一种关系代数操作无论多复杂最终都能拆成这五个动作的组合反过来这五种运算中无论去掉哪一个都会出现某种表达不了的操作。我一般会这么理解它们的分工。并和差负责处理两个关系之间的“集合关系”并是把元组累加差是从一个关系里去掉另一个关系也有的元组。笛卡尔积负责把两个关系的元组两两配对是连接运算的地基。投影是垂直切分只保留你关心的列选择是水平切分只保留满足条件的行。关系数据库里的一切查询本质上就干两件事切列、切行、重新组合。五种运算各管一个方向谁也不可替代——投影切不掉行选择删不掉列没有笛卡尔积就拼不出多表数据没有差就构造不出“不包含”这种否定语义。答案里用 SPJ 数据库演示的四个查询每一种都能映射到这五个动作上。比如“求供应工程 J1 零件的供应商号码”先对 SPJ 做选择σJnoJ1再做投影πSno。这是最基础的两步。而“求没有使用城市T供应商生产的红色零件的工程号”答案写成πJno(SPJ) - πJno(σcity城市T∧Color红(S∞SPJ∞P))这里“没有使用”四个字落到底层就是一个差运算。正因为差运算是基本运算SQL 里才能用 NOT IN、NOT EXISTS 这类结构去表达它。学关系代数时如果只记符号、不琢磨“为什么差运算如此核心”等遇到 NOT IN 与 NULL 纠缠的诡异查询结果时就很难追到根上。2.2 除运算从“全组合差集”反推出的语义五种基本运算给齐了除运算却是理解门槛最高的一个。答案第 6 题里“求至少用了供应商 S1 所供应的全部零件的工程号 JNO”关系代数写法是πJno,Pno(SPJ) ÷ πPno(σSnoS1(SPJ))从符号上看这是“除”但它在语义上问的是哪些工程号对 S1 供应的每一个零件都有使用记录。这不是数值相除而是集合的包含判断。要理解它得先理解答案文档里没有展开的数学内核R ÷ S πX(R) - πX((πX(R) × S) - R)其中 X 是 R 中不属于 S 的属性集合。拆开看这个公式在干一件很巧妙的事先用 πX(R) 取出所有工程号再拿这些工程号去和 S 做笛卡尔积——相当于“假设每个工程都用过 S1 的全部零件”构造一个理想的全组合表然后用这个理想表减掉真实表 R差出来的就是“某些工程缺了某些零件”最后用 πX(R) 再减一次把缺零件的工程号全部剔除剩下的就是零件齐活的工程。这套“两个差夹一个笛卡尔积”的结构就是除运算的全部秘密。它很反直觉的地方在于除法不是从 R 里“除掉”什么而是先构造一个完整的世界再和现实比对把对不上的筛掉。理解到这一层你就能看懂为什么答案里那道题的 SQL 要分两步写——先查 S1 供应了哪些零件再查哪个工程同时用了这些零件。SQL 不是表达能力不够而是它把除运算的语义显式地拆成了“构造零件集合”和“逐一比对工程”两个阶段。2.3 SPJ 例题对照关系代数一行SQL 两段差异在哪里把同一道题用两种语言各写一遍差异就会显形。关系代数里“至少用了 S1 全部零件”是整体性的集合判断一次除运算概念上是原子的SQL 里没有直接的除法操作符答案给出的做法是-- 第一步找出 S1 供应的全部零件号 SELECT DIST PNO FROM SPJ WHERE SNOS1; -- 结果是 (P1, P2) -- 第二步找出同时使用了 P1 和 P2 的工程号 SELECT JNO FROM SPJ WHERE PNOP1 AND JNO IN (SELECT JNO FROM SPJ WHERE PNOP2);这段 SQL 的每一步都很直观第一条语句定下“全部零件”这个集合第二条语句用 IN 嵌套表达“同时满足两个条件”。如果把零件扩充到三个、四个SQL 里的 AND 条件也要跟着加长而关系代数的除运算写法一字不变。这就是数据模型层操作与语言层操作的差别关系代数把“对全部值成立”抽象成一个整体操作SQL 选择把它翻译成逐值比对。答案里还留了一个细节值得注意它特意注明“由于某个桌面数据库不允许子查询嵌套太深将查询分为两步”。这提醒我们关系代数讨论的是逻辑等价性而真实数据库还要受语法深度、执行计划、优化器能力的限制。逻辑上一条式子能表达的东西物理执行时可能被拆成多条语句——这不叫缺陷叫工程现实。学习时把两种表达都吃透将来写复杂查询时你才能判断是该信优化器还是该自己手动拆查询。3. SQL 与视图非过程化语言为什么需要一个“虚表”来补位3.1 高度非过程化把“怎么做”交给优化器是有代价的答案第 3 章第 1 题对 SQL 特点的概括里“高度非过程化”最容易被人滑过去。它说的是用户只需声明“做什么”不需要声明“怎么做”存取路径由系统自动选择。这句话翻译成机制就是SQL 的背后站着一个查询优化器它负责把声明式的 SELECT 翻译成具体的连接顺序、索引选择、扫描方式。代价在哪里优化器不是万能的。当你写一条多表连接查询时优化器要在连接顺序上做选择先连接哪两个表、用哪一列的索引、是否走嵌套循环还是哈希连接。答案文档里所有 SELECT 语句都默认了“系统会自己选对”但实际问题里一张表的统计信息过期、一个连接列上没有索引都可能导致优化器选出一条慢几个数量级的执行路径。这就是为什么程序员要学执行计划、要会看 EXPLAIN——SQL 的非过程化是给用户的承诺不是给数据库的免责声明。答案里有不少这样的例子比如“找出使用工程X的零件代码及其数量”这类多表查询SELECT 语句里只写表名和连接条件完全不涉及“先扫描哪张表”“用不用索引”。这在教学场景里是简洁的在真实系统里一张百万行的 SPJ 表就足以让不带索引的笛卡尔积连接慢到不可接受。理解非过程化本质上是理解“语言把控制权收走了一部分你得用索引和统计信息把控制权拿回来”。3.2 视图是虚表数据库只存定义不存数据答案第 3 章第 6 题对视图的定义说得很干净视图是从一个或几个基本表导出的表本身不独立存储在数据库中是一个虚表。这句话的机制含义是CREATE VIEW 语句执行时数据库做的事情只是把这条 SELECT 语句存进数据字典不会像 CREATE TABLE 那样分配存储页、建行存储结构。当我执行 SELECT * FROM VSP 时数据库拿到的是视图名然后去数据字典里查出 VSP 对应的那条 SELECT 定义把这条定义和我的查询合并、重写再对基本表执行。这个展开过程叫视图消解。答案里“在视图上可以再定义视图”这句话顺着这个机制就很好懂——既然视图只是存了一段查询文本那么在视图上再建视图不过是把两段查询文本嵌套起来。这个设计带来一个直接收益视图的控制粒度比表细。答案第 3 章第 7 题列举的视图优点里“对机密数据提供安全保护”和“提供一定程度的逻辑独立性”都源于同一件事——视图是逻辑层的东西可以随时改定义而不动底层表。基表结构变了视图定义重写一下应用程序的查询就可能不用改。这种“逻辑与物理分离”的思想和索引、外模式的设计一脉相承。3.3 不可更新视图为什么聚合视图不能直接 UPDATE答案第 8 题问哪类视图可更新、哪类不可更新结论里有句话是关键对视图的更新最终要转换为对基本表的更新如果这种转换不能唯一有意义地完成视图就不可更新。看一个典型例子。假如建了这样一个视图统计每个部门的工资情况CREATE VIEW DeptSalary AS SELECT Deptno, MAX(Sal) AS MaxSal, AVG(Sal) AS AvgSal FROM EMP GROUP BY Deptno;现在执行 UPDATE DeptSalary SET MaxSal 8000 WHERE Deptno 10数据库会懵住MaxSal 是 MAX(Sal) 聚合出来的值它不是 EMP 表里某个真实的列。要改 8000是改部门 10 里工资最高的那个人还是按比例缩放所有人的工资还是把这个部门的工资上限设成一个新列三种解释都“有意义”但数据库没法替你选所以干脆禁止更新。反观行列子集视图比如 CREATE VIEW ViewEmp AS SELECT Empno, Ename, Sal FROM EMP视图的行与基本表的行一一对应每行都能唯一映射回一条基本表记录更新就能可靠地转换过去。答案里说“基本表的行列子集视图一般是可更新的”背后的判定标准就是一个映射是否唯一。以后你写代码遇到“视图不可更新”的报错先别骂数据库先问自己一句这条 UPDATE 能毫无歧义地翻译成对哪一行、哪一列基本表数据的修改翻译不出来就该直接操作基本表而不是跟视图较劲。4. 数据库安全性DAC、MAC、审计与视图组成的纵深防线4.1 自主存取控制GRANT/REVOKE 背后的授权传播链答案第 4 章介绍了实现数据库安全性控制的常用方法其中自主存取控制DAC是 SQL 语言直接内置的一套机制它的核心语句只有两个GRANT 和 REVOKE。这套机制有趣的地方在于权限本身也是一种可以被传递的数据。看这段授权语句GRANT SELECT, INSERT ON EMP TO U1 WITH GRANT OPTION;WITH GRANT OPTION 的含义是U1 不仅获得了 SELECT 和 INSERT 权限还获得了“把这些权限再转授给别人”的资格。数据库在执行这条语句时会在权限表里记两条U1 对 EMP 的 SELECT/INSERT 权限以及 U1 对这俩权限的“转授标记”。此后 U1 执行 GRANT SELECT ON EMP TO U2U2 的权限就有了一个来源链U2 ← U1 ← DBA。这条链最值得警惕的是回收时的行为。答案里 REVOKE INSERT ON EMP FROM U1 CASCADE 的 CASCADE 字样就是在说收回 U1 权限的同时把这条链上所有由 U1 转授出去的权限一并收回。如果回收时忘了写 CASCADE或者数据库默认行为是 RESTRICT那么 U1 被收回权限后U2 手里可能还拿着 U1 当年给的权限形成权限残留。真实系统里的权限回收事故一大半出在这个传播链没理清上。答案里还有一个容易漏看的点GRANT SELECT ON EMP WHEN USER()NAME TO ALL 这种行级授权。它在机制上不是简单地记“谁能读这张表”而是把“只能读自己那行”的约束折叠进了权限定义。这类需求在执行层面往往要借助视图或行级安全策略来实现因为标准的 GRANT 只能控制到表和列控制不到具体行。4.2 强制存取控制让敏感度标记替用户做决定自主存取控制的缺陷在于权限的授予由用户自己决定数据的拥有者说了算。可如果拥有者本人就是一个被植入恶意程序的账号DAC 根本拦不住。答案里讲的强制存取控制MAC就是针对这个问题设计的——它不让用户“自主”而是给数据和用户各贴一个敏感度标记存取是否允许由 DBMS 按标记比较的结果强制决定。MAC 里的两个概念很清晰主体是活动实体包括用户和代表用户的进程主体身上的标记叫许可证级别客体是被动的数据对象比如表、视图、索引客体身上的标记叫密级。系统规则是只有当主体的许可证级别不低于客体的密级时主体才能读该客体写操作的规则更严格一般要求主体级别等于客体级别防止高密级主体把机密数据写进低密级客体。这套机制和 DAC 的本质区别在于“谁在做决定”。DAC 里是对象的所有者在授权MAC 里是系统在按标记强制比较。答案把它描述为 C2 级与 B1 级安全标准的差别——C2 靠用户声明和检查B1 靠系统强制标记。对这个机制有个常见的误解以为把某个用户标记为“机密”就能随意读机密数据。实际上标记解决的是“数据流向”问题高密级数据不能流向低密级用户但高密级用户也不能把数据降到低密级文件里带走——那个是写规则管的。4.3 审计与视图事后取证与事前隐藏审计机制的定位和 DAC/MAC 完全不同。DAC 和 MAC 试图在操作发生前拦住非法访问审计则在操作发生的同时把用户、时间、操作内容全部追加进审计日志。答案说得很直白任何安全措施都不是完美无缺的蓄意破坏的人总可能存在审计的价值在于事后可以重现导致当前状态的一系列事件。这块审计日志本质上是个黑匣子它平时只记录不干预出了事才被 DBA 拿出来回放。设计审计功能时有个常见矛盾——审计粒度越细越安全但日志量增长极快。每一条 SELECT 都记录高峰期可能每秒产生上千条日志。所以真实系统的做法往往是分级审计只对敏感表、敏感操作开审计普通查询不开或者只记录失败的登录与越权尝试不记录正常访问。视图在安全体系里扮演的角色则是“事前隐藏”。答案第 4 章有一段经典例子给每个职工查自己工资的权利但看不到别人的工资靠的就是创建视图只暴露本职工号的行再把这视图的 SELECT 权限授出去。这个机制的价值在于它把“数据过滤”做在了查询发生之前——无权用户连数据都看不见而不是看见了再被拦截。5. 避坑完整性约束与 SQL 查询里最容易翻车的五个细节5.1 外码空值、删除策略与授权回收现象按答案第 2 章的说法外码属性的值“可以为空”于是有同学写表结构时把外码一律允许 NULL结果插入数据时出现大量语义上不该为空、却成功插入的悬空记录。原因外码为空有一个隐含前提——该外码不是主属性。答案原文的完整表述是“属性 F 本身不是主属性则可以取空值否则不能取空值。”如果 F 同时是主码的一部分空值会直接违反实体完整性因为主属性不能为空。外码之所以允许空语义是“参照关系尚未确定”比如一个新入职的员工还没分配部门外码 DEPTNO 为空是合理的但如果一个关系用 (学号, 课程号) 做联合主码课程号同时又参照课程表那课程号就既不能为空、又必须参照有效。解决建表前先问两个问题这个外码参与主码吗参与则不允许 NULL不参与则要区分“暂时未分配”和“永远没有”两种语义前者保留 NULL后者要考虑是否用特殊值代替。现象删除父表中的一个部门元组数据库直接报错提示违反参照完整性。原因默认情况下外键约束的违约处理是拒绝执行。答案第 5 章讲了三种策略——级联删除、受限删除、置空值删除——但前提是你在定义外键时显式声明了 ON DELETE CASCADE 或 ON DELETE SET NULL否则数据库默认按受限处理。解决按业务语义选择策略。订单明细相对订单是强依赖删订单就该级联删明细员工相对部门是弱依赖部门撤销时员工应该保留、外码置空而不是跟着被删。答案第 3 章第 10 题“先删除 S 中的记录再删除 SPJ 中的对应记录”其实就是手工模拟级联删除——先删父表再删子表会违反外键顺序反过来的原因就在这里。从那以后我建外键时都会在注释里写清依赖方向和删除策略。5.2 NOT IN 遇上 NULL结果集秒变空的经典翻车现象用 NOT IN 写“找出没有使用城市T产品的工程号”子查询没问题可结果集却是空的明明有几条工程记录从没使用过任何城市T产品。原因NOT IN 的语义等价于“不等于子查询结果中的每一个值”。如果子查询结果里含有 NULL那么对任何一行来说判断“X NOT IN (集合)”会变成对 NULL 的比较而 SQL 三值逻辑里与 NULL 比较的结果是 UNKNOWN——WHERE 子句只保留 TRUE 的行UNKNOWN 的行全部被滤掉。答案第 3 章第 5 题第 (7) 问也提到了这个坑特别区分了两种写法用 NOT IN 在 JNO 唯一或不唯一时都适用而city 城市T的写法只在 JNO 唯一时正确。解决优先用 NOT EXISTS 替代 NOT IN。NOT EXISTS 是逐行判断“子查询里存在这样的行吗”NULL 不会影响判定语义更稳。或者确保子查询的返回列不含 NULL——但这要求你对数据有十足把握一旦将来有脏数据混入查询就悄悄变空。这个坑的隐蔽性在于它不报错只给错结果而且在数据量小、没有 NULL 的行上测试时完全正常。5.3 对聚合视图执行 UPDATE数据库不是不给改是根本无从下手现象对统计类视图比如每个部门的最高工资执行 UPDATE报“视图不可更新”。原因这类视图的行不是基本表的一行而是一组行的聚合结果。UPDATE 一个视图行数据库需要找出该改哪些基本表行但聚合计算已经抹掉了一对一的映射关系。答案的表述是“不能唯一有意义地转换成对基本表的更新”——MAX、AVG 这类聚合函数的结果没有对应的存储位置数据库连“改动目标”都确定不了只能拒绝。解决既然视图更新最终要翻译成基本表更新就别试图改视图。想要改数据直接对基本表发 UPDATE条件写在 WHERE 里视图留着做查询展示不要把视图当成可写的表来设计。判断一个视图能否更新有一条快捷准则它是不是由单个基本表的行列子集构成是则大概率可更新涉及连接、聚合、DISTINCT、GROUP BY 的基本都不可更新。这条准则能帮你省掉大量调试时间。6. 范式理论从“消除异常”到“接受冗余”的设计取舍范式理论在答案里以定义和证明的面目出现但它本质上是给数据设计者的一台“异常探测器”。函数依赖描述的是属性之间的决定关系部分函数依赖和传递函数依赖的本质都一样——码不能完整地决定每一个非主属性。2NF 消除非主属性对码的部分依赖3NF 消除非主属性对码的传递依赖BCNF 把决定因素的门槛抬到“必含码”。答案里“任何一个二目关系都属于 BCNF”的证明是理解这套逻辑的钥匙二目关系里要么只有一个决定因素这个决定因素天然就是码要么 A1 和 A2 互相决定两者就都是码——决定因素必含码所以 BCNF 必然成立。但把范式当终极目标是设计上最大的误区。我见过一个经典的过度设计为了把一张订单表拆到 BCNF把一个订单的主信息、明细信息、商品快照拆成三张表外加一个商品历史表最后查询详情要连接四次性能掉了一个数量级。范式消除的是更新异常代价是查询时多表连接。现实业务里大量场景是读多写少商品名称这种列即使偶尔冗余它的更新频率也低到可以忽略——反范式化留一个冗余列换来的是少一次 JOIN。范式和反范式不是对错之分而是同一枚硬币的两面。从那以后我每接到一个建表需求都强制自己先写一遍函数依赖集标出哪些是部分依赖、哪些是传递依赖再做决定核心业务表按 3NF 起步能到 BCNF 最好查询密集的维度表、统计表主动引入受控冗余并在注释里写清冗余的理由和数据一致性方案。规范让你知道哪里会出问题取舍让你知道哪些问题值得接受——希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑