关系模型、NULL 与三值逻辑

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

1. 举例说明函数依赖 X→Y 如何用于判定候选键,并阐述 Armstrong 公理(自反律、增广律、传递律)及其推论的完整推导。

请举一个具体例子,说明函数依赖 X→Y 如何用于判定候选键,并完整阐述 Armstrong 公理(自反律、增广律、传递律)及其推论(分解律、合并律、伪传递律)的推导过程?

  • 函数依赖定义与属性闭包计算判定候选键
  • Armstrong 三条基本公理的内容
  • 由基本公理推导分解律、合并律、伪传递律

函数依赖 X→Y 表示关系中任意两行元组在 X 上取值相同则 Y 上也必然相同。判定候选键的方法:对属性集 X 反复应用已知函数依赖求其闭包 X⁺,若 X⁺ 覆盖关系全部属性(X 是超键),且 X 的任一真子集的闭包都不能覆盖全部属性(无冗余),则 X 是候选键。例如关系 R(A,B,C,D) 上有依赖 A→B、A→C、B→D,计算 A 的闭包:A⁺={A},由 A→B 加入 B,由 A→C 加入 C,由 B→D 加入 D,得 A⁺={A,B,C,D}=全属性,且 A 的任一真子集为空集不能决定任何属性,故 A 是候选键。

Armstrong 公理:自反律(若 Y⊆X 则 X→Y)、增广律(若 X→Y 则 XZ→YZ)、传递律(若 X→Y 且 Y→Z 则 X→Z)。推论推导:分解律(X→YZ 则 X→Y),由 Y⊆YZ 及自反律得 YZ→Y,再与 X→YZ 用传递律得 X→Y;合并律(X→Y 且 X→Z 则 X→YZ),由 X→Y 增广 Z 得 XZ→YZ,由 X→Z 增广 X 得 X→XZ,传递得 X→YZ;伪传递律(X→Y 且 WY→Z 则 WX→Z),由 X→Y 增广 W 得 WX→WY,再与 WY→Z 传递得 WX→Z。Armstrong 公理是完备且可靠的:可靠指推出的依赖必然成立,完备指所有蕴含的依赖都能推出,这两点共同保证可用闭包算法穷尽所有可推导依赖。

候选键判定本质是闭包计算:反复应用依赖直到闭包不再增长,闭包覆盖全部属性即为超键,去掉冗余属性才得到候选键。答题时先讲方法再举例,再完整陈述公理与推论推导,体现对"可靠性+完备性"的理解,这是数据库理论面试的高频考点。

#
★★★

2. 元组(Tuple)与记录(Record)的差异是什么?关系是集合而文件是序列,从 SQL 模型与物理存储两个层面分别说明。

请从 SQL 模型与物理存储两个层面说明元组(Tuple)与记录(Record)的差异,并解释"关系是集合而文件是序列"这一论断?

  • 元组(逻辑概念)与记录(物理概念)的区别
  • 集合语义:行序不承载语义
  • 物理存储必须有寻址位置(TID、rowid)

元组(Tuple)是关系模型中的逻辑概念,指属性值的有序组合,是构成关系的基本单元;记录(Record)是物理存储层面的概念,指磁盘上连续存放的一行字节序列,具有物理地址(PostgreSQL 堆表的 TID、Oracle 的 rowid)。从 SQL 模型看,关系是集合:元组之间没有固有顺序,行序不承载任何语义,查询结果只有显式 ORDER BY 才保证顺序;从物理存储看,文件是序列:记录按字节有序排列,必须有物理位置才能被寻址和定位。

两层并不矛盾:逻辑层用"无序集合"保证查询结果的确定性可由 ORDER BY 控制、便于优化器重排访问路径;物理层用"有序序列"满足寻址需求。SQL 标准允许结果中出现重复行(即袋语义),进一步表明逻辑上关心的是"元素集合/多重集",而不是位置序列。这也是为什么教科书强调"关系"与"表文件"是两个抽象层次的术语。

本题的关键是区分抽象层次:元组/关系是逻辑层术语,记录/文件是物理层术语。逻辑上无序、物理上有序并不冲突,因为 ORDER BY 只负责把物理访问翻译成逻辑顺序,而优化器可以在物理层自由重排。抓住"逻辑集合 vs 物理序列"这一主线即可答全。

#
★★★

3. 实体完整性、参照完整性、用户定义完整性的违约处理(NO ACTION、CASCADE、SET NULL、SET DEFAULT)各自适用场景是什么?

请说明实体完整性、参照完整性、用户定义完整性三类完整性约束的含义,并阐述参照完整性违约处理(NO ACTION、CASCADE、SET NULL、SET DEFAULT)各自适用的业务场景?

  • 三类完整性约束的定义与区别
  • 参照动作 NO ACTION、CASCADE、SET NULL、SET DEFAULT 的语义
  • 各动作的适用业务场景与选择准则

实体完整性要求主键列非空且唯一,保证每行可被唯一标识;参照完整性要求外键列的值要么为 NULL,要么等于被引用表主键中存在的值,保证跨表引用不悬空;用户定义完整性由 CHECK、NOT NULL、触发器、DOMAIN 等表达业务规则。三类约束共同构成数据库的完整性体系,违反时的处理策略主要在参照完整性上展开。

参照动作的取舍:NO ACTION/RESTRICT 直接拒绝删除或更新父行,适合"存在即有效"的强引用场景(如订单行引用订单头,有子行就不允许删父行);CASCADE 把删除或更新级联传导到子表,适合父子生命周期一致的场景(如用户与地址,删用户连带删地址);SET NULL 在删除父行时把子表外键置为 NULL,适合"子行可独立存在、只是暂时失去归属"的场景(如员工离职后部门记录删除,员工的部门外键置空);SET DEFAULT 把外键设为默认值,适合有明确兜底归属(如"未知部门")的场景。审计、回收站等软删除场景则应避免 CASCADE 造成数据不可恢复,改用 NO ACTION 配合软删除标记。

答题先列三类完整性的定位,再逐项展开四种参照动作的语义与场景。核心是理解"级联是生命周期跟随,SET NULL 是解除绑定,NO ACTION 是保护父行",并把审计、软删除等工程场景纳入取舍,体现理论联系实际。

#
★★★

4. 数据库中的 NULL 与业务空值(0、空字符串)有何本质区别?三值逻辑(TRUE、FALSE、UNKNOWN)如何影响等值比较与唯一约束?

数据库中的 NULL 与业务空值(0、空字符串)有何本质区别?三值逻辑(TRUE、FALSE、UNKNOWN)如何影响等值比较与唯一约束?

  • NULL 表示"未知/不适用",0 与空字符串是具体值
  • 三值逻辑下等值比较的结果
  • NULL 对唯一约束与查询过滤的影响

NULL 不是值,而是"值缺失"的标记,表示未知(unknown)、不适用(not applicable)或尚未确定;0、空字符串 '' 都是具体的业务值,参与运算有确定结果。例如员工表中"手机号"为 NULL 表示没有记录该信息,而 '' 表示明确记录为空字符串,二者在业务语义和统计口径上完全不同,NULL 不能参与算术与比较运算,NULL+1 仍为 NULL。

三值逻辑下,任何与 NULL 的比较(NULL = NULL、1 = NULL)都返回 UNKNOWN,WHERE 只保留 TRUE 的行,因此 NULL 行被过滤掉;必须用 IS NULL / IS NOT NULL 判断。对唯一约束,SQL 标准认为 NULL 彼此不相等,因此唯一列允许多个 NULL(PostgreSQL 默认如此,Oracle 相同,SQL Server 单列唯一索引只允许一个 NULL)。这导致"业务空值用 NULL 还是 '' 表示"必须结合查询过滤、唯一约束、索引使用等统一决策,例如软删除场景常用 NULL 表示未删除、时间戳表示已删除以绕过唯一约束。

答题核心是"NULL 是状态不是值",由此推出三值逻辑、IS NULL 判断、唯一约束放行多个 NULL 等一系列行为。面试时把 NULL 与 0/'' 的本质区别讲清楚,再落到等值比较与唯一约束两个具体影响点即可。

#
★★★

5. 数据库系统中的元数据(Catalog、Schema、Database)三层命名空间结构是怎样的?PostgreSQL 的 search_path 与 MySQL 的数据库限定如何工作?

数据库系统中的元数据(Catalog、Schema、Database)三层命名空间结构是怎样的?PostgreSQL 的 search_path 与 MySQL 的数据库限定各自如何工作?

  • Catalog、Schema、Table 三层命名空间
  • PostgreSQL search_path 解析规则
  • MySQL 以 Database 为命名空间的方式

SQL 标准采用 Catalog(目录)→ Schema(模式)→ 对象(表/视图等)三层命名空间,其中 Catalog 通常对应一个数据库实例或集群,Schema 是 Catalog 内的逻辑分组,对象在 Schema 内唯一。PostgreSQL 中一个实例(集群)包含多个 Database,每个 Database 内含多个 Schema,表通过 database.schema.table 全限定,未限定的对象名按 search_path 中列出的 Schema 顺序查找,默认 search_path 是 "$user", public;SET search_path 可修改解析顺序。MySQL 没有独立的 Schema 概念,CREATE DATABASE 即 Schema,表名限定为 dbname.tablename,切换命名空间用 USE dbname。

三层结构的意义:多应用共享同一实例时可用 Schema 隔离对象名冲突、控制权限;PostgreSQL 的 search_path 让应用无需写全限定名,但同时也带来"同名对象被错误解析"的风险(如 search_path 攻击);MySQL 的 db.tablename 限定简单直接,但跨库引用必须写库名前缀。理解这套命名空间解析规则是排查"对象找不到"或"引用到错误对象"类问题的基础。

答题分两层:先讲标准的三层结构,再对比 PostgreSQL 与 MySQL 的实现差异。重点突出 search_path 的解析顺序与安全风险、MySQL 的 Database=Schema 简化,体现对命名空间解析机制的掌握。

#
★★★

6. 请解释关系模型中关系(Relation)的定义、属性、度与基数,并说明关系为何是无序元组的集合而非数组?

请解释关系模型中关系(Relation)的定义、属性(Attribute)、度(Degree)与基数(Cardinality),并说明关系为何是无序元组的集合而非数组?

  • 关系、属性、度、基数的精确定义
  • 关系的集合语义与无序性
  • 集合论对关系建模的意义

关系(Relation)是定义在若干域(Domain)上的笛卡尔积的子集,即一组元组(Tuple)的集合;属性(Attribute)是关系的列,每个属性取自一个域;度(Degree)是属性的个数;基数(Cardinality)是元组的个数。例如 R(A,B,C) 度为 3,若含 5 行则基数为 5。每个属性都必须来自合法域,保证了列值的类型约束。

关系是无序元组的集合而非数组,原因有三:其一,关系基于集合论,元组间无顺序概念,行序不承载语义,可任意重排而不改变关系本身;其二,无序性使优化器可以自由重排访问路径与连接顺序而不影响查询结果;其三,集合语义要求元组可比较、可去重(严格关系代数中不允许重复元组)。数组强调位置与顺序,而关系强调元素与结构,这是关系模型"数据独立于物理布局"的核心体现,也是 SELECT 结果必须用 ORDER BY 才能获得确定顺序的根本原因。

先给出四个术语的精确定义(含例子),再论证无序集合的三大理由:集合论基础、优化自由、语义独立。把"关系=集合"与"SQL 结果需 ORDER BY"联系起来,体现理论与实践的统一。

#
★★★

7. 为什么 SQL 标准允许关系中出现重复元组,而关系代数严格要求集合?请从 MULTISET 与 SET 的角度分析 SELECT DISTINCT 的语义代价。

为什么 SQL 标准允许关系中出现重复元组,而关系代数严格要求集合?请从多重集(MULTISET)与集合(SET)的角度分析 SELECT DISTINCT 的语义代价?

  • 集合语义与多重集(袋)语义的区别
  • SQL 默认采用多重集的原因
  • SELECT DISTINCT 的去重代价

关系代数基于集合论,要求关系中不允许重复元组,保证运算结果可预测;SQL 标准出于实用考虑采用多重集(Multiset/Bag)语义:表在逻辑上是多重集,SELECT 默认不去重,因为数据库表常含重复行(没有主键或 SELECT 了非键列),完全强制去重会带来不必要的排序/哈希开销,且与"物理存储一行为一条记录"的直觉一致。UNION 与 SELECT DISTINCT 是显式回到集合语义的操作。

SELECT DISTINCT 的语义代价:它要求对结果做全量去重,优化器通常用排序(Sort + Unique)或哈希聚合(HashAggregate)实现,时间复杂度 O(n log n) 或 O(n),且需要额外内存或临时文件;若结果集很大、重复率低,去重开销接近全量排序。因此无重复需求时应避免 DISTINCT,用 EXISTS 等改写可显著降本。理解 SET 与 MULTISET 的差异,才能正确解释"为什么同一查询加不加 DISTINCT 性能差异巨大"以及 UNION 与 UNION ALL 的取舍。

答题主线是"关系代数追求理论纯粹(集合),SQL 追求实用高效(多重集)":默认不去重省掉排序/哈希开销,需要集合语义时显式 DISTINCT/UNION。把去重的两种实现与复杂度讲清,即完成从语义到代价的完整回答。

#
★★★

8. 为什么说主键的本质是 UNIQUE NOT NULL + 复制标识符的语义?是否所有表都必须有主键?堆表(Heap Table)与索引组织表(IOT)的差异是什么?

为什么说主键的本质是"UNIQUE NOT NULL + 复制标识符"的语义?是否所有表都必须有主键?堆表(Heap Table)与索引组织表(IOT)的差异是什么?

  • 主键 = 唯一 + 非空 + 复制/行标识语义
  • 无主键表的存在性与风险
  • 堆表与 IOT 的物理组织差异

主键在语义上等价于 UNIQUE NOT NULL:保证取值唯一且非空;在复制与高可用场景下,主键还承担"复制标识符"职责——逻辑复制(如 PostgreSQL 的 walsender、基于行的事件流)用主键定位、去重、合并目标行,无主键的表在同步复制、CDC、分库分表时难以标识同一业务行,这也是"主键是复制标识符"说法的来源。

并非所有表都必须有主键:日志表、临时表、纯追加的事实表可以无主键,但无主键会带来重复数据无法去重、复制与 CDC 失效、索引效率下降、行难以精确 UPDATE/DELETE(需 ctid)等风险,因此生产环境建议每张表都有主键。物理组织上,堆表(Heap Table,PostgreSQL 默认、MySQL InnoDB 的二级索引)数据按插入顺序存放在堆页中,主键索引是独立的二级结构,插入不要求有序,更新可能产生行迁移;索引组织表(IOT,Oracle、InnoDB 主键索引本身)数据按主键顺序存储在 B+ 树叶子,主键查询免二次回表,但插入必须按序定位、主键变更代价高。堆表适合频繁更新与插入模式随机,IOT 适合主键范围扫描密集的场景。

答题分三块:语义层(UNIQUE NOT NULL + 复制标识符)、必要性层(可以没有但风险多)、物理层(堆 vs IOT 的组织与代价)。把复制标识符、ctid/rowid、回表这些关键概念带出,即体现深度。

#
★★★

9. 主键选择 INT 自增、UUID、雪花 ID、复合键、哈希键的依据分别是什么?请从空间、索引分裂、分布式唯一性、调试可读性四维评分。

主键选择 INT 自增、UUID、雪花 ID、复合键、哈希键的依据分别是什么?请从空间占用、索引分裂、分布式唯一性、调试可读性四个维度进行评分?

  • 五类主键方案的实现原理
  • 空间、索引分裂(页分裂)、分布式唯一、可读性四维权衡
  • 按业务场景选型的方法

五类主键各有依据:INT 自增(SERIAL/AUTO_INCREMENT)空间最小(4-8 字节)、插入有序、缓存友好,是单机 OLTP 首选,但无法跨库唯一且暴露业务量;UUID v4 随机 128 位,可离线生成、全局唯一,但占用大(16 字节,文本 36 字符)、随机插入导致索引页分裂与缓存命中率下降;雪花 ID 64 位(时间戳+机器 ID+序列号)有序可分布式生成,兼顾唯一与趋势递增,是分布式主键主流方案;复合键(多列主键)表达业务自然键,省去关联,但占用大、二级索引放大、ORM 映射复杂;哈希键(如 UUID 哈希)用于消除热点、均匀分布,但牺牲范围扫描能力。

四维评分:空间上 INT 自增最优、雪花次之、UUID 最差;索引分裂与顺序写入上 INT 自增/雪花最优、随机 UUID 最差;分布式唯一性上 UUID、雪花、哈希键最优,自增需额外方案(号段、Snowflake 变体);调试可读性上 INT/复合键(业务可读)最优、UUID 文本可接受、雪花含时间信息。工程上单机选 INT 自增、分布式选雪花 ID(或号段自增)、离线导入选 UUID,并配合 B+ 树顺序插入特性避免页分裂与写放大。

答题采用"方案+四维评分+选型结论"结构:先逐一说明每种键的原理与适用依据,再按四个维度打分,最后给出单机/分布式/离线三种典型场景的结论。能讲清"随机键导致页分裂与缓存失效"是加分项。

#
★★★

10. 代理键(Surrogate Key)与自然键(Natural Key)在数据迁移、ORM 映射、跨系统集成中的优劣是什么?业务字段作为主键的潜在风险。

代理键(Surrogate Key)与自然键(Natural Key)在数据迁移、ORM 映射、跨系统集成中的优劣是什么?业务字段作为主键存在哪些潜在风险?

  • 代理键与自然键的定义与区别
  • 数据迁移、ORM、跨系统集成中的影响
  • 业务字段作为主键的风险(变更、长度、耦合)

代理键(Surrogate Key)是与业务无关、由系统生成的键(自增、UUID、雪花 ID);自然键(Natural Key)是业务本身具有唯一性的字段(身份证号、订单号、ISBN)。数据迁移上,代理键可任意生成、不依赖源系统,但跨系统映射需要维护 key 映射表;自然键天然可对接外部系统、迁移可校验,但源系统字段格式变化会导致全表改键。ORM 映射上,代理键简单稳定、适合作为实体标识(Hibernate、SQLAlchemy 的 identity),自然键则要求实体实现 equals/hashCode 且一旦业务规则变化就要级联修改。跨系统集成上,自然键是系统间对账、去重的公共标识,代理键只是内部实现细节,不能暴露为对外接口键。

业务字段作为主键的潜在风险:业务字段可能变更(如手机号换号、税号合并),修改主键会级联波及所有外键引用与索引;字段可能偏长、含业务含义导致存储膨胀与索引变大;业务唯一性规则(如"手机号+渠道"才唯一)演化后原主键失效;暴露给外部后成为遍历与注入的入口。因此主键原则上用代理键,自然键用 UNIQUE 约束保证,主键与业务键职责分离。

答题按三个场景展开优劣,再集中列业务字段作主键的风险:可变性、长度膨胀、规则演化、安全暴露。最后给出"代理键作主键+自然键加唯一约束"的分离式结论,是工程界标准答案。

#
★★★

11. 关系数据库与键值存储、文档数据库在关系运算上的根本差异是什么?为什么 NoSQL 系统通常不支持 JOIN?

关系数据库与键值存储、文档数据库在关系运算上的根本差异是什么?为什么 NoSQL 系统通常不支持 JOIN?

  • 关系代数运算与数据模型的关系
  • 键值/文档模型的按键访问特征
  • JOIN 的分布式代价与去规范化策略

根本差异在数据模型与运算能力:关系数据库基于集合论与关系代数,数据按规范化后的多表组织,通过 JOIN、聚合、子查询等关系运算在查询时动态重组信息,模型与运算分离(数据怎么存与怎么查无关);键值存储(Redis、DynamoDB)只提供按 Key 的 Get/Put,数据组织围绕键;文档数据库(MongoDB)以嵌套文档表达一对多关系,查询以单文档为主、支持索引与聚合管道,但跨文档关联能力有限。

NoSQL 不支持(或弱支持)JOIN 的原因:其一,为水平扩展,数据按键或分片键分布到多节点,跨节点 JOIN 需要把相关数据拉到一处,产生大量网络传输与两阶段汇聚,违背"分区可扩展"的设计目标;其二,弱一致性与去规范化模型下数据冗余,Join 语义无法保证;其三,追求低延迟单键访问,牺牲表达力换取性能与扩展性。工程对策是数据建模时做反规范化(冗余字段、嵌套文档、聚合键)或引入宽表、物化视图、搜索索引(如 Elasticsearch)承担关联查询,应用层编排替代数据库 JOIN。

答题主线:模型差异(集合论关系代数 vs 键访问 vs 文档聚合)→ NoSQL 拒绝 JOIN 的三点原因(分片网络代价、一致性、性能目标)→ 工程替代方案(反规范化、应用层组装)。能讲清"JOIN 是关系模型的自然运算,与分布式扩展冲突"即为高分。

#
★★★

12. 关系模型中是否允许一个表引用自身(自引用外键)?典型的树形结构(parent_id)与图结构(边表)如何用关系建模?

关系模型中是否允许一个表引用自身(自引用外键)?典型的树形结构(parent_id)与图结构(边表)如何用关系建模?

  • 自引用外键的合法性与语义
  • 邻接表(parent_id)建树的方法与查询
  • 边表建模图结构及其递归遍历

关系模型允许表引用自身:自引用外键(Self-Referential FK)把外键指向同一张表的主键,是合法的参照完整性用法。典型的树形结构用邻接表建模:节点表含 id 与 parent_id,parent_id 引用同表 id,根节点的 parent_id 为 NULL。树的中序遍历、路径查询依赖递归 CTE(WITH RECURSIVE)自连接父子,深度可任意;若需高效子树查询,可用物化路径(path)、嵌套集或 closure table 辅助。

图结构用边表(Edge Table)建模:节点表存顶点,边表存 (from_id, to_id, 属性),每行是一条边,两点间多条边或多类型边(带 label)都能表达。图上的可达性、最短路径、环检测需要递归查询或专用图引擎(如 PGQ、Neo4j)。自引用表的陷阱:无限层级必须校验深度(防止数据错误导致递归死循环)、删除父节点需考虑子节点处理(CASCADE 或先置空)、递归 CTE 要设终止条件与 UNION 去重防环。

答题先肯定自引用外键合法,再分别讲树(邻接表 + 递归 CTE)与图(边表 + 遍历算法)的建模方式,最后指出深度校验、递归终止等实践陷阱,覆盖理论到工程的完整链路。

CREATE TABLE category (
  id INT PRIMARY KEY,
  parent_id INT REFERENCES category(id),
  name TEXT
);
WITH RECURSIVE tree AS (
  SELECT id, parent_id, name FROM category WHERE parent_id IS NULL
  UNION ALL
  SELECT c.id, c.parent_id, c.name FROM category c JOIN tree t ON c.parent_id = t.id
) SELECT * FROM tree;
#
★★★

13. 关系模型的封闭性如何保证 SQL 查询的输出仍是关系?这对组合查询(子查询、CTE、视图嵌套)的语义有何影响?

关系模型的封闭性(Closure)如何保证 SQL 查询的输出仍是关系?这对子查询、CTE、视图嵌套等组合查询的语义有何影响?

  • 封闭性的定义:运算的输入输出都是关系
  • 组合查询可嵌套的数学基础
  • 视图、CTE、子查询共享同一语义模型

封闭性(Closure)指关系代数运算以关系为输入、也以关系为输出,因此运算结果可以继续参与其他运算,任意复合表达式的结果仍是关系。SQL 继承这一性质:SELECT 的输出是表,可以再被 FROM、子查询、UNION 等消费,于是子查询、CTE、视图嵌套在语义上都成立——每个嵌套层都处理一个"表"。

封闭性对组合查询的影响:其一,子查询可在 FROM(派生表)、WHERE(相关/非相关)、SELECT 列表(标量)中出现,因为中间结果都是关系;其二,CTE 与视图本质是把"一段查询"命名为可复用的关系,视图嵌套多层不会破坏语义;其三,优化器可以利用封闭性做等价改写(视图合并、子查询提升、CTE 内联),只要改写保持输入输出关系不变。理解封闭性就能解释"为什么 SQL 写多复杂都不会跳出关系模型"以及"为什么任何子查询都可以改写为等价的 JOIN 或 CTE"。

答题从"运算封闭→结果可再参与运算→嵌套合法"的链条展开,再落到子查询/CTE/视图三类组合形式,最后点出封闭性支撑优化器等价改写。概念清晰、层次分明即可。

#
★★★

14. 包含依赖(Inclusion Dependency)与函数依赖(Functional Dependency)在参照完整性表达上的等价性如何?哪些完整性不能用函数依赖表达?

包含依赖(Inclusion Dependency)与函数依赖(Functional Dependency)在参照完整性表达上的关系是什么?哪些完整性约束不能用函数依赖表达?

  • 函数依赖与包含依赖的定义
  • 外键约束的依赖表达
  • 函数依赖无法表达的约束类型

函数依赖(FD)是关系内部属性间的依赖:X→Y 表示 X 的值决定 Y 的值,它刻画"关系内部的确定性";包含依赖(IND)是跨关系的包含关系:R 的某些列取值必须包含于 S 的某些列取值集合,外键约束(FOREIGN KEY)正是典型的包含依赖。二者刻画不同层面的完整性:FD 表达实体内部约束(如学号决定姓名),IND 表达跨表引用约束(如选课表的学号必须存在于学生表),不能互相替代。

不能用函数依赖表达的完整性包括:参照完整性(外键是包含依赖而非 FD)、NOT NULL/域约束(值域范围,如 CHECK)、键约束以外的唯一性(UNIQUE 是 FD 的特殊形式但表达为约束对象)、以及涉及多表多行的事务级条件(如余额总和必须为 0 的跨行断言,需 CHECK 子查询或触发器)。函数依赖主要用于关系模式设计(范式判定、候选键识别),而约束体系(PRIMARY KEY、FOREIGN KEY、CHECK、UNIQUE、NOT NULL)才是数据库完整性的完整表达,二者的对应与区别是数据建模理论的基础。

答题关键是区分"FD 是模式设计工具(内部依赖)"与"IND 是引用完整性表达(跨表包含)",再列举 FD 覆盖不到的约束类型(外键、CHECK、域约束)。能说出外键本质是包含依赖即抓住考点。

#
★★★

15. 参照动作(Referential Action)选择 CASCADE 与 SET NULL 在审计、回收站、级联删除场景下的取舍准则是什么?

在选择参照动作(Referential Action)时,CASCADE 与 SET NULL 在审计、回收站、级联删除等场景下的取舍准则是什么?

  • CASCADE 与 SET NULL 的语义差异
  • 审计与回收站场景下的数据保留要求
  • 级联删除的深度与不可恢复风险

CASCADE 表示删除父行时自动删除所有引用它的子行,适合父子生命周期严格一致的场景(如订单头与订单明细),能保证数据不产生孤儿引用;SET NULL 表示删除父行时把子表外键置为 NULL,保留子行但解除归属关系,适合子行可独立存在的场景。取舍准则:先问"父行删除后子行是否还有存在价值",有则 SET NULL/SET DEFAULT,无则 CASCADE;再问"删除是否可恢复",涉及审计与回收站时必须保留数据痕迹。

审计场景禁止物理 CASCADE:审计表要求保留历史关联(如"该订单曾属于某客户"),物理级联删除会毁掉审计链路,应使用 NO ACTION 加软删除(deleted_at 标记)或把引用改为不级联的冗余快照;回收站场景同样如此,父行"删除"实际是标记,子行不能随之物理消失,否则无法整体还原;级联删除本身有深度风险:多层级外键(祖→父→子)会递归传导,一旦链路配置错误可能误删大量数据,且跨表级联不产生逐行审计日志。因此生产环境默认用 NO ACTION/RESTRICT,明确业务需要时才启用 CASCADE,并用事务包裹、先查后删、保留审计。

答题给出"生命周期一致→CASCADE,子行独立→SET NULL,需审计/可恢复→禁止物理级联"的准则框架,再分别阐述审计、回收站、深层级联三类场景,突出"级联删除不可逆、无审计"的工程共识。

#
★★★

16. 多对多关系在关系模型中如何表达?连接表(Join Table)与数组外键、JSON 数组外键的取舍依据是什么?

多对多(M:N)关系在关系模型中如何表达?连接表(Join Table)与数组外键、JSON 数组外键各自的取舍依据是什么?

  • M:N 关系必须拆分为两个 1:N
  • 连接表的规范设计(复合主键、联合唯一、索引)
  • 数组/JSON 外键的适用场景与代价

关系模型无法直接表达 M:N,标准做法是引入连接表(Junction Table):把 M:N 拆成两个 1:N,连接表同时引用双方主键,复合主键或联合唯一约束防止重复关联,并为两侧外键建索引。例如学生与课程通过选课表 student_course(student_id, course_id, score) 关联,student_id 与 course_id 联合唯一。连接表还能携带关系属性(成绩、选课时间),这是关系建模的黄金标准。

数组外键(PostgreSQL 数组列存外键 id 列表)与 JSON 数组外键(如 MySQL JSON 列存 id 数组)的取舍:优点是单表读写、避免 JOIN,适合"关系数量少且只整体读写、无需按关联成员查询"的场景(如文章标签);代价是丧失参照完整性(数组无法声明外键)、无法高效反查(查"含某标签的文章"需全文/JSON 扫描或额外索引)、并发更新整个数组会产生写冲突与行锁放大、数据冗余难以保证一致性。取舍依据:需要参照完整性、按关联侧反查、关联规模大或更新频繁时用连接表;关系只作附属信息、整体读写、规模小时才考虑数组/JSON,并接受一致性由应用保证。

答题先讲规范做法(连接表拆两个 1:N、复合主键、索引、可带关系属性),再对比数组/JSON 外键的优缺点,给出"需要完整性/反查/并发→连接表"的取舍结论,体现工程判断。

CREATE TABLE student_course (
  student_id INT NOT NULL REFERENCES student(id),
  course_id  INT NOT NULL REFERENCES course(id),
  score      NUMERIC(5,2),
  PRIMARY KEY (student_id, course_id)
);
#
★★★

17. 关系代数中的选择(σ)与 SQL 中的 WHERE 子句有何对应?投影(π)与 SELECT 列表有何对应?

关系代数中的选择(σ)与 SQL 中的 WHERE 子句、投影(π)与 SELECT 列表之间有何对应关系?

  • 选择 σ 与 WHERE 的行过滤对应
  • 投影 π 与 SELECT 列表的列裁剪对应
  • 二者在逻辑执行顺序中的位置

选择(Selection, σ)按谓词过滤行:σ_{age>30}(R) 选取 R 中满足 age>30 的所有元组,对应 SQL 的 WHERE 子句(含 HAVING 的过滤语义);投影(Projection, π)按属性列裁剪:π_{name,age}(R) 只保留 name、age 两列并去重(严格关系代数投影去重),对应 SQL 的 SELECT 列表(SQL 不去重,除非 DISTINCT)。σ 是行级过滤、π 是列级裁剪,二者叠加实现"行列同时裁剪"。

对应关系在优化上意义重大:SQL 的逻辑执行顺序中,FROM(关系)→ WHERE(选择)→ SELECT 列表(投影)→ 去重/排序,与关系代数表达式 σ(π(R)) 或 π(σ(R)) 的复合一致;优化器可把选择下推(σ 先于连接执行,减少连接输入行数)与投影下推(π 先裁剪无用列,减少行宽与 IO),这正是谓词下推(Predicate Pushdown)与投影裁剪(Column Trimming)的理论基础。理解 σ/π 与 WHERE/SELECT 的对应,才能解释"为什么 WHERE 过滤多的查询更快"以及"为什么不要 SELECT *"。

答题先给出一一对应(σ↔WHERE、π↔SELECT 列表),再讲清行过滤与列裁剪的层次差异,最后上升到优化器下推原理,把理论对应与工程性能联系起来。

#
★★★

18. 在 SQL 中如何声明主键?请给出 PostgreSQL、MySQL、SQL Server 三种主流方言的等价写法。

在 SQL 中如何声明主键?请分别给出 PostgreSQL、MySQL、SQL Server 三种主流方言的等价写法?

  • 列级与表级主键声明
  • 三种方言的语法等价性
  • 隐式索引与约束名的差异

主键声明有两种位置:列级内联(CONSTRAINT pk PRIMARY KEY 紧跟列定义)与表级声明(所有列定义之后单独一行 PRIMARY KEY (...))。三种方言在核心语法上等价,SQL 标准语法为 PRIMARY KEY (col1, col2),都可写成表级或列级,并可命名(CONSTRAINT name PRIMARY KEY)。差异在细节:PostgreSQL 支持列级 PRIMARY KEY 内联、自动创建同名唯一索引,非空自动满足;MySQL InnoDB 表级 PRIMARY KEY 自动创建聚簇索引(主键索引即数据),列级写法同样支持;SQL Server 表级主键自动创建唯一聚集索引(默认 CLUSTERED),可指定 NONCLUSTERED,还可声明为延迟检查(DEFERRABLE 仅 PostgreSQL 支持)。

写法的等价性意味着跨库迁移时主体语法可复用,但要注意:MySQL 的 PRIMARY KEY 必须配合 ENGINE=InnoDB 才有聚簇语义;PostgreSQL 主键列默认 NOT NULL;SQL Server 的 CONSTRAINT 名是全局 schema 内唯一。面试与实战中推荐表级命名写法 CONSTRAINT pk_xxx PRIMARY KEY (...) 以利于运维识别。

答题给出三种方言各自的等价 DDL,并说明共性与差异(聚簇索引、约束命名、NULL 处理)。能主动指出"三种方言核心语法一致,差异在物理实现"体现对跨库迁移的把握。

-- PostgreSQL / MySQL / SQL Server 通用表级写法
CREATE TABLE users (
  id INT,
  name VARCHAR(50),
  CONSTRAINT pk_users PRIMARY KEY (id)
);
-- SQL Server 显式非聚集
CREATE TABLE users (
  id INT,
  name VARCHAR(50),
  CONSTRAINT pk_users PRIMARY KEY NONCLUSTERED (id)
);
#
★★★

19. 集合运算(并、交、差)与 SQL 的 UNION、INTERSECT、EXCEPT 的对应关系如何?MySQL 8.0.31+ 才支持 INTERSECT/EXCEPT,早期版本如何模拟?

集合运算(并、交、差)与 SQL 的 UNION、INTERSECT、EXCEPT 的对应关系如何?MySQL 8.0.31 之前不支持 INTERSECT/EXCEPT 时如何模拟?

  • 并/交/差与 UNION/INTERSECT/EXCEPT 的对应
  • UNION 与 UNION ALL 的区别
  • 用 JOIN、NOT EXISTS 模拟 INTERSECT/EXCEPT

集合运算一一对应:并(∪)对应 UNION(去重)或 UNION ALL(不去重,对应多重集并);交(∩)对应 INTERSECT;差(−)对应 EXCEPT(PostgreSQL/SQL Server 语法;Oracle 用 MINUS)。三者都要求两侧列数相同且对应列类型兼容,结果列名取左侧;INTERSECT/EXCEPT 默认去重。UNION 与 UNION ALL 的差异是去重:UNION 隐式去重(排序或哈希),UNION ALL 直接拼接,性能更高。

MySQL 8.0.31 之前不支持 INTERSECT/EXCEPT,模拟方式:交集用 INNER JOIN 加 DISTINCT,或 IN 子查询(SELECT DISTINCT col FROM a WHERE col IN (SELECT col FROM b));差集用 LEFT JOIN ... WHERE b.key IS NULL 或 NOT EXISTS(SELECT DISTINCT col FROM a WHERE NOT EXISTS (SELECT 1 FROM b WHERE b.key = a.col)),注意 NULL 参与 NOT IN 的陷阱,优先用 NOT EXISTS。模拟时保持列对齐与类型一致,并确认结果唯一性语义与原生运算一致。

答题先列标准对应关系并强调 UNION/UNION ALL 的去重差异,再给出 MySQL 早期的 JOIN/NOT EXISTS 模拟写法,最后提醒 NOT IN 的 NULL 陷阱,覆盖理论与工程双层面。

-- 模拟 INTERSECT:两表共有的 id
SELECT DISTINCT a.id FROM a JOIN b ON a.id = b.id;
-- 模拟 EXCEPT:a 有而 b 没有的 id
SELECT a.id FROM a WHERE NOT EXISTS (SELECT 1 FROM b WHERE b.id = a.id);
#
★★★

20. 关系代数中的赋值(Assignment)符号如何用于表达迭代式查询(如传递闭包)?为什么 SQL:1999 之前的标准缺乏递归查询能力?

关系代数中的赋值(Assignment)符号如何用于表达迭代式查询(如传递闭包)?为什么 SQL:1999 之前的标准缺乏递归查询能力?

  • 赋值运算与中间关系的绑定
  • 迭代式查询与传递闭包
  • SQL:1999 引入递归 CTE 的历史原因

关系代数通过赋值(Assignment, ←)把表达式结果绑定为命名关系,如 T ← π(σ(R)),之后可引用 T。结合迭代,可表达传递闭包等递归查询:R₁ ← 基础关系;R_{i+1} ← R_i ∪ (R_i ⋈ 边关系),反复应用直到结果不再变化(不动点),最终得到可达性闭包。赋值+迭代使关系代数具备计算能力,等价于 Datalog 的递归语义。

SQL:1999 之前缺乏递归查询能力的原因:其一,标准以"单条 SELECT 语句的声明式组合"为模型,没有为"反复应用同一查询直到不动点"定义语义,而递归必须引入迭代与终止判定;其二,需要处理无限结果与循环(须规定 UNION 去重或 UNION ALL 加深度限制的语义),语言层面必须新增 WITH RECURSIVE 与递归工作表的规则;其三,优化器与执行器需要专门的递归算法(迭代式工作队列、半朴素评估),技术成熟较晚。SQL:1999 引入递归 CTE(WITH RECURSIVE),SQL Server 2005、PostgreSQL 8.4、MySQL 8.0 相继实现,把传递闭包、树遍历等迭代查询纳入标准 SQL。

答题分两步:先用赋值+不动点迭代说明理论表达力,再从语义定义、终止性、执行技术三点解释 SQL:1999 之前缺失递归的历史原因,最后落到递归 CTE 的引入。

#
★★★

21. 关系的笛卡尔积(Cartesian Product)与自然连接的执行代价差异如何?为什么现代优化器倾向于避免显式笛卡尔积?

关系的笛卡尔积(Cartesian Product)与自然连接的执行代价差异如何?为什么现代优化器倾向于避免显式笛卡尔积?

  • 笛卡尔积的行数爆炸与 IO 代价
  • 连接条件缺失导致的嵌套循环成本
  • 优化器对笛卡尔积的处理策略

笛卡尔积是两关系所有元组的组合,结果行数 = |R|×|S|,若 R 有 100 万行、S 有 100 万行,结果就是 10¹² 行,内存与 IO 完全不可承受;自然连接(或带 ON 条件的等值连接)在笛卡尔积基础上按连接条件过滤,结果行数通常远小于乘积,且可用哈希连接、归并连接等高效算法,代价差异可达数量级。即便只用一列做连接,优化器也会先过滤再连接,避免中间结果膨胀。

现代优化器避免显式笛卡尔积的原因:其一,无连接条件的 CROSS JOIN 或"忘写 ON 的 JOIN"是最常见的性能事故来源,优化器必须识别并提示;其二,真正的笛卡尔积无索引可利用,只能嵌套循环全量扫描,复杂度 O(|R|×|S|);其三,连接顺序(Join Order)规划时,优化器总是优先安排有连接条件的表对,把笛卡尔积作为最后手段(甚至禁止,如 PostgreSQL 对隐式笛卡尔积给出提示、Hive 等需要显式加 distribute 才能容忍)。工程上应始终为 JOIN 提供连接条件,用 WHERE 过滤 + 连接条件分解来消灭隐式笛卡尔积。

答题从行数爆炸的数学本质出发,对比连接算法(哈希/归并 vs 嵌套循环全扫描),再解释优化器为何在连接顺序规划中把笛卡尔积排到最后并给出工程建议。

#
★★★

22. 半连接(Semi-Join)与反连接(Anti-Join)在 EXISTS / NOT EXISTS 子查询中的等价表达是什么?请从关系代数角度证明 EXISTS 等价于半连接。

半连接(Semi-Join)与反连接(Anti-Join)在 EXISTS / NOT EXISTS 子查询中的等价表达是什么?请从关系代数角度证明 EXISTS 等价于半连接?

  • 半连接与反连接的定义(⋉、⋊)
  • EXISTS/NOT EXISTS 与半连接/反连接的对应
  • 从关系代数推导等价性

半连接(Semi-Join, R ⋉ S)返回 R 中"在连接属性上与 S 至少匹配一行"的元组,但不输出 S 的列,等价于 SELECT DISTINCT r.* FROM R r WHERE EXISTS (SELECT 1 FROM S s WHERE 连接条件);反连接(Anti-Join, R ⋉̄ S)返回 R 中与 S 无任何匹配的元组,等价于 NOT EXISTS。EXISTS 子查询的核心语义就是"是否存在匹配行",与半连接完全一致:EXISTS 只关心匹配存在性、不产生连接重复行,因此 EXISTS 查询在物理上可由半连接算子(Semi Join)直接执行。

关系代数证明:EXISTS (SELECT 1 FROM S WHERE r.a = s.a) 对每个 R 元组 r 判断 ∃s ∈ S: r.a = s.a,该谓词恰为半连接的定义条件,故 R 中满足 EXISTS 的元组集合 = R ⋉ S(连接属性 a),即 EXISTS 等价于半连接;同理 NOT EXISTS 等价于反连接。推论:任何 EXISTS/NOT EXISTS 查询可改写为 INNER JOIN + DISTINCT / LEFT JOIN + IS NULL,优化器也常做子查询提升(Subquery Unnesting)把 EXISTS 转成半连接算子执行。注意 NULL 语义:等值连接中 NULL 不匹配,半连接定义一致,而 NOT IN 与反连接在存在 NULL 时语义不同(NOT IN 可能返回空),因此 NULL 列上应优先用 NOT EXISTS。

答题用定义→等价→证明→推论的结构:先定义 ⋉ 与 ⋊,再说明 EXISTS/NOT EXISTS 恰好对应,用"∃ 谓词即半连接定义"完成证明,最后补充优化器子查询提升与 NOT IN 的 NULL 陷阱。

#
★★

23. 外连接(LEFT/RIGHT/FULL OUTER JOIN)在关系代数中的扩展记号是什么?如何用关系代数符号表达“保留左表所有元组”?

外连接(LEFT/RIGHT/FULL OUTER JOIN)在关系代数中的扩展记号是什么?如何用关系代数符号表达"保留左表所有元组"?

  • 左/右/全外连接的记号(⟕、⟖、⟗)
  • 未匹配元组补 NULL 的语义
  • 用基本运算组合表达外连接

关系代数用扩展记号表示外连接:左外连接 R ⟕ S、右外连接 R ⟖ S、全外连接 R ⟗ S,语义是在内连接结果的基础上,把未匹配一侧的元组也保留,缺失列以 NULL 填充。R ⟕ S 保留左表 R 的全部元组:匹配的元组正常拼接,不匹配的元组仍输出一行、S 侧全部置 NULL,因此结果行数 ≥ |R|。

外连接可用基本运算组合表达:R ⟕ S = (R ⋈ S) ∪ (R − π_{R 的属性}(R ⋈ S)) × {NULL 填充的 S 模式},即"匹配部分并上未匹配左元组与 NULL 行的拼接";右外连接与全外连接可对称或并集得到(R ⟗ S = (R ⟕ S) ∪ (R ⟖ S))。这一组合表达说明外连接是关系代数的派生运算而非新基本运算。工程注意:外连接补出的 NULL 与业务 NULL 无法直接区分(可用标记列),以及 WHERE 对外连接补 NULL 行的过滤会使其退化为内连接(LEFT JOIN 变 INNER JOIN 的经典陷阱)。

答题先给记号(⟕/⟖/⟗)与补 NULL 语义,再重点展开"保留左表所有元组"的集合论表达(匹配 ∪ 未匹配补 NULL),最后落到工程陷阱(WHERE 过滤致退化)。

#
★★

24. 聚集运算(SUM、COUNT、AVG、MIN、MAX)在关系代数扩展中如何形式化?为什么它们打破了封闭性(输出不再是关系而是标量或集合)?

聚集运算(SUM、COUNT、AVG、MIN、MAX)在关系代数扩展中如何形式化?为什么它们打破了封闭性(输出不再是关系而是标量或集合)?

  • 聚集扩展(ℑ)与分组聚集
  • 无 GROUP BY 时输出单行标量
  • 封闭性在聚集上的例外与 SQL 处理

关系代数扩展用聚集运算符号 ℑ 形式化:ℑ_{group_attrs, agg_funcs}(R) 表示按分组属性对 R 分组,每组用聚集函数(SUM、COUNT、AVG、MIN、MAX)计算汇总值。SQL 对应 SELECT 分组列, SUM(col) FROM R GROUP BY 分组列,输出每组的聚集结果;不写 GROUP BY 时对全表作单组聚集,输出恰好一行。

聚集运算打破封闭性:普通关系运算输入输出都是关系,而聚集把"多行归约为一行",无分组时输出的是标量(单行单值),有分组时输出是"分组键+汇总值"构成的表——严格说输出仍是表,但行不再对应原关系中的元组,行数被压缩且与原始行失去一一对应,因此聚集被称为"破坏封闭性的扩展"。SQL 的处理:把聚集结果作为新的关系(表)继续参与运算(如再套 WHERE 需用 HAVING 或子查询),从而在语言层面恢复封闭性;SELECT 列表同时出现普通列与聚集列时,普通列必须出现在 GROUP BY 中,否则语义不确定(MySQL 的宽松模式下会出现任意行取值,PostgreSQL 直接报错)。

答题先给出 ℑ 形式化与 GROUP BY 的对应,再论证"多行归约一行"如何打破封闭性,最后说明 SQL 用 HAVING/子查询恢复封闭性以及 GROUP BY 强制规则,体现理论到语法的贯通。

#
★★

25. 自然连接与等值连接(Equi-Join)的差异体现在哪些列名冲突场景?ON 子句、USING 子句、WHERE 子句三者的语义如何区分?

自然连接与等值连接(Equi-Join)的差异体现在哪些列名冲突场景?ON 子句、USING 子句、WHERE 子句三者的连接语义如何区分?

  • 自然连接按同名列自动等值连接
  • ON/USING/WHERE 的过滤时机与语义差异
  • 同名列冲突与结果列的处理

自然连接(NATURAL JOIN)自动以两表所有同名列做等值连接,结果中同名列只保留一份;等值连接(Equi-Join)通过 ON 条件显式指定等值条件,同名列在结果中各自保留(除非显式裁剪)。差异体现在列名冲突场景:两表都有 id、dept_id 等同名业务列时,自然连接会把"非连接用途的同名列"也纳入连接条件,产生非预期的结果或多余过滤;等值连接只按声明的列连接,行为可控。因此工程上默认用显式 ON,避免 NATURAL JOIN 的隐式耦合。

ON 子句在连接时应用过滤(先连接后过滤的语义,但优化器可下推),结果集包含两表全部列;USING (col) 是等值连接简写,结果中合并为单列,常用于同名列;WHERE 在连接完成后过滤,语义上对内连接 ON 与 WHERE 等价(优化器可互换),但对外连接不同:WHERE 过滤补 NULL 的行会使其退化为内连接(LEFT JOIN 变 INNER JOIN 陷阱)。三者的记忆要点:ON 控制连接配对、USING 是等值连接的列合并简写、WHERE 是连接结果的最终过滤。

答题分两块:自然连接 vs 等值连接(自动同名列 vs 显式条件、结果列合并差异),再对比 ON/USING/WHERE(连接时过滤 vs 列合并简写 vs 连接后过滤),最后点出外连接中 WHERE 的退化陷阱。

#
★★

26. 袋语义(Bag Semantics)与集合语义(Set Semantics)下,关系代数的运算结果有何不同?SQL 默认采用哪种?GROUP BY 引入去重的语义是什么?

袋语义(Bag Semantics)与集合语义(Set Semantics)下,关系代数的运算结果有何不同?SQL 默认采用哪种?GROUP BY 分组后列值的去重语义如何理解?

  • 袋(多重集)与集合的重复元组差异
  • SQL 默认袋语义、显式去重方式
  • GROUP BY 的分组归约与聚集

集合语义不允许重复元组,并、交、差、投影等运算天然去重;袋(多重集)语义允许重复元组,运算保留重复次数,例如袋语义下 UNION 是多重集并(重复计数相加)、投影不去重。SQL 默认采用袋语义:SELECT 结果默认不去重、表可以含重复行,这与物理存储和性能目标一致;需要集合语义时用 DISTINCT、UNION(去重版)、INTERSECT、EXCEPT。两种语义在 COUNT 上差异明显:袋语义 COUNT(*) 统计全部行(含重复),集合语义只数不同元组。

GROUP BY 的语义不是"去重"而是"分组归约":按分组键把行分成若干组,每组输出一行,配合聚集函数(SUM、COUNT、AVG)把组内多行压缩为汇总值;若 SELECT 只有分组键列,其效果类似去重,但机制不同——DISTINCT 是行级整体去重,GROUP BY 是按列分组并可携带聚集。理解袋/集合差异是解释"UNION 与 UNION ALL 结果行数不同""DISTINCT 与 GROUP BY 等价性"等问题的理论基础。

答题先对比两种语义在并、投影、计数上的差异,确认 SQL 默认袋语义,再澄清 GROUP BY 是分组归约而非简单去重,最后用 COUNT(*) 与 UNION 的例子固化理解。

#
★★

27. 请完整列出关系代数的八种基本运算(并、差、积、选择、投影、连接、自然连接、除),并分别给出形式化符号与等价 SQL 写法。

请完整列出关系代数的八种基本运算(并、差、积、选择、投影、连接、自然连接、除),并分别给出形式化符号与等价 SQL 写法?

  • 八种基本运算的符号与定义
  • 每种运算的 SQL 等价表达
  • 连接与自然连接、除运算的 SQL 翻译

八种基本运算:并 R∪S(UNION,去重;UNION ALL 为袋语义)、差 R−S(EXCEPT/MINUS)、笛卡尔积 R×S(CROSS JOIN)、选择 σ_cond(R)(WHERE)、投影 π_attrs(R)(SELECT 列列表,SQL 默认不去重、加 DISTINCT 才严格对应)、连接 R⋈S(JOIN ... ON)、自然连接 R⋈S(NATURAL JOIN 或 USING 同名列)、除 R÷S(没有直接 SQL 关键字,需组合表达)。

等价 SQL:σ_{age>30}(R) → SELECT * FROM R WHERE age>30;π_{name}(R) → SELECT DISTINCT name FROM R;R⋈S 按等值条件 → SELECT ... FROM R JOIN S ON 条件;自然连接 → NATURAL JOIN(慎用)或 JOIN ... USING(同名列);除运算 R÷S("包含 S 全部值的那些 R 元组")→ 用 NOT EXISTS 双重否定或 GROUP BY + COUNT 实现,如"选修全部课程的学生"。除运算没有原生语法是 SQL 与关系代数的一个著名差距,需掌握其等价改写。

答题以"符号+定义+SQL 等价"三列结构展开,重点放在除运算(无原生语法、双重否定实现)与连接类运算的翻译上,展示对关系代数完整体系的掌握。

-- 除运算示例:找出选修了全部课程的学生
SELECT s.name FROM student s
WHERE NOT EXISTS (
  SELECT 1 FROM course c
  WHERE NOT EXISTS (SELECT 1 FROM enroll e WHERE e.sid = s.id AND e.cid = c.id)
);
#
★★

28. 除运算(Division)的语义“所有满足条件的元组”如何用 SQL 表达?常见的“找出选修了全部课程的学生”查询在 MySQL 与 PostgreSQL 中如何实现?

除运算(Division)"找出满足全部条件元组"的语义如何用 SQL 表达?"找出选修了全部课程的学生"这类查询在 MySQL 与 PostgreSQL 中如何实现?

  • 除运算的语义(R÷S = 包含 S 全部投影值的元组)
  • 双重否定 NOT EXISTS 实现
  • GROUP BY + COUNT 计数实现

除运算 R÷S 的语义:返回 R 中与 S 中每一个元组在公共属性上都匹配的那些元组,即"满足全部条件的元组"。经典实现是双重否定(Not-Exists 两重嵌套):先找"存在某门课程该生没选修",再排除之,等价于"不存在任何未选修的课程"。以"选修了全部课程的学生"为例:外层 NOT EXISTS 遍历学生,内层 NOT EXISTS 遍历课程并检查该生是否选了该课,两层都成立则学生被保留,逻辑上等价于全称量化 ∀课程 已选修。

另一实现是计数法:学生选课去重计数 = 课程总数 且 该生选的课程都是存在的课程。GROUP BY sid HAVING COUNT(DISTINCT cid) = (SELECT COUNT(*) FROM course)。注意与双重否定版的语义差异:计数法不校验选课记录是否指向存在课程(若数据干净两者一致),且课程总数用子查询快照。两种写法在 MySQL 与 PostgreSQL 中语法一致(标准 SQL),性能上大表场景双重否定依赖索引、计数法需要先聚合再比较,均可接受;空课程表时双重否定版返回全部学生(全称量化对空集为真),计数法返回空,需按业务定义处理。

答题先讲清除运算的"全称量化"语义,再给双重否定与计数两种实现,指出边界差异(空课程表)与性能要点,覆盖理论语义到 SQL 落地的完整链路。

SELECT s.name FROM student s
WHERE NOT EXISTS (
  SELECT 1 FROM course c
  WHERE NOT EXISTS (SELECT 1 FROM enroll e WHERE e.sid = s.id AND e.cid = c.id)
);
-- 计数法
SELECT s.name FROM student s
JOIN enroll e ON e.sid = s.id
GROUP BY s.id, s.name
HAVING COUNT(DISTINCT e.cid) = (SELECT COUNT(*) FROM course);
#
★★

29. 关系代数中“空集语义”如何处理?空关系参与 UNION、JOIN、INTERSECT 时各有什么约定?

关系代数中"空集语义"如何处理?空关系参与 UNION、JOIN、INTERSECT 时各有什么约定?

  • 空关系的定义与运算吸收元
  • 空集参与并、连接、交的结果
  • 空表在 SQL 中的等价行为

空关系(空集)是没有任何元组、但模式(属性集)仍存在的关系,它保留结构、没有数据。空关系的运算性质:R ∪ ∅ = R(并的空集是单位元)、R × ∅ = ∅、R ⋈ ∅ = ∅(连接空集得空)、R − ∅ = R、∅ − R = ∅、R ∩ ∅ = ∅(交的空集是零元/吸收元)、σ 条件不满足任何元组时结果为与 R 同模式的空关系。这些性质保证关系代数表达式对空输入仍语义一致,不会崩溃。

SQL 中的等价行为:空表参与 JOIN 得空结果;UNION 空结果得另一侧数据;子查询返回空集时,IN 恒为假、EXISTS 恒为假、标量子查询返回 NULL、聚合函数(SUM/AVG 无 GROUP BY)返回 NULL 或 0(COUNT 返回 0)。特别注意:NOT IN 遇到空子查询恒为真,但子查询含 NULL 时 NOT IN 返回空集(NULL 陷阱);全称量化(除运算)在空课程表时返回全部学生。掌握空集语义有助于写出对边界数据鲁棒的查询。

答题先定义空关系"有模式无元组",列举其在并/交/连接/差中的单位元与吸收元性质,再映射到 SQL 的空子查询行为(IN/EXISTS/聚合),最后点出 NOT IN 的 NULL 陷阱。

#
★★

30. 关系代数的“安全性”(Safety)问题是什么?为何 Domain Relational Calculus 可能产生无限结果?

关系代数的"安全性"(Safety)问题是什么?为何域关系演算(Domain Relational Calculus)可能产生无限结果?

  • 关系演算的安全性与有限结果要求
  • 域关系演算的自由变量与域范围
  • 安全表达式与受限演算

安全性(Safety)问题指:关系演算表达式的结果必须是有限的、可判定的关系,不能产生无限元组集合。元组关系演算与域关系演算若允许自由变量取遍"所有可能值",而可能的域无界(如任意整数、任意字符串),结果就可能是无限集,无法实际计算。关系代数天然安全:所有基本运算都在已知有限关系上操作,结果必然有限。

域关系演算可能产生无限结果的原因:公式如 { | ¬Student(x)} 中 x 的自由取值范围是整个定义域(未被任何原子公式限定到具体关系),"非学生"的集合在有限关系补集下仍然无限(除非定义域本身有限),因此违反安全性。解决方式:限定变量范围——要求演算公式中每个自由变量都出现在某个关系原子公式中(安全公式/受限演算),把取值约束在已存在关系的域上;数据库查询语言(SQL)通过 FROM/表引用天然完成这种限定,因此 SQL 查询总是产生有限结果。

答题先定义安全性(结果有限可计算),说明关系代数天然安全,再分析域关系演算自由变量取值无界导致无限结果,最后落到"变量受限出现在关系原子公式中"的解决方案与 SQL 的天然安全性。

#
★★

31. 在查询优化中,选择下推(Predicate Pushdown)与投影下推(Projection Pushdown)各自能减少多少 I/O?哪些场景下推无效?

在查询优化中,选择下推(Predicate Pushdown)与投影下推(Projection Pushdown)各自能减少多少 I/O?哪些场景下推无效?

  • 选择下推减少扫描与连接输入行数
  • 投影下推减少行宽与传输量
  • 下推失效的场景(外连接、聚合、窗口函数、视图)

选择下推(Predicate Pushdown)把 WHERE 条件尽可能下推到表扫描或连接之前执行,直接减少参与后续运算的行数:在表扫描阶段下推可减 IO(如索引条件下推 ICP 减少回表);在连接前下推可减小连接输入规模,I/O 与 CPU 收益接近"过滤掉的行占比"。投影下推(Projection Pushdown)把 SELECT 列裁剪下推到扫描层,只读取需要的列,对行宽很大的表收益显著(减少页读取量与网络传输),对列存引擎更是核心优化。

下推无效的场景:其一,外连接中针对被保留侧(如 LEFT JOIN 左表)的谓词不能下推到内表,否则改变连接语义(补 NULL 行被过滤,LEFT JOIN 退化为 INNER JOIN);其二,聚合与窗口函数之上无法下推行级谓词(须用 HAVING 或外层过滤);其三,视图、CTE、子查询若被优化器判定不可合并(如含 LIMIT、DISTINCT、聚合的物化边界),谓词无法穿透;其四,表达式无法改写(如函数包裹列且无表达式索引)时索引扫描下推失效。理解这些边界是分析执行计划"为何谓词没下推"的基础。

答题分三层:选择下推减行数、投影下推减列宽(各自 I/O 收益来源),再集中列下推失效场景(外连接保留侧、聚合窗口边界、不可合并视图),体现对优化器能力的边界认知。

#
★★

32. 外连接在关系代数中可以用基础运算与选择巧妙表达吗?请给出 LEFT OUTER JOIN 的等价组合(UNION ALL + 谓词选择)。

外连接在关系代数中可以用基础运算与选择组合表达吗?请给出 LEFT OUTER JOIN 的等价组合(如 UNION ALL 配合谓词选择)?

  • 外连接是派生运算
  • 匹配部分与未匹配部分的分治组合
  • SQL 中 UNION ALL 加条件判定的等价写法

可以。外连接是派生运算,可由内连接、差、笛卡尔积、投影、并等基础运算组合表达。LEFT OUTER JOIN 的思路是把结果分成两部分:匹配的元组(内连接结果)与未匹配的左元组(左表减去已匹配部分,再补 NULL 列),两部分用并运算合并。集合论表达:R ⟕ S = (R ⋈ S) ∪ ((R − π_R(R ⋈ S)) × {NULL 填充的 S 模式}),其中 × 构造"左元组×全 NULL 行"完成补列。

SQL 中更实用的等价写法是 UNION ALL 配合条件判定:先用内连接取出匹配行,再用"左表存在且无匹配"(NOT EXISTS 或 NOT IN)取出未匹配行并显式补 NULL 列,两部分 UNION ALL 拼接,列序与类型必须一致。例如 SELECT r., s.col FROM R r JOIN S s ON ... UNION ALL SELECT r., NULL FROM R r WHERE NOT EXISTS (...)。注意用 UNION ALL 而非 UNION(避免去重改变行数);该写法让"外连接补 NULL"可见化,便于理解与调试,但工程上仍优先用原生 LEFT JOIN(优化器更高效)。

答题先用集合论公式展示"匹配 ∪ 未匹配补 NULL",再给出 SQL 的 UNION ALL 等价写法,强调 UNION ALL 不去重与列对齐要点,最后说明原生 LEFT JOIN 仍是工程首选。

SELECT r.id, s.val FROM R r JOIN S s ON r.id = s.rid
UNION ALL
SELECT r.id, NULL FROM R r WHERE NOT EXISTS (SELECT 1 FROM S s WHERE s.rid = r.id);
#
★★

33. 用 SQL 写出关系代数表达式 π_{name}(σ_{age>30}(Student ⋈ Enroll)),并给出至少两种等价改写(如子查询与 CTE)。

请用 SQL 写出关系代数表达式 π_{name}(σ_{age>30}(Student ⋈ Enroll)) 的实现,并给出至少两种等价改写(如子查询与 CTE 形式)?

  • 关系代数到 SQL 的翻译
  • JOIN、派生表子查询、CTE 的等价改写
  • 连接条件与过滤位置的等价性

表达式含义:先做 Student 与 Enroll 的自然连接(公共列为学号 sid),再选择年龄大于 30 的行,最后投影出学生姓名 name。SQL 直译:SELECT DISTINCT s.name FROM Student s JOIN Enroll e ON s.sid = e.sid WHERE s.age > 30。注意 π 对应 DISTINCT(关系代数投影去重),若业务允许重复可去掉 DISTINCT。

等价改写一(派生表子查询):把连接与过滤放进 FROM 子查询,外层再投影:SELECT DISTINCT name FROM (SELECT s.name FROM Student s JOIN Enroll e ON s.sid = e.sid WHERE s.age > 30) t;改写二(CTE):WITH joined AS (SELECT s.name FROM Student s JOIN Enroll e ON s.sid = e.sid WHERE s.age > 30) SELECT DISTINCT name FROM joined;改写三(EXISTS 半连接):SELECT name FROM Student s WHERE s.age > 30 AND EXISTS (SELECT 1 FROM Enroll e WHERE e.sid = s.sid)。三种写法语义等价(自然连接隐含去重对,若 Enroll 对同一学生多行,DISTINCT 保证一致),优化器可互相转换,工程上按可读性与索引利用选择。

答题按"直译→两种改写"展开,强调自然连接→显式 ON、投影→DISTINCT 的翻译要点,并比较三种写法的性能差异(EXISTS 半连接可避免连接后膨胀),体现表达式改写能力。

-- 直译
SELECT DISTINCT s.name FROM Student s JOIN Enroll e ON s.sid = e.sid WHERE s.age > 30;
-- CTE 改写
WITH joined AS (
  SELECT s.name FROM Student s JOIN Enroll e ON s.sid = e.sid WHERE s.age > 30
) SELECT DISTINCT name FROM joined;
-- EXISTS 半连接改写
SELECT name FROM Student s WHERE s.age > 30 AND EXISTS (SELECT 1 FROM Enroll e WHERE e.sid = s.sid);
#
★★

34. INNER JOIN、LEFT JOIN、FULL JOIN 在关系代数扩展中分别如何记号化?

INNER JOIN、LEFT JOIN、FULL JOIN 在关系代数扩展中分别如何记号化?

  • 内连接 ⋈、左外连接 ⟕、右外连接 ⟖、全外连接 ⟗
  • 各记号的定义语义
  • 记号与 SQL 关键字的对应

关系代数扩展记号:内连接用 R ⋈ S(θ-连接、等值连接均用 ⋈ 加条件标注);左外连接 R ⟕ S(Left Outer Join,保留左表全部元组,未匹配补 NULL);右外连接 R ⟖ S(保留右表全部元组);全外连接 R ⟗ S(两侧都保留,未匹配侧补 NULL)。θ-连接可写为 R ⋈_θ S(θ 为条件谓词),等值连接是 θ 为等值条件的特例,自然连接是等值连接后去掉重复同名列的特例。

SQL 对应:INNER JOIN 对应 ⋈;LEFT JOIN/LEFT OUTER JOIN 对应 ⟕;RIGHT JOIN 对应 ⟖;FULL JOIN/FULL OUTER JOIN 对应 ⟗。记忆要点:记号中"开口"朝向被保留侧——⟕ 向左开口表示保留左表。四种连接在行数上满足:内连接 ≤ 左外/右外 ≤ 全外(对应关系中);理解记号与语义的对应,便于阅读论文与教材中的关系代数表达式,也便于把 SQL 连接翻译成代数形式做等价分析。

答题给出四个记号与其语义定义,特别说明"开口朝向保留侧"的记忆方法,再映射到 SQL 关键字,最后补充行数大小关系,形成完整对应表。

#
★★

35. COALESCE、NULLIF、ISNULL、IFNULL 这四个 NULL 处理函数在不同数据库方言中的对应关系与等价表达式是什么?

COALESCE、NULLIF、ISNULL、IFNULL 这四个 NULL 处理函数在不同数据库方言中的对应关系与等价表达式是什么?

  • COALESCE 的多参数取首个非 NULL
  • NULLIF 与 ISNULL/IFNULL 的方言差异
  • 等价表达式的相互转换

COALESCE(a, b, ...) 是 SQL 标准函数,返回参数列表中第一个非 NULL 值,全 NULL 则返回 NULL;NULLIF(a, b) 也是标准函数,a = b 时返回 NULL,否则返回 a。ISNULL 与 IFNULL 都是方言函数:SQL Server 的 ISNULL(a, b) 是两参数版"取非 NULL",语义等价 COALESCE(a, b);MySQL 的 IFNULL(a, b) 同样等价 COALESCE(a, b);注意 MySQL 也有 ISNULL(x) 但其语义是"判断 x 是否为 NULL"(返回 0/1),与 SQL Server 的 ISNULL 完全不同,这是最常见的跨方言坑。

等价表达式:IFNULL(a, b) = ISNULL(a, b) = COALESCE(a, b);NULLIF(a, b) = CASE WHEN a = b THEN NULL ELSE a END。工程建议:跨方言代码统一用标准 COALESCE 与 NULLIF,避免 ISNULL/IFNULL 的语义歧义;NULLIF 常用于把 0 转为 NULL(如 NULLIF(divisor, 0) 防止除零)或去重计数 COUNT(DISTINCT NULLIF(col, 0))。

答题先给出四个函数的定义与方言归属,重点警示 MySQL ISNULL 与 SQL Server ISNULL 的语义差异,再列等价表达式与 NULLIF 的典型用途,突出跨方言移植意识。

-- 等价关系
IFNULL(a, b)  = ISNULL(a, b)  = COALESCE(a, b)
NULLIF(a, b)  = CASE WHEN a = b THEN NULL ELSE a END
-- 防除零
SELECT amount / NULLIF(divisor, 0) FROM t;
#
★★

36. DISTINCT、GROUP BY、ORDER BY 在处理 NULL 时如何排序?NULLS FIRST、NULLS LAST 子句在 PostgreSQL 中的语法如何?

DISTINCT、GROUP BY、ORDER BY 在处理 NULL 时分别如何排序与归组?NULLS FIRST、NULLS LAST 子句在 PostgreSQL 中的语法如何?

  • GROUP BY/DISTINCT 把 NULL 归为同一组
  • ORDER BY 的 NULL 排序规则与方言差异
  • PostgreSQL NULLS FIRST/LAST 语法

DISTINCT 与 GROUP BY 都遵循"NULL 彼此相等"的归组规则:所有 NULL 行被视为同一组,DISTINCT 只保留一个 NULL,GROUP BY 把全部 NULL 归为一组。ORDER BY 的 NULL 排序规则因数据库而异:PostgreSQL 默认 NULL 最大(升序排最后、降序排最前),MySQL 默认 NULL 最小(升序排最前),Oracle 默认 NULL 最大,SQL Server 默认 NULL 最小。这种差异是跨库移植排序结果不一致的常见来源。

PostgreSQL 用 NULLS FIRST / NULLS LAST 显式控制:ORDER BY col ASC NULLS FIRST 把 NULL 排最前,ORDER BY col DESC NULLS LAST 把 NULL 排最后,NULLS 子句须紧跟排序方向。工程建议:涉及 NULL 的排序一律显式声明 NULLS FIRST/LAST(PostgreSQL、Oracle 支持;MySQL 8.0 需用 ISNULL(col) 或 CASE 模拟:ORDER BY (col IS NULL), col),保证排序语义跨库确定。

答题分两部分:归组层面(DISTINCT/GROUP BY 视 NULL 为同一值)与排序层面(各库默认 NULL 位置不同),再给出 PostgreSQL 的 NULLS FIRST/LAST 语法与 MySQL 的模拟写法,强调显式声明的重要性。

-- PostgreSQL:NULL 排最前 / 最后
SELECT * FROM t ORDER BY col ASC NULLS FIRST;
SELECT * FROM t ORDER BY col DESC NULLS LAST;
-- MySQL 8.0 模拟 NULL 排最后
SELECT * FROM t ORDER BY (col IS NULL), col;
#
★★

37. EXISTS 子查询遇到 NULL 时,EXISTS (SELECT NULL) 返回什么?为什么 EXISTS 不受三值逻辑影响?

EXISTS 子查询遇到 NULL 时如何处理?EXISTS (SELECT NULL) 返回什么?为什么 EXISTS 不受三值逻辑影响?

  • EXISTS 只判断"是否存在行"而非行值
  • SELECT NULL 作为行的存在性测试
  • EXISTS 与三值逻辑的关系

EXISTS 只关心子查询"是否返回至少一行",不关心行的列值,因此 SELECT 列表中写 NULL 完全合法:EXISTS (SELECT NULL) 只要子查询能产出任意一行即返回 TRUE,SELECT NULL 只是"生成一行占位"的写法,等价于 EXISTS (SELECT 1),也等价于 EXISTS (SELECT *)。它的语义是"行的存在性测试",与列值无关。

为什么 EXISTS 不受三值逻辑影响:存在性判定基于"是否有匹配行",这是一个二值问题(存在/不存在),不涉及 NULL 比较。子查询返回 NULL 行只是说明"该行存在但值未知",行存在性本身成立;而 =、IN 等运算要对"值"做比较,遇到 NULL 才落入三值逻辑返回 UNKNOWN。因此 EXISTS 的谓词是布尔测试,永远得到 TRUE/FALSE,不产生 UNKNOWN,也不会因 NULL 产生空结果——这正是 NOT EXISTS 在含 NULL 场景下比 NOT IN 安全的原因。

答题核心是"EXISTS 测行不测值":SELECT NULL 只是占位行生成,存在性判定是二值问题,故不受三值逻辑影响;对比 NOT IN 的 NULL 陷阱即可强化理解。

#
★★

38. IN、NOT IN、= ANY、<> ALL 与 NULL 的交互,当子查询返回 NULL 时,NOT IN 为何可能产生空结果?

IN、NOT IN、= ANY、<> ALL 与 NULL 如何交互?当子查询返回 NULL 时,NOT IN 为何可能产生空结果?

  • IN 与 = ANY 的等价关系
  • NOT IN 与 <> ALL 的等价关系
  • NULL 导致 NOT IN 空结果的机制

col IN (子查询) 与 col = ANY (子查询) 语义等价,等价于"存在一行使得 col = 子查询值";col NOT IN (子查询) 与 col <> ALL (子查询) 等价,等价于"对每一行,col 都不等于子查询值"。这些运算都是逐值比较,比较中出现 NULL 时落入三值逻辑。

NOT IN 空结果机制:NOT IN 要求"col 与子查询所有值都不相等",若子查询返回的集合中包含 NULL,则对任何 col(包括非 NULL 值),"col <> NULL" 的结果是 UNKNOWN;且 NOT IN 在集合层需要"全部比较都为 TRUE"才输出该行,只要有一个比较是 UNKNOWN,整体结果既不是 TRUE 也不是 FALSE 而是 UNKNOWN,WHERE 只保留 TRUE,于是整行被过滤;由于所有行都会与那个 NULL 比较,最终结果集为空。例如子查询返回 {1, NULL},col=2 的行:2=1 为 FALSE、2=NULL 为 UNKNOWN,整体 UNKNOWN 被过滤;col=1 的行:1=1 为 TRUE、1=NULL 为 UNKNOWN,同样被过滤,故结果恒为空。规避方法:用 NOT EXISTS 或先排除子查询中的 NULL(NOT IN (SELECT col FROM t WHERE col IS NOT NULL))。

答题先建立 IN/=ANY、NOT IN/<>ALL 的等价关系,再逐步推演"集合中含 NULL → 逐值比较产生 UNKNOWN → 需要全 TRUE 的 NOT IN 失败 → 结果为空"的机制,最后给 NOT EXISTS 等规避方案。

#
★★

39. IS NULL 与 IS NOT NULL 是否走索引?PostgreSQL 中如何创建带 IS NULL 谓词的索引?MySQL 在何种条件下使用?

IS NULL 与 IS NOT NULL 是否能走索引?PostgreSQL 中如何创建支持 IS NULL 谓词的索引?MySQL 在什么条件下会使用?

  • 空值在索引中的存储与扫描
  • PostgreSQL 部分索引支持 IS NULL
  • MySQL 的索引设计与 IS NULL 优化

标准 B+ 树索引把 NULL 当作普通键值存储(各库实现有差异),IS NULL / IS NOT NULL 原则上可走索引范围扫描,但实际是否使用取决于优化器成本估算与索引设计。PostgreSQL 的 B-tree 索引默认把 NULL 排在最前(按 NULLS FIRST 语义存储),IS NULL 查询可走索引;更精准的做法是部分索引:CREATE INDEX idx ON t(col) WHERE col IS NULL,索引只含 NULL 行,体积极小、IS NULL 直接命中。IS NOT NULL 的过滤性通常很差(大部分行非空),优化器往往选择全表扫描。

MySQL InnoDB 中 IS NULL 走索引的条件:索引包含该列且统计信息显示过滤性高(NULL 占比小)时用索引范围扫描;对 IS NULL 常用技巧是"索引列同时覆盖业务值",例如 (dept_id, deleted_at) 组合索引中 deleted_at IS NULL 表示未删除,查询"未删除的某部门记录"可用索引下推(ICP)高效过滤;8.0 的"索引跳跃扫描"(skip scan)在某些 IS NULL 场景也能派上用场。工程建议:高频 IS NULL 查询用部分索引或组合索引精确覆盖,并查看 EXPLAIN 确认使用 Index Scan 而非全表扫描。

答题先确认 NULL 可存索引、IS NULL 可走索引范围扫描,再分别给 PostgreSQL(部分索引)与 MySQL(组合索引+ICP)的设计方案,最后强调用 EXPLAIN 验证,覆盖"能不能"与"怎么用"。

-- PostgreSQL:只为 NULL 行建极小索引
CREATE INDEX idx_t_col_null ON t(col) WHERE col IS NULL;
-- MySQL:组合索引配合 IS NULL 过滤
CREATE INDEX idx_dept_deleted ON employee(dept_id, deleted_at);
SELECT * FROM employee WHERE dept_id = 10 AND deleted_at IS NULL;
#
★★

40. NULL 与空字符串('')的关系,PostgreSQL、Oracle、MySQL、SQL Server 各自如何区分?为何 Oracle 把 '' 与 NULL 视为等同?

NULL 与空字符串('')的关系在各数据库中有何差异?PostgreSQL、Oracle、MySQL、SQL Server 如何区分?为何 Oracle 把 '' 与 NULL 视为等同?

  • NULL 与 '' 的语义与存储差异
  • Oracle 的 '' 即 NULL 规则及原因
  • 其他数据库的区分处理

NULL 表示值缺失,'' 是长度为零的具体字符串值,理论上可以区分。各数据库处理不同:PostgreSQL 严格区分,'' 是空字符串、NULL 是 NULL,且 NOT NULL 约束允许 '' 而拒绝 NULL,'' 参与字符串函数正常;MySQL 默认区分,但 '' 与 NULL 在某些场景行为趋同(如 COUNT 都不计入);SQL Server 区分;Oracle 特殊:把长度为 0 的字符串 '' 视为 NULL(VARCHAR2 语义),INSERT '' 实际存入 NULL,IS NULL 可命中,'' IS NULL 为 TRUE。

Oracle 如此设计的原因:其一,Oracle 的 VARCHAR2 实现不区分"零长度字符串"与"未赋值"两种状态,物理上 NULL 与 '' 都以 NULL 表示,简化存储表示;其二,历史兼容:Oracle 早期(与 RDB 同源)沿用了"空串即 NULL"的语义,后续为兼容大量存量应用而保留。工程影响:跨库迁移时 Oracle 的空串数据迁到 PostgreSQL/MySQL 会变成 NULL 或反之,需显式映射('' → NULL 或 NULL → ''),并注意 NOT NULL 约束与 COUNT 统计口径的变化。

答题先给出"NULL 是缺失、'' 是零长字符串"的理论差异,再列四库的具体行为(重点 Oracle 的 '' = NULL),解释其历史与实现原因,最后落到跨库迁移的映射影响。

#
★★

41. NULL 在唯一约束(UNIQUE)中的处理,PostgreSQL 允许多个 NULL(认为 NULL 各不相等),SQL Server 与 Oracle 默认行为如何?

NULL 在唯一约束(UNIQUE)中的处理有何差异?PostgreSQL 允许多个 NULL(认为 NULL 各不相等),SQL Server 与 Oracle 的默认行为如何?

  • 标准语义:唯一约束下 NULL 互不相等
  • PostgreSQL 允许多 NULL、SQL Server 单 NULL、Oracle 多 NULL
  • 用唯一索引/过滤索引模拟差异

SQL 标准规定唯一约束下 NULL 彼此不相等,因此唯一列允许多个 NULL。PostgreSQL 遵循标准:普通唯一约束与唯一索引都允许多个 NULL;Oracle 同样允许多个 NULL(唯一索引多个 NULL 合法);SQL Server 特殊:唯一索引(UNIQUE INDEX)默认只允许一个 NULL(把 NULL 当作重复值),但唯一约束(UNIQUE CONSTRAINT)允许多个 NULL——行为与索引类型绑定。这一差异直接导致跨库迁移时"软删除唯一""可选业务码唯一"等建模方案行为不一致。

模拟方案:若想在 SQL Server 实现"多 NULL 唯一",用过滤索引 CREATE UNIQUE INDEX ... WHERE col IS NOT NULL(对非 NULL 值强制唯一、NULL 不建索引条目);若想在 PostgreSQL 实现"单 NULL 唯一",可用部分唯一索引 CREATE UNIQUE INDEX ... WHERE col IS NULL 之外再加触发器校验,或用 COALESCE(col, 唯一哨兵值) 的表达式唯一索引。理解各库默认行为是设计"可为空但非空时唯一"列(如手机号、邮箱)的必备知识。

答题先讲标准语义(NULL 互不相等→允许多 NULL),再对比 PostgreSQL/Oracle(多 NULL)与 SQL Server(唯一索引单 NULL、唯一约束多 NULL)的默认行为,最后给过滤索引的模拟方案。

-- SQL Server:只对非 NULL 值强制唯一
CREATE UNIQUE INDEX ux_email ON users(email) WHERE email IS NOT NULL;
-- PostgreSQL:非空时唯一(表达式兜底)
CREATE UNIQUE INDEX ux_email ON users(COALESCE(email, ''));
#
★★

42. NULL 在算术运算中的传播规则,NULL + 1、NULL * 0、NULL || 'abc'(字符串拼接)各自的结果是什么?

NULL 在算术运算中的传播规则是什么?NULL + 1、NULL * 0、NULL || 'abc'(字符串拼接)各自的结果是什么?

  • NULL 的传播性(任何运算结果仍为 NULL)
  • 算术、比较、字符串拼接中的 NULL 行为
  • NULL * 0 不等于 0

NULL 的传播规则:任何表达式只要包含 NULL 操作数,结果就是 NULL(除非函数专门处理 NULL,如 COALESCE、NULLIF)。NULL + 1 结果为 NULL;NULL * 0 也是 NULL——即使数学上任何数乘 0 等于 0,NULL 表示未知值,未知值乘 0 仍未知,SQL 不会按代数简化;NULL || 'abc'(PostgreSQL/标准字符串拼接)结果为 NULL。比较运算同理:NULL = NULL、NULL > 1、NULL < 1 全部是 UNKNOWN。

传播规则的实际影响:计算聚合时 SUM(col) 会忽略 NULL 行,但 SELECT col + 10 对 NULL 行输出 NULL;字符串拼接遇到 NULL 会整体变 NULL(PostgreSQL 的 || 与 MySQL 的 CONCAT 都传播 NULL,只有 CONCAT_WS、PG 的 concat() 等函数会跳过或兜底处理——方言细节需注意);除法、取模中 NULL 也传播。工程上常用 COALESCE(col, 0) + 10 或 NULLIF 把 NULL 转成具体值后再运算,避免结果被 NULL"污染"。

答题给出"NULL 操作数使整个表达式结果为 NULL"的总规则,逐一验证三类运算(算术、乘 0、拼接),强调 NULL * 0 与数学直觉的冲突,再补充方言差异与 COALESCE 兜底实践。

#
★★

43. SQL 中 NULL 的语义到底是什么?它究竟表示“未知”、“不适用”还是“不存在”?为什么 Codd 主张用 A-marks 与多重 NULL 取代单一 NULL?

SQL 中 NULL 的语义到底是什么?它表示"未知"、"不适用"还是"不存在"?为什么 Codd 主张用 A-marks 与多重 NULL 取代单一 NULL?

  • NULL 的多义性:未知/不适用/缺失
  • 单一 NULL 的信息损失
  • Codd 的 A-marks 与多重 NULL 方案

NULL 是一个"无值标记",可以承载多种语义:未知(Unknown,值存在但不知道,如某人年龄未采集)、不适用(Not Applicable,该属性对对象无意义,如未婚者的配偶名)、缺失/不存在(Missing/Inapplicable,如未填写的可选项)。单一 NULL 无法区分这三种情况,导致查询与统计(COUNT、AVG、WHERE)时对三种语义一刀切,业务上无法区分"没采集"与"不适用",这是 NULL 多义性的根本缺陷。

Codd 的方案:用 A-marks(可能标记)扩展关系模型,允许每个属性携带多个标记(如"未知-缺失""未知-不适用"),用不同的 NULL 变体(如"值未知""值不存在""值不适用")区分语义,使查询可以对不同标记做不同处理(如统计时排除"不适用"但计入"未知")。该方案理论上更精确,但实现复杂度高(标记传播、三值逻辑扩展为多值逻辑、存储开销),主流数据库未采纳,实践中用辅助列(如 status 枚举列 + 值列)或 CHECK 约束区分"未填写/不适用/未知",或用哨兵值(-1、'N/A')配合约定。

答题先剖析单一 NULL 的多义性缺陷,再阐述 Codd 的 A-marks 与多重 NULL 方案(按语义区分标记)及其未落地的原因,最后给出工程替代(状态列、哨兵值),体现理论深度与实践转化。

#
★★

44. 三值逻辑(TRUE、FALSE、UNKNOWN)下,AND、OR、NOT 的真值表是怎样的?UNKNOWN AND FALSE、UNKNOWN OR TRUE 各自的真值是什么?

三值逻辑(TRUE、FALSE、UNKNOWN)下,AND、OR、NOT 的真值表是怎样的?UNKNOWN AND FALSE、UNKNOWN OR TRUE 各自的真值是什么?

  • 三值逻辑的真值表
  • AND/OR 的"决定性操作数"规则
  • NOT UNKNOWN 的结果

三值逻辑中 AND 只要有一个 FALSE 结果就是 FALSE(FALSE 决定性),否则全 TRUE 才 TRUE、其余 UNKNOWN;OR 只要有一个 TRUE 结果就是 TRUE(TRUE 决定性),否则全 FALSE 才 FALSE、其余 UNKNOWN;NOT 反转 TRUE/FALSE,NOT UNKNOWN 仍是 UNKNOWN。具体真值:UNKNOWN AND TRUE = UNKNOWN,UNKNOWN AND FALSE = FALSE,UNKNOWN AND UNKNOWN = UNKNOWN;UNKNOWN OR TRUE = TRUE,UNKNOWN OR FALSE = UNKNOWN;NOT UNKNOWN = UNKNOWN。

UNKNOWN AND FALSE = FALSE 的原因:无论 UNKNOWN 实际是 TRUE 还是 FALSE,与 FALSE 相与结果都是 FALSE,结果不依赖未知部分,故可确定为 FALSE;UNKNOWN OR TRUE = TRUE 同理(TRUE 决定性)。这一"决定性(dominance)"性质是 SQL 过滤的关键:WHERE 中 OR 只要一侧条件为 TRUE 行就被保留,AND 中一侧为 FALSE 行就被剔除,而 UNKNOWN 则让该行"落空"(除非决定性条件存在),因此 NULL 条件与具体条件的组合行为可以预判。

答题先完整给出 AND/OR/NOT 真值表,再以 UNKNOWN AND FALSE、UNKNOWN OR TRUE 为例展示决定性规则,最后联系 WHERE 过滤行为,把抽象逻辑落到查询语义。

#
★★

45. 为什么 WHERE 子句中的 NULL 比较(column = NULL)永远返回 UNKNOWN 而非 TRUE?这对查询的过滤行为有什么影响?

为什么 WHERE 子句中的 column = NULL 永远返回 UNKNOWN 而非 TRUE?这对查询的过滤行为有什么影响?

  • NULL 不是值,比较无意义
  • WHERE 只保留 TRUE 行的规则
  • 必须用 IS NULL 的原因

column = NULL 返回 UNKNOWN 而非 TRUE,是因为 NULL 表示"值缺失/未知",未知值之间的等值比较没有确定结果:若 column 是 NULL,NULL = NULL 两边都是未知,无法断言相等;若 column 是非 NULL 值,该值与"未知"比较也无法断言相等。SQL 规定所有与 NULL 的等值比较都产生 UNKNOWN,唯一例外是 IS NULL / IS NOT NULL 的专门判定。

对过滤行为的影响:WHERE 子句只保留谓词为 TRUE 的行,UNKNOWN 与 FALSE 一样被过滤掉。因此 WHERE col = NULL 会过滤掉所有行(含 NULL 行与非 NULL 行),结果恒为空,且通常不报错、不提示,是最隐蔽的错误写法;正确的判空是 WHERE col IS NULL(返回 TRUE 的行保留),判非空用 IS NOT NULL。同理,CASE 表达式、CHECK 约束、JOIN 条件中的 = NULL 都按 UNKNOWN 处理(CHECK (col > 0) 对 NULL 行不触发违例,因为 UNKNOWN 不等于 FALSE)。

答题从"NULL 不是值,等值比较无法确定"推出 UNKNOWN,再讲 WHERE 只留 TRUE 导致 col = NULL 恒空,最后延伸 IS NULL 的正确用法与 CHECK 中 UNKNOWN 的宽容语义。

#
★★

46. 外连接产生的 NULL 与数据本身的 NULL 如何区分?业务上如何识别“左连接补全产生的未知值”?

外连接产生的 NULL 与数据本身的 NULL 如何区分?业务上如何识别"左连接补全产生的未知值"?

  • 补全 NULL 与业务 NULL 的混淆
  • 标记列、IS NULL 判定与 COALESCE 的局限
  • 用主键判定匹配与否

LEFT JOIN 未匹配时会在被保留侧补全 NULL 列,这与表中业务上本来就有的 NULL 在查询结果里无法直接区分——两行可能都是 NULL,一行是"本来就没值",一行是"关联失败补的"。数据库不区分这两类 NULL(结果集层面就是同一个 NULL),识别只能靠查询构造。

业务识别方法:其一,用被连接表的键列判定,被连接侧主键 IS NULL 即表示未匹配(被补全),例如 SELECT r., s.val FROM R r LEFT JOIN S s ON ...,s.id IS NULL 表示关联失败;其二,查询时显式标记:SELECT CASE WHEN s.id IS NULL THEN '未匹配' ELSE '已匹配' END AS matched;其三,匹配标志列:SELECT r., (s.id IS NOT NULL) AS matched;其四,数据建模时避免歧义——对业务上允许 NULL 的列与连接键分开设计,或用 COALESCE(s.val, 'N/A') 转义输出。注意 COALESCE 无法区分"本来 NULL"与"补 NULL",只能统一替换展示值;要区分必须先判连接键。

答题先点明"结果集层面两类 NULL 不可分",再给出以被连接侧主键为锚的识别方法(IS NULL 判定、CASE 标记、布尔列),最后说明 COALESCE 的局限与建模建议。

SELECT r.id, s.val,
       CASE WHEN s.id IS NULL THEN 'unmatched' ELSE 'matched' END AS status
FROM R r LEFT JOIN S s ON r.sid = s.id;
#
★★

47. NULL 与统计函数的相关性(CORR、COVAR_POP)计算时如何处理?包含 NULL 的列被排除后样本量如何调整?

NULL 与统计函数(CORR、COVAR_POP 等)计算时如何处理?包含 NULL 的列被排除后样本量如何调整?

  • 统计函数对 NULL 的成对忽略(pairwise deletion)
  • CORR/COVAR 的样本量口径
  • 聚合忽略 NULL 的通用规则

PostgreSQL 的统计函数(CORR、COVAR_POP、COVAR_SAMP、STDDEV、VARIANCE、REGR_* 等)默认忽略 NULL:只使用两列都非 NULL 的行对(成对删除,pairwise deletion)参与计算,NULL 不参与也不使结果变 NULL。样本量方面:CORR 与 COVAR_SAMP 按"两列都非 NULL 的配对行数减 1"(n-1)作为自由度口径,COVAR_POP 用 n;若配对行数不足(如 n<2),CORR 返回 NULL。COUNT 只统计非 NULL 值,SUM/AVG 忽略 NULL 行——这是聚合的通用规则。

理解要点:其一,"忽略 NULL"使统计结果与"预先过滤 NULL 后的子集"一致,所以对含 NULL 的列做统计前无需手工 WHERE IS NOT NULL;其二,聚合内部的忽略是逐行的(行中某列 NULL 不影响其他列统计),但相关性等双列统计必须行对同时有效,两列中任一为 NULL 的行对即被排除;其三,COUNT(*) 与 COUNT(col) 的差异正体现此规则(前者计行数含 NULL 行,后者忽略 NULL)。业务统计需明确口径:是"全量"还是"有效配对",避免样本量变化导致结论偏差。

答题先讲聚合忽略 NULL 的通用规则,再展开统计函数(CORR/COVAR)的成对删除与 n/n-1 口径,最后对比 COUNT(*) 与 COUNT(col) 并提醒统计口径的业务声明。

#
★★

48. NULL 参与的等值比较(如 NULL = NULL)应返回 UNKNOWN 而非 TRUE,但为什么 GROUP BY 把所有 NULL 视为同一组?

NULL = NULL 应返回 UNKNOWN 而非 TRUE,但为什么 GROUP BY 把所有 NULL 视为同一组?

  • 比较语义与分组语义的区别
  • 分组把 NULL 视作同一"键值"
  • IS DISTINCT FROM 等显式判定

这是 SQL 的两套不同规则:比较语义上,NULL = NULL 返回 UNKNOWN(NULL 未知,无法断言相等);分组语义上,GROUP BY(以及 DISTINCT、ORDER BY、UNION 去重)把全部 NULL 视为同一个键值归入同一组。二者不矛盾:分组不是"比较相等",而是按"分组键是否同一"归类,NULL 作为一个统一的"无值"类别参与归类,正如标准规定"两个 NULL 在分组与排序时视为相等"。

设计动机:若分组时 NULL 各自成组,结果将难以预测且无意义(NULL 组数量不定),统一归组让聚合(COUNT 每组行数)与去重语义确定;排序同理,NULL 需要确定的位置(NULLS FIRST/LAST)。需要"按 NULL 是否相等"做显式比较时,用 IS NOT DISTINCT FROM(两值都为 NULL 时为 TRUE)或 (a = b OR (a IS NULL AND b IS NULL)) 表达;去重合并 NULL 与具体值用 COALESCE 归一。掌握"分组视 NULL 相同、比较视 NULL 未知"的双轨规则,是正确编写聚合与去重查询的基础。

答题先指出两套规则的并存(比较 UNKNOWN vs 分组同组),解释分组统一归组的语义动机(确定性与可聚合),再给出 IS NOT DISTINCT FROM 等显式比较手段,厘清规则边界。

#
★★

49. NULL 在 CHECK 约束中如何处理?CHECK (col > 0) 遇到 col IS NULL 时是否触发违例?

NULL 在 CHECK 约束中如何处理?CHECK (col > 0) 遇到 col IS NULL 时是否触发违例?

  • CHECK 的 UNKNOWN 宽容语义
  • NULL 行不触发违例的原因
  • 显式 NOT NULL 判断的设计

CHECK 约束的判定遵循三值逻辑:约束表达式返回 FALSE 才触发违例,返回 TRUE 或 UNKNOWN 都放行。因此 CHECK (col > 0) 对 col IS NULL 的行返回 UNKNOWN,不触发违例——NULL 行可以通过 CHECK,除非约束表达式显式排除 NULL(如 CHECK (col IS NOT NULL AND col > 0))。这是"CHECK 只拒绝明确违反者,不拒绝未知者"的设计:未知值无法证明违反规则,故不拦截。

工程影响:其一,若业务要求"该列必须为正数且不允许缺失",CHECK 必须同时写 IS NOT NULL,或给列加 NOT NULL 约束(更常见、更明确);其二,CHECK (col > 0) 只保证"非 NULL 值时大于 0",统计口径要清楚;其三,跨行 CHECK(MySQL 8.0 前的 CHECK 被忽略、PostgreSQL 的 CHECK 不能引用其他行)与 NULL 结合时更要显式设计。实践上把"非空"用 NOT NULL 约束表达、"取值域"用 CHECK 表达,职责分离最清晰。

答题核心是"CHECK 拒绝 FALSE、放行 TRUE 与 UNKNOWN",由此推出 NULL 行默认通过 CHECK,再给"NOT NULL 约束 + CHECK"的分工建议与显式判空写法。

-- 仅约束取值域:NULL 可通过
CHECK (col > 0)
-- 同时约束非空:NULL 被拒绝
CHECK (col IS NOT NULL AND col > 0)
#
★★

50. NULL 在外键约束中的处理,插入 NULL 到外键列是被允许的(除非 NOT NULL),删除父表行时子表外键为 NULL 的处理规则是什么?

NULL 在外键约束中的处理规则是什么?插入 NULL 到外键列是否允许?删除父表行时子表外键为 NULL 如何处理?

  • 外键对 NULL 的匹配规则(MATCH SIMPLE)
  • NULL 外键不参与参照检查
  • 删除父行时 NULL 子行的行为

外键约束对 NULL 采用"不匹配即放行"规则(MATCH SIMPLE,SQL 默认):子表外键列的值若为 NULL,则不与父表做匹配检查,插入、更新都合法,不受参照完整性约束——即"NULL 外键表示尚未关联"。因此外键列可以插入 NULL(除非同时声明 NOT NULL),NULL 行不代表悬空引用。

删除父表行时:若子表外键为 NULL,子行不引用该父行(没有参照关系),删除父行不会影响 NULL 外键的子行——NULL 子行既不被 CASCADE 删除,也不被 RESTRICT 阻止,也不被 SET NULL 修改(本已是 NULL),它完全游离于参照动作之外。只有外键值"等于"被删父行主键的子行才触发参照动作。业务语义上,NULL 外键表示"尚未归属",如员工尚未分配部门,删除部门不影响未分配员工。若业务要求"必须关联",应在外键列上加 NOT NULL;若要求"删除父行时把子行归属置空",用 ON DELETE SET NULL(配合允许 NULL)。

答题先讲 MATCH SIMPLE 下 NULL 外键不参与匹配、插入合法,再明确删除父行时 NULL 子行不受参照动作影响,最后给出 NOT NULL 与 SET NULL 的业务组合建议。

#
★★

51. UPDATE ... SET col = NULL 与 UPDATE ... SET col = '' 的差异,对触发器、默认值、约束的影响是什么?

UPDATE ... SET col = NULL 与 UPDATE ... SET col = '' 的差异是什么?对触发器、默认值、约束各有什么影响?

  • 置 NULL 与置空串的语义差异
  • 触发器 NEW/OLD 的感知差异
  • 约束与默认值的不同反应

SET col = NULL 是把值改为"未知/缺失",SET col = '' 是把值改为"零长度字符串"(Oracle 中等价于 NULL)。差异体现在:比较与聚合(COUNT(col) 忽略 NULL 但计入 '')、索引(NULL 与 '' 在唯一约束下行为不同,多数库允许多 NULL 而 '' 只能一个)、校验(NOT NULL 拒绝 NULL 但允许 '',CHECK 对 NULL 返回 UNKNOWN 放行、对 '' 按表达式判定)、下游消费(应用收到 NULL 与空串的处理逻辑不同)。

对触发器的影响:BEFORE/AFTER 触发器中 NEW.col 会反映新值(NULL 或 ''),触发器可用 NEW.col IS NULL 区分;若只关心"值是否变化",需用 NEW.col IS DISTINCT FROM OLD.col 判断——因为 OLD.col 从非 NULL 变 NEW NULL 是变化,从 '' 变 NULL 也是变化,但 NULL 与 NULL 之间置 '' 再置回 NULL 才算变化。对默认值的影响:显式 SET NULL 不会触发列的 DEFAULT(DEFAULT 只在缺省该列时生效),SET col = NULL 与缺省写入不同;对约束的影响:NOT NULL 拒绝 NULL 不拒绝 '',UNIQUE 下 '' 参与唯一性而 NULL 不参与,CHECK 对 NULL 宽容。工程上"清空字段"前必须确认用 NULL 还是 '',并同步调整触发器与下游判断逻辑。

答题先对比两种赋值的语义与在比较、索引、约束上的差异,再分别讲触发器(NEW 值与 IS DISTINCT FROM 判定)、默认值(显式 NULL 不触发 DEFAULT)、约束(NOT NULL/UNIQUE/CHECK 的不同反应),最后给出工程建议。

#
★★

52. 为什么 IS NULL 不能用 = NULL 替代?SQL 标准为什么不直接定义 = NULL 为 IS NULL?

为什么 IS NULL 不能用 = NULL 替代?SQL 标准为什么不直接把 = NULL 定义为 IS NULL?

  • 比较运算的 UNKNOWN 语义
  • NULL 不是值、等值比较无意义
  • 保留三值逻辑的兼容性

IS NULL 是"属性值是否缺失"的确定性判定,返回 TRUE/FALSE;= NULL 是把 NULL 当作比较值参与等值运算,按三值逻辑必然返回 UNKNOWN——二者机制根本不同,不能互换。若用 col = NULL 替代判空,WHERE 只保留 TRUE,结果恒为空,因此标准强制用 IS NULL / IS NOT NULL 表达判空。

标准不把 = NULL 定义为 IS NULL 的原因:其一,保持三值逻辑的一致性——若 = NULL 特判为 IS NULL,则 NULL = NULL 返回 TRUE,与"未知值比较无确定结果"的语义冲突,破坏整个比较体系;其二,NULL 不是值(not a value),把它当作操作数与普通值并列会混淆"值缺失"与"具体值"两个层次;其三,历史兼容与数学基础——关系模型与 SQL 从 Codd 的三值逻辑延续而来,= 只定义在值域上,NULL 判定必须由专门的谓词承担。理解这一点就能解释"为什么判空必须写 IS NULL"以及"为什么 NOT IN 含 NULL 时语义会翻车"。

答题从"= NULL 恒 UNKNOWN 与 IS NULL 确定性判定的机制差异"切入,再从三值逻辑一致性、NULL 非值、历史继承三点论证标准不重定义 = NULL 的原因。

#
★★

53. 如何在查询中将 NULL 替换为业务默认值?COALESCE 与 CASE WHEN 的等价写法是什么?

如何在查询中将 NULL 替换为业务默认值?COALESCE 与 CASE WHEN 的等价写法是什么?

  • COALESCE 的多参数取值规则
  • CASE WHEN 等价的翻译
  • 默认值替换的常见场景

用 COALESCE 把 NULL 替换为业务默认值:COALESCE(col, 默认值) 返回 col 的第一个非 NULL 值,col 为 NULL 时返回默认值,可写多参数链(COALESCE(col1, col2, 默认值))逐级兜底。等价写法用 CASE:CASE WHEN col IS NOT NULL THEN col ELSE 默认值 END,二者对单列场景完全等价;NULLIF 组合也可表达特殊替换(NULLIF(col, 0) 把 0 变 NULL)。

典型场景:显示层默认文案(COALESCE(nickname, name, '未设置'))、数值统计(COALESCE(score, 0) 参与 SUM 防止 NULL 传播)、连接键归一(COALESCE(mobile, email) 取首选联系方式)、报表空值填充。注意点:COALESCE 各参数类型需兼容(隐式转换或显式 CAST);替换只影响输出、不改存储;若默认值本身可能为 NULL 则继续返回 NULL(COALESCE(NULL, NULL) = NULL);聚合中 COUNT(col) 与 COALESCE(COUNT(col), 0) 的组合是报表常用写法(COUNT 对空集返回 0,而 SUM 返回 NULL)。

答题给出 COALESCE 语义与 CASE WHEN 等价式,列出典型场景(文案、统计、键归一),再提醒类型兼容、输出层替换、COUNT/SUM 空集差异三个注意点。

SELECT COALESCE(nickname, name, '未设置') AS display_name,
       COALESCE(SUM(amount), 0) AS total
FROM orders;
-- 等价 CASE 写法
SELECT CASE WHEN col IS NOT NULL THEN col ELSE '默认值' END FROM t;
#
★★

54. 窗口函数中的 NULL 处理,FIRST_VALUE、LAST_VALUE、NTH_VALUE 遇到 NULL 时的行为是什么?RESPECT NULLS 与 IGNORE NULLS 子句的使用场景?

窗口函数中的 NULL 如何处理?FIRST_VALUE、LAST_VALUE、NTH_VALUE 遇到 NULL 时的行为是什么?RESPECT NULLS 与 IGNORE NULLS 子句的使用场景是什么?

  • 窗口函数对 NULL 的默认处理(RESPECT NULLS)
  • 三个取值函数在 NULL 下的行为
  • IGNORE NULLS 的应用场景

FIRST_VALUE、LAST_VALUE、NTH_VALUE 默认按 RESPECT NULLS 语义:把 NULL 当作普通值参与排序与取值,FIRST_VALUE 取窗口第一行的值(可能是 NULL)、LAST_VALUE 取窗口最后一行的值、NTH_VALUE 取第 n 行的值(可能为 NULL)。注意 LAST_VALUE 的经典陷阱:不写 ORDER BY 或未指定完整窗口框架时,默认框架是"当前行到当前行",LAST_VALUE 只返回当前行值,需显式 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。

IGNORE NULLS 语义:跳过 NULL 值,取窗口内"第一个非 NULL 值"(FIRST_VALUE ... IGNORE NULLS 即最近的非 NULL 值回溯,典型应用:填充稀疏序列——股票每日收盘价缺失时取最近有效值、传感器数据补点、价格表的"最近一次报价")。Oracle 原生支持 IGNORE NULLS;PostgreSQL 官方文档明确未实现 IGNORE NULLS(行为恒为 RESPECT NULLS),需用 LATERAL 子查询、自连接或 COALESCE 等技巧模拟;MySQL 8.0 同样尚不支持。工程建议:明确业务"NULL 是有意义的值还是缺失"再决定 RESPECT 还是 IGNORE,并固定窗口框架避免 LAST_VALUE 陷阱。

答题先给默认 RESPECT NULLS 下三个函数的行为与 LAST_VALUE 框架陷阱,再讲 IGNORE NULLS 的"取最近非空值"用途与各库支持差异,最后落到业务语义选择。

-- 取窗口内最近一个非 NULL 报价(Oracle 原生支持,PostgreSQL 需自连接/LATERAL 模拟)
SELECT ts, price,
       FIRST_VALUE(price IGNORE NULLS) OVER (ORDER BY ts) AS last_valid
FROM quotes;
#
★★

55. MySQL 中 IFNULL 与 PostgreSQL 中 COALESCE 的等价语义是什么?

MySQL 中 IFNULL 与 PostgreSQL 中 COALESCE 的等价语义是什么?二者能否互换?

  • IFNULL 的两参数语义
  • COALESCE 的多参数语义
  • 跨库移植的等价性

MySQL 的 IFNULL(expr1, expr2) 返回 expr1 若非 NULL,否则返回 expr2,与 COALESCE(expr1, expr2) 完全等价;PostgreSQL 没有 IFNULL,使用 COALESCE(标准函数,可多参数:COALESCE(a, b, c) 返回首个非 NULL)。因此"MySQL IFNULL → PostgreSQL COALESCE"是直接的语法替换,双参数场景语义一致;多参数兜底场景 IFNULL 需嵌套(IFNULL(a, IFNULL(b, c)))或改用 COALESCE。

差异细节:其一,COALESCE 是标准 SQL 函数,PostgreSQL、MySQL 8.0 都支持,跨库优先使用;其二,返回值类型推断上,COALESCE 取"最通用的类型"(如 INT 与 VARCHAR 混合时按兼容类型),IFNULL 类型规则与之相近但实现细节略有差异,混类型时建议显式 CAST;其三,MySQL 的 ISNULL(x) 是"判断是否为 NULL"(返回 0/1),与 SQL Server 的 ISNULL(a,b) 语义不同,移植时别混淆。工程结论:跨库代码统一写 COALESCE,IFNULL 仅在 MySQL 专属代码中使用,并避免与 ISNULL 混淆。

答题先确认双参数 IFNULL 与 COALESCE 语义等价、PostgreSQL 无 IFNULL,再讲多参数嵌套与类型推断差异,最后给出"统一用标准 COALESCE"的移植结论并警示 ISNULL 混淆。

#
★★

56. PostgreSQL 中如何将 NULL 排在结果集的最前面?请给出 ORDER BY 子句写法。

PostgreSQL 中如何将 NULL 排在结果集的最前面?请给出 ORDER BY 子句的写法?

  • PostgreSQL 默认 NULL 排最后(升序)
  • NULLS FIRST / NULLS LAST 语法
  • 多列排序时子句的书写位置

PostgreSQL 默认行为:升序时 NULL 排在最后、降序时 NULL 排在最前。要把 NULL 强制排最前,用 NULLS FIRST:ORDER BY col ASC NULLS FIRST 或简写 ORDER BY col NULLS FIRST(未写方向时默认升序)。多列排序时 NULLS 子句跟随各自的排序列:ORDER BY col1 ASC NULLS FIRST, col2 DESC NULLS LAST。NULLS FIRST/LAST 是 PostgreSQL 与 Oracle 的扩展语法,MySQL 需用技巧模拟:ORDER BY (col IS NULL) DESC, col(NULL 判定表达式排序)或 ORDER BY -col IS NULL。

使用场景:待办列表把"未处理(NULL)"排最前、报表把空值顶置、分页场景统一 NULL 位置。注意 NULLS 子句必须紧跟对应的 ASC/DESC 之后,不能独立成段;在表达式排序(ORDER BY COALESCE(col, 0))里同样可加 NULLS 控制表达式结果为 NULL 的行。

答题先说明默认位置(升序最后/降序最前),再给 NULLS FIRST/LAST 的标准写法与多列书写位置,最后补充 MySQL 模拟技巧与典型场景,形成可执行的答案。

-- NULL 排最前
SELECT * FROM tasks ORDER BY due_at ASC NULLS FIRST;
-- 多列:NULL 排最前、非空按时间倒序
SELECT * FROM tasks ORDER BY due_at ASC NULLS FIRST, created_at DESC;
#
★★

57. WHERE col IS NULL 与 WHERE col IS NOT NULL 在执行计划中可能使用索引吗?

WHERE col IS NULL 与 WHERE col IS NOT NULL 在执行计划中可能使用索引吗?各自的条件是什么?

  • B 树索引存储 NULL 键值
  • IS NULL 与 IS NOT NULL 的索引可用性
  • 部分索引与统计信息的配合

可能。B 树索引把 NULL 作为普通键值存储(PostgreSQL 默认按 NULLS FIRST 排序存储、MySQL InnoDB 把 NULL 当作最小键值),因此 IS NULL 与 IS NOT NULL 都可以作为范围条件走索引扫描。是否真的走索引取决于优化器成本:IS NULL 在"NULL 占比小"时过滤性好,优化器倾向索引扫描;IS NOT NULL 通常过滤性差(绝大多数行非空),优化器更倾向全表扫描,除非用部分索引(WHERE col IS NOT NULL 的索引只含非 NULL 行)或覆盖索引。

工程技巧:其一,高频 IS NULL 查询用部分索引精准命中(PostgreSQL);其二,IS NOT NULL 场景把条件与高选择性列组合成复合索引,配合索引下推(MySQL ICP)过滤;其三,用 ANALYZE 保持统计信息新鲜,让优化器准确估算 NULL 占比;其四,PostgreSQL 中 IS NULL 与 IS NOT NULL 两个互补谓词可用"排除部分索引"(WHERE col IS NOT NULL 的部分索引)加速取反查询。最终以 EXPLAIN 验证是 Index Scan 还是 Seq Scan。

答题先确认 NULL 可存索引、两类谓词都可作范围条件,再分析成本决策(NULL 占比、过滤性),给出部分索引与组合索引两种工程方案,最后强调 EXPLAIN 验证。

-- 只为 IS NULL 查询建极小部分索引
CREATE INDEX idx_t_col_null ON t(col) WHERE col IS NULL;
-- 覆盖 IS NOT NULL 的互补部分索引
CREATE INDEX idx_t_col_notnull ON t(col) WHERE col IS NOT NULL;
#

58. 三值逻辑下,TRUE AND UNKNOWN 的结果是什么?UNKNOWN AND UNKNOWN 呢?

三值逻辑下,TRUE AND UNKNOWN 的结果是什么?UNKNOWN AND UNKNOWN 的结果又是什么?

  • AND 的 TRUE 非决定性
  • UNKNOWN 的传播
  • 真值表的记忆规则

TRUE AND UNKNOWN = UNKNOWN:AND 中只有 FALSE 是决定性的(只要一侧 FALSE 结果必为 FALSE),TRUE 与 UNKNOWN 相与时,结果取决于 UNKNOWN 的实际取值——若它是 TRUE 则结果为 TRUE,若它是 FALSE 则结果为 FALSE,无法确定,故为 UNKNOWN。UNKNOWN AND UNKNOWN = UNKNOWN:两个未知操作数,结果随二者实际取值组合而变化(TRUE∧TRUE=TRUE、TRUE∧FALSE=FALSE、FALSE∧TRUE=FALSE、FALSE∧FALSE=FALSE),四种组合三种结果,无确定值。

记忆规则:AND 找 FALSE(含 FALSE 即 FALSE,全 TRUE 才 TRUE,其余 UNKNOWN);OR 找 TRUE(含 TRUE 即 TRUE,全 FALSE 才 FALSE,其余 UNKNOWN);NOT 反转 T/F、UNKNOWN 不变。这套规则直接决定 WHERE 组合条件的过滤行为,例如 WHERE a IS NULL AND b = 1 中 b = 1 为 TRUE 时整式仍为 UNKNOWN 被过滤,写条件时要清楚每个子条件的真值。

答题先给两个具体真值(均为 UNKNOWN)及推导理由(决定性/传播),再给 AND/OR/NOT 的记忆规则,最后联系 WHERE 过滤行为,把规则落地。

#

59. 如何把 NULL 当作“缺勤”或“未填”的语义保留在数据库中?请给出两条建议。

如何把 NULL 当作"缺勤"或"未填"的语义保留在数据库中?请给出至少两条建议?

  • 单一 NULL 的语义承载
  • 状态列 + 值列的分离设计
  • 哨兵值与 CHECK 约束方案

建议一(状态列 + 值列分离):增加状态枚举列(status:'FILLED'/'UNKNOWN'/'NA')与可空值列并用 CHECK 约束联动——值列为 NULL 时状态列必须标注原因(如 status = 'NOT_FILLED'),查询与统计按状态列区分"未填"与"不适用",避免把两类缺失混为一谈。建议二(哨兵值 + 约束约定):为"未填"定义显式哨兵值(如整数字段用 -1、字符串用 'NOT_APPLICABLE'),值列 NOT NULL 并用 CHECK 限定合法取值集合,缺勤语义由哨兵值表达,NULL 只留给真正"未知",配合注释与字典表固化约定。

补充建议:对时间字段用"缺失时间 + 创建时间"对(fill_time 与 created_at),用 COALESCE 在展示层兜底;或使用多值列(数组/JSON)保留"填了哪些"的痕迹。核心原则:NULL 只是"值缺失"的通用标记,业务语义(缺勤/未填/不适用)必须由额外的结构(状态列、哨兵值、字典)显式承载,并统一查询口径(COUNT 与统计按状态过滤),避免语义歧义导致的数据误读。

答题给出两条主建议(状态列联动约束、哨兵值+CHECK)并各配示例,再补充时间对、多值列等变体,最后点明"NULL 不承载业务语义、结构承载"的核心原则。

CREATE TABLE attendance (
  emp_id INT PRIMARY KEY,
  status TEXT CHECK (status IN ('PRESENT','ABSENT','NOT_FILLED')),
  clock_time TIMESTAMP,
  CHECK ((status = 'NOT_FILLED') = (clock_time IS NULL))
);
#

60. 交运算(Intersection)能否由差运算推导?请给出等价表达式。

交运算(Intersection)能否由差运算推导?请给出等价表达式?

  • 交与差的关系
  • R ∩ S 的差运算表达
  • 基本运算完备性的理解

可以。集合论中交可由差定义:R ∩ S = R − (R − S)。含义:先从 R 中减去"属于 S 的元组"得到"R 中有而 S 中没有的元组"(R − S),再从 R 中减去这部分,剩下的就是"同时属于 R 和 S 的元组",即交集。类似地,并也可由差推导:R ∪ S = (R − S) ∪ S(仍含并);更基础的结论是关系代数中 {差、并、积、选择、投影} 足以表达其他所有运算(含交、连接、除、外连接),形成完备运算集。

SQL 对应:INTERSECT 是原生语法;用差模拟为 SELECT 子查询组合(如 SELECT * FROM R WHERE NOT EXISTS (SELECT 1 FROM S ...) 与集合差 EXCEPT 的组合),或 NOT EXISTS 双重否定直接表达"既在 R 又在 S"。理解"交可由差推出"这类推导关系,有助于在缺少 INTERSECT 的旧版本数据库(如 MySQL 8.0.31 前)中用基础运算组合出等价结果。

答题给出 R ∩ S = R − (R − S) 的推导与语义解释,再上升到基本运算完备性(差、并、积、选择、投影可表达一切),最后联系 SQL 的 INTERSECT 与旧版 MySQL 的模拟。

#

61. 第一范式(1NF)要求的原子性具体含义是什么?多值属性、复合属性、重复组违反 1NF 的具体反例与修正方案是什么?

第一范式(1NF)要求的原子性具体含义是什么?多值属性、复合属性、重复组违反 1NF 的反例有哪些?各自的修正方案是什么?

  • 1NF 原子性(属性不可再分)
  • 三类反例:多值属性、复合属性、重复组
  • 拆表与拆列的规范化修正

1NF 要求关系的每个属性取值是原子的(不可再分),即每个元组的每个属性位置只能有一个值,不能是集合、数组、复合结构或重复组。违反 1NF 的三类典型:多值属性(一个属性存多个值,如 phone 列存"138xxx;139xxx")、复合属性(属性本身是结构,如 address 列包含省/市/街道)、重复组(一组同类字段反复出现,如 course1、course2、course3 三列,或一行存一门学生多门课程)。

修正方案:多值属性拆成独立实体(学生-电话 一对多表)或规范化集合;复合属性拆成多个原子列(province、city、street)或拆为引用表;重复组拆成关联表(student_course 一行一选课),本质都是"把非原子的属性组拆为满足原子性的单独表/列"。现代数据库允许 JSON/数组列(PostgreSQL 数组、JSONB),从建模上放松了严格 1NF,但需权衡查询便利性与约束表达能力;OLTP 规范建模仍以原子性为准。

答题先定义原子性,再列三类反例各配修正方案,最后讨论现代数据库 JSON/数组对 1NF 的放宽与取舍,体现对规范的理解深度。

#

62. 关系模型中的元组、属性、域分别对应 SQL 里的哪些元素?请列出三组对应关系。

关系模型中的元组、属性、域分别对应 SQL 中的哪些元素?请列出三组对应关系?

  • 元组 ↔ 行
  • 属性 ↔ 列
  • 域 ↔ 数据类型/域

三组对应:元组(Tuple)对应 SQL 中的行(Row)——关系的一个元组即表的一行;属性(Attribute)对应列(Column)——元组的每个属性即行的每个字段;域(Domain)对应数据类型或 DOMAIN(PostgreSQL 的 CREATE DOMAIN)——属性的取值集合由域约束,SQL 中由数据类型(INT、VARCHAR 等)及 CHECK/域约束表达取值合法性。

对应细节:关系(Relation)↔ 表(Table);键(Key)↔ 主键/唯一约束;关系代数的选择、投影、连接 ↔ WHERE、SELECT 列表、JOIN。理解这套术语映射是阅读数据库论文与教材(关系模型术语)并翻译为工程 SQL 的基础:例如"元组"出现即指行,"属性的域"即该列的数据类型与合法性约束。域在 SQL 标准里还有正式对象(CREATE DOMAIN),可携带默认值与 CHECK 约束,PostgreSQL 支持,MySQL 不支持,这是术语与实现的一个差异点。

答题直接给出三组映射(元组→行、属性→列、域→数据类型/域对象),再补充关系→表、键→主键等扩展映射,最后点出 DOMAIN 在标准与实现中的差异。

#

63. θ-连接(Theta Join)与自然连接的差别是什么?自然连接为何在多列同名列存在时容易产生非预期笛卡尔积?

θ-连接(Theta Join)与自然连接的差别是什么?自然连接在多列同名列存在时为何容易产生非预期笛卡尔积?

  • θ-连接与自然连接的定义差异
  • 自然连接的自动同名列匹配
  • 非预期笛卡尔积的产生机制

θ-连接(Theta Join)是带任意条件 θ 的连接:R ⋈_θ S,θ 可以是等值、不等、大于等任意谓词(等值连接是 θ 为等值条件的特例),结果保留两表全部列;自然连接(Natural Join)自动把所有同名列做等值连接,且结果中同名列只保留一份,不写任何连接条件。差别核心:θ-连接显式给条件、列保留;自然连接隐式用同名列、列合并。

非预期笛卡尔积机制:自然连接以"所有同名列相等"作为连接条件,若两表除了业务关联键外还有其他同名列(如都有 id、created_at、status),这些列也必须全部相等才匹配。当这些额外同名列值分布不同(如两表的 status 取值不重合或 created_at 不同),大量元组对匹配失败,但更危险的是——若同名列中某些列两边取值固定相同(如都是 1),它们实际不起区分作用,而业务关联键列的值域重叠度高时,匹配对数量接近 |R|×|S| 的乘积,形成隐式笛卡尔积,结果行数爆炸且语义错误。因此工程上禁用 NATURAL JOIN,改用显式 ON 指定真实关联键,避免同名列误并入连接条件。

答题先区分 θ-连接(显式条件、保留列)与自然连接(自动同名列、合并列),再剖析自然连接把所有同名列纳入条件导致匹配对膨胀的非预期笛卡尔积机制,最后给出禁用 NATURAL JOIN 的工程建议。

#

64. 关系代数与关系演算(Tuple Relational Calculus、Domain Relational Calculus)的等价性是如何证明的?Codd 定理的核心思想是什么?

关系代数与关系演算(元组关系演算、域关系演算)的等价性是如何证明的?Codd 定理的核心思想是什么?

  • 关系代数与两种关系演算的表达能力
  • Codd 定理(关系完备性)
  • 安全表达式的限定

Codd 定理(关系完备性定理)断言:关系代数的表达能力与安全的关系演算(元组关系演算 TRC 与域关系演算 DRC)等价——任何关系代数表达式都可以翻译为等价的(安全的)关系演算公式,反之亦然。证明方向:其一,把关系代数的每个基本运算(并、差、积、选择、投影,以及派生的交、连接、除)逐一用 TRC/DRC 的公式(存在量词、全称量词、逻辑联结词、关系原子公式)表达,证明代数 ⊆ 演算;其二,把安全演算公式的构造翻译回代数运算(选择投影并差积的组合),证明演算 ⊆ 代数;两方向合起来即表达能力等价。

核心思想:关系代数"过程式"(指明运算步骤),关系演算"声明式"(只描述结果性质),二者表达能力相同意味着"如何算"与"算什么"可以分离——这是查询优化器等价改写的理论基础(优化器可以在代数计划上变换而不改变演算层的结果语义)。安全(Safe)限定是等价成立的前提:演算公式中自由变量必须被限定到具体关系上,保证结果有限(域关系演算否则可能产生无限结果)。SQL 融合两者:SELECT...FROM...WHERE 结构同时包含声明式描述与可执行的代数实现。

答题先陈述 Codd 定理结论(三种表达方式能力等价),再讲双向证明思路(运算→公式、公式→代数)与安全条件,最后点出"声明式与过程式等价"对优化器与 SQL 设计的意义。

#

65. 关系的依赖保持分解(Dependency-Preserving Decomposition)与无损连接分解(Lossless Join Decomposition)分别如何判定?请给出 Chase 检验法的步骤。

关系的依赖保持分解(Dependency-Preserving Decomposition)与无损连接分解(Lossless Join Decomposition)分别如何判定?请给出 Chase 检验法的步骤?

  • 无损连接分解的判定
  • 依赖保持分解的判定
  • Chase 检验法步骤

无损连接分解(Lossless Join):把 R 分解为 R1、R2 后,对任意合法实例,自然连接 R1 ⋈ R2 能精确还原 R(无多余元组)。二元分解的充要判定:若 R1 ∩ R2 → R1 或 R1 ∩ R2 → R2(公共属性集是某一侧的超键),则分解无损。依赖保持分解(Dependency Preserving):R 上的每个函数依赖 F 都能由分解后各子关系上的依赖投影 F1 ∪ F2 推导出(即 (F1∪F2)⁺ = F⁺),保证更新各子表时约束仍可局部检查、无需跨表连接验证。

Chase 检验法步骤:构造一张"带下标变量"的表,列对应属性、行对应分解的每个子关系(子关系不含的属性填带行号下标的变量 aᵢⱼ,含有的填对应属性下标变量 aⱼ);对每个函数依赖 X→Y,若两行在 X 上取值一致,则把 Y 处的变量统一(把带下标的变量改成另一行对应的变量);反复应用全部依赖直到没有变化;若某行所有位置都变成无下标的"原始变量"(表示该行可以还原出 R 的全部属性),则分解无损。Chase 同时也可用于检验依赖保持(把目标依赖加入后看能否推出)。

答题先分别给出无损(二元分解充要条件)与依赖保持(依赖投影可推导)的判定,再完整叙述 Chase 检验法的变量表构造、规则应用、终止与判据,结构清晰即可覆盖考点。

#

66. 关系代数的可判定性,给定一个关系代数表达式,能否判定其结果是否为空?为何此问题是 NP 完全的?

关系代数的可判定性:给定一个关系代数表达式,能否判定其结果是否为空?为何此问题可能是 NP 完全的?

  • 表达式空集判定与包含判定
  • 等价到 SAT/子集问题的归约
  • 复杂度理论的基本理解

问题"给定关系代数表达式 E 与若干关系实例,判断 E 的结果是否为空"对固定实例是多项式可判定的(直接执行即可,复杂度随数据规模多项式增长);但"对任意(未知)实例,判断 E 是否恒为空(即 E 与空关系等价)"这类包含/等价判定是困难问题——表达式等价性判定(包含映射问题、Conjunctive Query 等价)可归约到 SAT 与子图同态等 NP 完全问题。经典结论:判断两个合取查询(CQ)是否等价、或一个 CQ 是否包含于另一个,是 NP 完全的(Chandra-Merlin 定理),而关系代数表达式等价性包含其作为特例。

NP 完全性的直觉来源:表达式等价涉及"对属性重命名/置换后是否存在映射使一查询结果包含于另一查询"的组合搜索,候选映射指数多,无法多项式验证;把 3-SAT 实例编码为数据库实例与查询后,满足性判定即编码为包含判定。实践意义:优化器不能奢求"证明两个任意查询等价"的通用算法,只能依赖规则的完备性与启发式(等价改写规则集合);理解这一定理有助于解释"为什么优化器改写不是万能的"以及"为什么数据库不提供通用的查询等价性校验功能"。

答题先区分"给定实例判空(可判定)"与"恒为空/等价判定(困难)",再以 Chandra-Merlin 的 CQ 包含判定 NP 完全结论说明归约思路,最后联系优化器等价改写的现实局限。

#

67. 关系代数表达式树(Expression Tree)如何映射到逻辑计划与物理计划?叶子节点与内部节点的运算符对应关系是什么?

关系代数表达式树(Expression Tree)如何映射到逻辑计划与物理计划?叶子节点与内部节点的运算符对应关系是什么?

  • 表达式树与逻辑计划的关系
  • 逻辑算子树与物理算子树
  • 逻辑算子到物理算子的实现映射

关系代数表达式本身构成一棵树:叶子节点是基表(扫描),内部节点是代数运算符(σ、π、⋈、∪、− 等),运算顺序自底向上。逻辑计划(Logical Plan)即这棵代数树的优化形式:保留运算语义(Scan、Filter、Project、Join、Aggregate、Sort),只做等价变换(下推、换序、去冗余);物理计划(Physical Plan)为每个逻辑算子选择实现算法与访问路径:Scan → Seq Scan/Index Scan/Bitmap Scan,Join → Nested Loop/Hash Join/Merge Join,Aggregate → HashAgg/Sort+GroupAgg,Filter 常下推合并进扫描节点。

映射要点:叶子节点对应基表的物理访问(全表扫描或按索引定位的索引扫描),内部节点对应具体执行算法,并标注数据传递方式(迭代/物化、并行度)。优化器工作流正是"代数表达式 → 逻辑计划(等价变换)→ 物理计划(算法选择,含代价估算)→ 执行";EXPLAIN 输出即物理计划的可视化。理解三层对应关系是阅读执行计划、诊断慢查询的基础。

答题先讲表达式树即代数结构,再分别定义逻辑计划(语义变换)与物理计划(算法选择),最后给出算子映射表(Scan/Join/Aggregate)与优化器工作流,形成完整链路。

#

68. 重命名运算(Rename, ρ)在自然连接消除列名冲突时的标准做法是什么?SQL 中 AS 别名与关系代数 ρ 的对应关系如何?

重命名运算(Rename, ρ)在自然连接消除列名冲突时的标准做法是什么?SQL 中 AS 别名与关系代数 ρ 的对应关系如何?

  • ρ 的两种形式(改关系名、改属性名)
  • 用重命名消除自然连接的同名列冲突
  • AS 别名与 ρ 的对应

重命名运算 ρ 有两种形式:ρ_{S}(R) 把关系 R 改名为 S;ρ_{S(a1,a2,...)}(R) 同时重命名关系与属性。消除自然连接冲突的标准做法:先对一侧关系做属性重命名,使同名列不再同名,再自然连接。例如 R(A,B) 与 S(A,C) 想按 R.B 与 S.A 连接,可先 ρ_{R'(X,B)}(R) 把 R 的 A 改名 X,再做 R' 与 S 的自然连接(此时公共列 B 与 A 不同名,不会误连;若仍想按 B 连接需配合投影选择)。更常见的做法是显式等值连接配合列重命名输出,避免 NATURAL JOIN。

SQL 中 AS 别名与 ρ 的对应:表别名对应关系重命名(FROM R AS r,等价 ρ_r(R)),列别名对应属性重命名(SELECT a AS x,等价把属性 a 改名为 x,且可在 ORDER BY 引用)。注意列别名可见性遵循 SQL 逻辑执行顺序(SELECT 阶段产生,WHERE/GROUP BY 中不可见、ORDER BY 可见),与关系代数"重命名产生新关系"的显式作用范围不完全相同。理解 ρ 与 AS 的对应有助于把 SQL 查询翻译成代数形式做等价分析。

答题先讲 ρ 的两种形式与"先改名再自然连接"的标准做法,再映射 AS 别名(表别名↔关系改名、列别名↔属性改名),最后点出别名可见性的执行顺序差异。

#

69. 关系代数与关系演算哪个更接近 SQL 语法?为什么说 SQL 是介于两者之间的语言?

关系代数与关系演算哪个更接近 SQL 语法?为什么说 SQL 是介于两者之间的语言?

  • 代数的过程式特征与演算的声明式特征
  • SQL 的声明式外壳与过程式内核
  • 术语与结构的双重继承

从语法表面看,SQL 更接近元组关系演算:SELECT ... FROM ... WHERE ... 的描述方式("选出满足条件的行")是声明式的,不必指明运算顺序,与演算的"描述结果性质"一致;而关系代数是过程式的(指明 σ、π、⋈ 的运算次序)。但 SQL 又大量借用代数术语与结构(SELECT 列表对应投影、WHERE 对应选择、FROM 对应关系、JOIN 对应连接),因此被称为"介于两者之间"的语言。

更精确地说:SQL 采用演算的声明式形式(用户描述想要什么),由优化器翻译为代数式执行计划(引擎按代数运算顺序算出来),即"演算层语义 + 代数层实现"的结合。例如 SELECT name FROM student WHERE age>30 声明"名字与条件"(演算味道),优化器会生成 σ→π 的代数计划(代数味道)。另外 SQL 的非过程性允许优化器自由变换执行顺序,这正是关系完备性(Codd 定理)保证"声明式描述可被过程式执行"的实践体现。

答题先表态(SQL 更接近元组关系演算的声明式语法),再解释"演算语义+代数实现"的融合机制,最后用 Codd 定理收束,说明这种中间性有理论依据。

#

70. 在关系代数中表达“查询所有课程成绩大于 90 的学生姓名”,请用 π、σ、⋈ 写出表达式。

请用关系代数(π、σ、⋈)表达"查询所有课程成绩大于 90 的学生姓名"?

  • 选择 σ 过滤成绩条件
  • 连接 ⋈ 关联学生与成绩
  • 投影 π 输出姓名

设学生关系 Student(学号 S#, 姓名 Sname, ...)、选课成绩关系 SC(S#, C#, Score)。表达式:π_{Sname}(Student ⋈ σ_{Score>90}(SC))。含义:先在 SC 上选择 Score>90 的成绩行(σ),再与学生表按 S# 自然连接(⋈,取出对应学生),最后投影出姓名 Sname(π)。若考虑"所有课程成绩都大于 90"(每门课都超 90)则是另一个语义(除运算或全称量化);本题"成绩大于 90 的学生"按"至少一门课成绩大于 90"理解(若指每门课,需用除运算结合)。

若显式写出连接键:π_{Sname}(σ_{Score>90}(Student ⋈{Student.S# = SC.S#} SC));也可先投影再连接(π{Sname}(π_{S#, Sname}(Student) ⋈ σ_{Score>90}(SC))),这是"投影下推"的代数形式,减少连接输入宽度。SQL 对应:SELECT DISTINCT sname FROM student JOIN sc ON student.sno = sc.sno WHERE score > 90。

答题按"选择→连接→投影"三步构造表达式并逐符号解释含义,补充"至少一门/每门课"的语义辨析与投影下推变体,最后给 SQL 翻译,展示代数→SQL 的熟练转换。

-- 至少一门课成绩大于 90 的学生姓名
SELECT DISTINCT s.Sname
FROM Student s JOIN SC sc ON s.S# = sc.S#
WHERE sc.Score > 90;
#

71. SQL 中如何判断一列是否为 NULL?请给出正确的语法示例。

SQL 中如何判断一列是否为 NULL?请给出正确的语法示例?

  • IS NULL / IS NOT NULL 语法
  • 不能使用 = NULL 的原因
  • 判空在 WHERE 与 CASE 中的用法

判断列是否为 NULL 必须用 IS NULL / IS NOT NULL 谓词,不能用 = NULL。语法:WHERE col IS NULL(筛选 NULL 行)、WHERE col IS NOT NULL(筛选非 NULL 行),也可用于 CASE、CHECK、JOIN 条件与 SELECT 列表:SELECT col IS NULL AS is_null, CASE WHEN col IS NULL THEN '缺失' ELSE '有值' END。IS NULL 返回布尔判定(TRUE/FALSE),可直接作为表达式出现在 SELECT 列表(PostgreSQL 返回 boolean,MySQL 返回 0/1)。

错误写法辨析:WHERE col = NULL 恒为 UNKNOWN 被过滤,结果恒为空;判空与等值比较是两个不同谓词体系。判空还可与其他条件组合:WHERE col IS NULL AND status = 1;对可空列的分组统计 COUNT(col) 自动忽略 NULL,需要补判 COUNT(CASE WHEN col IS NULL THEN 1 END) 统计缺失数。工程规范:所有对可空列的过滤必须显式写 IS [NOT] NULL,静态检查(SQL Lint)应拦截 = NULL 写法。

答题先给标准语法(IS NULL/IS NOT NULL)与 WHERE、CASE、SELECT 列表三种场景示例,再辨析 = NULL 的错误与原因,最后补充组合条件与统计口径,实用完整。

SELECT * FROM employee WHERE phone IS NULL;
SELECT name, CASE WHEN phone IS NULL THEN '未填写' ELSE phone END AS phone_display
FROM employee;
SELECT COUNT(*) FILTER (WHERE phone IS NULL) AS missing_phone FROM employee;