函数依赖、规范化与 ER 建模

共 66 题
📑 题目列表 66 题
#
★★★

1. 3NF 与 BCNF 的差异,BCNF 要求每个非平凡函数依赖的左侧都是超键?

第三范式(3NF)与鲍依斯-科德范式(BCNF)的差异是什么?为什么 BCNF 要求每个非平凡函数依赖的左侧都是超键?

  • 3NF 与 BCNF 的精确定义
  • BCNF 比 3NF 更严格的原因
  • 非平凡函数依赖左侧为超键的含义

3NF 要求消除非主属性对候选键的传递依赖,即每个非主属性既不部分依赖也不传递依赖任何候选键;等价地,对每个非平凡函数依赖 X→Y,要么 X 是超键,要么 Y 是主属性(即 Y 属于某个候选键)。BCNF 则要求每个非平凡函数依赖 X→Y 的左侧 X 都必须是超键,不区分 Y 是否为主属性。因此 BCNF 是 3NF 的严格强化:任何 BCNF 关系都满足 3NF,但反之不然。举例如关系 R(仓库, 物品, 管理员),若存在 "仓库→管理员" 且候选键为 (仓库,物品),则 "仓库→管理员" 的左侧不是超键,但管理员是主属性,所以该关系满足 3NF 却不满足 BCNF,仍存在冗余(每个仓库的管理员重复存储)。

BCNF 通过"左侧必须是超键"这一更严格条件,彻底消除了由主属性之间的依赖导致的冗余。3NF 允许"左侧非超键但右侧是主属性"的依赖,这是为了换取依赖保持性(BCNF 分解可能丢失依赖保持),实践中若业务对冗余敏感则优先 BCNF。

-- 演示 3NF 但非 BCNF 的关系(冗余随时间产生)
CREATE TABLE Warehouse_Keeper (
    warehouse_id INT,
    item_id      INT,
    keeper_name  TEXT,
    PRIMARY KEY (warehouse_id, item_id)
    -- keeper_name 由 warehouse_id 决定,但 (warehouse_id) 非超键
    -- 因此满足 3NF 不满足 BCNF
);
#
★★★

2. 候选键的求解算法,属性闭包(Attribute Closure)X+ 的计算?

候选键的求解算法是什么?请说明属性闭包(Attribute Closure)X+ 的计算过程?

  • 属性闭包的定义与计算算法
  • 用闭包判断超键与候选键
  • 从属性集出发逐步推导闭包

属性闭包 X+ 表示从属性集 X 出发,由给定函数依赖集 F 能推导出的所有属性集合。计算算法:令 result = X,然后反复扫描 F 中的每个依赖 A→B,若 A⊆result 则将 B 并入 result,直到 result 不再增长。候选键求解:属性集 K 是超键当且仅当 K+ = 全部属性;K 是候选键当且仅当 K+ = 全部属性且 K 的任何真子集的闭包都不等于全部属性。实际中常从"只在依赖左侧出现"的属性出发必属候选键,逐步扩展试探。例如 R(A,B,C,D),依赖 {A→B, B→C, C→D},则 {A}+ = {A,B,C,D},故 A 是候选键。

闭包是规范化理论的基石,判断候选键、验证函数依赖、求解最小覆盖、检查分解是否无损都依赖闭包计算。闭包算法是多项式时间的,若闭包等于全部属性则满足超键条件。

-- 概念性算法演示(伪SQL示意)
-- 函数依赖:A→B, B→C, C→D,求 {A}+
-- 初始 result = {A}
-- 应用 A→B: result = {A,B}
-- 应用 B→C: result = {A,B,C}
-- 应用 C→D: result = {A,B,C,D}  => 闭包覆盖全部属性,A 是候选键
#
★★★

3. 函数依赖(FD)的形式化定义,X → Y 的语义与公理化系统?

函数依赖(Functional Dependency, FD)的形式化定义是什么?X → Y 的语义与公理化系统是什么?

  • 函数依赖的形式化定义
  • 平凡函数依赖与非平凡函数依赖
  • Armstrong 公理化系统

函数依赖 X→Y 表示:在关系 R 中,若任意两个元组 t1、t2 满足 t1[X]=t2[X],则必有 t1[Y]=t2[Y],即 X 的值函数决定 Y 的值。若 Y⊆X 则称平凡函数依赖(必成立),否则为非平凡函数依赖。公理化系统即 Armstrong 公理:自反律(若 Y⊆X 则 X→Y)、增广律(若 X→Y 则 XZ→YZ)、传递律(若 X→Y 且 Y→Z 则 X→Z)。该公理系统是可靠的(只能推出成立的依赖)且完备的(能从 F 推出所有逻辑蕴含的依赖),是求闭包与规范化算法的理论基础。

函数依赖刻画了"属性间的关系",是规范化的基础。识别函数依赖是判断范式、发现冗余的第一步。X→Y 的语义强调"同一 X 必然同一 Y",是唯一性约束(主键、唯一键)的本质。

#
★★★

4. 最小函数依赖集(Minimal Cover)的求解算法,右部单属性化、去除冗余、化简左侧?

最小函数依赖集(Minimal Cover)的求解算法是什么?如何通过右部单属性化、去除冗余、化简左侧来实现?

  • 最小函数依赖集的定义
  • 三步算法:右部单属性化、去除冗余依赖、化简左侧
  • 与原始依赖集等价

最小函数依赖集 Fm 是与 F 逻辑等价、且满足以下三个条件的依赖集:右部均为单属性、不含冗余依赖(去掉任一依赖后不再等价)、左侧无冗余属性(去掉左侧任一属性后不等价)。求解算法分三步:①右部单属性化:将每个 X→Y(Y 为多属性)分解为 X→y1, X→y2, ...;②去除冗余依赖:对每个依赖 X→Y,检查在去掉该依赖后,能否由剩余依赖推出 X→Y(即用剩余依赖求 X+ 看是否包含 Y),若能则删除;③化简左侧:对每个依赖 X→Y,检查 X 的每个属性 A,若去掉 A 后 (X-A)+ 仍能推出 Y,则从 X 中删除 A。最终结果与 F 等价且最小。

最小覆盖用于 3NF 分解、依赖保持性验证等场景。需注意最小覆盖不唯一(不同顺序可能得到不同结果),但等价性保证了正确性。求解时先做右部单属性化是基础,因为最小覆盖要求右部为单属性。

#
★★★

5. 范式选择的约束冲突,高范式 vs 性能(反范式)的实际考量?

范式选择的约束冲突是什么?高范式与性能(反范式)之间如何权衡?

  • 高范式的冗余消除与依赖保持
  • 反范式对查询性能的优化
  • 实际业务中的权衡

高范式(3NF、BCNF)通过消除冗余减少更新异常、保证数据一致性,但代价是表拆得更细、查询需要更多 JOIN,这在读多写少的场景可能降低性能。反范式(Denormalization)主动引入冗余,减少 JOIN、可就地取数,提升读性能,但带来更新一致性维护成本(需同步冗余字段)。实际考量的核心是读写的比例与一致性要求:OLTP 交易系统通常以 3NF 为主,适度的反范式用于高频查询;OLAP 数据仓库则普遍采用星型模型等反范式设计。需权衡更新频率、查询模式、并发与一致性需求。

范式与反范式不是非此即彼,而是"以一致性换性能"的谱系。选择时要先识别核心查询模式,再决定哪些表保持高范式、哪些表做冗余或预聚合。反范式的数据一致性通常通过应用层、触发器或 CDC 保证。

#
★★★

6. 连接依赖(JD),五范式与无损分解的关系?

连接依赖(Join Dependency, JD)是什么?第五范式(5NF)与无损分解的关系是什么?

  • 连接依赖的定义
  • 5NF(PJNF)的定义
  • 无损分解与连接依赖的关系

连接依赖 JD(R1,R2,...,Rn) 表示关系 R 可以无损分解为多个投影 R1..Rn,即 R 等价于这些投影的自然连接。5NF(又称投影-连接范式,PJNF)要求:每个连接依赖都被 R 的某个候选键蕴含,即不存在"非平凡、非候选键蕴含"的连接依赖。若一个关系不满足 5NF,则存在不是由主键决定的连接依赖,拆解后能减少冗余。5NF 是最严格的无损连接范式,只有极少数实际问题涉及(超出 4NF 的连接依赖很少见)。无损分解是规范化的核心性质:任何依赖保持的分解都应保证无损连接,即分解后能通过自然连接恢复原关系。

函数依赖是多值依赖的特例,多值依赖是连接依赖的特例(4NF 处理多值依赖,5NF 处理更一般的连接依赖)。实践中 5NF 以下的 BCNF 已能解决绝大多数冗余,5NF 主要用于理论完备性。

#
★★★

7. 更新异常(Update Anomaly)的三种类型与各范式的解决方案?

更新异常(Update Anomaly)的三种类型是什么?各范式如何解决它们?

  • 插入异常、删除异常、修改异常
  • 各范式对异常的处理
  • 规范化的作用

更新异常指未充分规范化导致的三类数据不一致问题:①插入异常(Insertion Anomaly):因主键部分依赖,无法插入某些信息(如未赋主键某部分的值就无法插入);②删除异常(Deletion Anomaly):删除某一信息时连带删除了本应保留的其他信息;③修改异常(Modification Anomaly):修改一个值需在多个地方重复修改,否则不一致。解决方案:1NF 消除非原子属性;2NF 消除非主属性对候选键的部分依赖(解决插入和删除异常的主因);3NF 消除非主属性对候选键的传递依赖;BCNF 进一步消除主属性间的依赖。规范化越彻底,三类异常越少。

更新异常的本质是冗余——同一事实存储多处,导致维护不一致。规范化的目标就是通过消除冗余使"每个事实只存储一次",从而从根源上避免更新异常。

#
★★★

8. 传递函数依赖 X → Y → Z 的识别?

传递函数依赖 X → Y → Z 如何识别?它如何影响范式?

  • 传递依赖的定义
  • 识别方法
  • 对 3NF 的影响

传递函数依赖 X→Y→Z 指存在非平凡依赖 X→Y、Y→Z,且 Y 不函数决定 X(即 Y 不是 X 的超键),且 X 不函数决定 Z(Z 不在 X 中)。此时 Z 通过中间的 Y 间接依赖 X,形成传递依赖。识别方法:检查是否存在中间属性 Y,使得 X→Y、Y→Z 成立,且去掉 X 后 Z 仍只由 Y 决定。若存在传递依赖,则关系可能不满足 3NF(当 Z 是非主属性时)。例:表(学号, 系别, 系主任),有 学号→系别、系别→系主任,则 学号→系主任 是传递依赖,需拆分。

识别传递依赖是判断 3NF 的关键。拆解传递依赖(把 Y 和 Z 抽出成独立表)可消除冗余导致的更新异常。

#
★★★

9. 函数依赖 X → Y 与 X → → Y 的区别?

函数依赖 X → Y 与多值依赖 X →→ Y 的区别是什么?

  • 函数依赖与多值依赖的定义
  • 从属关系
  • 对范式的影响

函数依赖 X→Y 表示属性 X 的每个值唯一决定一个 Y 值(一对一对应);多值依赖 X→→Y 表示 X 的每个值对应 Y 的一组值,且这组值独立于其余属性(一对多对应)。函数依赖是多值依赖的特例:多值依赖中的"每组只有单个 Y 值"退化为函数依赖。多值依赖用于处理 4NF:当关系存在非平凡的多值依赖且 X 不是超键时,应拆分表以消除冗余。典型例子:课程、教师、教材三者的完全多值依赖,需拆成两个二元关系。

函数依赖隐藏"一对一"或"一对多"的确定性,多值依赖隐藏"多对多"的独立性。4NF 专门处理多值依赖,而函数依赖在 3NF/BCNF 中处理。二者都是规范化的分析工具。

#
★★★

10. 非平凡函数依赖 X → Y 中 Y ⊈ X 的含义?

非平凡函数依赖 X → Y 中 Y ⊈ X 的含义是什么?

  • 平凡与非平凡函数依赖
  • Y ⊈ X 的符号含义
  • 对规范化的意义

Y ⊈ X 表示 Y 不是 X 的子集,即 Y 中至少有一个属性不在 X 中。非平凡函数依赖 X→Y 是指 X 函数决定 Y,且 Y 包含 X 以外的属性。与之相对,若 Y⊆X 则为平凡函数依赖(必然成立,无信息量)。规范化的分析只关注非平凡函数依赖,因为平凡依赖不产生冗余。判断范式(如 BCNF 要求每个非平凡依赖左侧是超键)时都特指非平凡函数依赖。

平凡函数依赖是逻辑恒真,没有建模价值;非平凡函数依赖才体现属性间的真实约束。理解 Y ⊈ X 有助于正确识别哪些依赖需要被规范化处理。

#
★★★

11. ER 模型到逻辑模型的常见错误,M:N 误拆为 1:N、ISA 误用外键?

ER 模型到逻辑模型的常见错误有哪些?M:N 误拆为 1:N、ISA 误用外键是什么?

  • ER 到关系模型的转换规则
  • M:N 联系必须建中间表
  • ISA 继承的映射方式

常见错误包括:①M:N 联系未建中间表,错误地在外键多侧放外键(把 M:N 当 1:N 处理),导致信息丢失或冗余;正确做法是 M:N 必须新建连接表(关联表),存放两个外键。②ISA 继承误用外键:把子类当作父类的外键关联,而不是用继承映射策略(单表/类表/具体表)。③漏掉多值属性(多值属性需单独建表)。④1:1 联系外键放错侧。⑤弱实体集未正确使用部分键。这些错误会导致数据冗余、无法表达真实语义或查询困难。

ER 建模的核心是"联系是实体间的关系",M:N 需要中间表来表达;ISA 表达"是-是"的继承关系,需用继承表模式而非外键。掌握正确的转换规则可避免常见建模错误。

#
★★★

12. ISA 继承的转换,单表继承(Single Table)、类表继承(Class Table)、具体表继承(Concrete Table)?

ISA 继承的三种转换策略——单表继承(Single Table)、类表继承(Class Table)、具体表继承(Concrete Table)分别是什么?

  • 三种继承映射策略
  • 各自的优缺点
  • 适用场景

用员工/经理的 ISA 继承举例:①单表继承(Single Table):所有类(父类与子类)的属性合并到一张表,用类型判别列(type)区分具体类型。优点:查询简单、无 JOIN、性能好;缺点:子类特有字段产生大量 NULL、扩展性差、约束缺失。②类表继承(Class Table):每个类一张表,父类表存公共属性,子类表存特有属性并以父表主键作为主键和外键。优点:无冗余、规范化好、易扩展;缺点:查询子类需 JOIN 父表。③具体表继承(Concrete Table):每个具体类一张完整表,包含所有字段(含继承字段)。优点:无 JOIN、独立;缺点:公共字段冗余、多态查询困难(需 UNION ALL)。实际选择取决于查询模式、字段重叠程度与扩展需求。

三种策略是面向对象与关系模型的经典映射,Hibernate 等 ORM 也实现了这三种策略(SINGLE_TABLE、JOINED、TABLE_PER_CLASS)。单表最适合字段差异小、查询频繁的场景,类表最规范,具体表适合子类差异大。

-- 类表继承
CREATE TABLE person (id INT PRIMARY KEY, name TEXT);          -- 父类
CREATE TABLE student (id INT PRIMARY KEY, gpa NUMERIC,
    FOREIGN KEY (id) REFERENCES person(id));                  -- 子类
#
★★★

13. 一对一(1:1)联系的转换,外键放在哪一侧?

一对一(1:1)联系转换为关系模式时,外键应放在哪一侧?

  • 1:1 联系的外键放置规则
  • 完全参与与部分参与
  • 外键唯一约束

一对一联系的外键可以放在任意一侧,但通常放在"完全参与"(total participation)的一侧,即该侧每条记录都必须关联对方记录,从而避免 NULL。若一侧是部分参与(可能没有对应记录),则把外键放在完全参与侧,或用唯一约束保证一对一。若联系本身带有属性,则把这些属性并入外键所在表。外键列应加 UNIQUE 约束以保证一对一语义。

1:1 联系在逻辑上等价于把两个实体合并或通过外键关联。选择外键所在侧的原则是"减少 NULL、提升查询效率"。完全参与侧放外键最干净。

-- 员工与工牌:1:1,工牌完全参与(每个员工一张工牌)
CREATE TABLE employee (
    id   INT PRIMARY KEY,
    name TEXT
);
CREATE TABLE badge (
    employee_id INT PRIMARY KEY,           -- 外键同时作主键
    number      TEXT,
    FOREIGN KEY (employee_id) REFERENCES employee(id)
);
#
★★★

14. 一对多(1:N)联系的转换,外键放在多方?

一对多(1:N)联系转换为关系模式时,外键应放在哪一侧?

  • 1:N 联系外键放多方
  • 外键列不唯一
  • 联系属性并入多方

一对多(1:N)联系转换时,把"一"方的主键作为外键放入"多"方的表中,即外键放在多方。这样一方的多条记录可以被多方引用,且无需额外中间表。若联系带有属性,也并入多方表。外键列不要求唯一(可重复),因为"一"方的一条记录可对应多方多条记录。这是最常用的外键放置方式。

1:N 联系一方放外键不可行(无法表达多对一),只能放多方。外键列不唯一是 1:N 与 1:1 的区分点。

-- 部门(一) 与 员工(多):外键 dept_id 放员工表
CREATE TABLE department (
    id   INT PRIMARY KEY,
    name TEXT
);
CREATE TABLE employee (
    id      INT PRIMARY KEY,
    name    TEXT,
    dept_id INT REFERENCES department(id)   -- 外键放多方,可重复
);
#
★★★

15. PostgreSQL 中表继承(INHERITS)的实现,父表查询是否返回子表行?

PostgreSQL 中表继承(INHERITS)的实现是怎样的?父表查询是否返回子表行?

  • INHERITS 语法与继承机制
  • 父表查询默认包含子表行
  • ONLY 关键字

PostgreSQL 通过 CREATE TABLE child (...) INHERITS (parent) 创建子表,子表继承父表的所有列。默认情况下,查询父表会返回父表及所有子孙表的行,这是通过"继承扫描"实现的多态行为。若只想查询父表自身,需使用 ONLY 关键字。父表上的约束(如 CHECK)默认会继承到子表。注意:父表与子表之间没有类似外键的强约束,且父表无主键约束,需自行保证一致性。

表继承是 PG 的特色能力,用于实现多态或分区。父表查询默认包含子表是继承的核心语义,ONLY 用于精确控制范围。

CREATE TABLE measurement (city_id INT, logdate DATE, peaktemp INT);
CREATE TABLE measurement_2024 INHERITS (measurement);
-- 查询父表会包含子表行
SELECT * FROM measurement WHERE logdate >= '2024-01-01';
-- 只查父表自身
SELECT * FROM ONLY measurement;
#
★★★

16. 类表继承(Class Table Inheritance),每个类一张表,主键关联?

类表继承(Class Table Inheritance)如何实现?为什么子表以父表主键关联?

  • 类表继承的建表方式
  • 父子表主键关联
  • 查询 JOIN

类表继承中,每个类(父类与子类)各有一张表。父类表存公共属性,子类表存特有属性,子类表的主键同时作为外键引用父类表的主键,从而保证子类记录与父类记录一一对应。查询子类实例时需 JOIN 父表与子表。优点:无冗余、规范化好、易扩展新子类;缺点:查询需 JOIN、父表与子表一致性需应用层保证。

子类主键同时是父类外键,使得"一条子表记录必然对应一条父表记录",这是类表继承的核心。Hibernate 的 JOINED 策略即此实现。

CREATE TABLE person (id INT PRIMARY KEY, name TEXT);
CREATE TABLE student (
    id  INT PRIMARY KEY REFERENCES person(id),  -- 同时是主键与外键
    gpa NUMERIC
);
-- 查询学生及其姓名
SELECT p.name, s.gpa FROM person p JOIN student s ON p.id = s.id;
#
★★★

17. MySQL 中无原生继承,如何实现(单表加 type 列)?

MySQL 没有原生继承机制,如何实现类似继承的多态建模?

  • MySQL 无 INHERITS
  • 单表 + type 列实现
  • 其他模拟方式

MySQL 没有原生表继承语法,常用单表加 type 判别列实现多态:所有子类的字段放在一张表中,用 type 列区分具体类型,子类特有字段在非该类型行中为 NULL。除此之外也可用类表继承(父表 + 子表外键关联)或具体表继承(每类一张完整表)模拟。MySQL 无数据库级的继承约束,一致性需应用层或触发器保证。单表 + type 列最简单,但存在 NULL 冗余与约束缺失问题。

MySQL 与 PostgreSQL 不同,无继承,多态必须靠模式设计。单表 + type 列是最常用、最直观的方案,适合字段差异不大的场景。

CREATE TABLE animal (
    id     INT PRIMARY KEY,
    type   ENUM('cat','dog'),   -- 判别列
    name   TEXT,
    fur    INT NULL,            -- 仅 cat 使用
    barks  BOOLEAN NULL         -- 仅 dog 使用
);
#
★★★

18. PostgreSQL 中 ONLY 关键字与 INHERITS 的协同?

PostgreSQL 中 ONLY 关键字与 INHERITS 如何协同使用?

  • ONLY 限定查询范围
  • 与继承的关系
  • 分区裁剪

ONLY 关键字用于限定只操作父表本身,不包含继承的子表。在 SELECT、UPDATE、DELETE 等语句中,ONLY 可限制扫描范围。它与 INHERITS 协同:INHERITS 定义继承关系,ONLY 则精确控制查询/更新是否下探到子表。当配合分区时,ONLY 可避免全分区扫描。若不加 ONLY,父表查询默认包含所有子表。

ONLY 是控制继承树扫描范围的关键字,用于"只针对父表"的场景,避免误操作子表数据。

-- 只更新父表,不更新子表
UPDATE ONLY measurement SET peaktemp = 0;
-- 只查询父表
SELECT * FROM ONLY measurement;
#
★★★

19. PostgreSQL 中 partition 与 inheritance 的关系?

PostgreSQL 中分区(partition)与继承(inheritance)的关系是什么?

  • 声明式分区与继承的关系
  • 历史演进
  • 区别

在 PostgreSQL 10 之前,分区是通过继承(INHERITS)+ CHECK 约束 + 触发器/规则手工实现的。自 PG 10 起引入声明式分区(PARTITION BY),分区表在底层仍是一种继承表(父表与分区表),但由数据库自动管理分区键、约束与裁剪,无需手工维护。声明式分区是继承的"受控受限"版本:更安全、更简洁、支持分区裁剪。二者关系:继承是基础机制,声明式分区是其之上针对分区的专门封装。官方推荐使用声明式分区而非手工继承。

理解父表查询包含子表、ONLY、约束继承等继承特性,是理解声明式分区内部机制的基础。声明式分区把继承的复杂度封装起来。

CREATE TABLE measurement (
    city_id INT, logdate DATE, peaktemp INT
) PARTITION BY RANGE (logdate);
CREATE TABLE measurement_2024 PARTITION OF measurement
    FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
#
★★★

20. PostgreSQL 中约束 INHERIT 与 NO INHERIT?

PostgreSQL 中约束的 INHERIT 与 NO INHERIT 是什么?如何控制约束继承?

  • 约束的继承行为
  • NO INHERIT 选项
  • CHECK 约束默认继承

PostgreSQL 中,父表上的 CHECK 约束默认会继承到所有子表(子表插入必须满足父表约束)。选项 NO INHERIT 可让约束只作用于定义它的表,不传播到子表。主键、外键、唯一约束不会自动继承到子表(每个子表需独立定义)。约束的继承性通过 pg_constraint 的 conislocal、coninhcount 等字段记录。NO INHERIT 常用于父表约束只对父表有意义、不希望子表遵循的场景。

约束继承是继承机制的一部分,理解可避免"父表约束未作用到子表"或"子表意外继承"的问题。

CREATE TABLE parent (id INT, val INT CHECK (val > 0));  -- 默认继承到子表
CREATE TABLE child (pid INT) INHERITS (parent)
    CHECK (val < 100) NO INHERIT;   -- 只作用于 child,不继续继承
#
★★★

21. 现代 ORM(Hibernate)中的继承映射策略?

现代 ORM(如 Hibernate)中的继承映射策略有哪些?

  • SINGLE_TABLE
  • JOINED
  • TABLE_PER_CLASS

Hibernate 提供三种继承映射策略:①SINGLE_TABLE(单表):所有类字段合并到一张表,用 discriminator(判别)列区分类型,查询快、无 JOIN,但字段冗余、NULL 多;②JOINED(类表):每个类一张表,子表通过主键/外键关联父表,规范化、无冗余,但查询需 JOIN;③TABLE_PER_CLASS(具体表):每个具体类一张完整表,无 JOIN 但公共字段冗余、多态查询困难。选择取决于查询性能、字段重叠与扩展性需求。

这与关系模型中的单表/类表/具体表继承一一对应。Hibernate 通过 @Inheritance(strategy=...) 注解配置。

@Inheritance(strategy = InheritanceType.JOINED)  // 类表继承
@Entity
public abstract class Animal { @Id Long id; String name; }
@Entity
public class Dog extends Animal { String breed; }
#
★★★

22. PostgreSQL 中 PERIOD 类型的实现?

PostgreSQL 中 PERIOD 类型如何实现?

  • PG 无原生 PERIOD
  • 范围类型(range)
  • 用两列 + 范围类型

PostgreSQL 没有像 SQL:2011 那样的原生 PERIOD(时期)类型,但提供了范围类型(range types),如 daterange、tsrange、tstzrange,可表达一段时期。范围类型支持包含、重叠、并集等操作,并可用 GiST 索引加速。时态建模通常用两个时间列(start_time, end_time)或范围类型表示 period,配合 EXCLUDE 约束(如 GiST 排除约束)保证时间不重叠。SQL:2011 的 PERIOD 语法在 PG 中未完全实现,需靠应用或约束模拟。

范围类型是 PG 表达 period 的标准方式,配合 GiST 索引与 EXCLUDE 约束可实现时态约束(如预定时间不重叠)。

CREATE TABLE booking (
    room   INT,
    period tstzrange,
    EXCLUDE USING gist (room WITH =, period WITH &&)  -- 同一房间时间不重叠
);
#
★★★

23. SQL:2011 时态表(Temporal Table)的标准化,SYSTEM_TIME、APPLICATION_TIME?

SQL:2011 时态表(Temporal Table)标准中 SYSTEM_TIME 与 APPLICATION_TIME 是什么?

  • SQL:2011 时态表标准
  • SYSTEM_TIME(系统时间/事务时间)
  • APPLICATION_TIME(应用时间/有效时间)

SQL:2011 标准定义了时态表(Temporal Table)与 PERIOD 语法。SYSTEM_TIME(系统时间)表示数据库行的事务时间,由数据库自动维护,用于回答"数据库历史上某时刻该表的值是什么"(如闪回查询)。APPLICATION_TIME(应用时间)表示数据在现实世界中的有效时间,由应用维护,用于回答"该业务事实在现实世界中何时有效"。同时具有两种时间维度的表称为双时态表(Bi-Temporal Table)。SQL:2011 用 FOR SYSTEM_TIME AS OF 等子句查询历史版本。

SYSTEM_TIME 与 APPLICATION_TIME 双维度是时态建模的核心。SQL Server 2016+ 已实现 SYSTEM_TIME(系统版本化临时表),APPLICATION_TIME 的完整支持较少。

-- SQL Server 2016+ 系统版本化临时表(SYSTEM_TIME)
CREATE TABLE dbo.Employee (
    Id INT PRIMARY KEY,
    Name NVARCHAR(50),
    SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START,
    SysEndTime   DATETIME2 GENERATED ALWAYS AS ROW END,
    PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)
) WITH (SYSTEM_VERSIONING = ON);
-- 查询某时刻的历史
SELECT * FROM dbo.Employee FOR SYSTEM_TIME AS OF '2024-01-01';
#
★★★

24. 历史快照表(History Table)的设计模式,触发器、CDC、专用审计?

历史快照表(History Table)有哪些设计模式?触发器、CDC、专用审计分别如何实现?

  • 触发器历史表
  • CDC 历史表
  • 专用审计表

历史快照表用于记录数据的历史版本,常见模式:①触发器(Trigger):在源表上定义 INSERT/UPDATE/DELETE 触发器,把变更写入历史表,强一致、实现简单,但影响写入性能、维护成本高;②CDC(Change Data Capture):基于数据库日志捕获变更(如 SQL Server CDC、Debezium + Binlog),对业务无侵入、性能好,但需额外组件;③专用审计表:应用层显式写审计日志,灵活但依赖应用配合。选择取决于变更频率、性能要求与历史完整度。

触发器历史表是"审计触发器"的经典做法;CDC 更现代、低侵入,也用于数据同步。历史表通常含版本号、变更时间、操作类型等字段。

CREATE TABLE emp_audit (id INT, name TEXT, op CHAR(1), changed_at TIMESTAMP);
CREATE FUNCTION log_emp_change() RETURNS trigger AS $$
BEGIN
    INSERT INTO emp_audit VALUES (NEW.id, NEW.name, 'U', now());
    RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_emp AFTER UPDATE ON emp FOR EACH ROW
    EXECUTE FUNCTION log_emp_change();
#
★★★

25. 审计日志(Audit Log)的实现,触发器、log 表、CDC?

审计日志(Audit Log)有哪些实现方式?触发器、log 表、CDC 分别如何实现?

  • 触发器审计
  • 独立 log 表
  • CDC 审计

审计日志记录"谁在何时做了什么",实现方式:①触发器:在源表挂触发器,把变更写入审计表,记录旧值/新值、操作者、时间,强一致但影响性能;②独立 log 表:应用层统一写审计日志表,灵活但需应用配合、可能遗漏;③CDC:基于日志捕获变更,无侵入、适合海量审计。审计表通常包含操作类型(I/U/D)、操作时间、操作人、变更前后值。对不可篡改需求可叠加哈希链。

审计日志与历史表侧重不同:审计关注"who/when/what",历史表关注"数据当时的版本"。大型系统常结合 CDC 与独立审计表。

CREATE TABLE audit_log (
    id BIGSERIAL PRIMARY KEY,
    table_name TEXT, record_id BIGINT, op CHAR(1),
    old_val JSONB, new_val JSONB, by_user TEXT, at TIMESTAMP
);
#
★★★

26. 有效时间(Valid Time)与事务时间(Transaction Time)的双时态(Bi-Temporal)建模?

有效时间(Valid Time)与事务时间(Transaction Time)的区别是什么?双时态(Bi-Temporal)建模如何实现?

  • 有效时间与事务时间定义
  • 双时态表
  • 建模与查询

有效时间(Valid Time)是数据在现实世界中真实生效的时期,由应用维护;事务时间(Transaction Time)是数据在数据库中被记录/修改的时间,由数据库维护。双时态(Bi-Temporal)表同时记录两个时间维度,能回答"数据库在某个事务时刻,记录了现实世界某个有效时间点的数据"这类复杂问题。建模时通常一张表有四个时间列(valid_start, valid_end, tx_start, tx_end),并配合版本号。

双时态是 SCD Type 2 的扩展,同时跟踪"历史修改"与"历史事实",是金融、司法等强审计场景的标配。查询需同时过滤两个时间维度。

CREATE TABLE contract (
    id INT, price NUMERIC,
    valid_start DATE, valid_end DATE,   -- 有效时间
    tx_start TIMESTAMP, tx_end TIMESTAMP -- 事务时间
);
-- 查询"截至 2024-06-01 当天,有效期为 2024-03-01 的价格"
SELECT * FROM contract
WHERE valid_start <= '2024-03-01' AND valid_end > '2024-03-01'
  AND tx_start <= '2024-06-01' AND tx_end > '2024-06-01';
#
★★★

27. 闪回查询(Flashback Query)的实现,Oracle SCN、PostgreSQL xmin?

闪回查询(Flashback Query)如何实现?Oracle SCN 与 PostgreSQL xmin 有什么差异?

  • Oracle 闪回与 SCN
  • PostgreSQL 的 MVCC 与 xmin
  • 两者的差异

Oracle 闪回查询利用 UNDO 数据,通过 AS OF SCN 或 AS OF TIMESTAMP 查询过去某个时间点/系统变更号(SCN)的数据快照,无需恢复数据库。PostgreSQL 没有 Oracle 那样的 AS OF 闪回语法,但基于 MVCC,每行有 xmin 等隐藏列记录事务版本,可读取历史版本实现近似效果;PostgreSQL 的闪回通常依赖时间点恢复(PITR)或逻辑备份/CDC。二者机制不同:Oracle 基于 UNDO 回滚段,PostgreSQL 基于 MVCC + WAL。

Oracle 闪回是内建功能,PostgreSQL 闪回需借助 PITR 或扩展。理解 xmin 是理解 PG MVCC 与可见性判断的基础。

-- Oracle 闪回查询
SELECT * FROM emp AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' HOUR);
-- PostgreSQL 查看 xmin 隐藏列
SELECT xmin, xmax, ctid, * FROM emp;
#
★★★

28. PostgreSQL 中 xmin 隐藏列?

PostgreSQL 中 xmin 隐藏列是什么?它有什么作用?

  • xmin 的定义
  • xmax、ctid 等隐藏列
  • MVCC 可见性

PostgreSQL 每个表都有系统隐藏列,其中 xmin 记录插入该行版本的事务 ID(Transaction ID),xmax 记录删除或更新该行的事务 ID(未删除时为 0),ctid 是行在页中的物理位置。这些列用于 MVCC 并发控制:判断某个行版本对当前事务是否可见、是否被删除。用户可在查询中显式引用 xmin、xmax、ctid 等列进行调试,但通常不应依赖其值做业务逻辑。

xmin/xmax 是 MVCC 可见性判断的核心,配合事务快照(snapshot)决定行的可见性。ctid 用于索引定位行。

SELECT xmin, xmax, ctid, * FROM emp;
#
★★★

29. 审计日志的不可篡改性(Hash Chain)?

如何通过哈希链(Hash Chain)保证审计日志的不可篡改性?

  • 哈希链原理
  • 篡改检测
  • 与锚点结合

哈希链(Hash Chain)让每条审计记录包含前一条记录的哈希值,形成一条链:每条记录 hash = H(本条数据 + prev_hash)。若有人篡改中间某条记录,其哈希改变,导致后续所有记录的 prev_hash 不匹配,从而被检测出。配合外部锚点(如把链尾哈希定期发布到区块链、可信时间戳或异构存储)可进一步增强不可篡改保证。这是审计日志防止事后篡改的关键技术。

哈希链把"篡改检测"从单条记录扩展到整条链,篡改任意节点都会破坏链的完整性。常用于金融、合规、身份认证等强审计场景。

-- 伪代码示意
-- prev_hash = 上一条记录的 hash
-- record_hash = SHA256(json(record) || prev_hash)
-- 定期把链尾 hash 发布到外部锚点(如区块链/可信时间戳)
#
★★★

30. 闪回恢复(Flashback Recovery)的应用?

闪回恢复(Flashback Recovery)有哪些应用场景?如何实现?

  • 闪回恢复场景
  • 数据库级与表级闪回
  • 与 PITR 的关系

闪回恢复用于在误操作、误删除、错误更新后快速把数据恢复到过去某个状态,无需完整恢复整个备份。Oracle 提供多种闪回:闪回查询(Flashback Query)、闪回表(Flashback Table)、闪回数据库(Flashback Database,基于闪回日志)、闪回删除(Flashback Drop,基于回收站)。应用场景包括误删数据、错误 DDL、应用回滚等。PostgreSQL 无内建闪回,通常依赖 PITR(时间点恢复,基于 WAL 重放)实现数据库级恢复。

闪回恢复的核心价值是"快速、精确、低开销"地回退到特定时间点。Oracle 闪回数据库基于闪回日志与增量镜像,PostgreSQL 更多依赖 WAL 与基础备份的 PITR。

-- Oracle 闪回表到指定时间点
FLASHBACK TABLE emp TO TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' HOUR);
-- PostgreSQL PITR:恢复时指定目标时间点
-- recovery_target_time = '2024-06-01 12:00:00'
#
★★★

31. Armstrong 公理(自反律、增广律、传递律)及其推论(合并律、伪传递律、分解律)的完整推导?

Armstrong 公理(自反律、增广律、传递律)及其推论(合并律、伪传递律、分解律)如何推导?

  • 三条基本公理
  • 合并律、伪传递律、分解律的推导
  • 公理系统的完备可靠

Armstrong 基本公理:①自反律(Reflexivity):若 Y⊆X 则 X→Y;②增广律(Augmentation):若 X→Y 则 XZ→YZ;③传递律(Transitivity):若 X→Y 且 Y→Z 则 X→Z。推论:合并律(Union):若 X→Y 且 X→Z 则 X→YZ,证明:由 X→Y 增广得 X→XY,由 X→Z 增广得 XY→YZ,再由传递律得 X→YZ;伪传递律(Pseudotransitivity):若 X→Y 且 WY→Z 则 XW→Z,证明:由 X→Y 增广得 XW→WY,再由 WY→Z 传递得 XW→Z;分解律(Decomposition):若 X→YZ 则 X→Y,证明:由自反律 YZ→Y,再由 X→YZ 传递得 X→Y。

这些公理是关系数据库理论的基础,用于推导依赖、计算闭包、判断依赖蕴含。理解推导过程有助于掌握规范化算法的正确性。

#
★★★

32. 数据冗余(Redundancy)的类型,值冗余、键冗余、计算冗余?

数据冗余(Redundancy)有哪些类型?值冗余、键冗余、计算冗余分别是什么?

  • 值冗余
  • 键冗余
  • 计算冗余(派生数据)

数据冗余的类型:①值冗余(Value Redundancy):同一信息在多个位置重复存储,如订单表冗余客户姓名,可减少 JOIN 但引入更新不一致风险;②键冗余(Key Redundancy):主键/唯一键被重复或冗余定义,如多列复合唯一键包含冗余列,增加存储与维护成本;③计算冗余(Derived/Computed Redundancy):可由其他数据计算得出的值,如订单总金额、用户统计,直接存储可加速查询,但需保证与源数据一致(通过触发器、批处理或物化视图)。冗余的本质是用空间换时间,需权衡一致性与性能。

冗余是反范式化的核心手段,但每种冗余都伴随一致性维护成本。值冗余靠事务/触发器保证,计算冗余常用物化视图自动维护。

-- 计算冗余:订单表存 order_total(可由 qty*price 计算)
CREATE TABLE orders (id INT, qty INT, price NUMERIC, order_total NUMERIC);
-- 物化视图自动维护派生聚合
CREATE MATERIALIZED VIEW sales_summary AS
SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id;
#
★★

33. PostgreSQL 表继承(INHERITS)的历史角色,声明式分区出现之前如何用继承实现分区与多态?它有哪些限制?

声明式分区出现之前,PostgreSQL 如何用继承(INHERITS)实现分区与多态?它有哪些限制?

  • 继承实现分区的经典做法
  • 触发器/规则分发
  • 限制与不足

在 PG 10 声明式分区之前,分区通过继承 + CHECK 约束 + 触发器/规则实现:子表 INHERITS 父表,每个子表加 CHECK 约束限定分区范围,父表上加规则或触发器把 INSERT 路由到合适子表。这种方式能实现分区裁剪与数据分布,但存在大量限制:需手工维护约束与触发器、分区 DDL 繁琐、部分操作(如分区裁剪)依赖约束匹配、易出错、无内建的分区管理。PG 10 引入声明式分区后,官方推荐取代手工继承。继承也可用于多态(不同子表存不同类型)。

继承分区是历史时期的典型方案,理解它有助于厘清 PostgreSQL 分区机制的演进。多态(不同子表存不同类型)也是继承的重要用途。

-- 旧式分区:继承 + CHECK + 触发器
CREATE TABLE measurement_2024 INHERITS (measurement);
ALTER TABLE measurement_2024 ADD CHECK (logdate >= '2024-01-01' AND logdate < '2025-01-01');
-- 需手工创建 INSERT 触发器路由各行
#
★★

34. ER 模型转换为关系模式的基本规则,实体集、联系集、多值属性的转化?

ER 模型转换为关系模式的基本规则是什么?实体集、联系集、多值属性如何转化?

  • 实体集转表
  • 联系集转外键/联系表
  • 多值属性转表

基本规则:①实体集→一张表,实体属性→列,主键→表主键;②一对一联系→外键放入任一侧(通常完全参与侧);一对多联系→外键放入多方;多对多联系→新建联系表,包含双方主键及联系属性;③多值属性→单独建表(一个属性值一行),外键关联主表;④弱实体→独立表,主键含强实体主键;⑤多元联系→新建联系表,外键为各参与实体主键。

这些规则是 ER 到逻辑模型的标准映射,保证数据不冗余、关系正确表达。多值属性必须单独建表是因为关系模型要求原子值。

-- 多值属性:员工有多个电话 -> 单独表
CREATE TABLE emp (id INT PRIMARY KEY, name TEXT);
CREATE TABLE emp_phone (
    emp_id INT REFERENCES emp(id),
    phone  VARCHAR(20),
    PRIMARY KEY (emp_id, phone)
);
#
★★

35. 多元联系(N 元)的转换,复杂多元联系如何分解为二元?

多元联系(N 元联系)如何转换为关系模式?复杂多元联系如何分解为二元?

  • 多元联系直接建联系表
  • 分解为二元联系
  • 语义保持

多元联系(三个及以上实体参与)直接转换为一张联系表,外键为各参与实体的主键,主键通常为各外键的组合(或单独代理键)。也可把多元联系分解为多个二元联系,但需注意:不经约束的分解可能改变语义(如三元联系不能简单表示为三个二元联系,会产生冗余或丢失约束),需通过额外约束(如联合唯一)保持一致性。实践中可直接建一张多元联系表更简洁。

多元联系与二元联系的语义往往不等价,分解时需谨慎,通常用联系表加复合唯一约束表达完整语义。

-- 三元联系:供应商-零件-项目
CREATE TABLE supply (
    supplier_id INT, part_id INT, project_id INT, qty INT,
    PRIMARY KEY (supplier_id, part_id, project_id)
);
#
★★

36. 多对多(M:N)联系的转换,必须新建联系表(连接表)?

多对多(M:N)联系为什么必须新建联系表(连接表)?

  • M:N 联系无法用外键直接表达
  • 连接表设计
  • 联系属性

多对多联系中,一方实体的一条记录可对应另一方多条记录,反之亦然,无法通过在某张表加一个外键表达(外键只能表达一个方向)。因此必须新建联系表(连接表/交叉表),包含两个实体的主键作为外键,主键通常为两外键组合,可携带联系自身的属性。连接表把 M:N 拆成两个 1:N 联系,从而在关系模型中正确表达。

连接表是关系模型表达 M:N 的唯一标准方式。它让"学生-课程"这类多对多通过"选课表"表达。

CREATE TABLE student (id INT PRIMARY KEY, name TEXT);
CREATE TABLE course  (id INT PRIMARY KEY, title TEXT);
CREATE TABLE enrollment (
    student_id INT REFERENCES student(id),
    course_id  INT REFERENCES course(id),
    grade      CHAR(1),
    PRIMARY KEY (student_id, course_id)
);
#
★★

37. PowerDesigner、ER/Studio、dbdiagram.io 的 ER 建模工具对比?

PowerDesigner、ER/Studio、dbdiagram.io 等 ER 建模工具如何对比?

  • 各工具特点
  • 商业 vs 开源
  • 正向/反向工程

PowerDesigner:SAP 商业工具,功能强,支持正向/反向工程、物理与逻辑模型、生成 DDL,适合大型企业级建模,但商业授权且较重。ER/Studio:IDERA 商业工具,侧重数据建模与元数据管理,支持多数据库、团队协作。dbdiagram.io:轻量在线工具,用 DBML 文本描述模型并自动生成 ER 图,支持 PostgreSQL/MySQL 等 DDL 导出,适合快速原型与开源协作,但功能较简单。其他还有 draw.io、MySQL Workbench、pgModeler 等。选择取决于团队规模、预算与建模复杂度。

工具对比侧重"建模能力、协作、导出、成本"。轻量文本化工具(DBML)更利于版本控制,商业工具更强于复杂企业建模。

-- dbdiagram.io 使用 DBML 描述模型
Table users {
  id int [pk]
  name varchar
}
Table orders {
  id int [pk]
  user_id int [ref: > users.id]
}
#
★★

38. Chen 记法与 Crow's Foot 记法的差异?

Chen 记法与 Crow's Foot 记法在 ER 建模中的差异是什么?

  • Chen 记法的图形元素
  • Crow's Foot 记法
  • 适用范围

Chen 记法用矩形表示实体、菱形表示联系、椭圆表示属性,强调概念层面的完整表达(尤其适合表达 N 元联系与属性),但图形较复杂、不适合大模型。Crow's Foot 记法用矩形表示实体、实体间用带符号(如鸟爪、圆、竖线)的连线表示联系与基数(1:N、M:N、1:1),直观易读,广泛用于数据库设计工具(如 MySQL Workbench、ER/Studio)。Crow's Foot 更贴近物理实现,Chen 更偏向概念设计。

两种记法表达同一概念模型,但图形风格不同。Crow's Foot 用连接符表达基数,更受欢迎;Chen 更适合学术与复杂概念建模。

#
★★

39. 具体表继承(Concrete Table Inheritance),每个类一张完整表?

具体表继承(Concrete Table Inheritance)如何实现?有什么特点?

  • 每类一张完整表
  • 冗余
  • 多态查询问题

具体表继承(Concrete Table Inheritance)中,每个具体子类各有一张完整表,包含基类所有字段(含公共字段)与子类特有字段。查询子类无需 JOIN,但公共字段在每张表重复存储,造成冗余;且无法在单一查询中统一处理所有子类(多态查询困难,需 UNION ALL 合并各子表)。Hibernate 对应 TABLE_PER_CLASS 策略。

具体表继承的取舍是"无 JOIN 但冗余多、多态查询难"。适合子类字段差异大、且不常做统一多态查询的场景。

CREATE TABLE cat (id INT PRIMARY KEY, name TEXT, fur INT);
CREATE TABLE dog (id INT PRIMARY KEY, name TEXT, barks BOOLEAN);
-- 多态查询需 UNION ALL
SELECT id, name FROM cat UNION ALL SELECT id, name FROM dog;
#
★★

40. 单表继承(Single Table Inheritance)的取舍,所有字段在父表,类型列区分?

单表继承(Single Table Inheritance)的取舍是什么?所有字段在父表、用类型列区分有什么利弊?

  • 单表 + type 列
  • NULL 冗余
  • 查询简单性

单表继承把所有类(父类与子类)的字段都放在一张表中,用一个 type(判别)列区分具体类型,子类特有字段在非该类型行中为 NULL。优点是查询简单、无 JOIN、类型切换容易;缺点是大量 NULL 占用存储、字段约束缺失、扩展新子类需加列(大表加列成本高)、类型间语义混在一张表。适合类层次简单、字段重叠多的场景。

单表继承是三种策略中最简单直观的,但 NULL 与扩展性是主要代价。Hibernate 默认 SINGLE_TABLE。

CREATE TABLE animal (
    id INT PRIMARY KEY, type VARCHAR(10),  -- 'cat'/'dog'
    name TEXT, fur INT NULL, barks BOOLEAN NULL
);
#
★★

41. Partition by Inheritance 的应用?

PostgreSQL 中基于继承的分区(Partition by Inheritance)有哪些应用?

  • 继承分区的实现
  • 应用场景
  • 与声明式分区的区别

基于继承的分区(Partition by Inheritance)是声明式分区引入前(PG 10 前)的经典分区方式:父表定义结构,子表 INHERITS 父表,每个子表用 CHECK 约束限定范围,父表上通过规则或触发器把 INSERT 路由到对应子表。应用于日志、历史数据等大表按时间/范围分片,加速查询(分区裁剪)并便于归档删除。其局限是需手工维护。PG 10 后推荐声明式分区,但旧系统仍可能使用继承分区。

理解继承分区有助于迁移与维护旧系统。核心是继承 + CHECK + 触发器路由。

CREATE TABLE log_entry (id BIGINT, ts TIMESTAMP, msg TEXT);
CREATE TABLE log_2024 INHERITS (log_entry);
ALTER TABLE log_2024 ADD CHECK (ts >= '2024-01-01' AND ts < '2025-01-01');
-- 父表触发器路由 INSERT 到合适子表
#
★★

42. SQL Server 中无继承,多态如何实现?

SQL Server 没有表继承语法,多态如何实现?

  • SQL Server 无继承
  • 单表 type 列
  • 类表外键关联

SQL Server 没有表继承语法,通常用这些方式实现多态:①单表 + 判别列(type 列),所有子类字段放一张表,子类特有字段为 NULL;②类表继承:父表与子表分开,子表主键同时作为外键引用父表,应用层维护一对多关系;③具体表继承:每个子类一张完整表。SQL Server 2016+ 还提供系统版本化临时表(SYSTEM_VERSIONING)用于历史管理,但多态建模仍靠应用层。选择取决于字段重叠与查询需求。

SQL Server 与 MySQL 类似,无原生继承,多态需通过模式设计与应用层维护。系统版本化临时表是 SQL Server 的时态特性,不属于继承。

-- 类表继承:SQL Server 中父表与子表通过主键/外键关联
CREATE TABLE dbo.Person (id INT PRIMARY KEY, name NVARCHAR(50));
CREATE TABLE dbo.Student (id INT PRIMARY KEY, gpa FLOAT,
    FOREIGN KEY (id) REFERENCES dbo.Person(id));
#
★★

43. SCD Type 2 的实现,effective_date、end_date、is_current 字段?

SCD Type 2 如何实现?effective_date、end_date、is_current 字段的作用是什么?

  • SCD Type 2 概念
  • 有效期与当前标志字段
  • 历史版本保留

SCD Type 2 保留历史版本:当维度属性变化时,不覆盖旧记录,而是插入一条新记录,旧记录用 end_date 标记失效,新记录用 effective_date 标记生效,并用 is_current 标志当前版本。这样既保留历史,又能快速定位当前版本。查询历史需用时间过滤,查询当前版本用 is_current = 1。缺点是表变大、需要自然键(business key)关联各版本。

SCD Type 2 是数据仓库维度建模的标配,用三字段(effective_date、end_date、is_current)表达版本生命周期。新记录的 effective_date 通常等于上一版的 end_date。

CREATE TABLE dim_customer (
    customer_sk INT PRIMARY KEY,       -- 代理键
    customer_nk VARCHAR(20),           -- 自然键
    name TEXT,
    effective_date DATE, end_date DATE,
    is_current BOOLEAN
);
-- 查询当前版本
SELECT * FROM dim_customer WHERE is_current = TRUE;
#
★★

44. 慢变化维(Slowly Changing Dimension, SCD)类型 1、2、3、4、6 的差异?

慢变化维(SCD)类型 1、2、3、4、6 的差异是什么?

  • SCD Type 1 覆盖
  • SCD Type 2 保留历史
  • SCD Type 3 增加历史列

SCD 类型:①Type 1(覆盖):直接更新旧值,不保留历史,简单但丢失历史;②Type 2(保留历史):新增版本行,保留完整历史,最常用;③Type 3(增加历史列):在维度表增加"当前值"与"上一值"等列,只保留有限历史;④Type 4(历史表):当前数据在维度表,历史数据放独立的"历史维度表";⑤Type 6(混合):结合 Type 1、2、3(如同时有当前值、历史行、历史列),灵活但复杂。选择取决于是否需历史、保留多深历史。

SCD 类型选择是数据仓库设计的核心决策。Type 2 最常用(保留完整历史),Type 1 适用于不关心历史的场景,Type 6 是组合方案。

-- Type 1:覆盖
UPDATE dim_customer SET name = 'NewName' WHERE id = 1;
-- Type 2:新增版本行
INSERT INTO dim_customer(name, effective_date, is_current)
VALUES ('NewName', '2024-01-01', TRUE);
UPDATE dim_customer SET end_date = '2024-01-01', is_current = FALSE WHERE id = 1;
#
★★

45. 时序数据建模的四种模式,append-only、snapshot、delta、temporal table?

时序数据建模的四种模式:append-only、snapshot、delta、temporal table 是什么?

  • append-only
  • snapshot
  • delta

时序数据建模四种模式:①append-only(只追加):只插入新记录,数据不可变,适合日志、事件流、传感器数据,查询简单、利于分区;②snapshot(快照):定期保存全量状态快照,可回溯任意快照时刻,但存储开销大;③delta(增量):只保存变化的部分,节省空间但重建状态需重放,适合变化稀疏的数据;④temporal table(时态表):用数据库时态机制(SCD Type 2、系统版本化)记录版本历史,兼顾历史与查询。选择取决于数据量、历史回溯需求与存储成本。

四种模式在"存储开销"与"历史回溯能力"间权衡。append-only 适合高吞吐事件,snapshot 适合小数据集状态,delta 适合变化稀疏大数据,temporal table 适合业务维度。

-- append-only:日志表,只插入
CREATE TABLE events (id BIGSERIAL, ts TIMESTAMP, payload JSONB);
-- delta:状态变化表
CREATE TABLE state_changes (entity_id INT, ts TIMESTAMP, new_value NUMERIC);
#
★★

46. GDPR / 数据删除请求与历史数据保留的冲突?

GDPR 数据删除请求与历史数据保留(如审计、SCD Type 2)之间的冲突如何解决?

  • GDPR 删除权
  • 历史数据保留需求
  • 冲突解决(匿名化、最小化)

GDPR 赋予用户"被遗忘权"(Right to Erasure),要求删除个人数据;但审计、合规、历史分析(SCD Type 2、审计日志)需要保留历史。两者冲突的解决方式:①匿名化/假名化:删除或覆盖可识别字段,保留不可识别化的统计与审计数据;②数据最小化:只保留必要的元数据(如时间戳、操作类型),删除具体个人内容;③明确保留期限与合规理由(如法律要求保留 N 年);④用不可逆哈希替代 PII。从而在满足删除权的同时保留必要的审计与历史。

冲突的实质是"删除权"与"数据保留"的合规平衡。核心策略是匿名化与最小化,既满足删除权又不破坏审计链。

-- 满足删除权:匿名化 PII 字段
UPDATE users SET email = NULL, name = 'anonymous' WHERE id = 123;
-- 保留审计元数据但不含 PII
INSERT INTO audit_log(table_name, record_id, op, at)
VALUES ('users', 123, 'D', now());
#
★★

47. CDC(Change Data Capture)与历史表的关系?

CDC(Change Data Capture)与历史表的关系是什么?

  • CDC 捕获变更
  • 历史表作为 CDC 的下游
  • 与触发器审计的区别

CDC(Change Data Capture)通过读取数据库日志(如 MySQL binlog、SQL Server CDC、PostgreSQL 逻辑解码)捕获数据变更,不侵入业务表。CDC 捕获的变更流可以写入历史表(或数据仓库、消息队列),从而实现历史版本的记录。与触发器历史表相比,CDC 对源表无性能影响、更可靠,但需要额外组件与配置。历史表是 CDC 消费端的一种,用于留存变更记录。

CDC 是"变更捕获"手段,历史表是"变更留存"目标,二者结合可实现低侵入的历史记录与数据同步。Debezium + Kafka 是常见组合。

-- SQL Server 开启 CDC
EXEC sys.sp_cdc_enable_db;
EXEC sys.sp_cdc_enable_table @source_schema='dbo', @source_name='orders',
    @role_name='cdc';
-- CDC 变更表 cdc.dbo_orders_CT 记录 __$operation 与旧值/新值
#
★★

48. GDPR 的删除权(Right to Erasure)与历史数据?

GDPR 的删除权(Right to Erasure)对历史数据有什么影响?如何应对?

  • 删除权范围
  • 对历史数据的影响
  • 合规策略

GDPR 第 17 条赋予数据主体删除权,要求控制器在无正当理由时删除个人数据。历史数据(如维度历史、审计日志、备份)若含个人数据,需能响应删除请求。应对策略:建立 PII 清单与删除流程;对历史数据匿名化或假名化;设定数据保留期限(retention policy);备份中被删除数据需在合理时间内清理或匿名化;对无法删除的(如法律要求保留)需明确告知用户。数据删除需跨数据库、备份、日志等所有副本。

删除权要求"数据"而非"记录"能被删除,涉及跨系统与副本的一致性。匿名化是保留数据同时又满足删除权的常用手段。

-- 删除请求:删除用户主记录
DELETE FROM users WHERE id = 123;
-- 匿名化历史表副本
UPDATE user_history SET email = NULL WHERE user_id = 123;
#
★★

49. Oracle AS OF TIMESTAMP 的语法?

Oracle 中 AS OF TIMESTAMP 的语法是什么?有什么应用?

  • AS OF TIMESTAMP 语法
  • 闪回查询
  • 限制

Oracle 闪回查询用 AS OF TIMESTAMP 或 AS OF SCN 查询过去某个时间点的数据快照。语法:SELECT ... FROM table AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' HOUR); 或 AS OF SCN 123456。它依赖 UNDO 数据,UNDO 保留期(undo_retention)内可查询。适用于误操作恢复、历史数据分析。限制:受 UNDO 保留期限制,超出则报 ORA-01555 或快照太旧错误。

AS OF TIMESTAMP 是 Oracle 事务时间(闪回)查询的核心语法,基于 UNDO 的多版本能力。可用于临时查看历史、审计取证。

-- 查询 1 小时前的数据
SELECT * FROM emp AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' HOUR);
-- 按 SCN 查询
SELECT * FROM emp AS OF SCN 123456;
#
★★

50. SCD Type 1(覆盖)与 Type 2(保留历史)的差异?

SCD Type 1(覆盖)与 Type 2(保留历史)的差异是什么?

  • Type 1 覆盖更新
  • Type 2 版本插入
  • 历史保留与查询

SCD Type 1 直接覆盖旧值,只保留最新状态,查询简单、表体积小,但丢失历史,无法追溯历史事实。SCD Type 2 变化时插入新版本行,保留完整历史,可回溯任一时刻的维度属性,但表体积增大、需有效期限字段与自然键管理、查询当前版本需过滤。选择:不关心历史用 Type 1,需要历史分析用 Type 2。

Type 1 与 Type 2 是 SCD 最基础的两种策略,核心差异是"是否保留历史版本"。Type 2 是数据仓库常用默认。

-- Type 1:UPDATE 覆盖
UPDATE dim_customer SET phone = '123' WHERE id = 1;
-- Type 2:INSERT 新版本 + UPDATE 旧版本失效
INSERT INTO dim_customer(customer_nk, phone, effective_date, is_current)
VALUES ('C1', '123', '2024-01-01', TRUE);
UPDATE dim_customer SET end_date = '2024-01-01', is_current = FALSE WHERE id = 1;
#
★★

51. SYSTEM_TIME PERIOD 的语法?

SQL:2011 中 SYSTEM_TIME PERIOD 的语法是什么?

  • SYSTEM_TIME PERIOD 语法
  • 系统版本化
  • 支持的数据库

SQL:2011 定义 SYSTEM_TIME PERIOD 语法,用于声明系统版本化时态表。典型语法(SQL Server 2016+):表内定义两个生成列(SysStartTime、SysEndTime)作为 PERIOD FOR SYSTEM_TIME 的边界,并开启 SYSTEM_VERSIONING。查询历史用 FOR SYSTEM_TIME AS OF / BETWEEN / CONTAINED IN 子句。类似语法在 Oracle(Temporal)、PostgreSQL(需扩展)等也有不同实现。它为自动维护事务时间历史提供了标准。

PERIOD FOR SYSTEM_TIME 让数据库自动维护行的生效区间,实现系统版本化,无需手写触发器。SQL Server 是支持最完整的商业实现之一。

CREATE TABLE dbo.Employee (
    Id INT PRIMARY KEY,
    Name NVARCHAR(50),
    SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START,
    SysEndTime   DATETIME2 GENERATED ALWAYS AS ROW END,
    PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeeHistory));
-- 查询某时刻
SELECT * FROM dbo.Employee FOR SYSTEM_TIME AS OF '2024-01-01';
#
★★

52. Triggers-based 历史表的副作用?

基于触发器(Triggers-based)历史表有什么副作用?

  • 写入性能开销
  • 维护复杂
  • 与批量操作、事务

基于触发器的历史表副作用包括:①性能开销:每次 DML 都执行触发器,写入延迟增加,批量/大表场景明显;②维护复杂:触发器逻辑分散、调试困难,历史表结构变更需同步维护;③与批量/复制工具冲突:某些分区迁移、逻辑复制、bulk load 不触发触发器,导致历史遗漏;④触发器内错误会使整个事务失败,风险高;⑤可移植性差:各数据库触发器语法不同。因此大数据量场景更倾向 CDC 或应用层记录。

触发器历史表实现简单、强一致,但性能与维护成本是其代价。需结合数据量评估,必要时用 CDC 替代。

-- BEFORE/AFTER 触发器在每次 DML 后写历史,大表批量写入时开销明显
CREATE TRIGGER trg_history AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW EXECUTE FUNCTION log_change();
#
★★

53. 时态数据库(Temporal Database)的应用场景?

时态数据库(Temporal Database)有哪些应用场景?

  • 时态数据库概念
  • 应用场景
  • 历史与未来查询

时态数据库支持数据的时间维度(有效时间、事务时间、双时态),应用于:金融与审计(需追溯交易历史与状态)、合规(数据留存与取证)、数据仓库维度管理(SCD 历史版本)、版本管理(产品价格、配置的历史)、医疗/法律(合同与病历的有效期)、系统恢复(回溯数据到某时刻)。核心价值是"回到过去某时刻查询数据状态"以及"处理现实世界与数据库时间不一致"。

时态数据库把"时间"作为一等公民,解决普通数据库只保留当前状态的问题。双时态能同时处理有效时间与事务时间。

-- 时态查询示例:查询某合同在某时刻的有效价格
SELECT * FROM contract_price
WHERE product_id = 1
  AND valid_start <= '2024-03-01' AND valid_end > '2024-03-01';
#
★★

54. 4NF、5NF(PJNF)涉及的多值依赖与连接依赖?

第四范式(4NF)与第五范式(5NF/PJNF)涉及的多值依赖与连接依赖是什么?

  • 多值依赖与 4NF
  • 连接依赖与 5NF
  • 范式层级

第四范式(4NF)基于多值依赖(MVD):要求每个非平凡多值依赖 X→→Y 中 X 是超键。它消除多值依赖导致的多余重复(如某人技能与爱好独立产生的笛卡尔积冗余)。第五范式(5NF,又称投影连接范式 PJNF)基于连接依赖(JD):要求每个非平凡连接依赖都由候选键蕴含。5NF 是 4NF 的进一步严格化,消除由连接依赖(涉及三元及以上)引起的冗余。任何 5NF 关系必满足 4NF,满足 4NF 必满足 BCNF。

4NF 处理多值依赖,5NF 处理连接依赖,都是对"候选键之外约束"的规范化。5NF 实际应用极少,因其识别与分解复杂。

-- 4NF 反例:R(员工, 技能, 爱好),员工->->技能 且 员工->->爱好
-- 4NF 分解:R1(员工,技能), R2(员工,爱好) 消除笛卡尔积冗余
#
★★

55. 多值依赖(MVD)X →→ Y 的语义与 4NF 的关系?

多值依赖(MVD)X →→ Y 的语义是什么?它与第四范式(4NF)的关系是什么?

  • 多值依赖语义
  • 与函数依赖的区别
  • 4NF 定义

多值依赖 X →→ Y 表示:给定 X 的值,Y 的取值集合与关系中其余属性(Z = R − X − Y)的取值相互独立,即对于 X 的某个值,Y 的所有取值会与 Z 的每个取值组合出现。当 X 与 Y 独立时会产生笛卡尔积式的冗余。函数依赖 X→Y 蕴含多值依赖 X→→Y。第四范式(4NF)要求每个非平凡多值依赖 X→→Y 中 X 是超键,从而消除多值依赖造成的冗余。4NF 比 BCNF 更严格。

多值依赖描述"属性组之间的独立性",是 4NF 的基础。典型例子是技能与爱好相互独立产生冗余。

-- R(员工, 技能, 爱好) 中 员工->->技能,员工->->爱好
-- 若非超键,则违反 4NF,分解为 R1(员工,技能), R2(员工,爱好)
#
★★

56. 第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、BCNF(Boyce-Codd NF)的精确定义与逐步严格化?

1NF、2NF、3NF、BCNF 的精确定义是什么?它们如何逐步严格化?

  • 各范式定义
  • 严格化关系
  • 依赖类型

1NF:属性值必须是原子的,无重复组/复合属性。2NF:满足 1NF,且每个非主属性完全函数依赖于候选键(消除部分依赖)。3NF:满足 2NF,且每个非主属性不传递依赖于候选键(消除传递依赖)。BCNF:每个非平凡函数依赖 X→A 的左侧 X 是超键。严格化关系:1NF ⊇ 2NF ⊇ 3NF ⊇ BCNF ⊇ 4NF ⊇ 5NF。即满足 BCNF 必满足 3NF,满足 3NF 必满足 2NF,依此类推。BCNF 把 3NF 中"右侧是主属性"的例外也消除。

范式逐级消除更复杂的依赖:部分函数依赖、传递依赖、非超键左部依赖。BCNF 是函数依赖范畴的最高范式;4NF/5NF 涉及多值/连接依赖。

-- 1NF:无重复组;2NF:消除部分依赖;3NF:消除传递依赖;BCNF:左部必为超键
-- 例:R(学号, 课程, 成绩, 系名, 系主任)
-- 部分依赖:学号->系名(仅依赖候选键的一部分)
-- 传递依赖:学号->系名->系主任
#
★★

57. 1NF 的原子性如何判定?

第一范式(1NF)中原子性(Atomicity)如何判定?

  • 原子性概念
  • 重复组与复合属性
  • 原子性的相对性

1NF 要求每个属性值是原子的,即不可再分。判定标准:①无重复组:一行中不能有多个值(如把多个电话用逗号分隔或数组存储在一个字段,违反 1NF);②无复合属性:属性不能由多个子部分组成(如"姓名"若分名和姓存入一个字段则违反,但地址这类整体对象可视为原子取决于语义);③值在应用语义上是不可再分的单一单位。原子性具有一定相对性,取决于应用使用方式,但标准做法是每个属性存单一值。

1NF 是关系模型的基础,违反时查询、索引、聚合都会困难。多值属性应拆成多行或独立表。

-- 违反 1NF:phone 存多个值
CREATE TABLE bad (customer_id INT, phone VARCHAR(100)); -- phone='123,456,789'
-- 满足 1NF:每行一个值
CREATE TABLE customer_phone (customer_id INT, phone VARCHAR(20));
#
★★

58. 2NF 消除什么类型的部分函数依赖?

第二范式(2NF)消除什么类型的部分函数依赖?

  • 部分函数依赖定义
  • 复合候选键
  • 2NF 要求

2NF 消除的是"部分函数依赖"(Partial Functional Dependency),即非主属性仅依赖于候选键的真子集(部分),而非整个候选键。这种情况只在候选键是复合键(多个属性)时出现。2NF 要求每个非主属性完全函数依赖于每个候选键。通过把部分依赖的非主属性拆到独立表消除。若候选键是单属性,则不存在部分依赖,自动满足 2NF。

部分依赖源于复合候选键。例如 (学号,课程) 为主键时,若"系名"仅依赖学号(部分),则违反 2NF,需拆出"学生"表。

-- 反例:主键(学号,课程),非主属性 系名 仅依赖 学号(部分依赖)
-- 2NF 分解:
-- 选课表(学号, 课程, 成绩)
-- 学生表(学号, 系名)
#
★★

59. 3NF 分解算法的步骤与正确性,如何从函数依赖集出发逐步消除传递依赖,分解的无损性与依赖保持性如何验证?

3NF 分解算法(合成算法)的步骤是什么?如何验证分解的无损性与依赖保持性?

  • 3NF 合成算法步骤
  • 无损性验证
  • 依赖保持性验证

3NF 分解算法(合成算法)步骤:①求函数依赖集 F 的最小覆盖 Fc;②对 Fc 中每个 FD X→Y,若 X 未出现在任何已建模式中,则建模式 XY;若 X 相同则合并;③若没有模式包含 R 的候选键,则把任一候选键单独作为一个模式;④删除被其他模式包含的冗余模式。该算法保证分解无损且保持函数依赖。无损性验证:若存在某模式包含候选键,则连接无损;依赖保持性验证:检查每个 FD 是否能在某个子模式中直接保持(X∪Y 都包含在同一模式中)。

3NF 分解是规范化算法的核心,优点是总能保持依赖,缺点是可能不完全规范化(存在重叠候选键时不满足 BCNF)。无损性用"候选键包含于某模式"判断。

-- 例:R(A,B,C,D), F={A->B, A->C, C->D}
-- 最小覆盖:{A->B, A->C, C->D}
-- 模式:AB、AC、CD;A 是候选键,包含于 AB/AC,故无损且保持依赖
#
★★

60. BCNF 分解算法的步骤,找出违反 BCNF 的函数依赖并逐步拆分,为什么 BCNF 分解可能牺牲依赖保持性,与 3NF 分解如何取舍?

BCNF 分解算法的步骤是什么?为什么 BCNF 分解可能牺牲依赖保持性?与 3NF 分解如何取舍?

  • BCNF 分解算法
  • 依赖保持性损失
  • 与 3NF 取舍

BCNF 分解算法:若非所有非平凡 FD 左部都是超键则已满足 BCNF;否则找出违反 BCNF 的 FD X→A(X 非超键),把 R 拆为 R1 = X∪A 和 R2 = R−A,递归处理直到每个子关系满足 BCNF。该算法保证无损连接,但可能丧失依赖保持性:因为被拆出的 FD 可能横跨多个子关系,任一子关系都无法单独保持它。3NF 分解则总能保持依赖,但可能不是 BCNF(存在重叠候选键时)。取舍:BCNF 消除全部冗余但可能丢依赖,3NF 保持依赖但可能保留少量冗余。实践中常选 3NF 或依赖保持优先。

BCNF 分解以"超键"为准则,彻底消除冗余;3NF 以"保持依赖"为准则。当 BCNF 无法保持依赖时,折中采用 3NF。

-- R(A,B,C), F={AB->C, C->B}
-- C->B 左部 C 非超键,违反 BCNF
-- BCNF 分解:R1(C,B), R2(A,C);AB->C 无法保持(在一子关系内)
-- 3NF 分解:保留 R(A,B,C) + R(C,B),保持依赖
#
★★

61. BCNF 比 3NF 更严格之处?

BCNF 比 3NF 更严格在哪里?

  • 3NF 的例外
  • BCNF 的消除
  • 主属性依赖

3NF 允许"非平凡函数依赖 X→A 中 X 不是超键、但 A 是主属性(属于某个候选键)"的情况;BCNF 则要求每个非平凡 FD 的左侧都是超键,不允许任何"非超键左部"的依赖,即使右侧是主属性也不行。因此 BCNF 比 3NF 更严格。当存在多个候选键且它们相交重叠时,可能出现满足 3NF 但不满足 BCNF 的关系。

两者的差异集中在"左侧非超键但右侧是主属性"的依赖上。BCNF 消除这种冗余,3NF 容忍它以保证依赖保持。

-- 例:R(A,B,C), F={AB->C, C->B}
-- 候选键:AB、AC;B、C 都是主属性
-- 满足 3NF;但 C->B 左部 C 非超键,违反 BCNF
#
★★

62. Chen 记法的图形元素?

Chen 记法的图形元素有哪些?它们分别表示什么?

  • 矩形、菱形、椭圆
  • 连线与基数
  • 弱实体、多值属性

Chen 记法(Chen Notation)的图形元素:矩形表示实体;菱形表示联系;椭圆表示属性;实线连接实体与联系、实体与属性;主键属性加下划线;多值属性用双椭圆表示;派生属性用虚线椭圆;弱实体用双矩形,弱联系用双菱形,弱实体部分键用虚线下划线。联系上标基数(1:N、M:N)。它强调概念层面的完整表达,适合学术与复杂概念建模。

Chen 记法是 E-R 模型的标准图形化表示,图形符号区分实体、联系、属性及它们的类型(多值、派生、弱实体)。

#
★★

63. ER 图到 DDL 的基本步骤?

从 ER 图生成 DDL 的基本步骤是什么?

  • 实体转表
  • 联系转外键/联系表
  • 属性、约束、索引

ER 图到 DDL 的基本步骤:①每个实体集转换为一张表,实体属性转为列,主键确定;②每个联系按基数转换:1:1 外键放完全参与侧,1:N 外键放多方,M:N 建联系表;③多值属性单独建表;④为属性添加数据类型、NULL/非空、唯一等约束;⑤定义主键、外键、索引;⑥生成 CREATE TABLE 语句。可借助建模工具(如 MySQL Workbench、PowerDesigner)自动完成正向工程。

从概念模型到物理模型是正向工程的过程,关键是正确转换联系与约束,并考虑数据类型与索引。

-- ER 实体"学生"与"课程"、M:N 联系"选课"
CREATE TABLE 学生 (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE 课程 (id INT PRIMARY KEY, title VARCHAR(50));
CREATE TABLE 选课 (
    student_id INT, course_id INT, grade CHAR(1),
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES 学生(id),
    FOREIGN KEY (course_id) REFERENCES 课程(id)
);
#
★★

64. ER 模型的扩展(EER)?

ER 模型的扩展(EER)是什么?

  • EER 的概念
  • 特殊化、泛化、聚合
  • 与 ER 的区别

EER(Extended Entity-Relationship,扩展 ER 模型)在 ER 基础上增加面向对象语义:特殊化/泛化(ISA 继承,如"学生"是"人"的子类)、类别(Category/Union)、聚合(Aggregation,把联系作为整体参与其他联系)。这些扩展能更精确表达复杂现实世界,尤其适合表达继承与组合关系。EER 图增加子类、超类、ISA 三角形、聚合等符号。

EER 是 ER 的面向对象扩展,用于表达继承层次与聚合。它为后续关系模式(单表/类表/具体表继承)的映射提供基础。

-- EER 的 ISA:学生 ISA 人
-- 转换采用类表继承
CREATE TABLE person (id INT PRIMARY KEY, name TEXT);
CREATE TABLE student (id INT PRIMARY KEY, gpa NUMERIC,
    FOREIGN KEY (id) REFERENCES person(id));
#

65. 单表继承的 NULL 列代价?

单表继承中 NULL 列有什么代价?

  • NULL 存储与空间
  • 约束缺失
  • 查询语义

单表继承把所有子类字段放在一张表,非该类型行在这些字段为 NULL。代价:①存储空间:虽数据库对 NULL 有优化(PG 用 NULL bitmap,通常是每 8 列 1 字节),但大量 NULL 仍增加行宽与索引/扫描成本;②约束缺失:无法对子类特有字段实施非空约束,因为 NULL 合法;③查询语义:查询需依赖 type 列过滤,容易误用;④索引:在大量 NULL 的列上建索引效率低(B-tree 通常不索引 NULL)。因此子类字段差异大时不适合单表继承。

NULL 冗余是单表继承的主要代价,字段差异大、独有字段多时应改用类表/具体表继承。

-- 单表继承:cat 行中 dog 字段为 NULL
CREATE TABLE animal (id INT PRIMARY KEY, type VARCHAR(10),
    name TEXT, fur INT NULL, barks BOOLEAN NULL);
#

66. 历史表与当前表的 JOIN 模式?

历史表与当前表的 JOIN 模式是什么?

  • 历史表结构
  • JOIN 模式
  • 时态查询

历史表(如 SCD Type 2 或系统版本化历史表)保存数据的所有历史版本,当前表保存当前状态。JOIN 模式:①查询当前状态:JOIN 当前表(或过滤 is_current=TRUE);②查询某时刻状态:JOIN 历史表并过滤 effective_date <= 目标时刻 AND end_date > 目标时刻;③关联事实表与历史表:用事实表的时间戳关联历史表的有效区间,得到当时有效的维度属性。常见用 BETWEEN 或范围条件匹配有效区间。

历史表与当前表的 JOIN 本质是"时态关联",需按时间区间匹配,而非简单主键等值。这是 SCD 与数据仓库查询的核心。

-- 查询订单发生时客户的当时名称
SELECT f.order_id, c.name
FROM fact_orders f
JOIN dim_customer c
  ON f.customer_nk = c.customer_nk
  AND f.order_date >= c.effective_date
  AND f.order_date < c.end_date;