Skip to content

一、数据库基本概念与关系模型(补-01 ~ 补-05) ​

补-01 ​

数据库系统的三级模式结构中,「外模式 / 模式映射」与「模式 / 内模式映射」分别保证的是( )。 A. 物理独立性;逻辑独立性 B. 逻辑独立性;逻辑独立性 C. 逻辑独立性;物理独立性 D. 两者都与数据独立性无关

答案:C

📌 考点定位: 数据库基本概念——三级模式与两级映射,对应国网考纲第 10 条,国企笔试常考。

【结论】 选 C。外模式 / 模式映射保证逻辑独立性,模式 / 内模式映射保证物理独立性;其本质是「哪一层发生了变化,就让映射去吸收这个变化,使相邻的另一层不受影响」。

【逐项辨析】

  • A 物理独立性;逻辑独立性:错。把两级映射的职责整体说反了——外层映射(外模式 / 模式)管的是逻辑独立性,内层映射(模式 / 内模式)管的才是物理独立性。
  • B 逻辑独立性;逻辑独立性:错。把「模式 / 内模式映射」也写成逻辑独立性,等于抹掉了物理独立性这一层,两级映射失去分工。
  • C 逻辑独立性;物理独立性:正确。 外模式 / 模式映射位于外模式与模式之间,模式改变时调整它即可保持外模式不变,故保证逻辑独立性;模式 / 内模式映射位于模式与内模式之间,内模式改变时调整它即可保持模式不变,故保证物理独立性。
  • D 两者都与数据独立性无关:错。两级映射恰恰是数据独立性的实现机制,说「无关」是错的。

【知识点】 三级模式是对数据的三个抽象级别,两级映射是相邻级别之间的转换规则,二者共同构成数据独立性的保障机制。

层次名称描述对象面向对象数量
外层外模式(子模式 / 用户模式)用户能看到的局部数据视图用户 / 应用程序多个
中层模式(逻辑模式)全体数据的全局逻辑结构数据库设计人员一个
内层内模式(存储模式)数据的物理存储结构DBMS一个

两级映射与两类独立性的推导逻辑是「变化 → 调整映射 → 上层保持不变」:

text
模式改变   → 改外模式 / 模式映射 → 外模式不变 → 应用程序不变 → 逻辑独立性
内模式改变 → 改模式 / 内模式映射 → 模式不变   → 外模式不变   → 物理独立性

判断的关键在于独立性保护的是谁:逻辑独立性保护的是「外模式(进而应用程序)不受模式变化影响」,物理独立性保护的是「模式不受内模式变化影响」。因此可以概括为「外层映射管逻辑、内层映射管物理」。

补充两点:外模式 / 模式映射可以有多个(多个外模式对应同一个模式),模式 / 内模式映射只有一个(一个模式对应一个内模式);两级映射均由 DBMS 负责维护,用户程序不感知。

【记忆锚点】 「外逻辑、内物理」——越靠外的映射管逻辑独立性,越靠内的映射管物理独立性。

【易混对比】

  • 换个问法:「当数据库的存储结构改变时,DBA 只需修改模式 / 内模式映射,从而保证数据的( )不变」→ 答物理独立性。注意题目问的是「保证谁不变」,与「哪个映射保证哪种独立性」是同一考点的两种问法。
  • 与三级模式的定义连考:问「一个数据库有几个外模式 / 模式 / 内模式」→ 外模式多个、模式一个、内模式一个。
  • 易混概念:逻辑独立性 ↔ 物理独立性。前者对应模式变化,后者对应内模式变化,两者不可互换。

【自测】

某数据库把数据文件的存储组织方式由顺序组织改为散列组织,DBA 只需修改模式 / 内模式映射,无需改动模式与外模式。这体现了( )。 A. 逻辑独立性 B. 物理独立性 C. 参照完整性 D. 实体完整性 答:B。改变的是内模式(物理存储结构),被保护不变的是模式,属物理独立性。(关联补-01 同一考点的反向问法)

【选项误解】

  • A 误解来源:把「外→内」的顺序误记成「物理→逻辑」。常见心理是「越靠外越物理(用户直接摸数据)」,实际恰恰相反——外层映射保护的是逻辑视图。
  • B 误解来源:认为「两级映射都是为了逻辑独立」,把物理独立性这一层抹掉。多出现在只背「数据独立性」四个字、未区分「逻辑/物理」的考生。
  • C 正确项:外模式/模式映射管逻辑,模式/内模式映射管物理。
  • D 误解来源:把「映射」当成可有可无的实现细节。实际上两级映射是数据独立性的实现机制,教材原话即「通过两级映像功能保证数据的逻辑独立性与物理独立性」。

【知识关联】

  • 主库关联:主库 60 题几乎全是 MySQL/Redis 产品机制,三级模式属教材原理层空缺,主库无直接对应题;可对照补-10(视图=外模式的实现载体)。
  • 国网/408:国网考纲第 10 条「数据库基本概念」直接命中;408 数据库部分常考「三级模式/两级映射/数据独立性」填空与选择,属送分题。
  • 面试追问:① 逻辑独立性在生产中如何体现?→ 基表加列/改列时,通过重定义视图(外模式)让旧应用 SQL 不改仍能跑。② 物理独立性如何体现?→ 换存储引擎、改行格式、调整页大小时只改内模式映射,SQL 层无感。③ 视图算外模式吗?→ 视图是外模式的常见实现手段之一,外模式还可包括用户级权限可见范围。

【拓展延伸】

  • 变式问法:①「模式改变时只需修改外模式/模式映射,保证了数据的(逻辑独立性)」;②「内模式改变时模式不变,体现的是(物理独立性)」;③「一个数据库中模式/内模式各有几个?」→ 各一个,外模式可有多个。
  • 工程参数/命令:MySQL 中 CREATE VIEW / ALTER VIEW 即在维护「外模式—模式」映射;ALTER TABLE ... ENGINE=InnoDB、调整 innodb_page_size(需重建实例)属内模式变化;information_schema.TABLES / SHOW CREATE VIEW 可查看视图定义(外模式)。Oracle 的 synonym、DB link 也承担类似外模式角色。
  • 工程直觉:ORM 框架 + 数据库视图常被联合用来做「逻辑独立性」的工程化落地——表结构演进时优先改视图,少改业务代码。

补-02 ​

在一个关系模式中,能唯一标识元组、且不含任何多余属性的属性组,称为( )。 A. 候选键(候选码) B. 超键 C. 主键 D. 外键

答案:A

📌 考点定位: 关系数据库基本理论——键的概念,对应国网考纲第 11 条;也是牛客笔试选择题「关系的键与完整性」的核心。

【结论】 选 A。能唯一标识元组且不含任何多余属性的属性组是候选键,其判定标准是「极小性」,题干中的「不含任何多余属性」正是极小性的表述。

【逐项辨析】

  • A 候选键(候选码):正确。 候选键 = 超键 + 极小性,即不含多余属性的超键,与题干「能唯一标识元组、且不含任何多余属性」逐字对应。
  • B 超键:错。超键只要求「能唯一标识元组」,不要求极小性,允许含多余属性,与题干后半句「不含任何多余属性」冲突。
  • C 主键:错。主键是从多个候选键中人为选定的那一个,题干描述的是「所有满足极小性的键」,未涉及选定行为,用主键来定义不够严谨。
  • D 外键:错。外键是本关系中引用另一关系主键(或候选键)的属性组,作用是表达关系之间的联系,并不唯一标识本关系的元组。

【知识点】 键的四个概念是层层递进、逐级加约束的关系:

概念定义唯一标识元组要求极小性人为指定
超键能唯一标识元组的属性组是否否
候选键不含多余属性的超键(极小超键)是是否
主键从候选键中选定一个是是是
外键引用另一关系主键 / 候选键的属性组否—否

包含关系可写成:候选键 ⊆ 超键,且主键 ∈ 候选键。

候选键的求解方法(属性闭包法):

  1. 把只在函数依赖左边出现的属性记为 L 类,只在右边出现的记为 R 类,两边都出现的记为 LR 类,都不出现的记为 N 类。
  2. 必然属于候选键的属性是 L 类与 N 类。
  3. 对 L + N 求属性闭包,若闭包已包含全部属性,则该组合即为候选键;否则依次加入 LR 类属性再求闭包,能推出全部属性的极小组合即为候选键。

注意:候选键可以由单个属性构成,也可以由多个属性联合构成(复合候选键);一个关系可以有多个候选键,但只能有一个主键。

【完整推导·属性闭包法】 例:R(A,B,C,D),FD = {A→B, B→C, A→D}。

  1. 出现位置:A 在左;B 左右都有(LR);C 只在右(R);D 只在右(R)。
  2. L={A},N=∅,R={C,D},LR={B}。先试 L:A⁺。
  3. A⁺:由 A→B 得 {A,B};由 B→C 得 {A,B,C};由 A→D 得 {A,B,C,D} = 全部属性。
  4. A 已是候选键(单属性、极小);任何真子集 ∅ 推不出 A,满足极小性。
  5. AB⁺、AC⁺、ABCD⁺ 虽也能唯一标识,但含多余属性 → 只是超键,不是候选键。 结论:候选键 = {A},若设计表则主键可取 A。

【记忆锚点】 「超键不极小,候选键极小,主键是候选键里挑出来的那一个」。

【易混对比】

  • 换个问法:「能唯一标识元组但可以含多余属性的属性组」→ 答超键;「能唯一标识元组且一个属性都不能少」→ 答候选键。两者的分界线就是「极小性」三个字。
  • 与实体完整性连考:主键的取值约束是「非空 + 唯一」;候选键只要求唯一,在 SQL 中通常以 UNIQUE 约束实现,允许多个 NULL,这与主键的 PRIMARY KEY 不同。
  • 易混概念:外键不属于键的「强弱」等级。候选键、超键、主键都是「标识本关系元组」,而外键是「引用其他关系」,二者不在同一分类维度上。

【自测】

关系模式 R(A, B, C, D) 的函数依赖集为 {A → B, B → C, A → D},则 R 的候选键是( )。 A. A B. AB C. AC D. ABCD 答:A。A 的闭包为 {A, B, C, D},已包含全部属性且为单属性,故 A 是候选键;B 推不出 A 与 D,AB、AC、ABCD 都含多余属性,属超键而非候选键。(关联补-02 候选键的极小性)

【选项误解】

  • A 正确项:候选键 = 超键 + 极小性,与「不含任何多余属性」逐字对应。
  • B 误解来源:只记住「能唯一标识元组」,漏掉后半句「不含多余属性」。超键允许含多余属性(如 ABCD 在单键 A 已够时仍是超键)。
  • C 误解来源:把「候选」与「主」混为一谈。主键是人为从候选键中选定的一个,题干描述的是客观满足极小性的那一类键,不是选定结果。
  • D 误解来源:把「键」字一概理解为主键/外键。外键解决的是关系之间的引用,不唯一标识本关系元组,与题干无关。

【知识关联】

  • 主库关联:M01(聚簇索引叶子=整行,主键即聚簇键)、M22(为何建议自增主键)、M23(UUID 作主键的缺点)。候选键/主键是这些题的理论前置。
  • 国网/408:国网考纲第 11 条「关系数据库基本理论」;408 常考「求候选键」计算题(属性闭包法)。
  • 面试追问:① 一个表能否没有主键?→ 逻辑上可以,但 InnoDB 会隐式生成 6 字节 ROWID 作聚簇索引键。② 候选键与唯一索引什么关系?→ 候选键在 SQL 中常以多个 UNIQUE NOT NULL 实现,但 UNIQUE 允许多 NULL 时不完全等价。③ 主键为何只能一个、候选键可多个?→ 主键是设计决策(选定一个),候选键是语义推导结果。

【拓展延伸】

  • 变式问法:①「不含多余属性的超键」→ 候选键;②「从候选键中选定的那个」→ 主键;③「能唯一标识但可含多余属性」→ 超键;④「R(A,B,C,D),FD={AB→C, C→D, A→B},求候选键」→ 闭包法实算:只在左边出现的是 A(B/C/D 均出现在右侧),A⁺:A→(A→B)AB→(AB→C)ABC→(C→D)ABCD = 全属性,故候选键唯一为 {A};AB 是超键但含多余属性,不是候选键。
  • 工程参数/命令:SHOW KEYS FROM t WHERE Key_name='PRIMARY';CREATE TABLE ... PRIMARY KEY (a,b);UNIQUE KEY 与主键差异:唯一键允许多 NULL、可有多个,主键非空唯一且仅一个。InnoDB 中主键长度会进入所有二级索引叶子,故主键宜短(自增 BIGINT 优于 UUID varchar(36))。
  • 属性闭包速算:L(只在左出现)必在候选键中;N(从不出现)必在候选键中;R(只在右出现)必不在;对 L+N 求闭包,不够再补 LR。

补-03 ​

下列约束中,属于「参照完整性」的是( )。 A. 主键的值不能为空且不能重复 B. 学生的年龄必须取值在 16~60 之间 C. 性别只能取「男」或「女」 D. 外键的取值必须是被参照关系中某个主键的值,或者取空值

答案:D

📌 考点定位: 关系数据库基本理论——三类完整性约束,对应国网考纲第 11 条,国企 / 银行笔试高频。

【结论】 选 D。参照完整性约束的是外键与被参照关系主键之间的取值对应关系,其标准表述正是「外键取值必须是被参照关系中某个主键的值,或者取空值」。

【逐项辨析】

  • A 主键的值不能为空且不能重复:错。这是实体完整性,约束对象是主键,与参照关系无关。
  • B 学生的年龄必须取值在 16~60 之间:错。这是针对具体业务语义的用户定义完整性(域约束),不涉及关系之间的引用。
  • C 性别只能取「男」或「女」:错。同样是用户定义完整性中的取值域约束,属业务规则。
  • D 外键的取值必须是被参照关系中某个主键的值,或者取空值:正确。 前半句保证「被引用的目标真实存在」,后半句允许「联系尚未确定」,二者合起来正是参照完整性的完整定义。

【知识点】 关系模型有三类完整性约束,作用对象与检查方式各不相同:

约束类型约束对象核心规则典型实现
实体完整性主键非空且唯一PRIMARY KEY
参照完整性外键取值等于被参照关系某主键值,或为 NULLFOREIGN KEY ... REFERENCES
用户定义完整性具体属性 / 业务规则取值范围、默认值、非空、CHECK 等CHECK、DEFAULT、NOT NULL

关于参照完整性,有三点必须掌握:

  1. 允许为空的前提:外键为 NULL 表示「该元组尚未建立联系」;若外键列同时被声明为 NOT NULL,则不允许取空值。
  2. 违约处理:当对被参照关系执行删除或更新导致外键失去引用目标时,DBMS 按声明的方式处理——RESTRICT / NO ACTION(拒绝)、CASCADE(级联删除或更新)、SET NULL(置空)。
  3. 检查时机:在默认的立即检查(NOT DEFERRABLE)模式下,约束在语句执行结束时校验,因此先插入被参照元组、再插入参照元组的顺序是安全的。

【记忆锚点】 「主键非空唯一 = 实体,外键有效 = 参照,业务规则 = 用户定义」。

【易混对比】

  • 换个问法:「CHECK(age BETWEEN 16 AND 60) 属于哪类完整性?」→ 答用户定义完整性,不是参照完整性。只要题干出现「具体数值范围、取值集合、默认值」,一律归用户定义完整性。
  • 与补-02 连考:候选键 / 主键属「键的概念」,实体完整性是「主键的取值规则」,两者常在同一道题里交叉出现。
  • 易混概念:参照完整性 ↔ 外键。参照完整性是规则,外键是实现该规则的载体;准确说法是「外键的取值约束属于参照完整性」。

【自测】

学生表 Student(Sno, Sname) 与选课表 SC(Sno, Cno, Grade) 中,SC.Sno 是外键。现要在 SC 中插入一条 Sno = '2025001' 的记录,而 Student 表中不存在该学号,DBMS 拒绝该插入,依据的是( )。 答:参照完整性。外键取值找不到对应的被参照主键值;若该外键列允许为空,也可改为插入 NULL 以表示联系待定。(关联补-03 三类完整性约束)

【选项误解】

  • A 误解来源:把「主键非空唯一」误当成参照完整性。主键规则约束的是本关系自身,属实体完整性;参照完整性约束的是外键与被参照表之间的取值对应。
  • B 误解来源:看到「必须」就往完整性上靠,但年龄范围是业务规则(用户定义完整性/域约束),不涉及关系引用。
  • C 误解来源:同 B,取值集合约束属用户定义完整性(CHECK)。
  • D 正确项:「等于被参照关系某主键值,或取空」是参照完整性的标准定义(Codd/教材原文口径)。

【知识关联】

  • 主库关联:M31(SQL 注入——参数校验属应用层完整性延伸);主库未覆盖外键/完整性,本题补原理缺口。
  • 国网/408:国网考纲第 11 条;408 数据库大题常考「指出违反哪类完整性」。
  • 面试追问:① 生产上还建议用数据库外键吗?→ 高并发互联网常逻辑外键(应用层校验 + 异步对账),因 FK 会带来锁竞争与级联风险;国企/银行核心库仍常用物理外键。② 外键为何常允许 NULL?→ 表示「联系尚未建立」,如订单尚未支付时 pay_id 可空。③ 删除被参照行时的四种策略?→ RESTRICT/NO ACTION(拒绝)、CASCADE(级联)、SET NULL(置空)。

【拓展延伸】

  • 变式问法:①「FOREIGN KEY (dept_id) REFERENCES dept(id) ON DELETE SET NULL 体现哪类完整性?」→ 参照完整性;②「CHECK (score BETWEEN 0 AND 100)」→ 用户定义完整性;③「插入 SC 时 Student 无此学号被拒」→ 参照完整性违约。
  • 工程参数/命令:
sql
ALTER TABLE sc ADD CONSTRAINT fk_sc_sno
  FOREIGN KEY (sno) REFERENCES student(sno)
  ON DELETE RESTRICT ON UPDATE CASCADE;
-- MySQL 查看外键
SELECT * FROM information_schema.TABLE_CONSTRAINTS
 WHERE CONSTRAINT_TYPE='FOREIGN KEY';
  • 级联风险:ON DELETE CASCADE 在父表误删时可瞬间清空子表,生产上更常用 RESTRICT + 应用层确认。

补-04 ​

关系代数中,「按条件筛选元组(行)」与「选取若干属性列」分别对应的运算符号是( )。 A. 投影 π;选择 σ B. 选择 σ;投影 π C. 连接 ⋈;除法 ÷ D. 并 ∪;差 −

答案:B

📌 考点定位: 关系数据库基本理论——关系代数运算,对应国网考纲第 11 条,国企笔试高频。

【结论】 选 B。选择 σ 按条件筛选行(元组),投影 π 选取指定列(属性);题干问的「筛选元组」对应 σ,「选取属性列」对应 π。

【逐项辨析】

  • A 投影 π;选择 σ:错。把两者的符号与含义整体颠倒——π 是投影(选列),σ 是选择(选行),题干后半句「选取若干属性列」应对应 π 而不是 σ。
  • B 选择 σ;投影 π:正确。 σ 后面跟条件表达式,作用于元组(行);π 后面跟属性名列表,作用于属性(列)。
  • C 连接 ⋈;除法 ÷:错。连接是按条件把两个关系组合成一个新关系,除法用于处理「对所有 / 全部」这类语义,二者都与题干描述的「单表筛选行与列」无关。
  • D 并 ∪;差 −:错。并、差是集合运算,要求参与运算的两个关系同构(属性个数与对应域相同),题干只涉及一个关系的行列筛选。

【知识点】 关系代数以关系为运算对象,按「能否由其他运算导出」分为基本运算与导出运算:

分类运算符号作用
基本运算并∪两个同构关系的元组合并
基本运算差−属于前者而不属于后者的元组
基本运算笛卡尔积×两关系元组的所有组合
基本运算选择σ按条件筛选行
基本运算投影π选取列并去除重复元组
导出运算交∩同属两个关系的元组
导出运算连接⋈按条件组合两个关系
导出运算除法÷处理「包含全部」语义

两个必须记住的细节:

  1. 投影会去重。π 的运算结果是一个集合,若选取列后出现完全相同的元组,会自动合并为一条。
  2. 选择的条件表达式可用 ∧(与)、∨(或)、¬(非)组合,例如 σ_age>20 ∧ sex='男'(R)。

写法示例:

text
σ_age>20(R)          从 R 中选出年龄大于 20 的元组(行)
π_name,age(R)        从 R 中取出 name、age 两列(列)

【记忆锚点】 「σ 是 Selection 选行、π 是 Projection 选列」——首字母 S 对 Selection、P 对 Projection,一一对应不会记混。

【易混对比】

  • 换个问法:「π_Sname(σ_Sdept='CS'(Student)) 的含义是什么?」→ 先按系别筛选行,再取姓名列,即「计算机系所有学生的姓名」。注意运算顺序由内向外。
  • 与补-05 连考:自然连接 ⋈ 属导出运算,其元组数受公共属性匹配情况影响,常与笛卡尔积 × 放在一起考。
  • 易混概念:选择 σ ↔ 投影 π(行 vs 列)、并 ∪ ↔ 交 ∩ ↔ 差 −(集合运算,要求同构)、连接 ⋈ ↔ 除法 ÷(组合关系 vs 全部语义)。

【自测】

关系代数表达式 π_Sname(σ_Sage>20(Student)) 的运算含义是( )。 A. 所有学生姓名 B. 年龄大于 20 的学生姓名 C. 所有学生年龄 D. 年龄大于 20 的学生学号 答:B。内层 σ 先按年龄筛选行,外层 π 再取姓名列。(关联补-04 选择与投影的组合)

【选项误解】

  • A 误解来源:σ/π 符号与含义整体对调。常见记忆错误是「π 像筛子(选行)」,实际 π 是 Projection(投影=选列)。
  • B 正确项:σ=Selection 选行,π=Projection 选列。
  • C 误解来源:把连接/除法当成基础筛选运算。它们处理的是多关系,题干只描述单表行列筛选。
  • D 误解来源:并/差是集合运算,要求同构关系,与「选行列」无关。

【知识关联】

  • 主库关联:M19(EXPLAIN type 访问路径——优化器把选择 σ 下推为索引 range/ref)、M20(Using filesort——投影/排序阶段代价)、M04(覆盖索引——投影所需的列都在索引里,免回表)。
  • 国网/408:国网考纲第 11 条;408 可能直接给关系代数表达式要求译义或写表达式。
  • 面试追问:① 为什么投影会去重?→ 关系是集合,集合元素唯一,π 结果自动去重。② SQL 的 WHERE 对应关系代数什么运算?→ 选择 σ。③ SELECT 列清单对应什么?→ 投影 π(但 SQL 结果是包/多重集,理论上去重不自动发生,除非 DISTINCT)。

【拓展延伸】

  • 变式问法:①「π_Sname(σ_Sdept='CS'(Student))」→ 计算机系学生姓名;②「列出选择运算的符号与含义」→ σ,按条件选行;③「哪些基本运算?」→ ∪、−、×、σ、π(五种)。
  • 工程参数/命令:优化器常把 WHERE 条件(σ)下推到存储引擎(ICP,见补-25);SELECT 列裁剪(π)影响是否走覆盖索引——SELECT * 会破坏覆盖索引,迫使回表。EXPLAIN 中 Using index = 投影所需列全在索引。
  • 推导顺序:表达式由内向外算——先 σ 筛行,再 π 取列;写 SQL 时逻辑执行顺序也是 WHERE(σ)先于 SELECT 列清单(π)生效。

补-05 ​

设关系 R(A, B) 有 3 个元组,关系 S(B, C) 有 4 个元组,则 R 与 S 的「自然连接」结果的元组个数( )。 A. 不超过 12 个,具体取决于公共属性 B 的取值匹配情况 B. 一定等于 12 个 C. 一定等于 3 个 D. 一定等于 7 个

答案:A

📌 考点定位: 关系数据库基本理论——自然连接与笛卡尔积的区别,国企 / 互联网笔试高频。

【结论】 选 A。自然连接先按公共属性做等值匹配再拼接,结果元组数取决于公共属性 B 的取值匹配情况,上界是 3 × 4 = 12、下界是 0,因此只能说「不超过 12 个」。

【逐项辨析】

  • A 不超过 12 个,具体取决于公共属性 B 的取值匹配情况:正确。 自然连接是「按公共属性等值匹配 + 去除重复列」的结果,能匹配出多少条取决于 B 的实际取值,故只能给出上界。
  • B 一定等于 12 个:错。这是笛卡尔积的结论;自然连接含等值筛选,B 值不匹配的元组会被丢弃,12 只是上界而非定值。
  • C 一定等于 3 个:错。3 是 R 的元组数,不是连接结果数;只有当 S 中每个元组至多匹配到 R 的一个元组时才会得到 3 个,题干并未给出这一前提。
  • D 一定等于 7 个:错。7 = 3 + 4 是「并」运算在同构前提下的元组数上限,与自然连接无关。

【知识点】 自然连接与笛卡尔积的区别集中在「是否做等值筛选」与「是否保留重复列」两点上:

对比项笛卡尔积 R × S自然连接 R ⋈ S
是否按公共属性筛选否,全组合是,只保留公共属性取值相等的组合
结果元组数恒为 |R| × |S| = 120 ≤ n ≤ |R| × |S| = 12
结果属性列R 与 S 的全部列,公共属性重复出现(A, B, B, C)公共属性只保留一次(A, B, C)
公共属性为空时—退化为笛卡尔积

自然连接元组数的推导过程:

text
设 R(A, B) 有 3 个元组,S(B, C) 有 4 个元组,公共属性为 B
step 1  取 R 的一个元组 r,在 S 中找 B 值相同的元组
step 2  r 与每个匹配元组拼接成一条新元组,属性列为 A, B, C
step 3  对 R 的 3 个元组重复 step 1 ~ 2,累加结果
上界:R 的 3 个元组都匹配上 S 的 4 个元组 → 3 × 4 = 12
下界:R 中所有 B 值在 S 中都不出现       → 0

三种典型取值情形(设 R、S 的 B 值都取自 {b₁, b₂, b₃},同侧各元组的 B 值可以重复):

  1. R 的 3 个元组 B 值全为 b₁,S 的 4 个元组 B 值也全为 b₁ → m(b₁)×n(b₁) = 3 × 4 = 12 个(取到上界的真实构造就是这种「两边取值完全对齐且可重复」);
  2. R 的 3 个元组 B 值互不相同(b₁、b₂、b₃),S 的 B 值只有 b₁ 且出现 4 次 → 结果为 1 × 4 = 4 个;
  3. S 的 B 值全部不在 R 的 B 值集合中 → 结果为 0 个。

补充:若连接条件不是「公共属性等值」而是任意条件,则称为 θ 连接(等值连接是 θ 取等号的特例),此时结果属性列不去重,元组数同样取决于匹配情况。

【完整推导·自然连接元组数】

text
形式化:R ⋈ S = π_{去重后属性}(σ_{R.B=S.B}(R × S))
步骤:
  1) 先做笛卡尔积:|R×S| = 3×4 = 12(上界)
  2) 再做选择 σ_{R.B=S.B}:只保留 B 值相等的组合
  3) 设 m(b) = |{r∈R | r.B=b}|,n(b) = |{s∈S | s.B=b}|
     则 |R⋈S| = Σ_b  m(b)×n(b)
  4) 由 Σ_b m(b)=3,Σ_b n(b)=4,且 m,n≥0
     ⇒ 0 ≤ Σ m(b)n(b) ≤ 3×4 = 12
     (柯西/排序不等式意义下,当两边取值完全对齐且可重复时取上界)
结论:自然连接元组数 ∈ [0, 12],题干未给 B 取值分布时只能选「不超过 12」。

对照表:

运算公式本题结果
笛卡尔积 R×S|R|×|S|恒 12
自然连接 R⋈SΣ_b m(b)·n(b)0~12
并 R∪S(若同构)≤ |R|+|S|≤7(本题不同构,不可并)

【记忆锚点】 「笛卡尔积是满配,自然连接是配对——配上了才有,配不上就没了」,即自然连接元组数 ≤ 笛卡尔积元组数。

【易混对比】

  • 换个问法:「若 R 有 3 个元组、S 有 4 个元组,则 R × S 的元组数与属性列数分别是多少?」→ 元组数 12,属性列数 = R 的列数 + S 的列数(公共属性重复计算)。
  • 再换问法:「若 R 的 B 值互不相同且都出现在 S 中,S 的每个 B 值恰好对应 1 个元组,则自然连接结果是多少个元组?」→ 3 个。可见「一定等于几」只有补充约束后才能确定。
  • 易混概念:自然连接 ⋈ ↔ 等值连接(前者去除重复列、后者保留重复列)、自然连接 ↔ 笛卡尔积(前者有筛选、后者无筛选)。

【自测】

关系 R(A, B) 有 5 个元组,S(B, C) 有 6 个元组,且 R 中 B 的取值互不相同、S 中 B 的取值也互不相同,两者 B 值有 2 个相同。则 R ⋈ S 的结果元组个数为( )。 A. 30 B. 2 C. 11 D. 无法确定 答:B。公共属性 B 上取值相同的只有 2 组,每组因两侧 B 值互不相同而只能拼出 1 个元组,故结果为 2 个;30 是笛卡尔积的结果。(关联补-05 自然连接的元组数)

【选项误解】

  • A 正确项:自然连接含等值筛选,上界 3×4=12,下界 0,具体取决于 B 的匹配情况。
  • B 误解来源:把自然连接当成笛卡尔积。笛卡尔积无筛选,元组数恒为 |R|×|S|。
  • C 误解来源:以为连接「以 R 为准」结果条数等于 R。只有当 S 中每个 B 值至多一条、且全部匹配时才是 3。
  • D 误解来源:3+4=7 是「并」运算在同构前提下的元组数上限,与连接无关。

【知识关联】

  • 主库关联:M04/M05(多表 JOIN 与索引使用——连接列建索引才能避免 NLJ 全表扫)、M19(EXPLAIN 中 join 类型)、M30(分库分表后跨库 JOIN 代价)。
  • 国网/408:国网第 11 条;408 数据库计算题高频——给 R、S 元组数与公共属性值域,求自然连接/笛卡尔积/外连接的元组数。
  • 面试追问:① 等值连接与自然连接区别?→ 自然连接去除重复公共属性列,等值连接保留。② 连接结果元组数何时最大?→ 公共属性值完全对齐且多对多时达到 |R|×|S|。③ MySQL 中 JOIN 默认是?→ INNER JOIN(内连接),不是自然连接。

【拓展延伸】

  • 变式问法:①「R×S 元组数?」→ 恒 12;②「R 有 3 元组、S 有 4 元组、公共属性 B 值全不同且 2 个相同」→ 自然连接 2 个;③「外连接元组数范围?」→ 左外 ≥|R|、右外 ≥|S|、全外 ≥max(|R|,|S|)(本例 R=3、S=4:左外最少 3 行,并非 max=4);上界取决于匹配,最坏 |R|×|S|。
  • 完整推导示例(补强):
text
R(B): {1,2,3}     S(B): {2,2,4,5}
自然连接:
  R 中 B=1 → S 中无 → 0
  R 中 B=2 → S 中 2 条 → 2
  R 中 B=3 → S 中无 → 0
  合计 2 条(属性 A,B,C)
笛卡尔积仍为 3×4=12
若 S(B)={1,2,3,3}:
  B=1→1 条,B=2→1 条,B=3→2 条,合计 4 条
  • 工程参数/命令:EXPLAIN 里 type=eq_ref 常表示被驱动表用唯一索引做等值连接;rows 列的乘积可粗估连接扇出。大表 JOIN 前务必确认连接列索引与驱动表选择。

补-06 ​

下列 SQL 语句中,属于 DCL(数据控制语言)的是( )。 A. CREATE、ALTER、DROP B. SELECT、INSERT、UPDATE、DELETE C. GRANT、REVOKE D. COMMIT、ROLLBACK、SAVEPOINT

答案:C

📌 考点定位: SQL 语言分类,对应国网考纲第 12 条,国企笔试常考。

【结论】 选 C。DCL 即数据控制语言,只管权限的授予与回收,GRANT(授权)与 REVOKE(收回权限)是它的全部成员。

【逐项辨析】

  • A CREATE、ALTER、DROP:错。这三条操作的是数据库对象的结构(库、表、索引、视图等),属 DDL(数据定义语言)。
  • B SELECT、INSERT、UPDATE、DELETE:错。这四条操作的是表中的数据,属 DML(数据操纵语言)。
  • C GRANT、REVOKE:正确。 二者负责把权限授予用户、从用户处回收权限,属 DCL(数据控制语言)。
  • D COMMIT、ROLLBACK、SAVEPOINT:错。这三条控制的是事务的提交、回滚与保存点,属 TCL(事务控制语言)。

【知识点】 SQL 按功能分为四类,分类依据是「操作对象是什么」:

类别全称操作对象代表语句
DDL数据定义语言数据库对象的结构CREATE、ALTER、DROP、TRUNCATE
DML数据操纵语言表中的数据SELECT、INSERT、UPDATE、DELETE
DCL数据控制语言访问权限GRANT、REVOKE
TCL事务控制语言事务边界COMMIT、ROLLBACK、SAVEPOINT

记忆要点:TRUNCATE 虽然效果是清空数据,但它属于 DDL(按对象结构操作、隐式提交、不可回滚),这一点最容易被误判成 DML。

【知识点扩写·四类语言的边界】

易混语句正确分类关键原因
TRUNCATE TABLE tDDL重定义表存储、隐式提交
DELETE FROM tDML逐行删除、可 WHERE、可回滚
CREATE INDEXDDL定义索引对象
SELECT ... FOR UPDATEDML(当前读)仍是查询语句,但加 X 锁
SET autocommit=0TCL/会话控制影响事务边界
GRANT/REVOKEDCL只改权限元数据

记忆:结构 DDL、数据 DML、权限 DCL、事务 TCL。

【记忆锚点】 「定义管结构、操纵管数据、控制管权限、事务管提交」。

【易混对比】

  • 换个问法:「TRUNCATE TABLE T 属于哪类语句?」→ 答 DDL,不是 DML;它与 DELETE FROM T 效果相似,但分类与可回滚性都不同。
  • 易混概念:DELETE(DML,可回滚,可带 WHERE)↔ TRUNCATE(DDL,隐式提交,清空全表)。

【自测】

  1. TRUNCATE TABLE t 与 DELETE FROM t(不带 WHERE)都能清空表,分类与后果差在哪? 参考答案:TRUNCATE 是 DDL——按对象结构操作、隐式提交、不能回滚、通常重置自增列;DELETE 是 DML——逐行删、可带 WHERE、可回滚,会写大量 undo/redo 并触发触发器。
  2. 只给用户查询 shop.orders 的权限,该用哪类语句?写出命令。 参考答案:DCL —— GRANT SELECT ON shop.orders TO 'app'@'10.%';(收回用 REVOKE SELECT ON shop.orders FROM 'app'@'10.%';,查看用 SHOW GRANTS FOR 'app'@'10.%';)。
  3. COMMIT、ROLLBACK、SAVEPOINT 属哪类?为什么不是 DCL? 参考答案:属 TCL(事务控制语言),管的是事务边界;DCL 只管权限的授予与回收(GRANT/REVOKE),二者别因"控制"二字混为一谈。

【选项误解】

  • A 误解来源:CREATE/ALTER/DROP 操作对象是结构,属 DDL;考生常因「DROP 会删数据」误判为 DML。
  • B 误解来源:最常见错误——把「最常写的 SQL」默认当成某一类。SELECT/INSERT/UPDATE/DELETE 全是 DML。
  • C 正确项:GRANT/REVOKE 只管权限,是 DCL 的全部成员(标准 SQL 口径)。
  • D 误解来源:COMMIT/ROLLBACK 控制事务边界,属 TCL;因与「控制」字面接近,容易被误塞进 DCL。

【知识关联】

  • 主库关联:M31(防 SQL 注入——参数化查询属应用层权限/校验延伸);主库未系统讲 SQL 语言分类。
  • 国网/408:国网考纲第 12 条「关系数据库标准语言 SQL」;408 可能考语言分类填空。
  • 面试追问:① TRUNCATE 属 DDL 还是 DML?→ DDL(隐式提交、通常不可回滚、重置自增)。② MySQL 权限体系如何落地?→ mysql.user/db/tables_priv/columns_priv 表 + FLUSH PRIVILEGES;8.0 起用角色 Role。③ GRANT 和视图如何配合做权限控制?→ 只对视图授权、不授权基表,用户只能看到视图暴露的行列。

【拓展延伸】

  • 变式问法:①「属于 DDL 的是?」→ CREATE/ALTER/DROP/TRUNCATE;②「属于 TCL 的是?」→ COMMIT/ROLLBACK/SAVEPOINT;③「GRANT SELECT ON db.v TO u@'%' 属哪类?」→ DCL。
  • 工程参数/命令:
sql
-- DCL
CREATE USER IF NOT EXISTS 'app'@'10.%' IDENTIFIED BY '******';   -- MySQL 8.0 起 GRANT 不再隐式建号,缺这句报 ERROR 1410
GRANT SELECT, INSERT ON shop.* TO 'app'@'10.%';
REVOKE INSERT ON shop.* FROM 'app'@'10.%';
SHOW GRANTS FOR 'app'@'10.%';
-- MySQL 8 角色
CREATE ROLE 'read_only';
GRANT SELECT ON *.* TO 'read_only';
GRANT 'read_only' TO 'app'@'10.%';
  • 安全实践:业务账号按最小权限授予;禁用 GRANT ALL;管理端与业务端账号分离;审计开启 general_log 或企业级审计插件。

补-07 ​

关于 SQL 查询中 WHERE 与 HAVING 子句,下列说法正确的是( )。 A. WHERE 作用于分组后的结果,HAVING 作用于分组前 B. WHERE 在分组前过滤行,不能使用聚合函数;HAVING 在分组后过滤组,可以使用聚合函数 C. 二者完全等价,可以互换使用 D. HAVING 必须与 ORDER BY 子句一起使用

答案:B

📌 考点定位: SQL 查询语句结构与执行顺序,对应国网考纲第 12 条;互联网笔试选择题高频。

【结论】 选 B。WHERE 作用于分组前的基表行、不能出现聚合函数;HAVING 作用于分组后的组、可以使用聚合函数,二者的差别源于它们在逻辑执行顺序中处于不同阶段。

【逐项辨析】

  • A WHERE 作用于分组后的结果,HAVING 作用于分组前:错。把两者的作用阶段整体说反了——WHERE 在 GROUP BY 之前,HAVING 在 GROUP BY 之后。
  • B WHERE 在分组前过滤行,不能使用聚合函数;HAVING 在分组后过滤组,可以使用聚合函数:正确。 这一表述同时命中了「执行阶段」与「能否使用聚合函数」两个判断维度。
  • C 二者完全等价,可以互换使用:错。作用阶段不同,WHERE 处理的是原始行、HAVING 处理的是分组结果,把 COUNT(*) > 5 写进 WHERE 会直接报错(注意报错阶段:语句能通过语法分析,是在语义/名称解析阶段被判定为非法使用聚合函数,MySQL 返回 ERROR 1111 (HY000): Invalid use of group function)。
  • D HAVING 必须与 ORDER BY 子句一起使用:错。HAVING 的配套子句是 GROUP BY,与 ORDER BY 没有必然关系;单独出现 HAVING 时通常隐含「整表作为一组」的语义。

【知识点】 SQL 语句的书写顺序与逻辑执行顺序并不一致,这是本题的核心:

书写位置逻辑执行顺序作用
SELECT5选取列、计算聚合值、生成别名
FROM1确定数据来源、完成连接
WHERE2对行做过滤(不能用聚合函数、不能用 SELECT 别名)
GROUP BY3按分组列把行聚成组
HAVING4对组做过滤(可以使用聚合函数)
ORDER BY6对结果排序
LIMIT7截取结果行数

由执行顺序可直接推出两条结论:

  1. WHERE 里不能用 SELECT 中定义的别名——别名在第 5 步才产生,而 WHERE 在第 2 步执行时它还不存在。
  2. 聚合函数只能出现在 HAVING 或 SELECT 中——聚合值要到第 3 步分组之后才产生,WHERE 在第 2 步执行时无组可分。

标准写法示例:

text
SELECT dept, COUNT(*) AS cnt
FROM   Student
WHERE  age > 18          -- 先筛行:年龄大于 18 的学生
GROUP  BY dept           -- 再分组:按院系分组
HAVING COUNT(*) > 5      -- 后筛组:人数超过 5 的院系
ORDER  BY cnt DESC;      -- 最后排序

【知识点扩写·为何聚合不能进 WHERE】

text
逻辑执行:
  FROM   → 读表/连接
  WHERE  → 逐行过滤(此时还没有「组」概念)
  GROUP  → 把行聚成组
  HAVING → 对组过滤(聚合值已产生)
  SELECT → 投影/别名
若 WHERE 中写 COUNT(*)>5:
  语法分析能过,但在语义/名称解析阶段被拒——聚合值在 WHERE 时刻尚不存在
MySQL 错误示例:ERROR 1111 (HY000): Invalid use of group function

书写顺序 ≠ 执行顺序 是本考点最大的误解来源,建议把执行顺序表背熟。

【记忆锚点】 「WHERE 管行、HAVING 管组;WHERE 在分组前、HAVING 在分组后」。

【易混对比】

  • 换个问法:「SELECT dept FROM Student WHERE COUNT(*) > 5 GROUP BY dept 报错的原因是什么?」→ 聚合函数出现在了 WHERE 中,违反「聚合函数只能出现在 HAVING 或 SELECT」的规则。
  • 再换问法:「WHERE age > 18 AND COUNT(*) > 1 这种写法能用吗?」→ 不能,COUNT 必须在 HAVING 中使用。
  • 与补-08、补-09 连考:WHERE / HAVING 的执行阶段常与「外连接保留行」「COUNT(*) 与 COUNT(列) 的差异」组合出题。
  • 易混概念:WHERE ↔ HAVING(行 vs 组)、GROUP BY ↔ ORDER BY(分组改变行数,排序不改变行数)。

【自测】

下列 SQL 中语法正确的是( )。 A. SELECT dept FROM Student WHERE COUNT(*) > 3 GROUP BY dept B. SELECT dept, COUNT(*) FROM Student GROUP BY dept HAVING COUNT(*) > 3 C. SELECT dept FROM Student GROUP BY dept WHERE COUNT(*) > 3 D. SELECT dept FROM Student HAVING COUNT(*) > 3 WHERE age > 18 答:B。A 把聚合函数写进了 WHERE;C 把 WHERE 写到了 GROUP BY 之后;D 中 WHERE 的位置与书写顺序均错误。(关联补-07 WHERE 与 HAVING 的执行阶段)

【选项误解】

  • A 误解来源:WHERE/HAVING 阶段对调。常见说法「HAVING 更高级应该先过滤」完全错误——执行顺序是 WHERE → GROUP BY → HAVING。
  • B 正确项:同时命中「执行阶段」与「能否用聚合」两个判据。
  • C 误解来源:认为两者都「过滤」所以可互换。COUNT(*)>5 写进 WHERE 会报错(ERROR 1111 (HY000),是语义解析阶段判定「非法使用聚合函数」,语法本身能过)。
  • D 误解来源:把 HAVING 与 ORDER BY 绑定。HAVING 的配套是 GROUP BY;ORDER BY 可独立出现。

【知识关联】

  • 主库关联:M20(Using filesort——ORDER BY 无索引时的排序代价)、M25(COUNT 口径)、M05(GROUP BY 列能否走索引避免临时表)。
  • 国网/408:国网第 12 条;408 SQL 大题常要求写「按条件筛选组」的查询,必须区分 WHERE/HAVING。
  • 面试追问:① WHERE 里为何不能用 SELECT 别名?→ 别名在 SELECT 阶段才生成,WHERE 更早执行(MySQL 有些方言宽松,但标准不允许)。② HAVING 能否用非聚合列?→ 可以,但该列必须在 GROUP BY 中或被聚合(ONLY_FULL_GROUP_BY)。③ 无 GROUP BY 的 HAVING 什么语义?→ 整表作为一组。

【拓展延伸】

  • 变式问法:①「过滤分组前的行」→ WHERE;②「过滤聚合后的组」→ HAVING;③「逻辑执行顺序排序题」→ FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT。
  • 工程参数/命令:
sql
-- 标准写法
SELECT dept, COUNT(*) AS cnt
FROM student
WHERE age > 18
GROUP BY dept
HAVING COUNT(*) > 5
ORDER BY cnt DESC
LIMIT 10;

-- 查看 ONLY_FULL_GROUP_BY
SELECT @@sql_mode;
-- 临时关闭(不推荐生产)
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';
  • 性能提示:能在 WHERE 完成的过滤绝不下放到 HAVING——WHERE 在分组前减少行数,分组/聚合代价随之下降;索引对 WHERE 的收益也远大于 HAVING。

补-08 ​

要查询「所有学生及其选课情况,包括没有选任何课的学生」,应采用( )。 A. 内连接(INNER JOIN) B. 交叉连接(CROSS JOIN) C. 右外连接(RIGHT JOIN),以选课表为主表 D. 左外连接(LEFT JOIN),以学生表为主表

答案:D

📌 考点定位: SQL 多表连接查询,对应国网考纲第 12 条;互联网笔试选择题高频。

【结论】 选 D。要保留「没有选任何课的学生」,必须以学生表为主表做左外连接,让未匹配的从表列以 NULL 填充;内连接会把这类学生直接丢弃。

【逐项辨析】

  • A 内连接(INNER JOIN):错。内连接只返回两表中匹配成功的行,未选课的学生在选课表中没有对应记录,会被直接过滤掉,与题干「包括没有选任何课的学生」矛盾。
  • B 交叉连接(CROSS JOIN):错。交叉连接即笛卡尔积,每个学生与每条选课记录都强行组合,会产生大量无意义的错配行,而非「每个学生对应其真实选课情况」。
  • C 右外连接(RIGHT JOIN),以选课表为主表:错。右外连接保留的是右表(选课表)的全部行,即「所有选课记录」,方向正好相反,未选课的学生仍然不会出现。
  • D 左外连接(LEFT JOIN),以学生表为主表:正确。 左表(学生表)的每一行都被保留,能在选课表中匹配到的显示真实选课信息,匹配不到的选课字段填 NULL,正好实现「所有学生 + 其选课情况(含未选课)」。

【知识点】 连接查询按「是否保留未匹配行」分为内连接与外连接两大类:

连接类型保留的行未匹配侧的列典型用途
内连接 INNER JOIN两表都匹配上的行—只关心有对应关系的数据
左外连接 LEFT JOIN左表全部 + 右表匹配行右表列填 NULL保留左表全集
右外连接 RIGHT JOIN右表全部 + 左表匹配行左表列填 NULL保留右表全集
全外连接 FULL JOIN两表全部行两侧都可能填 NULL保留双方全集(MySQL 不直接支持,需用 UNION 模拟)
交叉连接 CROSS JOIN两表所有组合—笛卡尔积

判断主表的口诀是「要保留谁的全部行,谁就是主表」。在 A LEFT JOIN B 中主表是 A(左表),在 A RIGHT JOIN B 中主表是 B(右表)。

补充两点:

  1. 左外连接与右外连接可以互相改写:A LEFT JOIN B 等价于 B RIGHT JOIN A,只要交换表的顺序并把 LEFT 换成 RIGHT 即可。
  2. 过滤条件写在不同位置,语义不同:写在 ON 中只影响匹配过程,写在 WHERE 中会过滤掉 NULL 填充行,从而把外连接「退化成」内连接——这是外连接最常见的坑。
text
-- 正确:未选课学生的选课字段为 NULL,但仍被保留
SELECT s.sno, s.sname, sc.cno
FROM   Student s LEFT JOIN SC sc ON s.sno = sc.sno;

-- 危险:WHERE 过滤 cno 会剔除 NULL 行,等价于内连接
SELECT s.sno, s.sname, sc.cno
FROM   Student s LEFT JOIN SC sc ON s.sno = sc.sno
WHERE  sc.cno IS NOT NULL;

【记忆锚点】 「谁的行要全留,谁就站在 LEFT JOIN 左边;右表补 NULL」。

【易混对比】

  • 换个问法:「要查询所有课程及其选课人数,包括无人选的课程」→ 以课程表为主表做左外连接(或把选课表放在右外连接的位置),与本题主表方向相反,注意切换。
  • 再换问法:「SELECT ... FROM A LEFT JOIN B ON ... WHERE B.col = 'x' 还保留 A 的全部行吗?」→ 不保留,WHERE 会剔除 B 侧为 NULL 的行,实际等价于内连接;若要保留应把该条件写进 ON。
  • 与补-07、补-09 连考:外连接产生的 NULL 行与 COUNT(*) / COUNT(列) 的差异常组合出题——统计「每个学生的选课门数(含 0 门)」时必须用 COUNT(sc.cno),因为未选课学生的 COUNT(*) 会返回 1(整行存在),而 COUNT(sc.cno) 才返回 0。
  • 易混概念:内连接 ↔ 外连接(是否保留未匹配行)、左外连接 ↔ 右外连接(主表方向)、ON 条件 ↔ WHERE 条件(是否影响 NULL 行的保留)。

【自测】

表 Course(Cno, Cname) 与 SC(Sno, Cno) 中,要查询「所有课程及选课人数,包括没有学生选的课程」,正确的 SQL 是( )。 A. SELECT c.Cno, COUNT(*) FROM Course c JOIN SC s ON c.Cno = s.Cno GROUP BY c.Cno B. SELECT c.Cno, COUNT(s.Sno) FROM Course c LEFT JOIN SC s ON c.Cno = s.Cno GROUP BY c.Cno C. SELECT c.Cno, COUNT(*) FROM Course c LEFT JOIN SC s ON c.Cno = s.Cno GROUP BY c.Cno D. SELECT c.Cno, COUNT(s.Sno) FROM Course c RIGHT JOIN SC s ON c.Cno = s.Cno GROUP BY c.Cno 答:B。需保留课程表全集故用 LEFT JOIN,且无人选的课程要计 0 人,必须用 COUNT(s.Sno) 跳过 NULL;C 的 COUNT(*) 会把 NULL 填充行也计为 1,导致无人选的课程显示 1 人。(关联补-08 外连接 + 补-09 聚合函数对 NULL 的处理)

【选项误解】

  • A 误解来源:日常 INNER JOIN 写多了,看到「学生+选课」默认内连接,忽略题干「包括没有选任何课」。
  • B 误解来源:把「连接」一词泛化成笛卡尔积。交叉连接会产生 m×n 错配行,语义完全错误。
  • C 误解来源:知道要用外连接,但主表方向搞反。右外连接保留的是选课表全集=所有选课记录,未选课学生仍不出现。
  • D 正确项:学生表在左,LEFT JOIN 保留学生全集,未匹配选课列填 NULL。

【知识关联】

  • 主库关联:M04(JOIN 列索引)、M05(联合索引最左前缀在连接条件上的应用)、M25(COUNT 与 NULL)。
  • 国网/408:国网第 12 条;408 SQL 题「查询没有选课的学生」是经典变体(LEFT JOIN + IS NULL 或 NOT IN/NOT EXISTS)。
  • 面试追问:① 「没选课的学生」另一种写法?→ SELECT * FROM Student s WHERE NOT EXISTS (SELECT 1 FROM SC WHERE sno=s.sno)。② LEFT JOIN 的 WHERE 写 IS NOT NULL 会怎样?→ 退化为 INNER JOIN。③ 外连接 ON 与 WHERE 区别?→ ON 只影响匹配;WHERE 过滤最终结果(含 NULL 行)。

【拓展延伸】

  • 变式问法:①「所有课程及选课人数,含 0 人课」→ 课程表 LEFT JOIN 选课表 + COUNT(sc.sno);②「查询从不选课的学生」→ LEFT JOIN ... WHERE sc.sno IS NULL;③「A LEFT JOIN B」等价改写 → B RIGHT JOIN A。
  • 工程参数/命令:
sql
-- 正确:保留未选课学生
SELECT s.sno, s.sname, sc.cno
FROM student s
LEFT JOIN sc ON s.sno = sc.sno;

-- 危险:退化成内连接
SELECT s.sno, s.sname, sc.cno
FROM student s
LEFT JOIN sc ON s.sno = sc.sno
WHERE sc.cno IS NOT NULL;

-- MySQL 不直接支持 FULL OUTER JOIN,可用 UNION 模拟
SELECT * FROM a LEFT JOIN b ON a.id=b.id
UNION
SELECT * FROM a RIGHT JOIN b ON a.id=b.id;
  • 性能:外连接驱动表固定为左表时,被驱动表连接列必须有索引,否则易出现全表扫描。

补-09 ​

已知表 T 共有 10 行,列 age 中有 3 行的值为 NULL,则 SELECT COUNT(*), COUNT(age) FROM T 的结果分别是( )。 A. 7, 7 B. 10, 10 C. 10, 7 D. 7, 10

答案:C

📌 考点定位: SQL 聚合函数与 NULL 处理,国企 / 互联网笔试高频。

【结论】 选 C。COUNT(*) 统计行数,与列值是否为 NULL 无关,结果为 10;COUNT(age) 统计该列非 NULL 值的个数,跳过 3 个 NULL,结果为 7。

【逐项辨析】

  • A 7, 7:错。把 COUNT(*) 也当成了忽略 NULL 的函数,而 COUNT(*) 数的是行,3 行 NULL 同样是 3 行。
  • B 10, 10:错。把 COUNT(age) 也当成了不忽略 NULL 的函数,而带列名的 COUNT 只统计非 NULL 值。
  • C 10, 7:正确。 COUNT(*) = 全部行数 = 10;COUNT(age) = age 中非 NULL 值的个数 = 10 − 3 = 7。
  • D 7, 10:错。两个结果正好说反,把「忽略 NULL」安到了 COUNT(*) 上,把「不忽略 NULL」安到了 COUNT(age) 上。

【知识点】 SQL 聚合函数对 NULL 的处理规则可以归纳为一句话:除 COUNT(*) 外,所有聚合函数都忽略 NULL。

聚合函数是否忽略 NULL计算口径本题数据下的结果
COUNT(*)否统计行数,不管列值是否为 NULL10
COUNT(age)是统计 age 的非 NULL 值个数7
SUM(age)是非 NULL 值之和只累加 7 个值
AVG(age)是SUM(age) / 非 NULL 个数分母是 7,不是 10
MAX(age) / MIN(age)是在非 NULL 值中取极值忽略 NULL

由「忽略 NULL」可推出两个高频推论:

  1. AVG 的分母会变小。设 10 行 age 之和为 S,则 AVG(age) = S / 7 而不是 S / 10。若想按全部行数求平均,须写成 SUM(age) / COUNT(*),并注意整数除法的取整问题。
  2. COUNT(*) 与 COUNT(列) 的结果可能不同,当且仅当该列不含 NULL 时二者才相等。

关于 NULL 的判定与运算:

text
NULL 不是 0,也不是空字符串,它表示「值未知」
NULL 参与算术运算 → 结果为 NULL(如 5 + NULL = NULL)
NULL 的比较必须用 IS NULL / IS NOT NULL,不能用 = NULL
聚合函数 SUM / AVG / MAX / MIN / COUNT(列) 在计算前先剔除 NULL
若一组内所有值都是 NULL:COUNT(列) 返回 0,SUM / AVG / MAX / MIN 返回 NULL

【完整推导·聚合与 NULL】

text
规则:除 COUNT(*) 外,所有聚合函数计算前先剔除 NULL
推论链:
  COUNT(*) 计行     → 与 NULL 无关
  COUNT(col) 计非空 → 与 NULL 负相关
  SUM(col)  只加非空 → NULL 贡献 0,但 SUM 结果仍可能非 0
  AVG(col)=SUM/COUNT(col) → 分母自动排除 NULL → 结果偏大(相对按总行数平摊)
反例验证:
  表 T: age = {20, NULL, 30, NULL, NULL, 40}
  COUNT(*)=6, COUNT(age)=3
  SUM=90, AVG=90/3=30
  若误用 SUM/COUNT(*)=90/6=15,会得到完全错误的平均值

【记忆锚点】 「COUNT(*) 数行不挑食,COUNT(列) 挑掉 NULL;AVG 忽略 NULL,所以分母变小、结果偏大」。

【易混对比】

  • 换个问法:「表 T 共 10 行,sal 列有 4 行为 NULL,则 SUM(sal) 与 AVG(sal) 的分母分别是多少?」→ SUM 没有分母概念,只累加 6 个非 NULL 值;AVG(sal) 的分母是 6。
  • 再换问法:「SELECT AVG(age) FROM T 与 SELECT SUM(age) / COUNT(*) FROM T 结果是否相同?」→ 通常不同;按本题数据(10 行、3 行为 NULL)前者分母是非空个数 7、后者分母是总行数 10,若 SUM(age)=210 则 210/7=30 > 210/10=21,后者结果更小。
  • 与补-08 连考:统计「每个学生的选课门数(含 0 门)」时,左外连接下必须用 COUNT(sc.cno);若误用 COUNT(*),未选课的学生会被算成 1 门。
  • 易混概念:COUNT(*) ↔ COUNT(列)(行数 vs 非 NULL 值个数)、= NULL ↔ IS NULL(前者结果恒为 UNKNOWN,永远查不到数据)。

【自测】

表 T 共 20 行,列 score 中有 5 行为 NULL,其余值之和为 300。则 SELECT AVG(score), COUNT(*), COUNT(score) FROM T 的结果是( )。 A. 15, 20, 15 B. 20, 20, 15 C. 20, 20, 20 D. 15, 15, 20 答:B。AVG(score) = 300 / (20 − 5) = 20,分母是非 NULL 个数 15;COUNT(*) = 20;COUNT(score) = 15。(关联补-09 聚合函数与 NULL 处理)

【选项误解】

  • A 误解来源:把 COUNT(*) 也理解成「忽略 NULL」。COUNT(*) 数的是行,整行 NULL 也是行。
  • B 误解来源:以为所有 COUNT 都不挑值。带列名的 COUNT(col) 明确跳过 NULL。
  • C 正确项:10 行 → COUNT(*)=10;3 个 NULL → COUNT(age)=7。
  • D 误解来源:两个结果对调——把「忽略 NULL」安到了 COUNT(*) 上。

【知识关联】

  • 主库关联:M25(COUNT 函数正确说法——COUNT(*)/COUNT(1)/COUNT(列) 差异与性能)、补-08(外连接 + COUNT(sc.col) 才能计 0)。
  • 国网/408:国网 SQL 部分;笔试计算题常直接给 NULL 行数要求写 COUNT 结果。
  • 面试追问:① COUNT(*)、COUNT(1)、COUNT(id) 哪个最快?→ InnoDB 优化器通常等价,都走最小可用索引;COUNT(列) 若列可空则可能更慢且结果更小。② 为何 AVG 分母不是总行数?→ AVG = SUM/COUNT(列),忽略 NULL。③ 大表 COUNT(*) 慢怎么办?→ 近似统计(information_schema.TABLES.TABLE_ROWS)、维护计数表、或缓存。

【拓展延伸】

  • 变式问法:①「10 行中 3 NULL,SUM(age)/AVG(age) 分母?」→ SUM 无分母;AVG 分母=7;②「COUNT(DISTINCT age)?」→ 去重且忽略 NULL,最大 7;③「全 NULL 列」→ COUNT(列)=0,SUM/AVG/MAX/MIN 返回 NULL。
  • 完整计算推导:
text
已知:总行数 N=10,age 非空个数 = 10-3=7
COUNT(*)     = N = 10
COUNT(age)   = 非空个数 = 7
若 SUM(age)=210,则 AVG(age)=210/7=30(不是 210/10=21)
SUM(age)/COUNT(*) = 210/10=21(若要按总行数平摊需显式这样写)
  • 工程参数/命令:
sql
SELECT COUNT(*), COUNT(age), SUM(age), AVG(age) FROM t;
-- 近似行数(MySQL)
SELECT TABLE_ROWS FROM information_schema.TABLES
 WHERE TABLE_SCHEMA='db' AND TABLE_NAME='t';
-- InnoDB 精确 COUNT 代价:需扫索引
EXPLAIN SELECT COUNT(*) FROM t;

补-10 ​

关于数据库视图(VIEW),下列说法正确的是( )。 A. 视图是物理存储的数据副本,占用与基表相同的存储空间 B. 视图是从一个或多个基表导出的虚表,只保存定义不保存数据,可简化查询并用于权限控制 C. 任何视图都可以执行 INSERT / UPDATE / DELETE D. 视图一旦创建,其定义永远不可修改

答案:B

📌 考点定位: SQL——视图的概念与作用,对应国网考纲第 12 条,国企笔试常考。

【结论】 选 B。视图是从一个或多个基表导出的虚表,数据库中只保存其查询定义、不保存数据,查询时才动态从基表生成结果,因此可用于简化查询与实现权限控制。

【逐项辨析】

  • A 视图是物理存储的数据副本,占用与基表相同的存储空间:错。视图不存储数据,只保存一条查询定义,因此不会占用与基表相同的存储空间。
  • B 视图是从一个或多个基表导出的虚表,只保存定义不保存数据,可简化查询并用于权限控制:正确。 「虚表」「只存定义」是视图的本质,「简化查询」「权限控制」是它的两大典型作用。
  • C 任何视图都可以执行 INSERT / UPDATE / DELETE:错。视图的可更新性受条件约束,含聚合函数、DISTINCT、GROUP BY、多表连接、表达式列等的视图通常不可更新,说「任何视图都可以」是错的。
  • D 视图一旦创建,其定义永远不可修改:错。视图定义可以用 ALTER VIEW 修改,也可以用 CREATE OR REPLACE VIEW 重建。

【知识点】 视图是从一个或几个基本表(或其它视图)导出的表,它是虚表——数据库中只存放视图的定义,不存放对应的数据。

对比项基本表视图
是否存储数据存储不存储,只存定义
数据来源自身基表(或其它视图)
是否占用数据空间占用不占用数据空间,仅定义存入数据字典
能否建立索引可以不可以(物化视图除外,MySQL 不原生支持)
增删改支持受可更新性条件限制

视图的三大作用:

  1. 简化操作:把复杂的多表连接、聚合封装成一个视图,用户只需查询视图。
  2. 逻辑数据独立性:基表结构变化时,只需重定义视图,用户程序不受影响。
  3. 安全保护:通过视图只暴露部分行或部分列,配合 GRANT 把访问权限限定在视图上,用户看不到基表的其它数据。

视图的可更新性判据是「能否与基表行一一对应」:

text
通常不可更新的视图:
  含聚合函数(SUM / COUNT / AVG / MAX / MIN)
  含 DISTINCT、GROUP BY、HAVING
  由多表连接产生(含 JOIN)
  含表达式列、子查询、UNION
通常可更新的视图:
  行列子集视图(从单个基表选出部分行、部分列,未做任何加工)

相关语句:

text
CREATE VIEW v_cs AS SELECT sno, sname FROM Student WHERE sdept = 'CS';
ALTER  VIEW v_cs AS SELECT sno, sname, age FROM Student WHERE sdept = 'CS';
DROP   VIEW v_cs;
SHOW   CREATE VIEW v_cs;   -- 查看视图定义

补充:MySQL 中若视图定义与基表之间的对应关系可被推导,则支持 INSERT / UPDATE / DELETE;使用 WITH CHECK OPTION 可以限制更新后的行必须仍满足视图的 WHERE 条件。

【知识点扩写·视图与三级模式】

text
用户程序
   ↓ 使用
外模式(视图/用户视图)     ← 逻辑独立性的保护对象
   ↑ 外模式/模式映射
模式(基表逻辑结构)
   ↑ 模式/内模式映射
内模式(物理存储)
视图创建:在数据字典中插入一条 SELECT 定义
视图查询:解析时把视图名替换为定义中的子查询,再优化执行
因此:基表数据变化 → 视图结果立即变化;基表加列 → 视图列不变(逻辑独立)

【记忆锚点】 「视图只占定义、不占数据;能更新的是行列子集视图,带聚合和多表连接的就别想改」。

【易混对比】

  • 换个问法:「SELECT * FROM v_cs 每次执行都会重新从基表取数吗?」→ 是。视图是虚表,每次查询都按定义实时生成结果,基表数据一变视图结果就变,不存在「数据快照」。
  • 再换问法:「下列哪个视图一定可以执行 UPDATE?」→ 从单表选出部分行列、无聚合、无 DISTINCT 的行列子集视图。
  • 与补-06 连考:视图的创建与删除属 DDL(CREATE VIEW / DROP VIEW),而通过视图操作数据属 DML,两者常被混考。
  • 易混概念:视图 ↔ 基本表(虚表 vs 实表)、视图 ↔ 物化视图(前者不存数据、后者存数据,MySQL 不原生支持物化视图)。

【自测】

下列关于视图的说法,错误的是( )。 A. 视图可以建立在基本表之上,也可以建立在其它视图之上 B. 视图可以用于实现权限控制,只向用户暴露部分行与列 C. 视图中的数据会随基表数据的更新而自动变化 D. 对视图执行 UPDATE 时,被更新的数据会存储到视图中 答:D。视图不存储数据,对视图的更新实际被转换为对基表的更新,数据存放在基表而不是视图。(关联补-10 视图的本质)

【选项误解】

  • A 误解来源:把视图当成「快照表」或「复制表」。视图只存定义,不存数据。
  • B 正确项:虚表 + 只存定义 + 简化查询 + 权限控制,四要素齐全。
  • C 误解来源:以为视图和表完全同构可任意增删改。含聚合/连接/GROUP BY/DISTINCT/表达式列的视图通常不可更新。
  • D 误解来源:以为定义冻结。ALTER VIEW / CREATE OR REPLACE VIEW 都可改定义。

【知识关联】

  • 主库关联:补-01(外模式/逻辑独立性——视图是外模式的工程载体);主库 M 无直接视图题。
  • 国网/408:国网第 12 条 SQL;408 可能考视图的更新性与安全性。
  • 面试追问:① 视图能建索引吗?→ 普通视图不能;物化视图才存数据(MySQL 不原生支持,Oracle/PG 有)。② 视图如何做权限隔离?→ GRANT 只授视图、不授基表。③ WITH CHECK OPTION 作用?→ 更新后的行必须仍满足视图 WHERE 条件,否则拒绝。

【拓展延伸】

  • 变式问法:①「视图是否占用与基表相同空间?」→ 否,只占数据字典中的定义;②「哪种视图一定可 UPDATE?」→ 单表行列子集、无聚合无 DISTINCT;③「视图数据会随基表变吗?」→ 会,每次查询实时生成。
  • 工程参数/命令:
sql
CREATE OR REPLACE VIEW v_cs AS
  SELECT sno,sname FROM student WHERE sdept='CS'
  WITH CHECK OPTION;
SHOW CREATE VIEW v_cs;
-- 可更新性粗判
SELECT TABLE_NAME, IS_UPDATABLE
  FROM information_schema.VIEWS WHERE TABLE_NAME='v_cs';
DROP VIEW v_cs;
  • 物化视图对照:真正把结果存成物理表,查询快但要维护刷新(定时/触发);MySQL 需用触发器+表或第三方方案模拟。

补-11 ​

要查询列 email 取值为空的记录,正确的 WHERE 子句是( )。 A. WHERE email IS NULL B. WHERE email = NULL C. WHERE email = '' D. WHERE email != NULL

答案:A

📌 考点定位: SQL 三值逻辑与 NULL 判断,国企/互联网笔试高频。

【结论】 判空必须用 IS NULL;= NULL 语法不报错但结果恒为 UNKNOWN,一行也查不出来 —— 选 A。

【逐项辨析】

  • A WHERE email IS NULL:正确。 IS NULL 是 SQL 专为 NULL 设计的一元判定运算符,返回值只有 TRUE / FALSE,永远不会是 UNKNOWN。
  • B WHERE email = NULL:错。错在把 NULL 当成普通值去比较 —— = 遇 NULL 结果恒为 UNKNOWN,而 WHERE 只保留结果为 TRUE 的行。
  • C WHERE email = '':错。错在把 NULL 与空字符串混为一谈 —— '' 是「已知的空值」,是一个确定的字符串值,语义是「填了,但填的是空」。
  • D WHERE email != NULL:错。与 B 同因 —— !=(等价写法 <>)遇 NULL 同样返回 UNKNOWN,恒不成立,看起来与 B 相反、实则一样查不到行。

【知识点】 SQL 的布尔域是三值逻辑:{TRUE, FALSE, UNKNOWN}。NULL 的含义是「值未知」,它不是空串、不是 0、也不是「没有值」,而是「不知道」。

核心规则:任何与 NULL 进行的比较或算术运算,结果都是 UNKNOWN 或 NULL。

表达式结果说明
NULL = NULLUNKNOWNNULL 与自身比较也不为真
NULL != NULLUNKNOWN同理,!= 也不成立
NULL = 1UNKNOWN未知值与确定值无法比较
1 + NULLNULL算术运算被 NULL 传染
NULL IS NULLTRUEIS 是唯一能「看见」NULL 的运算符
NULL IS NOT NULLFALSE与上一行互为反面

WHERE 子句的语义是「只保留条件为 TRUE 的行」——UNKNOWN 与 FALSE 待遇相同,都被过滤掉。因此 email = NULL 与 email != NULL 这对「看起来对称」的写法,实际结果都是 0 行,这是最阴险的陷阱。

三处补充口径:

  1. GROUP BY 与 DISTINCT 会把所有 NULL 归入同一组(分组用的是「是否相同」而非 =),这与「NULL 互不相等」并不矛盾。
  2. UNIQUE 约束允许多行同时为 NULL(因为 NULL 互不相等)。
  3. 标准 SQL 的 IS DISTINCT FROM 把 NULL 当普通值比较,但 MySQL 不支持该语法,MySQL 用 NULL 安全等于 <=> 替代:NULL <=> NULL 返回 TRUE。

【记忆锚点】 「NULL 跟谁比都是 UNKNOWN,判空只能用 IS NULL」——= NULL 不会报错,但会安静地返回空结果集。

【易混对比】

  • NULL vs '':NULL 是「未知」,'' 是「已知的空值」。COUNT(列) 计入 ''、跳过 NULL(见补-09)。
  • IS NULL vs <=>:IS NULL 是单目判定;<=> 是 MySQL 的 NULL 安全等值比较(1 <=> 1 为 TRUE,NULL <=> NULL 也为 TRUE)。
  • UNKNOWN vs FALSE:在 WHERE 里效果一样,但在 NOT 下不同 —— NOT UNKNOWN 仍是 UNKNOWN,所以 WHERE NOT (email = NULL) 同样查不到任何行。
  • 换问法:若题目问「WHERE email IS NOT NULL 能否查到空字符串那行」,答案是能 —— '' 不是 NULL,会被判定为 TRUE。

【自测】 表 T 有 10 行,其中 4 行的 email 为 NULL、1 行的 email 为 ''。分别执行 SELECT COUNT(*) FROM T WHERE email IS NULL 与 SELECT COUNT(*) FROM T WHERE email = NULL,结果各是多少?

答:4 与 0。IS NULL 正确匹配 4 行;= NULL 恒为 UNKNOWN,返回 0 行。若把条件换成 email IS NOT NULL 则为 6 行(含 '' 那行)。与补-09 连考;国企/互联网笔试高频。

【选项误解】

  • A 正确项:IS NULL 是唯一能判定 NULL 的标准运算符。
  • B 误解来源:最经典误解——把 NULL 当普通值用 = 比较。= 遇 NULL 结果为 UNKNOWN,WHERE 只保留 TRUE。
  • C 误解来源:把 NULL 与空字符串混为一谈。'' 是「已知的空值」,语义完全不同。
  • D 误解来源:与 B 对称的错误。!= NULL 同样得 UNKNOWN,结果也是 0 行。

【知识关联】

  • 主库关联:补-09(COUNT 忽略 NULL);主库未系统讲三值逻辑。
  • 国网/408:国网 SQL;408 数据库 NULL 语义是常见陷阱题。
  • 面试追问:① MySQL 的 NULL 安全比较?→ <=>,NULL<=>NULL 为 TRUE;标准 SQL 是 IS NOT DISTINCT FROM。② UNIQUE 约束允许多个 NULL 吗?→ MySQL InnoDB 允许(NULL 互不相等)。③ GROUP BY 如何处理 NULL?→ 所有 NULL 归为同一组。

【拓展延伸】

  • 变式问法:①「查非空」→ IS NOT NULL;②「NULL<=>NULL」→ TRUE;③「WHERE NOT (email=NULL) 能查到吗?」→ 不能,NOT UNKNOWN 仍 UNKNOWN。
  • 工程参数/命令:
sql
SELECT * FROM t WHERE email IS NULL;
SELECT * FROM t WHERE email <=> NULL;     -- MySQL NULL 安全等于
SELECT COUNT(*) FROM t WHERE email = NULL; -- 恒 0,且不报错(最坑)
-- 建表时的防御
ALTER TABLE t MODIFY email VARCHAR(64) NOT NULL DEFAULT '';
  • 工程取舍:业务上「无邮箱」更推荐 DEFAULT ''(空串)还是 NULL?→ 团队规范统一即可;若用 NULL,查询与唯一约束行为更「学术正确」但更易踩坑;金额/数量字段建议 NOT NULL + 默认 0。

补-12 ​

设关系 R(学号, 课程号, 成绩, 课程名),主键为 (学号, 课程号),且存在函数依赖「课程号 → 课程名」。则关系 R( )。 A. 满足 3NF,因为不存在传递函数依赖 B. 满足 2NF,因为所有非主属性都完全依赖于主键 C. 满足 1NF 且满足 2NF,只需再消除传递依赖即可达到 3NF D. 最高只满足 1NF,因为存在非主属性对主键的部分函数依赖

答案:D

📌 考点定位: 范式判定——部分函数依赖,对应国网考纲第 11 条,国企笔试必考。

【结论】 非主属性「课程名」只依赖主键的一部分(课程号)→ 部分函数依赖 → 违反 2NF → 最高只满足 1NF。选 D。

【逐项辨析】

  • A 满足 3NF、不存在传递依赖:错。错在跳跃判定 —— 连 2NF 都没满足,根本走不到 3NF(范式是层层递进的)。
  • B 满足 2NF、所有非主属性完全依赖主键:错。错在「完全依赖」这个字眼 —— 事实恰好相反,「课程名」是部分依赖。
  • C 满足 1NF 且满足 2NF,只需再消除传递依赖:错。错在「已满足 2NF」这个错误前提上,前提不成立,后半句再对也无意义。
  • D 最高只满足 1NF,因为存在非主属性对主键的部分函数依赖:正确。 主键是复合键 (学号, 课程号),而 课程号 → 课程名 说明「课程名」不需要完整的复合主键就能唯一确定。

【知识点】 判范式是逐级递进的,顺序不能跳:

范式要求违反时的典型特征
1NF属性不可再分出现「多值字段」(如一个格子存多个电话)
2NF消除非主属性对候选键的部分依赖复合主键下,某属性只依赖其中一部分
3NF再消除非主属性对候选键的传递依赖X → Y → Z 且 Y ↛ X
BCNF消除主属性对候选键的部分 / 传递依赖决定因素不是超键

本题判定链条:主键 (学号, 课程号) 是复合键 → 存在 课程号 → 课程名,即「课程名」只依赖复合键的一半 → 这是部分函数依赖 → 违反 2NF → 最高只到 1NF。

规范化分解:拆成 R1(学号, 课程号, 成绩) 与 R2(课程号, 课程名),两者都达到 3NF,且分解既无损连接又保持函数依赖。

一个必须记住的前提:单属性主键的表天然满足 2NF —— 单属性主键没有「真子集」可依赖,部分依赖无从谈起。所以 2NF 的坑只出现在复合主键上。

【完整推导·部分依赖与 2NF】

text
定义回顾:
  完全函数依赖:X→Y 且 X 的任何真子集都不能决定 Y
  部分函数依赖:存在 X 的真子集 X′,X′→Y
  2NF:消除非主属性对候选键的部分依赖
本题:
  X=(学号,课程号),Y=课程名,X′=课程号
  课程号→课程名 成立 ⇒ 部分依赖 ⇒ 违反 2NF
为何不是传递依赖?
  传递需要:X→Z→Y 且 Z↛X 且 Y 非主属性
  本题课程名直接由课程号决定,没有「绕弯」的中间非主属性链条
分解验证矩阵:
| 分解 | 主键 | 非主属性 | 部分依赖? | 传递依赖? | 范式 |
| R1(学号,课程号,成绩) | (学号,课程号) | 成绩 | 无 | 无 | 3NF |
| R2(课程号,课程名) | 课程号 | 课程名 | 不可能(单属性键) | 无 | 3NF |

【记忆锚点】 「先看部分依赖(2NF),再看传递依赖(3NF)」——判范式像爬楼梯,2NF 没过,3NF 免谈。

【易混对比】

  • 部分依赖 vs 传递依赖:部分依赖是「只靠主键的一半」,传递依赖是「绕了一道弯(A → B → C)」。前者卡 2NF,后者卡 3NF。
  • 2NF vs 3NF:2NF 只可能被复合主键违反;3NF 任何主键都可能违反。
  • 非主属性 vs 主属性:2NF、3NF 只管非主属性;BCNF 连主属性一起管(见补-13)。
  • 换问法:若题干把依赖改成「学号 → 姓名」(学号也是主键的一部分),仍是部分依赖;若改成「学号 → 系号、系号 → 系名」,则变成传递依赖(卡 3NF)。一字之差,卡在不同关卡。

【自测】 关系 R(学号, 姓名, 系号, 系名),主键为单属性「学号」,存在「学号 → 系号」「系号 → 系名」。R 最高满足第几范式?

答:2NF。主键是单属性,不存在部分依赖(满足 2NF);但「系号 → 系名」构成传递依赖,违反 3NF。与补-13、补-14 连考;国网真题库同型题。

【选项误解】

  • A 误解来源:跳跃判定——未先检查 2NF 就直奔 3NF。范式必须逐级满足。
  • B 误解来源:把「部分依赖」说成「完全依赖」,与事实相反。
  • C 误解来源:错误前提「已满足 2NF」;后半句再对也无效。
  • D 正确项:复合主键下存在「课程号→课程名」,非主属性课程名只依赖主键一部分 → 违反 2NF → 最高 1NF。

【知识关联】

  • 主库关联:主库无范式题(已知缺口);反范式化与 M30 分库分表、M04 覆盖索引设计相关——工程上常为性能做可控冗余。
  • 国网/408:国网第 11 条核心;408 数据库大题必考范式判断 + 分解。
  • 面试追问:① 单属性主键还需要检查 2NF 吗?→ 不需要,天然满足(无真子集可部分依赖)。② 反范式化怎么落地?→ 适当冗余、汇总表、宽表,用缓存/补偿保持最终一致。③ 本题如何分解?→ R1(学号,课程号,成绩) + R2(课程号,课程名),无损且保持 FD。

【拓展延伸】

  • 变式问法:①「存在部分依赖 → 违反(2NF)」;②「存在传递依赖 → 违反(3NF)」;③「单属性主键 + 传递依赖 → 最高 2NF」。
  • 完整判定推导:
text
关系:R(学号, 课程号, 成绩, 课程名)
候选键:先求闭包。学号⁺ 含成绩?不一定;课程号⁺ 含课程名,但推不出学号成绩
       (学号,课程号)⁺ = {学号,课程号,成绩,课程名} → 是候选键(唯一极小键)
非主属性:成绩、课程名
FD:
  (学号,课程号) → 成绩   完全依赖(少任何一半都定不了成绩)
  课程号 → 课程名        部分依赖(课程名只需主键的一半)
判定:
  1NF:属性原子 ✓
  2NF:存在非主属性对候选键的部分依赖 ✗
结论:最高 1NF
分解:
  R1(学号,课程号,成绩):主键(学号,课程号),无部分依赖,且无传递 → 3NF
  R2(课程号,课程名):主键课程号,课程名完全依赖 → 3NF
  分解无损:R1∩R2=课程号,课程号→课程名 在 R2 中
  保持函数依赖:原 FD 均被 R1 或 R2 保持
  • 工程提醒:笔试按范式答「最高 1NF」;实际业务中选课表常故意冗余课程名以减 JOIN,属反范式,需应用或触发器同步。

补-13 ​

关于 3NF 与 BCNF 的关系,下列说法正确的是( )。 A. BCNF 的约束条件比 3NF 更宽松 B. 满足 BCNF 的关系不一定满足 3NF C. 满足 BCNF 的关系一定满足 3NF,但满足 3NF 的关系不一定满足 BCNF D. 二者在定义上完全等价

答案:C

📌 考点定位: 范式——3NF 与 BCNF 的层级关系,对应国网考纲第 11 条。

【结论】 BCNF 比 3NF 更严,满足 BCNF 必然满足 3NF,反之不成立。选 C。

【逐项辨析】

  • A BCNF 的约束比 3NF 更宽松:错。错在「宽松」二字 —— BCNF 是在 3NF 之上加强约束,只能说更严。
  • B 满足 BCNF 的关系不一定满足 3NF:错。错在把蕴含方向说反 —— 层级是 1NF ⊂ 2NF ⊂ 3NF ⊂ BCNF,BCNF 是 3NF 的真子集。
  • C 满足 BCNF 一定满足 3NF,满足 3NF 不一定满足 BCNF:正确。 3NF 只管「非主属性」,BCNF 连「主属性」也管,条件更强,故前者成立、后者不成立。
  • D 二者定义上完全等价:错。错在「等价」二字 —— 等价意味着互相蕴含,而「3NF ⇒ BCNF」并不成立。

【知识点】 两个范式的定义对比(设 R 的函数依赖集为 F):

范式判定条件约束对象消除的依赖
2NF无非主属性对候选键的部分依赖仅非主属性部分依赖
3NF对每个非平凡依赖 X → Y,X 是超键 或 Y 是主属性仅非主属性部分 + 传递依赖
BCNF对每个非平凡依赖 X → Y,X 必为超键全部属性部分 + 传递依赖(含主属性)

推导「BCNF ⇒ 3NF」:设 R 满足 BCNF,任取非平凡依赖 X → Y,则 X 是超键 —— 这恰好满足 3NF 的「X 是超键或 Y 是主属性」(前半支成立即可),故 R 必然满足 3NF。方向不可逆。

反向不成立的标准反例: 关系 R(学生, 课程, 教师),语义为「每个教师只教一门课,每门课可由多位教师讲授」,候选键为 (学生, 课程) 与 (学生, 教师)。

  • 函数依赖:(学生, 课程) → 教师、(学生, 教师) → 课程、教师 → 课程。
  • 3NF 判定:前两个依赖的左部是候选键(超键);第三个依赖 教师 → 课程 左部虽非超键,但右部「课程」是主属性 → 满足 3NF。
  • BCNF 判定:教师 → 课程 中「教师」不是超键 → 违反 BCNF。

这个反例正好说明两者的分界:3NF 只要求「非主属性别出问题」,BCNF 要求「连主属性也不能被非超键决定」。

补充一条实践口径:3NF 分解可以做到既无损连接又保持函数依赖;BCNF 分解只能保证无损连接,不保证保持函数依赖。这正是实际设计中 3NF 仍被大量采用的原因。

【完整推导·BCNF ⇒ 3NF】

text
3NF 形式化:对 R 中每个非平凡 FD X→Y,
  或者 X 是超键,或者 Y 是主属性(含于任一候选键)
BCNF 形式化:对 R 中每个非平凡 FD X→Y,X 必为超键
证明 BCNF⇒3NF:
  设 R 满足 BCNF,任取非平凡 X→Y
  由 BCNF,X 是超键
  则 3NF 条件「X 是超键 OR Y 是主属性」的前半支已成立
  故 R 满足 3NF
逆命题不成立:见「教师→课程」反例
集合关系:BCNF ⊂ 3NF ⊂ 2NF ⊂ 1NF(真子集)
分解性质对比:
| 范式分解 | 无损连接 | 保持 FD |
| 3NF 合成法 | ✓ | ✓ |
| BCNF 分解 | ✓ | 不一定 |

【记忆锚点】 「3NF 只管非主属性,BCNF 连主属性一起管;3NF ⊃ BCNF」。

【易混对比】

  • 3NF vs BCNF:前者允许「主属性传递依赖于候选键」,后者不允许。判 BCNF 只看一句话 —— 每个决定因素是不是超键。
  • BCNF vs 4NF:BCNF 处理函数依赖;4NF 再处理多值依赖(消除非平凡多值依赖)。
  • BCNF 分解 vs 3NF 分解:BCNF 无损连接但不一定保持函数依赖;3NF 两者都能保证。
  • 换问法:若题目问「满足 3NF 是否一定满足 BCNF」,答案是不一定(这正是本题 C 项的后半句)。

【自测】 关系 R(学生, 课程, 教师),满足 教师 → 课程,候选键为 (学生, 课程) 与 (学生, 教师)。R 最高满足第几范式?

答:3NF。教师 → 课程 的决定因素「教师」不是超键,故违反 BCNF;但右部「课程」是主属性,3NF 允许。与补-12、补-14 连考;国网真题库同型题。

【选项误解】

  • A 误解来源:把「更严」理解成「更宽松」。BCNF 在 3NF 上加强了条件。
  • B 误解来源:蕴含方向说反。层级是 1NF⊂2NF⊂3NF⊂BCNF,BCNF⇒3NF 成立,反向不成立。
  • C 正确项:BCNF 满足必满足 3NF;3NF 不一定 BCNF(反例:教师→课程 且 教师非超键)。
  • D 误解来源:认为同义。若等价则互相蕴含,与标准结论矛盾。

【知识关联】

  • 主库关联:无直接对应(原理层缺口);工程上表设计常停在 3NF+可控反范式。
  • 国网/408:国网第 11 条;408 常考「判断到 BCNF/给出反例」。
  • 面试追问:① BCNF 分解一定能保持 FD 吗?→ 不一定,只能保证无损连接;3NF 分解可同时无损+保 FD。② 为何工程常用 3NF 而非强推 BCNF?→ 3NF 在无损与保依赖间更平衡,更新异常已大幅减少。③ 4NF 相对 BCNF 多处理什么?→ 多值依赖。

【拓展延伸】

  • 变式问法:①「满足 BCNF 是否一定 3NF?」→ 一定;②「满足 3NF 是否一定 BCNF?」→ 不一定;③「判 BCNF 的一句话」→ 每个非平凡 FD 的决定因素都是超键。
  • 完整反例推导:
text
R(学生,课程,教师)
业务:每位教师只教一门课;每门课可多位教师;每位学生可选多门课/多位教师
FD:
  (学生,课程)→教师
  (学生,教师)→课程
  教师→课程
候选键:(学生,课程) 与 (学生,教师)
主属性:学生、课程、教师(都在某个候选键中)
3NF 判定(对每个非平凡 FD,要求 X 是超键 OR Y 是主属性):
  (学生,课程)→教师:X 是超键 ✓
  (学生,教师)→课程:X 是超键 ✓
  教师→课程:X=教师 不是超键;但 Y=课程 是主属性 ✓
  ⇒ 满足 3NF
BCNF 判定(要求每个 X 都是超键):
  教师→课程:教师 不是超键 ✗
  ⇒ 违反 BCNF
结论:3NF 但非 BCNF
  • 工程对照:若把「教师→课程」拆成 Teacher(教师,课程) 与 SC(学生,教师),可到 BCNF,但查询「学生-课程」需要多一次连接——性能与规范化的权衡。

补-14 ​

若 X → Y、Y → Z,且 Y 不能决定 X(Y ↛ X),则称 X → Z 为( )。 A. 部分函数依赖 B. 传递函数依赖 C. 多值依赖 D. 平凡函数依赖

答案:B

📌 考点定位: 函数依赖的分类,范式判定的理论基础,对应国网考纲第 11 条。

【结论】 X → Y、Y → Z 且 Y ↛ X,中间「绕了一道弯」,称 X → Z 为传递函数依赖。选 B。

【逐项辨析】

  • A 部分函数依赖:错。错在「部分」这个字眼 —— 部分依赖的判据是「存在 X 的真子集 X′ 使 X′ → Y」,与本题的两段链条无关。
  • B 传递函数依赖:正确。 题干给出的正是 X → Y、Y → Z 且 Y 不能反过来决定 X 的标准三段式。
  • C 多值依赖:错。错在「多值」二字 —— 多值依赖记作 X ↠ Y,描述的是「一组值独立于另一组值」,属 4NF 范畴,不是函数依赖的链式形式。
  • D 平凡函数依赖:错。错在「平凡」二字 —— 平凡依赖的判据是「Y ⊆ X」(被决定属性是决定属性的子集,如 AB → A),本题的 Y、Z 与 X 无包含关系。

【知识点】 函数依赖 X → Y 的定义:对 R 的任意两个元组,若它们在 X 上的取值相同,则它们在 Y 上的取值也必然相同(X 函数决定 Y)。

四类依赖的定义与判据:

依赖类型形式化判据判定关键影响的范式
平凡函数依赖X → Y 且 Y ⊆ X右部是左部的子集恒成立,不影响范式
非平凡函数依赖X → Y 且 Y ⊄ X右部含左部之外的属性需具体判定
完全函数依赖X → Y 且不存在 X 真子集能决定 Y必须靠整个 X2NF 要求非主属性完全依赖
部分函数依赖存在 X 的真子集 X′ 使 X′ → Y靠 X 的一部分就够违反 2NF
传递函数依赖X → Y、Y → Z 且 Y ↛ X绕了一道弯,中间属性不能反向决定违反 3NF
多值依赖X ↠ Y一组值独立对应另一组值违反 4NF

推导「为什么 Y ↛ X 这个限定不可省」:若 Y → X 也成立,则 X 与 Y 互相决定(X ↔ Y),此时 Z 其实是被 Y(等价于 X)直接决定的,并没有「绕过中间属性」,故不构成传递依赖。所以题干特意强调「Y 不能决定 X(Y ↛ X)」——这句话是判定的必要条件,不是废话。注意别把它和「Y ⊄ X(右部不在左部之内,用来区分平凡依赖)」混为一谈:那是另一个条件。

由此得到 3NF 的完整表述:非主属性既不部分依赖、也不传递依赖于候选键。两条否定,一条对应 2NF、一条对应 3NF。

【完整推导·依赖分类速查】

类型形式卡哪个范式例
平凡X→Y, Y⊆X不影响AB→A
非平凡X→Y, Y⊄X需判定A→B
完全任意真子集不能决定 Y2NF 要求(学号,课号)→成绩
部分某真子集可决定 Y违反 2NF课程号→课程名
传递X→Y→Z, Y↛X违反 3NF学号→系号→系名
多值X↠Y违反 4NF课程↠教材(一课多教材独立于教师)

判定口诀:先看真子集(部分),再看有没有弯(传递),最后看多值独立(4NF)。

【记忆锚点】 「部分依赖靠「一半」(真子集),传递依赖绕「一道弯」(A → B → C)」。

【易混对比】

  • 部分依赖 vs 传递依赖:前者是「主键只用了一半」,后者是「中间隔了一层」。前者卡 2NF,后者卡 3NF。
  • 传递依赖 vs 多值依赖:前者是函数依赖的链式组合(→),后者是 ↠,描述独立的多值对应,属 4NF。
  • 平凡依赖 vs 非平凡依赖:唯一判据是「右部是否为左部的子集」。
  • 换问法:若题干改成「X → Y 且存在 X 的真子集 X′ → Y」,答案就变成部分函数依赖;若改成「Y ⊆ X」,答案是平凡函数依赖。

【自测】 关系 R(学号, 系号, 系主任),有 学号 → 系号、系号 → 系主任,且多个学生共享同一系号(即 系号 ↛ 学号)。学号 → 系主任 属于哪种依赖?R 最高满足第几范式?

答:传递函数依赖;主键为单属性「学号」,无部分依赖(满足 2NF),但存在传递依赖,故最高 2NF。与补-12、补-13 连考。

【选项误解】

  • A 误解来源:与「部分依赖」混淆。部分依赖的判据是「真子集可决定」,不是链式两步。
  • B 正确项:X→Y→Z 且 Y↛X,标准传递依赖定义。
  • C 误解来源:多值依赖记作 X↠Y,描述独立多值对应,属 4NF,不是函数依赖链。
  • D 误解来源:平凡依赖判据是 Y⊆X(如 AB→A),与链式无关。

【知识关联】

  • 主库关联:无直接对应;表设计与补-12/13 连考。
  • 国网/408:国网第 11 条;408 计算题常给出 FD 集要求「指出传递依赖/判断范式」。
  • 面试追问:① 为何传递依赖定义要求 Y↛X?→ 若 Y→X,则 X↔Y,Z 实质被 X 直接决定,无「传递」。② 传递依赖一定违反 3NF 吗?→ 当 Z 为非主属性时是;若涉及主属性则看 BCNF。③ 如何消除传递依赖?→ 分解出 Y→Z 到新表。

【拓展延伸】

  • 变式问法:①「X→Y 且存在真子集 X′→Y」→ 部分依赖;②「Y⊆X」→ 平凡依赖;③「X↠Y」→ 多值依赖。
  • 完整推导:
text
给定:学号→系号,系号→系名,且 系号↛学号(多生共享一系)
链:学号→系号→系名
因为 系号↛学号,满足传递依赖条件
故 学号→系名 是传递函数依赖
主键=学号(单属性)→ 无部分依赖 → 满足 2NF
存在传递依赖且系名非主属性 → 违反 3NF → 最高 2NF
分解:R1(学号,系号) + R2(系号,系名) → 均达 3NF
  • 工程提醒:冗余「系名」到学生表是常见反范式,换系时必须同步更新或接受短暂不一致。

补-15 ​

在 ER 模型中,若一个班级有多名学生,而一名学生只属于一个班级,则班级与学生之间的联系类型是( )。 A. 一对一(1:1) B. 多对多(m:n) C. 一对多(1:n),其中「多」在班级一侧 D. 一对多(1:n),其中「多」在学生一侧

答案:D

📌 考点定位: ER 模型与联系类型,对应国网考纲第 11、15 条,国企笔试高频。

【结论】 一个班级对应多名学生、一名学生只对应一个班级 → 班级 : 学生 = 1 : n,「多」在学生一侧。选 D。

【逐项辨析】

  • A 一对一(1:1):错。错在「一对一」——1:1 要求两端都是「一」(一个班只有一名学生),题干明确说「多名学生」。
  • B 多对多(m:n):错。错在「多对多」——m:n 要求两端都是「多」(一名学生也能属于多个班级),题干限定「只属于一个班级」。
  • C 一对多,但「多」在班级一侧:错。错在方位说反 —— 一个班级对应多名学生,「多」必然在学生一侧,不在班级一侧。
  • D 一对多(1:n),「多」在学生一侧:正确。 基数上班级为 1、学生为 n,与题干两句约束完全吻合。

【知识点】 ER 模型三要素:实体(矩形)、属性(椭圆)、联系(菱形),联系两端标注基数。

联系类型由「每一端的实例能对应另一端的几个实例」决定:

联系类型记法判定(A、B 两实体)ER 转关系模式的做法
一对一1:1A 的一个实例只对应 B 的一个,反之亦然合并为一张表,或在任一侧加外键
一对多1:nA 的一个实例对应 B 的多个;B 的一个实例只对应 A 的一个在 n 端(多端)加外键,指向 1 端主键
多对多m:n两侧都是「多」必须新建联系表,主键为两端主键的组合

本题推导:题干给了两句约束 ——「一个班级有多名学生」说明班级端指向学生端可取多个值;「一名学生只属于一个班级」说明学生端指向班级端只能取一个值。前者定「多」,后者定「一」,故为 1:n 且多在学生侧。

若把第二句改成「一名学生可选多个班级」,则两侧都是多 → m:n,必须拆出独立的选课关系表。

转换规则补充:1:n 联系转换时外键必须加在 n 端(即学生表增加「班级号」列)。不能加在 1 端 —— 否则班级表要在一个格子里存多个学生号,直接违反 1NF。这是「联系类型」与「ER 转关系模式」两个考点连考时的固定套路(见补-16)。

【记忆锚点】 「「多」的那一端拿外键」——一个班有多名学生,所以「班级号」放进学生表。

【易混对比】

  • 1:n vs m:n:看两端能否各自取多值。1:n 只需一端为多;m:n 两端都多,必须建联系表。
  • 1:1 vs 1:n:1:1 两端都是一;1:n 一端是一、一端是多。
  • 联系类型 vs 实体个数:联系类型由基数决定,与参与实体的个数(二元 / 三元)无关。
  • 换问法:若题干改成「一名学生可选多个班级、一个班级有多名学生」,答案变成 m:n;若改成「一个班只有一名班长、一名学生只能当一个班的班长」,则是 1:1。

【自测】 某系统规定「一名教师可以讲授多门课程,一门课程只能由一名教师讲授」。教师与课程的联系类型是什么?转关系模式时外键应加在哪张表?

答:1:n(「多」在课程一侧);外键「教师号」加在课程表,指向教师表主键。与补-16 连考;国网考纲第 11、15 条。

【选项误解】

  • A 误解来源:把「一个班多名学生」与「一对一」混听。1:1 要求两端都是一。
  • B 误解来源:只听到「多名」就选多对多,漏掉「学生只属于一个班」。
  • C 误解来源:一对多方向记反。「多」在学生侧不在班级侧。
  • D 正确项:班级 1 : 学生 n,多在学生侧。

【知识关联】

  • 主库关联:M30(分库分表时常按「班级/租户」维度拆,前提是 1:n 归属清晰)。
  • 国网/408:国网第 11、15 条;408 ER 转关系模式大题第一步就是判断联系类型。
  • 面试追问:① 1:n 联系外键加哪边?→ n 端(学生表加 class_id)。② m:n 为什么必须中间表?→ 否则一侧要存多值,违反 1NF。③ 1:1 何时合并成一张表?→ 两边属性都稳定访问时;否则仍拆表 + 单向外键 + UNIQUE。

【拓展延伸】

  • 变式问法:①「一师多课、一课一师」→ 1:n,多在课程侧,外键教师号进课程表;②「学生可选多课、课可被多生选」→ m:n,建 SC 表主键(sno,cno);③「一班一班长、一人只当一个班班长」→ 1:1。
  • 工程参数/命令:
sql
-- 1:n 落地:学生表持有班级外键
CREATE TABLE class (
  id BIGINT PRIMARY KEY,
  name VARCHAR(64) NOT NULL
);
CREATE TABLE student (
  id BIGINT PRIMARY KEY,
  name VARCHAR(64) NOT NULL,
  class_id BIGINT,
  FOREIGN KEY (class_id) REFERENCES class(id)
);
-- m:n 落地
CREATE TABLE sc (
  sno BIGINT, cno BIGINT, grade DECIMAL(5,2),
  PRIMARY KEY (sno,cno),
  FOREIGN KEY (sno) REFERENCES student(id),
  FOREIGN KEY (cno) REFERENCES course(id)
);
  • 设计口诀:谁是「多」,谁拿外键;多对多,中间表。

持续学习,持续积累。