约束、SQL 子句与方言

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

1. CHECK 约束在 MySQL 8.0 之前为何被静默忽略?8.0 之后如何处理跨行 CHECK(如 sum(balance) = 0)?

CHECK 约束在 MySQL 8.0 之前为何被静默忽略?8.0 之后如何处理跨行 CHECK(如 sum(balance) = 0)这类约束?

  • MySQL 8.0 前 CHECK 被解析但忽略
  • 8.0 后 CHECK 的启用与执行
  • 跨行 CHECK 的触发器替代方案

MySQL 8.0 之前,CHECK 子句在语法上被接受但语义上被忽略:建表时解析并丢弃,不生成约束对象、不校验数据,用户以为有约束实际上没有,这是"静默忽略"的根源(为兼容 SQL 标准语法而保留解析,却不实现执行)。5.7 里唯一例外是 CHECK 中的某些表达式会影响派生列等极少数场景,整体仍视为无效。8.0.16 起 CHECK 约束真正生效:支持列级与表级 CHECK、强制校验、命名与删除,违反时报错并可通过 SHOW CREATE TABLE、INFORMATION_SCHEMA.CHECK_CONSTRAINTS 查看。

跨行 CHECK(如 CHECK (sum(balance) = 0))在 8.0 依然不支持:CHECK 只能引用当前行的列,无法引用其他行或做聚合,标准上也不允许(断言 ASSERTION 才支持跨行,但无实现)。替代方案:用触发器在 INSERT/UPDATE/DELETE 后校验(BEFORE/AFTER TRIGGER 中 SELECT SUM 并 SIGNAL SQLSTATE 抛错),或用应用层事务内校验、定时对账任务;PostgreSQL 同样用触发器实现跨表/跨行校验,因为它也没有断言。设计建议:跨行不变式应记录在文档与测试中,用触发器加约束双重防护。

答题按时间线讲清"8.0 前解析即弃、8.0.16 后真正生效",再说明跨行 CHECK 在标准与实现上都不支持,给出触发器与对账替代方案,覆盖版本差异与替代设计。

-- MySQL 8.0.16+:生效的 CHECK
CREATE TABLE account (
  id INT PRIMARY KEY,
  balance DECIMAL(12,2) CHECK (balance >= 0)
);
-- 跨行校验改用触发器
CREATE TRIGGER trg_check_total BEFORE INSERT ON ledger
FOR EACH ROW
BEGIN
  IF (SELECT SUM(amount) FROM ledger) + NEW.amount <> 0 THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'total must be zero';
  END IF;
END;
#
★★★

2. DEFERRABLE 与 INITIALLY DEFERRED、INITIALLY IMMEDIATE 三种约束检查时机的语义差异是什么?多约束依赖的循环依赖如何用延迟约束解决?

DEFERRABLE 与 INITIALLY DEFERRED、INITIALLY IMMEDIATE 的约束检查时机语义差异是什么?多约束间的循环依赖如何用延迟约束解决?

  • 立即检查与延迟检查的时机差异
  • INITIALLY 子句的默认时机
  • SET CONSTRAINTS 与循环依赖

DEFERRABLE 声明约束"可延迟"(是否延迟可切换),不可延迟(NOT DEFERRABLE)的约束只能在每条语句结束时立即检查;INITIALLY IMMEDIATE 表示默认在每条语句结束时检查(即使可延迟,默认立即);INITIALLY DEFERRED 表示默认延迟到事务提交时检查。三者组合:DEFERRABLE + INITIALLY DEFERRED = 事务结束时检查;DEFERRABLE + INITIALLY IMMEDIATE = 默认语句级检查,但可在事务中用 SET CONSTRAINTS ALL DEFERRED 临时改为延迟;NOT DEFERRABLE 则永远立即检查(PostgreSQL 中唯一约束、主键、NOT NULL 默认不可延迟,外键可声明延迟)。

延迟约束解决循环依赖的机制:若两表相互外键引用(A 引用 B、B 引用 A),插入时无论先插谁都会违反尚不存在的父行,此时把一侧(或两侧)外键声明为 DEFERRABLE INITIALLY DEFERRED,事务内先插入全部子行、最后统一在提交时检查,即可完成"先建子后建父"的循环插入。典型场景:账户互转时同时写入两条关联行、分层数据同时建父与子节点、数据导入时解除加载顺序限制。注意延迟检查把失败推迟到提交,可能在大事务最后才报错,需配合合理的错误处理与回滚策略。

答题先厘清"可延迟性(DEFERRABLE)"与"默认时机(INITIALLY)"两个维度及其组合,再以相互外键的循环插入为例说明延迟约束的解决机制,最后提示提交时才报错的工程注意点。

CREATE TABLE a (
  id INT PRIMARY KEY,
  bid INT REFERENCES b(id) DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
INSERT INTO a ...;   -- 引用 b 的行尚不存在,延迟检查不报错
INSERT INTO b ...;
COMMIT;              -- 提交时统一检查
#
★★★

3. NOT NULL 约束在 PostgreSQL、MySQL、Oracle 中的实现差异是什么?为什么说 NOT NULL 是最廉价的完整性保证?

NOT NULL 约束在 PostgreSQL、MySQL、Oracle 中的实现差异是什么?为什么说 NOT NULL 是最廉价的完整性保证?

  • 三库 NOT NULL 的物理实现
  • 空串与 NULL 的方言差异
  • NOT NULL 的写入开销与优化

NOT NULL 在三大数据库都是列级约束,但实现细节不同:PostgreSQL 把 NOT NULL 信息存在 pg_attribute.attnotnull 标志位,不生成独立约束对象,检查发生在元组写入时;MySQL InnoDB 同样以列元数据标志实现,NULL 值在行格式中用 NULL 位图标记,NOT NULL 可省去位图并允许更紧凑的行格式;Oracle 的 NOT NULL 被实现为 CHECK 约束(SYS_C 开头的系统 CHECK),行为上与 CHECK 等价,因此可以禁用/启用(ALTER TABLE ... ENABLE/DISABLE)。空串差异:PostgreSQL、MySQL 允许 ''(非 NULL),Oracle 把 '' 视为 NULL,因此 Oracle 的 NOT NULL 列实际也拒绝 ''。

"最廉价的完整性保证"的原因:其一,检查成本极低——写入时只需判断一个标志位,无索引维护、无额外表扫描、无回滚复杂度,相比 UNIQUE(索引查找)、CHECK(表达式求值)、外键(跨表查询)几乎零开销;其二,受益面广——NOT NULL 保护下游查询逻辑、统计、索引、应用代码免于 NULL 分支;其三,它还是主键、部分索引、物化视图等机制的前提。工程上优先用 NOT NULL 表达"必填",比触发器与应用校验廉价得多,属于第一道且最便宜的防线。

答题先列三库实现差异(标志位 vs 系统 CHECK 约束 vs NULL 位图)与 Oracle 空串语义,再论证"最廉价"的三点理由(零成本检查、广泛受益、机制前提),突出性能视角。

#
★★★

4. 主键约束(PRIMARY KEY)与唯一约束(UNIQUE)的根本区别是什么?从空值允许性、索引类型、约束数量、外键引用四个维度对比。

主键约束(PRIMARY KEY)与唯一约束(UNIQUE)的根本区别是什么?请从空值允许性、索引类型、约束数量、外键引用四个维度对比?

  • 空值允许性(主键拒绝 NULL)
  • 索引类型与聚簇差异
  • 外键引用目标的约束

根本区别:主键 = UNIQUE + NOT NULL 的组合语义,是表的"行标识";唯一约束只保证唯一,允许 NULL。四维对比:空值允许性上,主键列完全禁止 NULL(唯一约束列可含多个 NULL,多数库默认);索引类型上,主键自动创建唯一索引,MySQL InnoDB 中主键索引即聚簇索引(数据按主键组织),唯一约束只建二级唯一索引(PostgreSQL 两者都是独立索引、无聚簇差异,Oracle 主键与唯一约束都可用 USING INDEX 指定);约束数量上,每表主键最多一个(唯一约束可多个,每表可建多个 UNIQUE);外键引用上,外键可引用任意唯一约束列(含 UNIQUE),但主键是默认且最常用的引用目标,部分工具与 ORM 默认只认主键,且复制/CDC 通常要求主键。

维度外的差异:主键具有复制标识符语义(逻辑复制、去重定位),唯一约束不具备;NULL 处理导致唯一约束不能完全替代主键(多 NULL 使引用失去确定性);主键列通常是查询与连接的默认关联列。选型:实体表必须有主键,UNIQUE 用于承载业务自然键(如邮箱、手机号),两者可并存,主键管身份、UNIQUE 管业务唯一。

答题按四个维度逐项对比(NULL、索引、数量、外键),再补充复制标识符与业务键选型,形成"主键管身份、UNIQUE 管唯一"的清晰结论。

#
★★★

5. 信息_SCHEMA 中的 TABLE_CONSTRAINTS、KEY_COLUMN_USAGE、REFERENTIAL_CONSTRAINTS、CHECK_CONSTRAINTS 视图结构与查询模式是什么?

信息_SCHEMA 中的 TABLE_CONSTRAINTS、KEY_COLUMN_USAGE、REFERENTIAL_CONSTRAINTS、CHECK_CONSTRAINTS 视图的结构与典型查询模式是什么?

  • 四个系统视图的字段与职责
  • 约束元数据的关联查询
  • PostgreSQL 与 MySQL 的实现差异

四个视图的职责:TABLE_CONSTRAINTS(每行一个约束:表名、约束名、约束类型 PRIMARY KEY/UNIQUE/FOREIGN KEY/CHECK、是否可延迟);KEY_COLUMN_USAGE(约束与列的关联:约束名、表名、列名、序号,用于查"某约束涉及哪些列",也可反查某列有哪些约束);REFERENTIAL_CONSTRAINTS(外键细节:约束名、被引用表、更新/删除动作 UPDATE_RULE/DELETE_RULE、延迟性,用于审计参照策略);CHECK_CONSTRAINTS(CHECK 表达式文本:约束名、CHECK_CLAUSE,可读约束内容)。

典型查询模式:查表全部约束(JOIN TABLE_CONSTRAINTS 与 KEY_COLUMN_USAGE 按约束名关联,列出类型与列);查外键引用关系(REFERENTIAL_CONSTRAINTS 关联 KEY_COLUMN_USAGE 得到"哪列引用哪表哪列");查 CHECK 内容(CHECK_CONSTRAINTS)。实现差异:PostgreSQL 自早期版本(8.x)即提供 CHECK_CONSTRAINTS(约束名按 schema 唯一)、MySQL 8.0.16+ 提供;PostgreSQL 查约束更常用 pg_constraint/pg_attribute(信息更全),MySQL 用 INFORMATION_SCHEMA 为主;SQL Server 需混用 sys.objects/sys.foreign_keys 与 INFORMATION_SCHEMA。信息_SCHEMA 是跨库标准的"只读元数据视图",查询模式统一但字段覆盖度有差异。

答题先逐个说明四个视图的字段与职责(外键细节、列关联、CHECK 文本),再给"查表全部约束、查引用关系、查 CHECK 内容"三类查询模式,最后对比 PostgreSQL/MySQL 的实现差异与原生目录替代。

-- 查某表全部约束及其列
SELECT tc.CONSTRAINT_NAME, tc.CONSTRAINT_TYPE, kcu.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
  ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
WHERE tc.TABLE_NAME = 'orders' AND tc.TABLE_SCHEMA = 'app';
-- 查外键级联策略
SELECT CONSTRAINT_NAME, TABLE_NAME, REFERENCED_TABLE_NAME,
       UPDATE_RULE, DELETE_RULE
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = 'app';
#
★★★

6. 参照完整性与分布式事务的两阶段提交(2PC)冲突时如何解决?外键检查在跨库场景下的三种实现模式(同步、异步、关闭)如何取舍?

参照完整性与分布式事务的两阶段提交(2PC)存在哪些冲突?外键检查在跨库场景下的同步、异步、关闭三种实现模式如何取舍?

  • 跨库外键无法由数据库保证
  • 2PC 与跨库引用的一致性代价
  • 同步/异步/关闭三种模式的取舍

冲突本质:外键约束是单库内的完整性机制,跨库(分库分表、微服务多库)时父表与子表分属不同节点,没有任何数据库能跨节点原子地检查引用存在性;分布式事务(2PC/XA)虽能保证"多库写入的原子性",但把参照检查放进分布式事务会显著放大失败面(任一参与者锁冲突、网络故障即整体回滚),且 2PC 的全局锁与长事务在高并发下代价高昂,因此分布式场景通常放弃数据库层外键,改为应用层保证。

三种实现模式取舍:同步模式(应用在事务内先查父表再写子表,或调用服务校验)一致性最强、实现简单,但引入分布式事务或跨节点读,延迟与故障率上升,适合一致性要求高的核心链路;异步模式(消息队列通知校验、对账任务兜底、延迟校验表)性能好、解耦,但存在短暂的不一致窗口,适合最终一致可接受的场景;关闭模式(完全不做跨库校验,靠业务约定与数据清洗)性能最优、架构最简,但产生孤儿数据的风险最高,适合内部系统或低价值数据。工程上常按数据域混合:核心域用同步或单库约束,边缘域关闭并靠对账修复。

答题先点明"外键是单库机制,跨库无法原生保证;2PC 可保原子但代价高",再逐一分析同步/异步/关闭三模式的优缺点与适用场景,最后给出按数据域混合的工程建议。

#
★★★

7. 唯一约束在 PostgreSQL 中允许多个 NULL(认为 NULL ≠ NULL),而在 SQL Server 中如何?能否通过唯一索引或条件索引模拟差异?

唯一约束对 NULL 的处理在 PostgreSQL 与 SQL Server 中有何差异?能否通过唯一索引或条件(过滤)索引模拟出对方的行为?

  • PostgreSQL 多 NULL 与 SQL Server 单 NULL 的差异
  • 唯一约束与唯一索引的行为绑定
  • 过滤索引/部分索引模拟

PostgreSQL 的唯一约束与唯一索引都允许多个 NULL(遵循标准"NULL 互不相等");SQL Server 的行为取决于对象类型:唯一索引(UNIQUE INDEX)把 NULL 视为重复值、默认只允许一个 NULL(NULL 计为一个键值),而唯一约束(UNIQUE CONSTRAINT)允许多个 NULL——行为与"索引还是约束"绑定,且 SQL Server 的过滤索引(Filtered Index)可以绕过默认。Oracle 与 PostgreSQL 类似允许多 NULL;MySQL 与 PostgreSQL 类似(唯一索引允许多 NULL)。

模拟对方行为:在 SQL Server 实现"多 NULL 唯一"用过滤索引 CREATE UNIQUE INDEX ux ON t(col) WHERE col IS NOT NULL(索引条目只含非 NULL 值,NULL 不参与唯一性);在 PostgreSQL 实现"单 NULL 唯一"可用部分唯一索引 CREATE UNIQUE INDEX ux ON t(COALESCE(col, 哨兵))(把 NULL 归一为哨兵值使其互相冲突),或 CREATE UNIQUE INDEX ux ON t(col) WHERE col IS NULL 再加触发器保证全局至多一行(部分索引内 NULL 唯一)。这两种"条件唯一"技巧也常用于软删除场景(deleted_at NULL 唯一、非 NULL 可重复)。迁移时务必把 NULL 语义差异纳入兼容性测试。

答题先明确各库默认行为(PG 多 NULL、SQL Server 索引单 NULL/约束多 NULL、Oracle 多 NULL),再给过滤索引与表达式索引的互相模拟方案,最后落到软删除应用与迁移测试提醒。

-- SQL Server:过滤索引实现"非空时唯一"
CREATE UNIQUE INDEX ux_email ON users(email) WHERE email IS NOT NULL;
-- PostgreSQL:表达式归一实现"至多一个 NULL"
CREATE UNIQUE INDEX ux_email ON users(COALESCE(email, ''));
#
★★★

8. 复合主键(Composite Primary Key)的选择准则是什么?三列及以上复合键在二级索引、外键引用、ORM 映射上的代价有哪些?

复合主键(Composite Primary Key)的选择准则是什么?三列及以上复合键在二级索引、外键引用、ORM 映射上有哪些代价?

  • 复合主键的适用场景与准则
  • 复合键对二级索引的体积放大
  • 外键引用与 ORM 映射的复杂度

复合主键适用于"业务天然由多列联合唯一且缺一不可"的场景(如订单明细 (order_id, line_no)、区域数据 (country, province)),准则:所有列必须非空、联合唯一、业务稳定不常变,且组合后仍能满足查询最左前缀;若仅为了"某组列唯一",更合适的是代理主键 + UNIQUE 约束(联合唯一),把"标识"与"业务唯一"分离。三列以上复合键风险显著上升。

代价分析:二级索引方面,InnoDB 二级索引叶子存主键值,复合主键的每个列都随二级索引复制,键越长二级索引体积越大(索引放大),且主键列数多导致页内键值密度下降、缓冲缓存命中率下降;外键引用方面,子表外键必须携带全部主键列(子表同样存多列),引用关系与连接条件变得冗长,级联操作也更重;ORM 映射方面,JPA/Hibernate 需 @EmbeddedId 或 @IdClass 定义主键类、equals/hashCode 必须覆盖全部主键列、查询需构造复合主键对象,更新时主键不可变导致改键困难;分布式与复制方面,复合主键无法用单一自增值、分片键选择受限。经验准则:业务复杂表优先单列代理主键,复合键只留给"联合唯一且频繁整组查询"的场景。

答题先给复合主键的适用准则(业务联合唯一、稳定、非空、最左前缀),再逐项分析三列以上在二级索引(体积放大)、外键(多列携带)、ORM(主键类与改键困难)上的代价,最后给出"代理主键+联合唯一"的选型结论。

#
★★★

9. 外键约束的参照动作(RESTRICT、CASCADE、SET NULL、SET DEFAULT、NO ACTION)在不同数据库中的默认行为与差异是什么?

外键约束的参照动作(RESTRICT、CASCADE、SET NULL、SET DEFAULT、NO ACTION)在不同数据库中的默认行为与差异是什么?

  • 五种参照动作的语义
  • RESTRICT 与 NO ACTION 的差异
  • 各库默认动作与级联深度差异

五种动作语义:NO ACTION 与 RESTRICT 都拒绝删除/更新被引用的父行(有引用即报错);CASCADE 把操作级联传导到子表(删父即删子、改父键即改子键);SET NULL 把子表外键置为 NULL(要求外键列可空);SET DEFAULT 置为列的默认值(要求有默认值且默认值满足外键)。RESTRICT 与 NO ACTION 的细微差异:标准语义中 NO ACTION 是"语句结束时检查",RESTRICT 是"立即检查",在延迟约束(DEFERRABLE)下 NO ACTION 可被推迟、RESTRICT 不能——但 PostgreSQL 中两者等同(都是立即检查,均不可延迟),MySQL InnoDB 中两者等同且都立即执行,Oracle 中默认 NO ACTION(等同 RESTRICT 立即检查)。

各库默认与差异:MySQL InnoDB 默认 NO ACTION(等同 RESTRICT),仅支持 RESTRICT/CASCADE/SET NULL/NO ACTION(无 SET DEFAULT 支持,声明 SET DEFAULT 会被忽略或报错);PostgreSQL 默认 NO ACTION,支持全部五种(含 SET DEFAULT);Oracle 默认 NO ACTION,支持全部;SQL Server 默认 NO ACTION,无 SET DEFAULT。级联深度:MySQL 与 PostgreSQL 均无固定层数上限(递归直到没有引用为止,循环引用场景靠行级去重与锁/死锁检测兜底);SQL Server 限制同一张表在级联链中只能出现一次(防循环)。删除/更新动作可分别指定(ON DELETE CASCADE, ON UPDATE SET NULL)。

答题先厘清五种动作语义,重点辨析 RESTRICT 与 NO ACTION(延迟性差异在各库的实现),再列四库的默认动作与支持矩阵(MySQL 无 SET DEFAULT、PG 全支持),最后提级联深度与 ON DELETE/ON UPDATE 独立配置。

#
★★★

10. 完整性约束的分类体系(域完整性、实体完整性、参照完整性、用户定义完整性)在 SQL 标准与各数据库方言中是如何实现的?

完整性约束的分类体系(域完整性、实体完整性、参照完整性、用户定义完整性)在 SQL 标准与各数据库方言中是如何实现的?

  • 四类完整性的定义
  • 每类对应的 SQL 机制
  • 各库实现差异

分类体系:域完整性(域约束,属性取值合法——数据类型、NOT NULL、CHECK、DOMAIN/ENUM);实体完整性(行标识——主键、唯一约束);参照完整性(跨表引用——外键及参照动作);用户定义完整性(业务规则——CHECK、断言、触发器、应用约束)。SQL 标准机制:域完整性由 CREATE DOMAIN + 列类型 + NOT NULL + CHECK 实现;实体完整性由 PRIMARY KEY/UNIQUE 实现;参照完整性由 FOREIGN KEY + 参照动作实现;用户定义完整性由 CHECK/ASSERTION/TRIGGER 实现。

方言差异:域对象 PostgreSQL 支持 CREATE DOMAIN(可携带 CHECK、默认值),MySQL 无 DOMAIN(用 ENUM/SET 与 CHECK 近似);实体完整性 PostgreSQL 与 Oracle 中主键是独立约束+自动索引,MySQL InnoDB 主键即聚簇索引;参照完整性 MySQL 需 InnoDB 引擎且无 SET DEFAULT,PostgreSQL 全动作;用户定义完整性 MySQL 8.0 前 CHECK 被忽略(用触发器补),PostgreSQL 的 CHECK 只限本行、跨表用触发器,断言(ASSERTION)标准有定义但四库均未实现,用触发器/物化视图/对账近似。理解这套体系与实现差异是设计数据模型与跨库迁移的基础。

答题按"分类→标准机制→方言差异"三层展开:先定义四类完整性,再给每类的标准 SQL 机制,最后列各库差异(DOMAIN、聚簇、SET DEFAULT、CHECK 忽略、断言缺失),体现体系化掌握。

#
★★★

11. 断言(ASSERTION)在 SQL 标准中定义但几乎所有数据库都未实现的原因是什么?PostgreSQL 的 DOMAIN CONSTRAINT 与触发器如何近似替代?

断言(ASSERTION)在 SQL 标准中已定义但几乎所有数据库都未实现的原因是什么?PostgreSQL 的 DOMAIN CONSTRAINT 与触发器如何近似替代断言?

  • 断言的语义(跨表/跨行不变式)
  • 未实现的技术与成本原因
  • DOMAIN 约束与触发器的替代方案

断言(ASSERTION)是 SQL 标准中的 schema 级约束:可引用任意表、做聚合与跨行比较(如"所有账户余额总和为 0"),是 CHECK 的泛化。几乎所有数据库都未实现,原因:其一,实现代价高——断言需要维护"约束依赖表"的触发关系,任何相关表的任何写入都可能使断言失效,需要全局审计与重校验,检查时机与优化器集成复杂;其二,性能不可控——聚合型断言每次写入都重算(或维护增量),高并发 OLTP 下是灾难;其三,使用率低、厂商动力不足——标准定义含糊(检查时机、与延迟约束的交互),社区普遍用触发器/应用约束代替;其四,与 MVCC、物化视图等机制的兼容设计复杂。

替代方案:PostgreSQL 的 DOMAIN 可携带 CHECK 约束与默认值,把"取值规则"下沉到域层(复用同一规则到多列),但只限单行表达式;跨行/跨表不变式用触发器:AFTER INSERT/UPDATE/DELETE 触发器内做聚合校验,违反时 RAISE EXCEPTION 使事务回滚;工程上还可配合物化视图(维护聚合视图并校验)、定时对账任务(异步发现违反)与唯一索引技巧。触发器方案把断言语义显式化,代价由业务数据规模决定,需在设计与测试中明确。

答题先定义断言并列出未实现的四类原因(实现代价、性能、标准含糊、厂商动力),再给 DOMAIN 约束(单行)与触发器(跨行/跨表)的替代方案与工程配套(物化视图、对账),形成完整替代路径。

CREATE DOMAIN positive_amount AS NUMERIC(12,2)
  CHECK (VALUE > 0);
-- 跨表不变式用触发器替代断言
CREATE FUNCTION trg_check_total() RETURNS trigger AS $$
BEGIN
  IF (SELECT SUM(amount) FROM ledger) <> 0 THEN
    RAISE EXCEPTION 'total balance must be zero';
  END IF;
  RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_total AFTER INSERT OR UPDATE OR DELETE ON ledger
  FOR EACH STATEMENT EXECUTE FUNCTION trg_check_total();
#
★★★

12. 约束的命名规范(CONSTRAINT name ...)在错误信息定位、迁移工具识别、运维管理上的价值是什么?请给出 PostgreSQL 与 MySQL 的命名惯例。

约束命名规范(CONSTRAINT name ...)在错误信息定位、迁移工具识别、运维管理上有何价值?请给出 PostgreSQL 与 MySQL 的命名惯例?

  • 命名约束的价值(报错定位、工具识别、运维)
  • 常见命名前缀惯例
  • 匿名约束的坑

显式命名约束的价值:其一,错误信息定位——未命名约束由数据库自动生成(PostgreSQL 的 table_pkey、MySQL 的 PRIMARY/列名,Oracle 的 SYS_C 编号),报错时只能看到自动名,难以判断哪张表哪个业务规则被违反;命名后报错直接提示 uk_users_email,排查效率大增;其二,迁移工具识别——Liquibase/Flyway 等按约束名比对 schema 差异(diff),匿名约束名随版本变化会导致误报漂移、重复建约束;其三,运维管理——启用/禁用(ALTER TABLE ... ENABLE CONSTRAINT)、删除、审计、文档化都依赖稳定名称,命名还表达意图(pk/uk/fk/ck 前缀一眼可读)。

命名惯例:统一前缀 + 表名 + 列名(+ 动作),如 pk_users_id(主键)、uk_users_email(唯一)、fk_orders_user_id(外键,可含引用表 fk_orders_users_user_id)、ck_users_age(CHECK)、nn_users_email(NOT NULL,PostgreSQL 命名 NOT NULL 需 ALTER TABLE 单独命名,MySQL 5.7 前不支持 NOT NULL 命名)。PostgreSQL 约束名在 schema 内唯一、自动索引同名(uk_users_email 既是约束也是索引名),MySQL 约束名在 schema 内唯一(外键名全库唯一)。规范要点:小写 + 下划线、长度受限(PostgreSQL 63 字节)、避免匿名约束、外键名不要依赖自动生成(迁移时外键删除需要名字)。

答题从三个价值(报错定位、迁移 diff、运维管理)展开,再给出各类型约束的命名惯例模板与 PostgreSQL/MySQL 的名称空间差异,最后提醒匿名约束与长度限制等细节。

CREATE TABLE users (
  id INT,
  email VARCHAR(100),
  age INT,
  CONSTRAINT pk_users_id PRIMARY KEY (id),
  CONSTRAINT uk_users_email UNIQUE (email),
  CONSTRAINT ck_users_age CHECK (age >= 0 AND age <= 150),
  CONSTRAINT fk_users_role_id FOREIGN KEY (role_id) REFERENCES roles(id)
);
#
★★★

13. 约束的禁用与启用(ALTER TABLE ... DISABLE/ENABLE CONSTRAINT)在 MySQL、Oracle、PostgreSQL 中各自如何实现?

约束的禁用与启用(ALTER TABLE ... DISABLE/ENABLE CONSTRAINT)在 MySQL、Oracle、PostgreSQL 中各自如何实现?有何差异?

  • Oracle 的 DISABLE/ENABLE CONSTRAINT
  • PostgreSQL 的约束延迟与 NOT VALID
  • MySQL 的禁用约束手段

三库能力差异显著。Oracle 原生支持 ALTER TABLE ... DISABLE/ENABLE CONSTRAINT(以及 ENABLE NOVALIDATE):禁用后不再检查、可用触发器替代,启用时可选择是否校验存量数据(NOVALIDATE 跳过存量检查),是最完整的能力。PostgreSQL 不提供 DISABLE/ENABLE CONSTRAINT:主键/唯一/外键约束无法直接禁用(需 DROP 后重建),CHECK 约束支持 ALTER TABLE ... DROP CONSTRAINT 后用 NOT VALID 重新添加(ALTER TABLE t ADD CONSTRAINT ck CHECK (...) NOT VALID; 只校验新写入,存量数据需再 VALIDATE CONSTRAINT 补检),唯一/主键可通过延迟(DEFERRABLE)推迟到事务提交,外键可声明 DEFERRABLE INITIALLY DEFERRED。

MySQL InnoDB 不提供约束禁用:外键无法 ALTER 禁用(只能 DROP FOREIGN KEY 后重建,8.0 支持 ALGORITHM=INPLACE 在线操作),CHECK(8.0.16+)同样只能删除重建,且 MySQL 没有 NOT VALID 概念。工程上"禁用约束导入数据"的模式:Oracle 用 DISABLE + ENABLE NOVALIDATE;PostgreSQL 用延迟约束(DEFERRABLE)或 NOT VALID CHECK;MySQL 用"先 DROP 约束→批量导入→校验数据→重建约束"三步,并注意重建期间的一致性窗口。禁用约束是高风险操作,需在生产低峰执行、配套数据校验脚本。

答题按库分别说明:Oracle 原生 DISABLE/ENABLE 与 NOVALIDATE、PostgreSQL 无 DISABLE 但有 NOT VALID 与延迟约束、MySQL 只能删除重建,最后给出三库各自的工程化导入模式与风险提示。

-- Oracle:禁用与跳过存量校验
ALTER TABLE child DISABLE CONSTRAINT fk_child_parent;
ALTER TABLE child ENABLE NOVALIDATE CONSTRAINT fk_child_parent;
-- PostgreSQL:新增 CHECK 不校验存量
ALTER TABLE t ADD CONSTRAINT ck_positive CHECK (amount > 0) NOT VALID;
ALTER TABLE t VALIDATE CONSTRAINT ck_positive;
#
★★★

14. CHECK 约束在 PostgreSQL 中如何引用其他列?跨表 CHECK 约束如何用触发器实现?

CHECK 约束在 PostgreSQL 中如何引用其他列?跨表 CHECK 约束如何用触发器实现?

  • 表级 CHECK 引用同表多列
  • CHECK 不能引用其他表/行
  • 跨表不变式的触发器实现

PostgreSQL 的 CHECK 可以引用本行内的其他列:列级 CHECK 只能引用所在列,表级 CHECK(写在列定义之后)可引用同表的任意列,如 CHECK (end_date > start_date)、CHECK (price >= cost)。CHECK 表达式必须是单行的不可变表达式:不能引用其他表、不能做子查询、不能调用 volatile 函数(如 now()),这是标准限制,保证约束可被逐行检查且优化器可推导。

跨表 CHECK 无法用约束实现,标准方案是触发器:在涉及的外键两端表上建 AFTER INSERT/UPDATE/DELETE 触发器,函数体内做跨表校验(如 SELECT 目标表),违反时 RAISE EXCEPTION 使语句/事务回滚。实现要点:FOR EACH ROW 或 FOR EACH STATEMENT 的选择(语句级触发器对批量导入开销更低)、触发器的全面覆盖(插入、更新、删除都要挂)、循环依赖(A 表触发器查 B 表、B 表触发器查 A 表)需要约束延迟或事务级检查(PostgreSQL 触发器函数可用延迟触发器 DEFERRABLE 在提交前统一校验)、以及性能——跨表校验每次写操作都要读另一表,需索引支撑,高频场景慎用,可改为应用层校验或定时对账。

答题先明确 CHECK 可引用本行多列(表级)但不可跨表跨行,再给跨表不变式的触发器实现要点(AFTER 触发器、语句级选择、RAISE EXCEPTION、延迟触发器防循环),最后提示性能代价。

-- 表级 CHECK 引用同表多列
CREATE TABLE order_item (
  id INT PRIMARY KEY,
  price NUMERIC(12,2),
  cost NUMERIC(12,2),
  CONSTRAINT ck_price_ge_cost CHECK (price >= cost)
);
-- 跨表校验触发器
CREATE FUNCTION check_parent_exists() RETURNS trigger AS $$
BEGIN
  IF NOT EXISTS (SELECT 1 FROM parent p WHERE p.id = NEW.parent_id) THEN
    RAISE EXCEPTION 'parent % does not exist', NEW.parent_id;
  END IF;
  RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_parent BEFORE INSERT ON child
  FOR EACH ROW EXECUTE FUNCTION check_parent_exists();
#
★★★

15. 参照完整性检查时机的同步(immediate)与异步(deferred)在多表更新事务中的取舍是什么?

参照完整性检查的同步(immediate)与异步(deferred)时机在多表更新事务中的取舍是什么?

  • immediate 语句级检查 vs deferred 提交级检查
  • 多表更新的正确性保障
  • 延迟检查的代价与适用场景

同步(immediate)检查在每条 SQL 语句结束时执行:违反立即报错、错误定位准确、事务可及时回滚部分语句,代价是约束要求"语句结束时引用关系已完整",多表联动的写入必须控制顺序(先父后子),否则中间语句可能暂时违反而报错——这限制了多表更新的灵活性。异步(deferred)检查把校验推迟到事务提交(DEFERRABLE INITIALLY DEFERRED 或 SET CONSTRAINTS ALL DEFERRED):事务内可以任意顺序写入,只要提交时引用关系完整即可,适合循环引用、双向引用、先建子后建父、批量导入等"中间状态不完整"的场景。

取舍:正确性上两者都保证最终一致性(提交时引用必须完整),差异在"检查点"与失败时机——deferred 把错误推迟到提交,事务内已做的大量工作可能白费(回滚成本高)、大事务提交时才报错定位困难;immediate 错误早发现、代价小,但要求语句级完整。工程准则:常规 OLTP 用 immediate(默认),让数据库尽早把关;需要复杂多表联动或循环引用时用 deferred 并保持事务短小;批量导入(COPY、ETL)用 deferred 或临时禁用约束换取吞吐,导入后补校验。注意 deferred 期间"读"到的是中间状态,应用层查询需自行处理(读未提交或隔离级别)。

答题先对比两种时机的机制(语句末 vs 提交前)与错误定位差异,再给适用场景(多表联动、循环引用、批量导入 vs 常规 OLTP),最后落到"默认 immediate、特殊场景 deferred 且事务要短"的工程准则。

#
★★★

16. 唯一约束与主键约束在 NULL 处理上的差异如何影响业务建模?业务上“软删除唯一”的常见解法(条件唯一索引、版本列)是什么?

唯一约束与主键约束在 NULL 处理上的差异如何影响业务建模?"软删除唯一"的常见解法(条件唯一索引、版本列)是什么?

  • 主键拒绝 NULL、唯一约束允许多 NULL
  • 软删除下的唯一性冲突
  • 部分索引与版本列的解法

建模影响:主键列必须非空(行标识),唯一约束列可空且允许多个 NULL——因此"业务上可选但唯一"的字段(手机号、邮箱、税号)用唯一约束而非主键表达,NULL 表示"未设置"且互不冲突;反过来,若业务要求"非空且唯一",必须组合 UNIQUE + NOT NULL。这一差异直接催生软删除问题的解法。

软删除唯一问题:业务要求"未删除的记录中某字段唯一"(如用户名、订单号),而软删除(deleted_at 标记)后历史数据仍占着唯一值,新插入同值记录会违反唯一约束。解法一(条件唯一索引/部分索引):PostgreSQL 用 WHERE deleted_at IS NULL 的部分唯一索引(CREATE UNIQUE INDEX ... ON t(col) WHERE deleted_at IS NULL),只对未删除行强制唯一;SQL Server 用过滤索引(WHERE deleted_at IS NULL)同样实现;MySQL 8.0 没有部分索引,需用生成列技巧(generated column:CASE WHEN deleted_at IS NULL THEN col END 加唯一索引)。解法二(版本列/时间戳列):把唯一约束改成 (业务键, deleted_at 或版本号) 联合唯一,每次删除写新的 deleted_at 值使组合唯一;解法三:删除时改名(用户名加后缀)。选择依据:部分索引最直接但 PostgreSQL/SQL Server 专属,MySQL 用生成列,跨库迁移时统一用"业务键+版本列"联合唯一更可移植。

答题先讲 NULL 差异的建模影响(可空业务唯一用 UNIQUE),再重点展开软删除唯一的两类解法(部分/过滤索引、生成列、业务键+版本列联合唯一),最后给出按库选择与可移植性建议。

-- PostgreSQL / SQL Server:部分(过滤)唯一索引
CREATE UNIQUE INDEX ux_users_username ON users(username) WHERE deleted_at IS NULL;
-- MySQL 8.0:生成列技巧
ALTER TABLE users ADD COLUMN active_username VARCHAR(50)
  GENERATED ALWAYS AS (CASE WHEN deleted_at IS NULL THEN username END) STORED;
CREATE UNIQUE INDEX ux_users_active ON users(active_username);
#
★★★

17. 唯一约束在多列(联合唯一)下的 NULL 处理,两列均为 NULL 时是否违反?部分列为 NULL 时如何?

唯一约束在多列(联合唯一)下如何处理 NULL?两列均为 NULL 时是否违反约束?部分列为 NULL 时呢?

  • 联合唯一在全部 NULL 时的行为
  • 部分 NULL 时的行为
  • 与 NULLS NOT DISTINCT 的方言扩展

联合唯一约束(UNIQUE (a, b))按"整行键值是否重复"判定:两列均为 NULL 时,该行键值与任何其他行都不相等(NULL 互不相等),不违反约束——因此允许多个 (NULL, NULL) 行并存;部分列为 NULL(如 (1, NULL))时,该键与 (1, NULL) 的另一行比较:1=1 为 TRUE、NULL=NULL 为 UNKNOWN,整体 UNKNOWN 不算重复,也不违反;与 (1, 2) 相比 1=1 TRUE、NULL=2 UNKNOWN,同样不冲突。规则概括:只要键中任一列为 NULL,该键与任何行(包括自身形态相同的行)都不构成"重复",唯一性只对"全部列都非 NULL"的键强制。

方言差异:PostgreSQL 15+ 支持 NULLS NOT DISTINCT(UNIQUE NULLS NOT DISTINCT (a,b)),把 NULL 视为相等,此时多个 (NULL, NULL) 会违反;Oracle 与 PostgreSQL 默认行为一致(NULL 不算重复);SQL Server 唯一索引按"索引键"整体处理,含 NULL 的键与 NULL 键视为不同(与标准一致,但单列唯一索引的 NULL 只允许一个;多列时部分 NULL 键同样互不冲突)。建模提示:若业务要求"联合键的 NULL 也参与唯一",用 COALESCE 归一(UNIQUE (COALESCE(a, 哨兵), b))或 NULLS NOT DISTINCT;若 NULL 表示"未设置且互不冲突",默认行为正合需求。

答题先给"任一列 NULL 即不与任何行重复"的判定规则(含全 NULL 与部分 NULL 两种情况),再讲方言扩展 NULLS NOT DISTINCT 与 COALESCE 归一方案,最后落到业务建模选择。

-- 默认:多个 (NULL, NULL) 与 (1, NULL) 都合法
CREATE TABLE t (a INT, b INT, UNIQUE (a, b));
-- PostgreSQL 15+:NULL 视为相等
CREATE TABLE t (a INT, b INT, UNIQUE NULLS NOT DISTINCT (a, b));
#
★★★

18. 域(DOMAIN)与类型(TYPE)的差异,自定义类型如何携带 CHECK 约束?PostgreSQL 的 CREATE DOMAIN 用法与限制是什么?

域(DOMAIN)与类型(TYPE)的差异是什么?自定义类型如何携带 CHECK 约束?PostgreSQL 的 CREATE DOMAIN 用法与限制有哪些?

  • DOMAIN 与 TYPE 的本质差异
  • DOMAIN 携带约束与默认值
  • CREATE DOMAIN 的限制

DOMAIN 是基于现有类型的"受限版本":它复用底层类型(INT、VARCHAR),在其上附加约束(CHECK)、默认值与 NOT NULL,多个列共享同一域时规则一处定义、处处生效;TYPE(CREATE TYPE)定义全新类型(复合类型、枚举、范围、自定义基础类型),需要完整的输入/输出函数与存储表示,不直接携带业务约束。差异总结:DOMAIN 是"类型+规则"的轻量包装,TYPE 是"新的数据表示",DOMAIN 不改变存储与运算(底层类型决定),TYPE 定义新存储语义。

CREATE DOMAIN 用法:CREATE DOMAIN positive_money AS NUMERIC(12,2) DEFAULT 0 CHECK (VALUE >= 0);可加 NOT NULL(域级非空);列声明用域名代替类型(amount positive_money)。限制:其一,域的 CHECK 只能引用 VALUE 单值表达式,不能引用同表其他列、不能跨表;其二,底层类型变更会影响所有使用域的地方(需注意 ALTER 传播);其三,域不参与类型等价——把域列与其他列比较可能触发隐式转换问题,函数参数类型匹配时域与底层类型不完全等价(可用 ALTER DOMAIN SET/DROP 调整);其四,域不能继承其他域(PostgreSQL 不支持域继承),约束组合需在域内一次写全;其五,性能上域列有轻微的类型检查开销,可忽略。用途:统一金额、手机号、状态码等列规则,防规则漂移。

答题先对比 DOMAIN(类型+约束的包装)与 TYPE(新数据表示),再给 CREATE DOMAIN 的语法与可携带规则(CHECK、DEFAULT、NOT NULL),最后列出限制(单值表达式、类型等价、无继承)与使用场景。

CREATE DOMAIN positive_amount AS NUMERIC(12,2) DEFAULT 0
  CONSTRAINT ck_amount CHECK (VALUE >= 0);
CREATE TABLE orders (
  id INT PRIMARY KEY,
  total positive_amount
);
#
★★★

19. 外键列上是否会自动创建索引?MySQL InnoDB 的处理是怎样的?为何 PostgreSQL 不自动创建?

外键列上是否会自动创建索引?MySQL InnoDB 的处理是怎样的?为何 PostgreSQL 不自动创建?

  • 各库外键索引的自动创建行为
  • InnoDB 自动建索引的原因
  • PostgreSQL 不自动建的原因与后果

SQL 标准不要求外键列有索引,但实践上外键列索引至关重要(子表侧按外键查询、父表删除时扫描子表检查引用都需要)。MySQL InnoDB 自动处理:若外键列上没有合适索引(列上已有索引则不重复建),InnoDB 在定义外键时自动创建索引(索引名等于约束名),这是 InnoDB 的强制要求——无索引的外键定义会被拒绝或自动补索引;因此 MySQL 外键列几乎总有索引。Oracle 不会自动创建,建议手工建。SQL Server 不自动创建(有提示),需要手工建。

PostgreSQL 不自动创建的原因:其一,设计哲学——约束与索引职责分离,自动建索引会带来"意外的存储与写放大"(每次外键定义都产生隐藏索引),且开发者可能已有更优的复合索引设计;其二,性能语义透明——PostgreSQL 允许开发者按查询模式决定索引形态(单列、复合、部分),自动建索引反而限制优化;其三,索引非外键语义的必要部分(约束可延迟、可卸载)。后果:漏建外键索引时,父表 DELETE/UPDATE 会全表扫描子表(检查引用),子表 JOIN 无索引,OLTP 性能严重劣化。工程规范:PostgreSQL 定义外键后主动检查是否需要补建索引(或把外键列纳入已有复合索引),用 pg_indexes 核对。

答题先给出"外键列索引的必要性(父子双向访问)",再分别讲 InnoDB 自动建(强制、索引名=约束名)与 PostgreSQL 不自动建(设计哲学、职责分离、性能透明)及其后果(父表删除全扫描子表),最后给工程补建建议。

-- PostgreSQL:建表后主动为外键列补索引
ALTER TABLE child ADD CONSTRAINT fk_child_parent FOREIGN KEY (parent_id) REFERENCES parent(id);
CREATE INDEX idx_child_parent ON child(parent_id);
#
★★★

20. 外键约束在自引用(parent_id)场景下的设计陷阱有哪些?为什么无限层级的树需要额外的层级深度校验?

外键约束在自引用(parent_id)场景下有哪些设计陷阱?为什么无限层级的树需要额外的层级深度校验?

  • 自引用外键的建模陷阱
  • 环与无限层级的产生
  • 深度校验的时机与手段

自引用外键(parent_id 引用本表 id)的陷阱:其一,环路——数据可构造 A.parent=B、B.parent=A 的环,外键约束只保证"引用的 id 存在",不保证"不构成环",环会导致递归查询(WITH RECURSIVE)无限循环或死循环;其二,删除级联——若误设 ON DELETE CASCADE,删除根节点会级联删除整棵子树,且环存在时级联删除可能循环处理(数据库有深度限制但语义易错);其三,根节点语义——根节点 parent_id 为 NULL 与"引用不存在"的区分、以及用 NULL 表达根时外键的 MATCH SIMPLE 放行;其四,并发与锁——父节点更新/删除时子节点的检查范围扩大,高并发树操作易锁冲突。

无限层级树需要深度校验的原因:递归依赖数据正确性,一旦数据错误(如某节点 parent_id 指向自己的后代)或配置错误(环),递归 CTE 的终止条件失效,查询会不断展开直至数据库层数限制(PostgreSQL 默认递归深度受限、报错),或产生逻辑死循环消耗资源;同时业务上"树深度"往往有隐性上限(如组织架构 10 层内),无校验时数据可无限嵌套,统计与权限计算复杂度失控。手段:应用层/触发器在写入时校验 parent 链不构成环(路径追踪)并设定最大深度;查询侧用 UNION ALL 时加深度计数与终止条件,或用物化路径、嵌套集、closure table 等结构规避深度递归。

答题从三类陷阱展开:孤儿引用与环路(A 的 parent 指回 B、B 的 parent 又指回 A,无环检测机制,递归查询死循环)、根节点 NULL 语义与删除级联风险(误设 ON DELETE CASCADE 会级联删除整棵子树)、性能与锁(自引用递归查询慢、父节点更新锁放大);再论证无限层级必须深度校验的原因:数据错误(手误把子节点指向孙节点)会让递归 CTE 不终止或栈溢出,维护侧需要深度与环的显式校验手段,最后落到"深度校验(触发器/应用层约束,设定最大深度)"与"工程侧用物化路径/嵌套集优化递归"的方案。

#
★★★

21. 如何在 PostgreSQL 中查询违反约束的具体数据行?请给出违反信息的查询模式(pg_constraint、触发器报错回溯)。

如何在 PostgreSQL 中查询违反约束的具体数据行?请给出基于 pg_constraint 与触发器报错回溯的查询模式?

  • 从报错信息定位约束
  • pg_constraint 的结构与查询
  • 主动扫描找违反行的 SQL 模板

定位违反约束的数据行分两步:先确定约束,再按约束条件扫描数据。约束确定:违反报错信息形如 ERROR: duplicate key value violates unique constraint "uk_users_email",约束名(含 schema)直接给出;从 pg_constraint 可查全部约束的定义:SELECT conname, contype, pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'users'::regclass,contype 区分 p(主键)、u(唯一)、f(外键)、c(CHECK)、x(排他),pg_get_constraintdef 还原定义表达式。

按约束扫数据:唯一/主键冲突——按约束涉及的列分组查重复:SELECT email, COUNT() FROM users GROUP BY email HAVING COUNT() > 1(唯一冲突一定是重复组,pg_catalog 可查 pg_index 的 indisunique 列确定唯一索引列);外键违反——父表删除失败时,查子表是否存在"引用了不存在父行"的数据:SELECT child.* FROM child LEFT JOIN parent ON child.parent_id = parent.id WHERE parent.id IS NULL AND child.parent_id IS NOT NULL;CHECK 违反——按 pg_get_constraintdef 还原的表达式取反扫描:SELECT * FROM t WHERE NOT (约束表达式)。触发器报错回溯:若约束由触发器实现(RAISE EXCEPTION),报错信息带函数名与触发行,用消息内容 + 触发条件反查;也可临时用 EXCEPTION 块记录明细(BEGIN ... EXCEPTION WHEN OTHERS THEN RETURN SQLERRM)。

答题按"报错定位约束→pg_constraint 查定义→按约束类型扫描违反行"三步展开,分别给唯一(GROUP BY HAVING)、外键(LEFT JOIN IS NULL)、CHECK(取反表达式)的查询模板,最后讲触发器报错的回溯方法。

-- 查表上的全部约束与定义
SELECT conname, contype, pg_get_constraintdef(oid)
FROM pg_constraint WHERE conrelid = 'users'::regclass;
-- 查唯一冲突行
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;
-- 查外键悬空行
SELECT c.* FROM child c LEFT JOIN parent p ON c.parent_id = p.id
WHERE c.parent_id IS NOT NULL AND p.id IS NULL;
#
★★★

22. 完整性约束的性能开销如何评估?CHECK、NOT NULL、UNIQUE 在写入路径上的代价排序是怎样的?

完整性约束的性能开销如何评估?CHECK、NOT NULL、UNIQUE 在写入路径上的代价排序是怎样的?

  • 各类约束的写入代价机制
  • 代价排序:NOT NULL < CHECK < UNIQUE < FK
  • 索引维护与锁的开销

约束性能开销 = 检查成本 + 附属结构维护成本。NOT NULL:仅检查标志位,无附属结构,代价近乎零;CHECK:对表达式求值(表达式复杂度决定成本),无索引维护,代价低,但复杂表达式(子查询函数、大计算量)会显著变慢;UNIQUE:检查需在索引中查找键是否存在——每个插入/更新都要做 B 树探针(O(log n))且持有索引锁(唯一检查需短暂锁防止并发重复),批量写入时索引插入本身还有写放大,代价中等且随表规模增长;主键与 UNIQUE 同层;外键:检查"父行存在"需要读父表索引(可能缓存命中也可能随机读),父表删除/更新时还要扫描子表外键索引,跨表随机 IO 使外键成为最贵的约束(无索引时退化为全表扫描子表)。

代价排序(写入路径):NOT NULL < CHECK(简单表达式)< UNIQUE/主键 < 外键(有子表扫描场景最贵)。评估方法:EXPLAIN 观察约束检查是否引入额外节点(如 PostgreSQL 的约束触发在语句级)、用 pg_stat_user_tables 的索引读写观察维护开销、压测对比有/无约束吞吐差异;优化方向:简化 CHECK 表达式、给外键列建索引、把低频表的外键检查延迟(DEFERRABLE)或异步对账、避免过度约束(能用 NOT NULL 不用 CHECK、能用 CHECK 不用触发器)。注意约束的正确性收益通常远大于开销,评估目的是防止"约束设计导致写入路径瓶颈",而非砍约束。

答题先建立"检查成本+结构维护"的评估框架,再按机制推导代价排序(NOT NULL<CHECK<UNIQUE<FK),给评估方法(EXPLAIN、统计视图、压测)与优化方向,最后强调正确性优先。

#
★★★

23. 延迟约束在批量数据导入(COPY、INSERT ... SELECT)场景下的优势是什么?为何在 OLTP 高频写入场景应避免使用?

延迟约束在批量数据导入(COPY、INSERT ... SELECT)场景下的优势是什么?为何在 OLTP 高频写入场景应避免使用?

  • 批量导入的约束检查放大
  • 延迟约束减少重复校验
  • 延迟约束在 OLTP 的代价(提交期峰值、锁、错误定位)

批量导入的优势:COPY/INSERT ... SELECT 逐行插入时,非延迟约束每行都做完整检查(外键每行查父表索引、唯一每行探索引),大批量数据下检查次数 = 行数 × 检查成本,且中间态(先插子表后插父表)会被立即拒绝;延迟约束(DEFERRABLE INITIALLY DEFERRED)把检查推迟到提交前一次完成(或按事务内变更集合统一校验),配合"先导全部数据再统一验证"消除逐行重复开销与中间态问题,导入吞吐显著提升;同时延迟期间可任意写入顺序,适合循环引用与父子混合导入。

OLTP 高频场景应避免的原因:其一,提交期检查峰值——所有约束检查集中在提交瞬间,长事务或高并发提交时形成性能尖峰(锁持有时间变长);其二,错误推迟到提交——业务写入失败在最后才发现,事务内已做的更新全部回滚,重试成本高、错误定位难;其三,锁与并发——延迟约束期间为支持回滚需要保留更多行版本/锁信息(PostgreSQL 的 deferred 外键用特殊锁机制),高并发下死锁与锁等待概率上升;其四,读一致性问题——延迟期间其他事务可能读到违反约束的中间状态。工程结论:批量导入/ETL 用延迟约束或临时禁用约束换取吞吐(导入后校验),OLTP 保持立即检查,让错误尽早暴露。

答题先讲批量导入中延迟约束的两大优势(检查次数从逐行降为一次、允许任意写入顺序),再列 OLTP 场景的四类代价(提交期峰值、错误推迟回滚成本、锁放大、中间态可见),最后给出按场景选择的工程结论。

#
★★★

24. 约束的级联删除(CASCADE)在多层级外键(祖→父→子)下的传递行为是怎样的?递归删除的终止条件与死循环如何检测?

约束的级联删除(CASCADE)在多层级外键(祖→父→子)下的传递行为是怎样的?递归删除的终止条件是什么?死循环如何检测?

  • 多层级 CASCADE 的递归传导
  • 递归终止条件(无引用、深度限制)
  • 环与死循环的检测机制

多层级级联删除:删除祖表行时,数据库递归地处理每层外键引用——先删子表引用行,子表行又触发孙表的级联,直到没有引用或被引用表无匹配行为止;行为等价于自顶向下的深度遍历(父→子→孙),最终删除整棵引用子树。实现上数据库用"删除工作队列 + 逐层触发"机制(PostgreSQL 的事件触发器链、InnoDB 的级联处理),保证同事务内原子完成;若子表外键为 NULL(不引用)则不参与。

递归终止条件与死循环检测:终止条件一(自然终止)——某层没有子引用或无匹配行,递归即停;终止条件二(唯一性/去重)——同一行只处理一次(已处理集合),避免重复入队;PostgreSQL 与 MySQL InnoDB 均无固定层数上限,依靠"无引用自然终止 + 行级去重"结束,SQL Server 则限制同一表在级联链中只能出现一次(防循环)。死循环场景:环形外键(A 引用 B、B 引用 A 且都有 ON DELETE CASCADE)删除任意一侧可能触发"删除→级联→再触发"的环,数据库依靠"行级去重(已处理集合)"终止,并配合锁等待/死锁检测兜底;人工检测方法:用递归 CTE 遍历外键依赖图查环(从 pg_constraint 提取外键关系后做环检测),或在测试环境执行删除并观察报错。工程建议:级联删除应"只向叶子方向"设计(环外键避免 CASCADE),对多层级链先做影响分析(SELECT 预估删除行数),生产删除用事务包裹。

答题先描述多层级 CASCADE 的递归传导机制(深度遍历、同事务原子),再讲三类终止条件(无引用、层数上限、行去重)与环形外键的死循环检测(依赖图环检测、层数报错),最后给工程建议(单向设计、影响分析、事务包裹)。

#
★★★

25. 触发器与约束的执行先后顺序如何?BEFORE INSERT 触发器能否修改 NEW.col 以满足后续 CHECK 约束?

触发器与约束的执行先后顺序如何?BEFORE INSERT 触发器能否修改 NEW.col 以满足后续的 CHECK 约束?

  • BEFORE 触发器先于约束检查执行
  • 触发器内修改 NEW 列值
  • 约束与触发器的顺序模型

PostgreSQL 与 MySQL 的执行顺序:BEFORE 行级触发器先执行(可修改 NEW 值),随后执行约束检查——NOT NULL、CHECK、唯一/主键在行写入时检查(先于 AFTER 行级触发器),非延迟外键在语句结束时检查(MySQL 中各类约束均在行写入时立即检查)——最后执行 AFTER 行级触发器(PostgreSQL 中行级 AFTER 触发器在语句结束时触发、先于语句级 AFTER 触发器)。因此 BEFORE INSERT 触发器可以修改 NEW.col——在触发器函数中给 NEW.col 赋值(如默认值、去空格、规范格式化),后续 CHECK/NOT NULL/唯一约束检查看到的是修改后的值,从而让"数据规范化"先于"规则校验"发生,这是实现"写入前清洗 + 校验"的标准手段。

细节注意:其一,触发器对 NEW 的修改不影响其他行也不产生递归(除非显式再写表);其二,BEFORE 触发器可用 RETURN NULL 阻止插入(PostgreSQL),MySQL 无返回值直接执行;其三,修改后仍需满足约束——若触发器把值改坏,约束照样报错;其四,约束检查失败的报错不会回滚"触发器已执行的副作用"以外的内容(事务内可回滚);其五,AFTER 触发器看到的是约束通过后的最终行。理解"BEFORE → 约束 → AFTER"的顺序模型,是设计写入管道(清洗、校验、审计)的基础。

答题先给出执行顺序链(BEFORE 触发器 → 约束检查 → AFTER 触发器),再确认 BEFORE 触发器可修改 NEW.col 且修改发生在约束检查之前,最后补充 RETURN NULL 阻止、改坏值仍报错等细节。

CREATE FUNCTION normalize_email() RETURNS trigger AS $$
BEGIN
  NEW.email := lower(trim(NEW.email));
  RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_norm BEFORE INSERT OR UPDATE ON users
  FOR EACH ROW EXECUTE FUNCTION normalize_email();
#
★★★

26. CHECK 约束的典型用法示例(CHECK (age >= 0 AND age <= 150))?

请举例说明 CHECK 约束的典型用法,如 CHECK (age >= 0 AND age <= 150)?

  • CHECK 的取值域校验
  • 多条件组合与枚举校验
  • 与 NOT NULL 的配合

CHECK 约束用于限定列取值的合法范围,典型用法:数值范围 CHECK (age >= 0 AND age <= 150)、CHECK (quantity > 0);枚举取值 CHECK (status IN ('NEW','PAID','SHIPPED','CANCELLED'));格式/逻辑关系 CHECK (end_date >= start_date)、CHECK (discount BETWEEN 0 AND 1);跨列一致性 CHECK (total = price * quantity)。列级写法 CHECK (age >= 0 AND age <= 150) 直接跟在列定义后;表级写法可引用多列且可命名:CONSTRAINT ck_users_age CHECK (...)。

要点:其一,CHECK 对 NULL 放行(返回 UNKNOWN 不违例),若要"必填且合法"需配合 NOT NULL 或显式 IS NOT NULL 条件;其二,枚举校验优先用 ENUM 类型或外键字典表(更易扩展),CHECK 适合静态小集合;其三,约束表达式应是确定的(不能用 now()、随机函数),否则优化器无法推导且行为漂移;其四,CHECK 只拒绝明确违反,不约束"未被定义的情况"——业务规则外变化时需更新约束并校验存量数据(PostgreSQL 的 NOT VALID + VALIDATE 流程)。工程上把 CHECK 当作"第一道数据防线",与触发器/应用校验分层。

答题先给 CHECK 的典型场景清单(范围、枚举、跨列、格式)与列级/表级写法,再讲 NULL 放行、枚举替代方案、表达式确定性、存量校验四个要点,突出实战细节。

CREATE TABLE person (
  id INT PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  age INT CONSTRAINT ck_person_age CHECK (age >= 0 AND age <= 150),
  status VARCHAR(10) CHECK (status IN ('ACTIVE','INACTIVE')),
  CONSTRAINT ck_person_status_age CHECK (status = 'ACTIVE' OR age IS NOT NULL)
);
#
★★★

27. MySQL 中如何查询某张表上所有的约束信息?请给出 INFORMATION_SCHEMA 查询。

MySQL 中如何查询某张表上的所有约束信息?请给出基于 INFORMATION_SCHEMA 的查询?

  • INFORMATION_SCHEMA.TABLE_CONSTRAINTS 与 KEY_COLUMN_USAGE
  • 外键信息的查询
  • MySQL 8.0 的 CHECK_CONSTRAINTS

MySQL 查约束主要用两个视图:TABLE_CONSTRAINTS(约束名、类型 PRIMARY KEY/UNIQUE/FOREIGN KEY/CHECK)与 KEY_COLUMN_USAGE(约束涉及的列与引用关系)。查询某表全部约束:SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = 'db' AND TABLE_NAME = 'orders';查外键明细(引用表、列、级联动作):JOIN KEY_COLUMN_USAGE(REFERENCED_TABLE_NAME、REFERENCED_COLUMN_NAME)与 REFERENTIAL_CONSTRAINTS(UPDATE_RULE、DELETE_RULE);查 CHECK 内容(8.0.16+):SELECT c.CONSTRAINT_NAME, c.CHECK_CLAUSE FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS c JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON tc.CONSTRAINT_NAME = c.CONSTRAINT_NAME AND tc.CONSTRAINT_SCHEMA = c.CONSTRAINT_SCHEMA WHERE tc.TABLE_NAME = 'orders'(MySQL 8.0 的 CHECK_CONSTRAINTS 没有 TABLE_NAME 字段,需关联 TABLE_CONSTRAINTS 定位到表;带 TABLE_NAME 字段的是 MariaDB)。

替代命令:SHOW CREATE TABLE orders(直接给出完整 DDL 含全部约束);SHOW INDEX FROM orders(索引层面的唯一/主键信息)。注意 MySQL 的外键名在 schema 内唯一、约束名与索引名可能一致(外键自动索引同约束名),查询时按 TABLE_SCHEMA + TABLE_NAME 过滤即可;MySQL 8.0 的 CHECK_CONSTRAINTS 与其他库(PostgreSQL)在字段上有差异,跨库脚本需兼容。

答题给出三类查询:约束清单(TABLE_CONSTRAINTS)、外键明细(JOIN KEY_COLUMN_USAGE + REFERENTIAL_CONSTRAINTS)、CHECK 文本(CHECK_CONSTRAINTS),再补充 SHOW CREATE TABLE 与 SHOW INDEX 的快速途径及 8.0 差异。

-- 全部约束
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = 'app' AND TABLE_NAME = 'orders';
-- 外键引用与级联
SELECT kcu.CONSTRAINT_NAME, kcu.REFERENCED_TABLE_NAME, rc.DELETE_RULE
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
  ON rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
WHERE kcu.TABLE_SCHEMA = 'app' AND kcu.TABLE_NAME = 'orders';
-- CHECK 文本(8.0.16+,MySQL 8.0 的 CHECK_CONSTRAINTS 无 TABLE_NAME,需关联 TABLE_CONSTRAINTS)
SELECT c.CONSTRAINT_NAME, c.CHECK_CLAUSE
FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS c
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
  ON tc.CONSTRAINT_NAME = c.CONSTRAINT_NAME AND tc.CONSTRAINT_SCHEMA = c.CONSTRAINT_SCHEMA
WHERE tc.TABLE_NAME = 'orders';
#
★★★

28. 为什么 NOT NULL 约束在数据迁移时经常被遗漏?请给出两种检测方法。

为什么 NOT NULL 约束在数据迁移时经常被遗漏?请给出两种检测方法?

  • NOT NULL 被遗漏的原因(默认值、工具导出、文档)
  • 迁移后约束对比检测
  • 数据层面 NULL 检测

遗漏原因:其一,NOT NULL 是"列属性"而非独立约束对象(PostgreSQL 存于 pg_attribute.attnotnull 标志、MySQL 列定义内),导出工具与迁移脚本按"约束对象"维度处理时容易跳过;其二,模型文档常只画"主键/外键/唯一",可空性标注被忽略,DDL 手写时默认不写 NOT NULL(多数库列默认可空);其三,迁移工具(mysqldump、pg_dump)导出完整度依赖版本与选项,部分场景(如 Oracle 的 NOT NULL 以系统 CHECK 存在)导出映射不一致;其四,测试库数据"恰好无 NULL"掩盖了约束缺失,上线后生产数据才暴露。

检测方法一(元数据对比):迁移前后分别导出"列可空性"清单做 diff——PostgreSQL 查 information_schema.columns 的 is_nullable 字段(或 pg_attribute.attnotnull),MySQL 查 information_schema.columns.is_nullable,SQL Server 查 sys.columns.is_nullable,比对源库与目标库差异,列出"源库 NOT NULL 而目标库可空"的列;检测方法二(数据抽样/全量):对目标表执行"查 NULL 行"扫描确认约束缺失的影响面——SELECT COUNT(*) FROM t WHERE col IS NULL,若返回大于 0 且源库该列非空,说明数据或约束有问题;更彻底的是用 CHECK 约束暂时代替(ALTER TABLE ... ADD CONSTRAINT ... CHECK (col IS NOT NULL) NOT VALID 后 VALIDATE)触发全量校验,校验通过再落为正式 NOT NULL。

答题先分析四类遗漏原因(列属性非对象、文档缺失、导出映射、测试数据掩盖),再给两种检测方法(元数据 is_nullable 对比 diff、NULL 数据扫描或 NOT VALID 校验),覆盖元数据与数据两个层面。

-- 方法一:对比源/目标库的可空列
SELECT table_name, column_name, is_nullable
FROM information_schema.columns WHERE table_schema = 'app';
-- 方法二:数据层面检测 NULL
SELECT COUNT(*) AS null_rows FROM users WHERE email IS NULL;
-- 校验存量后落为 NOT NULL(PostgreSQL)
ALTER TABLE users ADD CONSTRAINT nn_users_email CHECK (email IS NOT NULL) NOT VALID;
ALTER TABLE users VALIDATE CONSTRAINT nn_users_email;
#
★★

29. 主键约束的 SQL 写法,在 CREATE TABLE 与 ALTER TABLE 中分别如何声明?

主键约束在 CREATE TABLE 与 ALTER TABLE 中分别如何声明?请给出写法示例?

  • 建表时列级与表级声明
  • ALTER TABLE 追加主键
  • 复合主键与命名

CREATE TABLE 中两种位置:列级内联——id INT PRIMARY KEY(或 id INT CONSTRAINT pk_users PRIMARY KEY),紧跟列定义,适合单列主键;表级声明——所有列定义后写 PRIMARY KEY (id, dept_id) 或 CONSTRAINT pk_users PRIMARY KEY (id),可声明复合主键、可命名,推荐命名写法便于运维。ALTER TABLE 追加:ALTER TABLE users ADD PRIMARY KEY (id) 或 ALTER TABLE users ADD CONSTRAINT pk_users PRIMARY KEY (id);追加约束要求现有数据满足主键语义(非空且唯一),不满足则报错(如含 NULL 或重复)。

细节:其一,追加前需清理重复/空值(分组查重 + 补默认值);其二,主键自动创建唯一索引(InnoDB 中为聚簇索引),ALTER 大表加主键需评估锁与重建代价(MySQL 8.0 在线 DDL、PostgreSQL 建议低峰期或借助新表迁移);其三,DROP 用 ALTER TABLE users DROP CONSTRAINT pk_users(PostgreSQL)或 DROP PRIMARY KEY(MySQL),部分库要求先删依赖外键;其四,主键列默认 NOT NULL(PostgreSQL 隐式、MySQL 需显式或由约束隐含),声明复合主键时所有列都不允许 NULL。

答题按"建表(列级/表级)+ ALTER 追加 + 注意事项"结构给出完整写法,重点提醒追加前数据校验、自动索引、删除语法与 NOT NULL 语义,形成可直接落地的语法清单。

-- CREATE TABLE:表级命名主键
CREATE TABLE users (
  id INT,
  email VARCHAR(100),
  CONSTRAINT pk_users PRIMARY KEY (id)
);
-- ALTER TABLE 追加
ALTER TABLE users ADD CONSTRAINT pk_users PRIMARY KEY (id);
-- 复合主键
ALTER TABLE order_item ADD CONSTRAINT pk_order_item PRIMARY KEY (order_id, line_no);
#
★★

30. 外键约束声明 ON DELETE CASCADE 的含义是什么?请用一个父子表举例说明。

外键约束声明 ON DELETE CASCADE 的含义是什么?请用一个父子表举例说明?

  • ON DELETE CASCADE 的语义
  • 父子表示例与级联行为
  • 使用风险

ON DELETE CASCADE 声明"父表行被删除时,引用它的所有子表行自动级联删除",数据库在同事务内自动完成,无需应用逐行删除。示例:父表 orders(订单头)、子表 order_items(订单明细,order_id 外键引用 orders.id 并带 ON DELETE CASCADE),DELETE FROM orders WHERE id = 1001 会先自动删除 order_items 中 order_id=1001 的所有明细行,再删除订单头——保证"订单没了,明细也不残留",避免孤儿明细。若无 CASCADE(默认 NO ACTION),删除订单头会被拒绝(有明细引用),必须先删明细或改明细归属。

使用要点:其一,级联删除是隐式行为,事故影响面大——误删父行会连带删除大量子行且逐层传导(祖→父→子),删除前应先用 SELECT 预估影响行数;其二,级联只删"引用该父行的行",若子表外键为 NULL 或指向其他父行则不受影响;其三,级联删除不产生逐行应用日志,审计需另行设计(软删除或触发器记录);其四,更新用 ON UPDATE CASCADE 类似(父键变更同步子键),但父键变更场景更少见。适合"父子生命周期严格一致"(订单与明细、用户与收货地址),不适合"子行需保留"(审计表、历史记录)。

答题先给语义定义,再用订单/明细父子表实例完整演示删除过程,随后列出风险(隐式连锁、影响预估、审计缺失)与适用边界,形成"语义+示例+取舍"的完整回答。

CREATE TABLE order_items (
  order_id INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
  line_no INT NOT NULL,
  product VARCHAR(50),
  PRIMARY KEY (order_id, line_no)
);
-- 删除订单头 1001:明细自动级联删除
DELETE FROM orders WHERE id = 1001;
#
★★

31. CASE WHEN 表达式与 COALESCE、NULLIF 的等价转换规则是什么?搜索式 CASE 与简单式 CASE 的差异如何?

CASE WHEN 表达式与 COALESCE、NULLIF 的等价转换规则是什么?搜索式 CASE 与简单式 CASE 的差异是什么?

  • 搜索式与简单式 CASE 的语法差异
  • COALESCE/NULLIF 与 CASE 的等价
  • 等价转换的边界

等价规则:COALESCE(a, b) 等价于搜索式 CASE WHEN a IS NOT NULL THEN a ELSE b END;NULLIF(a, b) 等价于 CASE WHEN a = b THEN NULL ELSE a END(比较为 UNKNOWN 时返回 a)。反向也可:单值判空/判等场景用 COALESCE/NULLIF 简写 CASE。搜索式 CASE(CASE WHEN 条件 THEN 结果 ... ELSE 结果 END)每个 WHEN 是完整布尔表达式,条件可任意组合(范围、多列、NULL 判断);简单式 CASE(CASE 表达式 WHEN 值 THEN ...)只做"表达式与值的等值比较",不可写范围条件,NULL 匹配也有陷阱——简单式 CASE col WHEN NULL THEN 与 col = NULL 等价,恒不匹配,判空必须用搜索式 WHEN col IS NULL。

差异总结:简单式是等值分派的简写(适合状态映射),搜索式是完整布尔逻辑(适合范围与复杂条件);两者可互相改写(简单式 → WHEN 表达式 = 值)。执行层面两者等价(优化器生成相同表达式求值),差异纯在语法表达力与可读性。NULLIF 注意点:NULLIF(a, b) 中 b 是 NULL 时(NULLIF(col, NULL))任何 a 都返回 a,恒等于 col,无意义;COALESCE 与嵌套 CASE 可表达多级兜底(COALESCE(a, b, c) = CASE WHEN a IS NOT NULL THEN a WHEN b IS NOT NULL THEN b ELSE c END)。

答题先给两组等价转换公式(COALESCE/NULLIF ↔ CASE),再对比搜索式(完整条件)与简单式(等值分派、NULL 陷阱),最后补充多级兜底的 CASE 展开与 NULLIF 的边界提醒。

-- 等价转换
COALESCE(a, b)        ≡ CASE WHEN a IS NOT NULL THEN a ELSE b END
NULLIF(a, b)          ≡ CASE WHEN a = b THEN NULL ELSE a END
-- 简单式不能判 NULL
CASE status WHEN NULL THEN 'x' END  -- 恒不命中,应写 WHEN status IS NULL
#
★★

32. DISTINCT 与 GROUP BY 的等价关系是什么?两者在执行计划与性能上的差异如何?

DISTINCT 与 GROUP BY 的等价关系是什么?两者在执行计划与性能上有何差异?

  • DISTINCT 与无聚集 GROUP BY 的等价
  • 排序去重与哈希聚合的实现
  • 性能差异与选择

等价关系:SELECT DISTINCT a, b FROM t 与 SELECT a, b FROM t GROUP BY a, b 结果相同(都是对 (a,b) 组合去重),即"无聚集函数的 GROUP BY 等价于 DISTINCT";但语义不同——DISTINCT 是"行的去重",GROUP BY 是"分组归约"(可携带聚集),有聚集时只有 GROUP BY 能做。SQL 标准与优化器都允许将 DISTINCT 改写为 GROUP BY(反之不一定),PostgreSQL 优化器常把简单 DISTINCT 转成 HashAggregate/GroupAggregate 执行。

执行计划差异:两者都可能用排序去重(Sort + Unique / GroupAggregate)或哈希(HashAggregate),差异取决于优化器选择而非语法本身;性能上,DISTINCT 只输出去重结果、无分组开销,GROUP BY 若带聚集需逐组计算;实践中二者代价接近,选择依据是语义——要聚集用 GROUP BY,只去重用 DISTINCT;需要注意:SELECT DISTINCT 参与排序的列少,ORDER BY 配合 DISTINCT 时排序键必须包含在 DISTINCT 列中;GROUP BY 版本对"分组列"建索引(或保证输入有序)时可避免排序,DISTINCT 版本同样受益。经验结论:不要用 DISTINCT 替代带聚集的 GROUP BY,也不要为了"看起来快"把 GROUP BY 换成 DISTINCT——先 EXPLAIN 再定。

答题先给出"无聚集 GROUP BY ≡ DISTINCT"的等价性与语义差异,再分析执行计划的实现重合(Sort/Hash),最后落到"按语义选择、EXPLAIN 验证"的工程结论。

#
★★

33. GROUP BY 的扩展语法(GROUPING SETS、ROLLUP、CUBE)在 PostgreSQL 与 SQL Server 中的支持差异如何?请给出多维聚合的等价写法。

GROUP BY 的扩展语法(GROUPING SETS、ROLLUP、CUBE)在 PostgreSQL 与 SQL Server 中的支持差异如何?请给出多维聚合的等价写法?

  • GROUPING SETS/ROLLUP/CUBE 的语义
  • 两库支持情况
  • UNION ALL 等价展开

GROUPING SETS 一次性按多个分组组合聚合(GROUP BY GROUPING SETS ((a), (b), ()),() 为总计);ROLLUP 生成"层级小计链"(ROLLUP (a, b) = (a,b)、(a)、());CUBE 生成全组合小计(CUBE (a, b) = (a,b)、(a)、(b)、())。支持差异:PostgreSQL 9.5+ 原生支持三者;SQL Server 2008+ 原生支持(且支持 ROLLUP/CUBE 的 WITH ROLLUP 旧语法与 GROUPING() 函数);Oracle 也原生支持;MySQL 8.0 只支持 WITH ROLLUP(且不能与 ORDER BY/LIMIT 混用),不支持 GROUPING SETS 与 CUBE。

等价写法:无原生支持时用 UNION ALL 展开——GROUP BY ROLLUP(a, b) 等价于 GROUP BY a, b UNION ALL GROUP BY a UNION ALL GROUP BY ();CUBE 需四段 UNION ALL;GROUPING SETS 同理逐组拼接。注意 UNION ALL 展开时分组列缺失处需补 NULL(GROUP BY a 的分组里 b 列不存在,输出 b 为 NULL),并可用 GROUPING()/GROUPING_ID() 函数区分"聚合产生的小计 NULL"与"数据本身的 NULL"。工程上跨库代码优先用 GROUPING SETS 标准语法(PG/SQL Server/Oracle),MySQL 用 WITH ROLLUP 或应用层二次聚合。

答题先定义三种扩展的聚合语义(组合、层级小计、全组合),再列支持矩阵(PG/SQL Server/Oracle 原生、MySQL 仅 WITH ROLLUP),最后给出 UNION ALL 等价展开与 GROUPING() 判小计的方法。

-- PostgreSQL / SQL Server
SELECT dept, role, COUNT(*) FROM emp
GROUP BY GROUPING SETS ((dept, role), (dept), ());
-- 等价 UNION ALL(MySQL 可用)
SELECT dept, role, COUNT(*) FROM emp GROUP BY dept, role
UNION ALL SELECT dept, NULL, COUNT(*) FROM emp GROUP BY dept
UNION ALL SELECT NULL, NULL, COUNT(*) FROM emp;
#
★★

34. LIMIT/OFFSET 的分页语义与 OFFSET 性能问题,大表 OFFSET 1000000 的代价如何?

LIMIT/OFFSET 的分页语义是什么?大表 OFFSET 1000000 的代价如何?如何优化深分页?

  • LIMIT/OFFSET 的分页语义
  • 深分页的扫描浪费
  • 游标/键集分页等优化

分页语义:LIMIT n OFFSET m 跳过前 m 行、返回接下来的 n 行(PostgreSQL/MySQL/SQL Server 用 OFFSET ... FETCH NEXT;ORDER BY 决定顺序),语义上是"先按 ORDER BY 排序,再跳过 m 行取 n 行"。代价:深分页时数据库必须完整执行排序并"数过"前 m 行——OFFSET 1000000 需要扫描/排序一百万行再丢弃,复杂度随偏移量线性增长,越到后面的页越慢;若排序键无索引,还需全量排序;LIMIT 本身可以提前终止(找到 n 行即停),但 OFFSET 无法提前终止,只能一直数。

优化方案:其一,键集分页(Keyset Pagination / Seek Method)——用上一页最后一条的排序键做条件:WHERE (sort_key) > 上一页末值 ORDER BY sort_key LIMIT n,靠索引定位跳过已读部分,复杂度 O(log n),页深无关,是最优解;其二,游标分页(API 场景传 cursor);其三,限制可用页深(产品上最多 100 页)或改用"先取主键集再回表";其四,用覆盖索引避免排序。注意键集分页要求排序键唯一且稳定(复合主键兜底),且不能随意跳页;OFFSET 只适合页浅(<几百)的普通场景。

答题先给 LIMIT/OFFSET 的语义(排序后跳过取行)并解释 OFFSET 无法提前终止导致线性代价,再重点展开键集分页(Seek Method)的原理与条件,最后补充游标、页深限制等工程对策。

-- 深分页:每次都要数过前 1000000 行
SELECT * FROM orders ORDER BY id LIMIT 20 OFFSET 1000000;
-- 键集分页:基于上一页末尾 id
SELECT * FROM orders WHERE id > 1000010 ORDER BY id LIMIT 20;
#
★★

35. ORDER BY 子句的稳定性(Stable Sort)保证,相同排序键值的元组在多次执行中顺序是否一致?PostgreSQL 与 MySQL 的实现差异?

ORDER BY 子句的稳定性保证是什么?相同排序键值的元组在多次执行中顺序是否一致?PostgreSQL 与 MySQL 的实现有何差异?

  • 排序稳定性与结果确定性
  • SQL 标准对 ORDER BY 的确定性要求
  • 两库实现差异

SQL 标准要求 ORDER BY 的排序是"稳定的":相同排序键的行之间,相对顺序保持稳定,多次执行结果一致——只有排序键列相等时它们的次序才是不确定的(标准允许,因为无法规定);因此"相同排序键值的元组顺序是否一致"要分两种:若行本身可区分(含排序列以外的差异),稳定排序保证其相对顺序不变;若两行完全相同(所有列一致),则结果不可区分,顺序无意义。PostgreSQL 的实现:排序基于输入顺序的稳定排序,相同键按输入顺序排列,因此相同键行顺序在多次执行中一致(只要底层扫描顺序稳定),且 PostgreSQL 提供"确定性"保证——相同输入、相同查询、相同数据必然相同输出(并行扫描时部分场景除外)。

MySQL 差异:MySQL 的 ORDER BY 排序实现依赖 filesort 算法(带或不带缓冲),对相同键行不保证稳定顺序——早期版本相同键行顺序不可预测;8.0 起 filesort 仍是稳定排序(保留输入顺序),但若查询没有 ORDER BY 限制的隐含顺序(如 LIMIT 无 ORDER BY)则完全不确定;另外 InnoDB 查询通常按索引顺序返回,相同键行顺序取决于索引内顺序(二级索引含主键,实际可区分)。工程准则:应用层永远不要把"未在 ORDER BY 中列出的列的顺序"当作约定,需要唯一确定顺序时在 ORDER BY 追加唯一列(如 id)作为决胜键。

答题先明确"稳定排序保证相同键行相对顺序一致,完全相同行不可区分",再对比 PostgreSQL(稳定、确定性)与 MySQL(filesort 实现差异、无 ORDER BY 时不确定),最后给"ORDER BY 加唯一决胜列"的工程规范。

#
★★

36. SELECT 语句的完整逻辑执行顺序(FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT)与书写顺序的差异如何影响查询理解?

SELECT 语句的逻辑执行顺序(FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT)与书写顺序有何差异?这对查询理解有什么影响?

  • 逻辑执行顺序的完整链路
  • 别名可见性(WHERE 不可见、ORDER BY 可见)
  • 书写顺序与逻辑顺序的错位

逻辑执行顺序(标准语义):FROM(取关系,含 JOIN)→ WHERE(行过滤)→ GROUP BY(分组)→ HAVING(组过滤)→ SELECT 列表(计算投影列,含聚集与表达式)→ DISTINCT(去重)→ ORDER BY(排序)→ LIMIT/OFFSET(截断)。书写顺序是 SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT,两者错位——SELECT 虽写在最前,但其投影计算发生在中间阶段。

对查询理解的影响:其一,别名可见性——SELECT 中定义的列别名在 WHERE、GROUP BY、HAVING 中不可用(这些阶段先于 SELECT 计算),但 ORDER BY 可用(后于 SELECT),MySQL 对 GROUP BY/HAVING 使用别名的宽松支持是方言例外(PostgreSQL 严格拒绝);其二,WHERE 不能用聚集函数(行过滤先于分组),须用 HAVING;其三,谓词求值位置(WHERE 与 ON 的差异、HAVING 与 WHERE 的差异)都可从顺序推出;其四,ORDER BY 可使用 SELECT 列表别名与未选列(PostgreSQL 允许未选列排序,MySQL 默认也允许,DISTINCT 时限制);其五,LIMIT 在排序后截断,影响"先排序再取前 N"。理解这条顺序链是读懂复杂查询、定位"为何报错/为何行为不同"的基础。

答题先完整列出逻辑执行顺序链,指出与书写顺序的错位(SELECT 前置书写、中间执行),再以别名可见性、聚集函数位置、HAVING/WHERE 分工等具体影响收束,展示对 SQL 求值模型的理解。

#
★★

37. SQL 中标量子查询(Scalar Subquery)的使用限制(单值、单列)是什么?返回多行或多列时各报什么错误?

SQL 中标量子查询(Scalar Subquery)的使用限制(单值、单列)是什么?返回多行或多列时各报什么错误?

  • 标量子查询的合法位置与要求
  • 多行返回的错误(PostgreSQL/MySQL 差异)
  • 多列返回的错误

标量子查询出现在"期望单个值"的位置(SELECT 列表、WHERE 比较右侧、CASE 条件、函数参数等),要求返回"一行一列":子查询结果必须是单行单列。返回多行时报错:PostgreSQL 报 "more than one row returned by a subquery used as an expression";MySQL 报 "Subquery returns more than 1 row"(若该位置是 = 比较);Oracle 报 "ORA-01427: single-row subquery returns more than one row"。返回多列时:PostgreSQL 报 "subquery must return only one column"(若位置只接受标量);MySQL 报 "Operand should contain 1 column(s)";若恰好用于行构造器(SELECT (a, b))则多列合法——PostgreSQL 支持行值比较(ROW 构造)。

处理方式:确保子查询语义上最多返回一行——加聚合(MAX/MIN)、LIMIT 1、或改成相关子查询保证唯一;多行取值需求应改用子查询集合运算(IN、EXISTS、派生表 JOIN),而不是标量位置。注意空结果:标量子查询返回零行时结果为 NULL(不报错),这与多行报错不同,是"最多一行"的两种合法结果之一(0 行→NULL、1 行→该值)。

答题先界定标量子查询的"单行单列"约束与典型位置,再分别给出多行、多列的报错信息(三库),最后讲空结果→NULL 与行构造器例外及修法,覆盖语义与排错。

#
★★

38. SQL 子句的执行阶段如何影响列别名可见性?为什么 SELECT 定义的别名在 WHERE 中不可用但在 ORDER BY 中可用?

SQL 子句的执行阶段如何影响列别名可见性?为什么 SELECT 中定义的别名在 WHERE 中不可用但在 ORDER BY 中可用?

  • 逻辑执行顺序与别名作用域
  • WHERE 先于 SELECT、ORDER BY 后于 SELECT
  • 方言例外(MySQL 宽松、PG 严格)

别名可见性由逻辑执行顺序决定:WHERE、GROUP BY、HAVING 阶段在 SELECT 投影计算之前执行,此时 SELECT 列表中的别名尚未生成,因此不可引用;ORDER BY 阶段在 SELECT 之后执行,别名已存在,因此可用;LIMIT 在 ORDER BY 之后,同样可用。这正是"为什么 WHERE 不能用别名、ORDER BY 能用"的机制答案——不是语法限制,而是求值阶段差异。

细节:其一,FROM 子句内已定义的表别名/派生表别名是另一作用域(更早生成,WHERE 可用);其二,SELECT 列表内同一层的别名不能互相引用(同时求值);其三,方言差异——MySQL 允许 GROUP BY/HAVING 引用 SELECT 别名(宽松实现),PostgreSQL 严格按标准拒绝(报错 column does not exist),跨库代码不应依赖 MySQL 的宽松行为;其四,ORDER BY 除别名外还可用"序号"(ORDER BY 2 按第 2 列)与未选列(PostgreSQL 允许,MySQL 在 DISTINCT 时受限)。理解阶段模型即可解释别名相关的绝大多数报错。

答题以逻辑执行顺序为主线回答"WHERE 先于 SELECT、ORDER BY 后于 SELECT",再列细节(FROM 内别名、同层别名、MySQL 宽松/PG 严格、序号排序),把语法现象还原为求值模型。

#
★★

39. EXISTS 与 IN 在处理 NULL 时的差异如何?请用三值逻辑分析 NOT IN 包含 NULL 时的语义陷阱。

EXISTS 与 IN 在处理 NULL 时的差异是什么?请用三值逻辑分析 NOT IN 包含 NULL 时的语义陷阱?

  • EXISTS 的行存在性语义
  • IN 的逐值比较与三值逻辑
  • NOT IN 含 NULL 的空结果陷阱

EXISTS 判定"子查询是否有行"(二值,不受 NULL 影响),IN 判定"外层值是否等于子查询集合中的某个值"(逐值比较,NULL 参与时落入三值逻辑)。差异示例:col IN (SELECT val FROM t) 当 t 含 NULL 行时,NULL 不参与匹配但对整体有影响;EXISTS (SELECT 1 FROM t WHERE t.val = col) 逐行比较,NULL 使该行比较为 UNKNOWN 只是"该行不匹配",不影响其他行匹配,只要有一行匹配即 TRUE。

NOT IN 的陷阱:NOT IN (子查询) 要求"col 与集合中每个值都不相等"(等价于 <> ALL),若集合含 NULL,则对任意 col,"col <> NULL" 为 UNKNOWN;NOT IN 需要"全部比较为 TRUE"才保留该行,一旦存在 UNKNOWN(NULL 值参与),整体结果不是 TRUE,行被过滤——且对每一行都如此,最终结果恒为空集(除非 col 本身可确定地等于集合中某值使比较链包含 FALSE,但 NULL 仍使其为 UNKNOWN,同样被过滤)。结论:子查询可能返回 NULL 时,NOT IN 结果不可靠(恒空或漏行),应用 NOT EXISTS 改写或先过滤 NULL(NOT IN (SELECT val FROM t WHERE val IS NOT NULL))。记忆要点:NOT IN 与 NULL 不兼容,NOT EXISTS 与 NULL 兼容。

答题先对比 EXISTS(行存在性、二值)与 IN(值比较、三值)的机制,再用"<> NULL 恒 UNKNOWN + NOT IN 需全 TRUE"推演空结果陷阱,最后给 NOT EXISTS 或过滤 NULL 的改写方案。

-- 陷阱:t.val 含 NULL 时结果恒为空
SELECT * FROM a WHERE a.col NOT IN (SELECT val FROM t);
-- 正确:NOT EXISTS 改写
SELECT * FROM a WHERE NOT EXISTS (SELECT 1 FROM t WHERE t.val = a.col);
-- 或过滤 NULL
SELECT * FROM a WHERE a.col NOT IN (SELECT val FROM t WHERE val IS NOT NULL);
#
★★

40. FROM 子句中的派生表(Derived Table)与 CTE 的语义差异如何?派生表能否引用自身?CTE 的 MATERIALIZED 关键字的作用是什么?

FROM 子句中的派生表(Derived Table)与 CTE 的语义差异如何?派生表能否引用自身?CTE 的 MATERIALIZED 关键字的作用是什么?

  • 派生表与 CTE 的语法与作用域差异
  • 递归只能由 WITH RECURSIVE 表达
  • MATERIALIZED/NOT MATERIALIZED 语义

语义差异:派生表(FROM (SELECT ...) AS t)是"内嵌在单条语句 FROM 中的子查询",作用域仅限该语句,不可复用、不可被其他部分引用,且不能引用自身(无递归能力);CTE(WITH cte AS (SELECT ...))把子查询提升到语句前部命名,同一语句内可多次引用(cte 可被主查询引用两次,优化器决定是否物化),多个 CTE 可相互引用(顺序声明),并支持 WITH RECURSIVE 递归——派生表无法递归,递归只能靠递归 CTE。可读性上 CTE 先声明后使用、可分层(WITH a AS (...), b AS (...) SELECT ... FROM a JOIN b),复杂查询更清晰;派生表内联书写。

MATERIALIZED 关键字(PostgreSQL 12+):CTE 默认可能被优化器内联(像宏一样展开,多次引用时重复计算)或物化(计算一次存临时结果);显式 MATERIALIZED 强制物化——适合"CTE 计算昂贵且需多次引用"(避免重复执行)或"CTE 含 volatile 函数需固定求值一次";NOT MATERIALIZED 强制内联——适合"CTE 轻量、内联可让谓词下推、利用索引"的场景。MySQL 8.0 无此关键字(CTE 行为由优化器决定,默认物化倾向);SQL Server 的 CTE 总是内联(无物化控制),这是方言差异。

答题先对比派生表与 CTE 的作用域/复用/递归差异(派生表不可自引用、递归必须 CTE),再讲 MATERIALIZED/NOT MATERIALIZED 的物化与内联语义及适用场景,最后补三库差异。

-- CTE 多次引用并强制物化(PostgreSQL)
WITH recent AS MATERIALIZED (
  SELECT * FROM orders WHERE created_at > now() - interval '7 days'
)
SELECT * FROM recent r1 JOIN recent r2 ON r1.user_id = r2.user_id;
-- 递归只能 CTE(派生表不可)
WITH RECURSIVE tree AS (SELECT ... UNION ALL SELECT ...) SELECT * FROM tree;
#
★★

41. GROUP BY 中使用别名(按 SELECT 别名分组)在 MySQL 中的允许性与 PostgreSQL 的严格性差异如何?

GROUP BY 中使用 SELECT 别名(按别名分组)在 MySQL 与 PostgreSQL 中的允许性差异如何?

  • MySQL 允许 GROUP BY/HAVING 使用别名
  • PostgreSQL 严格拒绝
  • 可移植写法

差异:MySQL 允许 GROUP BY 与 HAVING 引用 SELECT 列表中的列别名(如 SELECT dept AS d, COUNT(*) FROM emp GROUP BY d 合法),这是 MySQL 的宽松实现(先扩展 SELECT 表达式再分组);PostgreSQL 严格遵循标准逻辑顺序——GROUP BY 先于 SELECT 执行,别名不存在,引用即报错 "column d does not exist"(除非 d 恰好是真实列名)。SQL Server 允许 GROUP BY 使用别名(与 MySQL 类似,宽松);Oracle 也拒绝(严格)。

原因与风险:宽松实现便于书写(少写一遍表达式),但歧义大——别名与真实列同名时以真实列优先(MySQL 有冲突规则),且掩盖了"分组键究竟是表达式还是列"的语义;严格实现强制分组键可溯源,语义清晰。可移植写法:GROUP BY 中直接写完整表达式(GROUP BY dept),或复用 SELECT 中的表达式(可读性差);需要重命名时在 SELECT 用别名、GROUP BY 用表达式本身。工程规范:跨库代码一律不用别名分组,GROUP BY 与 SELECT 表达式保持一致,避免依赖方言行为。

答题先给出 MySQL(允许)与 PostgreSQL(拒绝)的行为对比及原因(逻辑顺序),再列 SQL Server/Oracle 阵营,最后给"GROUP BY 写完整表达式"的可移植规范与歧义风险。

#
★★

42. IN 与 = ANY 与 EXISTS 的等价条件与性能差异,在大子查询场景下各自的最优选择是什么?

IN 与 = ANY 与 EXISTS 的等价条件与性能差异是什么?大子查询场景下各自的最优选择是什么?

  • IN 与 = ANY 的语法等价
  • EXISTS 的相关性差异
  • 优化器改写与选择策略

等价条件:col IN (子查询) 与 col = ANY (子查询) 语法等价(都做存在性等值比较,NULL 语义一致);EXISTS 通常写成相关形式 EXISTS (SELECT 1 FROM t WHERE t.col = outer.col),语义上是"逐外层行匹配",与 IN 在"无 NULL 且值可比较"时结果等价(优化器会把非相关 IN 改写为半连接,把相关 EXISTS 也改写为半连接,最终执行计划可能相同)。性能差异主要来自改写与执行算子:现代优化器(PostgreSQL、MySQL 8.0、SQL Server)会把 IN/EXISTS/ANY 统一转成 Semi Join(哈希或嵌套循环),执行代价取决于数据分布与索引,语法本身的差距很小。

大子查询场景的最优选择:子查询结果集大而外层行少——相关 EXISTS(带内层索引)用嵌套循环半连接,避免构建大哈希表,优先 EXISTS;子查询结果集小或已有序——IN/ANY 让优化器选择哈希半连接(一次性构建小表)或物化子查询,更优;子查询与外层规模都大——哈希半连接是通用最优,三者改写后等价,任选可读性好的。NULL 场景:IN 与 ANY 受 NULL 影响(NOT IN 陷阱),EXISTS 无 NULL 问题。工程结论:让优化器决定(三者改写空间大),必要时 EXPLAIN 对比;编码优先用 EXISTS 表达存在性、IN 表达成员性,避免 NOT IN。

答题先讲 IN 与 = ANY 的语法等价及与 EXISTS 的语义差异(相关/非相关、NULL),再分析优化器统一改写为半连接的事实,最后按"外层小/子查询大→EXISTS、子查询小→IN/ANY、都大→哈希半连接"给出选择策略。

#
★★

43. JOIN 子句中 ON 与 WHERE 的差异,ON 在连接前过滤,WHERE 在连接后过滤。请用 LEFT JOIN 举例说明误用 WHERE 导致 INNER JOIN 化的陷阱。

JOIN 子句中 ON 与 WHERE 的差异是什么?请用 LEFT JOIN 举例说明误用 WHERE 导致 LEFT JOIN 退化为 INNER JOIN 的陷阱?

  • ON 连接时过滤 vs WHERE 连接后过滤
  • 内连接中两者可互换
  • 外连接中 WHERE 过滤补 NULL 行的退化

语义差异:ON 是连接条件,在连接配对阶段生效(决定哪些行配对);WHERE 是结果过滤,在连接完成后对结果集过滤。对内连接(INNER JOIN),两者逻辑等价(优化器可互换并做下推);对外连接(LEFT/RIGHT/FULL JOIN),两者不等价——ON 中被保留侧(如 LEFT JOIN 的左表)的条件只影响"是否配对",不影响保留侧行的输出;WHERE 条件作用于"连接后的完整结果",会过滤掉被保留侧未匹配的行(补 NULL 行)。

LEFT JOIN 退化陷阱:SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'PAID'——WHERE 过滤 o.status = 'PAID' 会把"没有订单的用户"(o.status 为补全 NULL)过滤掉,结果等价于 INNER JOIN(只输出有已付订单的用户),LEFT JOIN 形同虚设。正确写法:把被保留侧无关的条件放 ON:LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'PAID'——此时无订单用户仍输出(o 列全 NULL)。通用规则:外连接中"针对被保留侧行的过滤"放 WHERE(本来就要排除)、"针对被连接侧行的配对条件"放 ON;判断方法是执行 EXPLAIN 看是否出现 Filter 于 Join 之后。

答题先讲 ON 与 WHERE 的阶段差异及内连接的可互换性,再以外连接退化陷阱为核心:WHERE 过滤补 NULL 行导致 LEFT JOIN 变 INNER JOIN,给出正确改写(条件移到 ON)与判断方法。

-- 陷阱:过滤掉无订单用户,LEFT JOIN 退化为 INNER JOIN
SELECT u.*, o.id FROM users u LEFT JOIN orders o ON u.id = o.user_id
WHERE o.status = 'PAID';
-- 正确:配对条件放 ON,保留无订单用户
SELECT u.*, o.id FROM users u LEFT JOIN orders o
  ON u.id = o.user_id AND o.status = 'PAID';
#
★★

44. WHERE 子句中 AND、OR、NOT 的优先级规则是怎样的?括号缺失时的常见陷阱有哪些?

WHERE 子句中 AND、OR、NOT 的优先级规则是怎样的?括号缺失时的常见陷阱有哪些?

  • 优先级:NOT > AND > OR
  • 括号缺失的语义漂移
  • 三值逻辑与组合过滤

优先级规则(从高到低):NOT > AND > OR,括号可覆盖默认优先级。即 WHERE a OR b AND c 解析为 a OR (b AND c),WHERE NOT a AND b 解析为 (NOT a) AND b。常见陷阱:其一,条件混合时意图偏差——"status = 'A' OR status = 'B' AND amount > 100" 实际是 status='A' OR (status='B' AND amount>100),若想"两种状态且金额条件"必须加括号 (status='A' OR status='B') AND amount > 100;其二,NOT 作用范围——WHERE NOT a = 1 OR b = 2 解析为 (NOT (a=1)) OR (b=2),易误读为 NOT (a=1 OR b=2);其三,IN 与 OR 混写——WHERE col IN (1,2) OR col IS NULL 已明确,但写成 col IN (1,2) AND col IS NULL 恒假(无行同时满足);其四,NULL 组合——WHERE col IS NULL OR col = 0 与 WHERE col = 0 OR col IS NULL 等价,但漏判空(col IS NULL OR col = 0)与漏判值(只写 col = 0)是常见 bug;其五,优化器按等价布尔代数重写(AND/OR 交换、下推),加括号只影响可读性与优先级,不影响最终结果正确性(前提是语义想清楚了)。

工程规范:条件组合一律显式加括号表达意图,复杂条件拆成多行或子查询/CTE,静态检查工具可预警优先级歧义。

答题先给 NOT > AND > OR 的优先级表,再列三类典型陷阱(OR 与 AND 混用、NOT 作用域、IN 与 NULL 组合)各配反例与正确写法,最后给加括号规范。

-- 陷阱:被解析为 status='A' OR (status='B' AND amount>100)
WHERE status = 'A' OR status = 'B' AND amount > 100;
-- 正确:加括号表达真实意图
WHERE (status = 'A' OR status = 'B') AND amount > 100;
#
★★

45. HAVING 子句能否单独使用而不带 GROUP BY?请举例说明。

HAVING 子句能否单独使用而不带 GROUP BY?请举例说明?

  • HAVING 的语义(组过滤)
  • 无 GROUP BY 时的单组隐式分组
  • 与 WHERE 的分工

可以。HAVING 不强制要求显式 GROUP BY:没有 GROUP BY 时,整张表被视为"一个隐式分组",HAVING 对该单组过滤,因此 HAVING 中可写聚集函数(对整个表聚合)。例如 SELECT COUNT() FROM orders HAVING COUNT() > 100 合法:返回一行(总数),满足 HAVING 则输出、否则输出空集;SELECT SUM(amount) FROM orders HAVING SUM(amount) > 10000 同理。PostgreSQL 与 MySQL 都支持这种写法(MySQL 8.0 中 HAVING 与 WHERE 可互换部分场景但语义不同)。

注意点:其一,无 GROUP BY 时 SELECT 列表只能有聚集函数或常量(没有分组列可输出,输出普通列会报错);其二,HAVING 中的非聚集列引用无意义(单组无列可取,PostgreSQL 会报错、MySQL 宽松);其三,语义分工——WHERE 在分组前过滤行(减少聚合输入),HAVING 在分组后过滤组(过滤聚合结果),能用 WHERE 就别用 HAVING(性能上 WHERE 更早过滤);其四,HAVING 配合显式 GROUP BY 是最常见形态,单独使用的场景主要是"全表聚合条件筛选"(如报表阈值判断)。工程上该写法可读性一般,等价写法是子查询(SELECT * FROM (SELECT COUNT(*) c FROM orders) x WHERE c > 100)。

答题先确认"无 GROUP BY 时 HAVING 视全表为单组、可写聚集",再举例并讲 SELECT 列表限制(只能聚集/常量)、与 WHERE 的分工(行过滤 vs 组过滤)及子查询等价写法。

-- 无 GROUP BY 的 HAVING:全表单组过滤
SELECT COUNT(*) FROM orders HAVING COUNT(*) > 100;
-- 等价子查询写法
SELECT * FROM (SELECT COUNT(*) AS c FROM orders) x WHERE c > 100;
#
★★

46. LIMIT 10 OFFSET 20 表示跳过多少行、取多少行?

LIMIT 10 OFFSET 20 表示跳过多少行、取多少行?分页语义如何理解?

  • LIMIT 与 OFFSET 的参数语义
  • 起始行定位(从 21 行开始取)
  • 与分页页码的换算

LIMIT 10 OFFSET 20 表示:跳过(OFFSET)前 20 行,再返回(LIMIT)最多 10 行,即取第 21 行到第 30 行(若总行数不足则返回剩余行)。语义顺序:先按 ORDER BY 排序(无 ORDER BY 则顺序不确定),跳过 20 行,取 10 行。分页换算:第 N 页(每页 size 行)的写法是 LIMIT size OFFSET (N-1)*size,第 3 页每页 10 行即 LIMIT 10 OFFSET 20。SQL Server 用 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY 表达等价语义;Oracle 12c+ 用 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY。

注意点:其一,OFFSET 与 LIMIT 的书写顺序在 MySQL/PostgreSQL 为 LIMIT 先、OFFSET 后(LIMIT 10 OFFSET 20),SQL Server/Oracle 为 OFFSET 先行;其二,OFFSET 值必须非负、LIMIT 非负(0 表示不返回行);其三,无 ORDER BY 时分页结果不稳定(每次可能不同行集),分页必须配 ORDER BY;其四,深分页性能线性恶化,页深时用键集分页。

答题直接给出"跳过 20 取 10 → 第 21-30 行"的解读与页码换算公式,再补方言写法、参数顺序差异、必须配 ORDER BY 与深分页注意点,简洁完整。

-- 跳过 20 行取 10 行(第 3 页,每页 10 行)
SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 20;
-- SQL Server / Oracle 12c+
SELECT * FROM orders ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
#
★★

47. SELECT DISTINCT 与 SELECT 的差异如何?

SELECT DISTINCT 与普通 SELECT 的差异是什么?使用场景与代价如何?

  • DISTINCT 的去重语义
  • 与 GROUP BY 无聚集的等价
  • 去重代价

差异:普通 SELECT 返回"所有符合条件的行"(袋语义,含重复行),SELECT DISTINCT 对结果集去重——只有完全相同的行(所有选中列都相等)才合并为一行。例:SELECT city FROM users 返回全部用户的 city(有重复),SELECT DISTINCT city FROM users 返回不同城市列表(去重)。NULL 处理:DISTINCT 把多个 NULL 视为同一值(只保留一个)。与 GROUP BY 的关系:SELECT DISTINCT a, b 等价于 SELECT a, b GROUP BY a, b(无聚集),但有聚集时必须 GROUP BY。

使用场景:枚举值列表(不同城市/状态)、报表去重统计、数据清洗查重。代价:去重需要排序(Sort + Unique)或哈希聚合(HashAggregate),额外内存/临时文件与 O(n log n) 或 O(n) 时间;数据量大且重复率低时开销接近全量排序。工程建议:确需去重才用 DISTINCT;避免 SELECT DISTINCT *(列越多去重越贵);统计去重优先 COUNT(DISTINCT col)(可走索引);能用 EXISTS/半连接表达的"存在性"需求不要用 DISTINCT 大表拼接。

答题先讲语义差异(袋 vs 去重、NULL 归一并)与 GROUP BY 等价关系,再给使用场景与两类实现(排序/哈希)的代价,最后给工程建议,全面覆盖考点。

#
★★

48. WHERE col IN (1, 2, 3) 与 WHERE col = 1 OR col = 2 OR col = 3 是否等价?

WHERE col IN (1, 2, 3) 与 WHERE col = 1 OR col = 2 OR col = 3 是否等价?

  • IN 与 OR 的语义等价
  • 优化器的等价改写
  • 边界差异(NULL 与类型)

语义上等价:col IN (1, 2, 3) 展开即为 col = 1 OR col = 2 OR col = 3,两者在结果集与真值语义上一致(都是"col 等于三者之一")。优化器通常也把 IN 列表改写为 OR 集合或位图索引扫描(MySQL 的 IN 优化、PostgreSQL 的 SAOP 数组扫描),执行计划往往等价或 IN 更优(列表大时 MySQL 自动构建哈希、PostgreSQL 用数组任意元素操作符)。

边界差异:其一,NULL——col IN (1,2,3) 与 OR 版对 NULL col 都返回 UNKNOWN 被过滤,一致;但 IN 子查询含 NULL 与 NOT IN 的陷阱不适用于字面量列表;其二,类型转换——IN 列表中的值会统一类型比较,OR 版每个比较独立求值,个别方言在隐式转换上可能有细微差异(规范上要求一致);其三,表达式重复——OR 版若 col 是昂贵表达式(如函数),IN 写法让优化器只求值一次(通常如此,但不是标准保证);其四,列表为空时 IN () 在 MySQL 恒为假(8.0 报错或警告、PostgreSQL 语法不允许),OR 版空无意义。工程结论:字面量列表用 IN(简洁、易优化),不要手工展开成 OR;要判空与值并存时写 IN (...) OR col IS NULL。

答题先确认等价(IN 即 OR 的简写,优化器等价改写),再列边界差异(NULL 语义一致、类型转换、空列表、表达式求值次数),最后给出"字面量用 IN"的工程建议。

#
★★

49. CTE(Common Table Expressions)在 SQL:1999 引入,递归 CTE 的标准语法与各数据库方言的差异?

CTE(Common Table Expressions)在 SQL:1999 引入,递归 CTE 的标准语法与各数据库方言有哪些差异?

  • 标准 WITH 与 WITH RECURSIVE 语法
  • 各库递归 CTE 的实现差异
  • 方言注意点(RECURSIVE 关键字、终止限制)

标准语法:WITH cte AS (SELECT ...) SELECT ... 用于非递归;WITH RECURSIVE cte AS (锚点查询 UNION [ALL] 递归查询) SELECT ... 用于递归。锚点(anchor)提供初始行,递归部分引用自身把新行并入结果,直到不再产生新行(不动点)终止;UNION 去重、UNION ALL 保留重复(适合树遍历计数)。方言差异:PostgreSQL 要求显式 RECURSIVE 关键字,支持 SEARCH/CYCLE 子句(PG 14+)、深度与路径控制,递归无固定层数上限(靠终止条件与内存约束);SQL Server 的递归 CTE 默认(MAXRECURSION 0 为无限,默认 100 层上限,超限报错)、不需要 RECURSIVE 关键字(靠 CTE 内自引用识别递归);MySQL 8.0 支持递归 CTE 但默认递归深度限制(cte_max_recursion_depth,默认 1000,超限报错);Oracle 支持递归 CTE 且历史上有 CONNECT BY 的层级查询替代语法(START WITH ... CONNECT BY PRIOR)。

实现差异要点:递归必须紧跟非递归部分、递归引用只能出现一次(部分方言)、递归查询中不能使用某些聚合与窗口函数(标准限制,各库执行差异)、UNION 与 UNION ALL 的终止语义差异(去重可终止环、ALL 需深度控制防环);MySQL 8.0 对递归 CTE 的物化与索引利用较基础,深层递归性能弱于 PostgreSQL。工程上:树/图遍历用递归 CTE 前先评估深度与环,显式 UNION ALL 加深度计数列,或改用物化路径/closure table。

答题先给标准语法结构(WITH RECURSIVE + 锚点 + UNION/UNION ALL),再逐库列差异(关键字要求、递归深度上限、Oracle CONNECT BY),最后讲终止语义与防环工程要点。

-- 标准递归 CTE:员工层级
WITH RECURSIVE emp_tree AS (
  SELECT id, name, manager_id, 1 AS depth FROM emp WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, e.manager_id, t.depth + 1
  FROM emp e JOIN emp_tree t ON e.manager_id = t.id
  WHERE t.depth < 10
) SELECT * FROM emp_tree;
#
★★

50. MERGE 语句在 SQL:2003 标准化,Oracle 早已支持,PostgreSQL 15 之前不支持、SQL Server 长期支持、MySQL 8.0 不支持的现状如何?

MERGE 语句的标准化与各数据库支持现状如何?Oracle、PostgreSQL、SQL Server、MySQL 各自的情况与替代方案是什么?

  • MERGE 的 SQL:2003 标准化
  • 四库支持现状
  • 无 MERGE 时的 UPSERT 替代

MERGE(又称 UPSERT 语义)按匹配与否合并源与目标:匹配则 UPDATE、不匹配则 INSERT(可选 DELETE 分支),SQL:2003 标准化。支持现状:Oracle 最早实现(1999 年起 MERGE,功能最全,支持多 WHEN 分支与 DELETE);SQL Server 2008+ 长期支持(WHEN MATCHED/WHEN NOT MATCHED 子句);PostgreSQL 15 才正式支持 MERGE(此前无),15 之前用 INSERT ... ON CONFLICT(UPSERT,9.5+)或 INSERT ... ON DUPLICATE KEY UPDATE(MySQL);MySQL 8.0 至今不支持 MERGE(官方建议用 INSERT ... ON DUPLICATE KEY UPDATE 或 INSERT IGNORE)。

替代方案对比:PostgreSQL 的 ON CONFLICT (col) DO UPDATE/DO NOTHING 功能强(可指定冲突目标、WHERE 条件),但语义聚焦"唯一键冲突"而非通用合并;MySQL 的 ON DUPLICATE KEY UPDATE 语义宽松(任何唯一键冲突都触发,无目标指定,多唯一键时行为易错);数据仓库批量合并常用 DELETE + INSERT 或临时表 MERGE 应用层实现。工程建议:优先用各库原生 UPSERT(PG 的 ON CONFLICT、MySQL 的 ON DUPLICATE KEY、SQL Server 的 MERGE),注意 MySQL 8.0 无 MERGE、多唯一键场景的语义陷阱与 MERGE 在并发下的锁行为。

答题按时间线列四库现状(Oracle 最早、SQL Server 长期、PG 15 才支持、MySQL 无),再对比替代方案(ON CONFLICT vs ON DUPLICATE KEY)的语义差异,最后给工程选型建议。

-- PostgreSQL(9.5+,15 前替代 MERGE)
INSERT INTO t (id, val) VALUES (1, 'x')
ON CONFLICT (id) DO UPDATE SET val = EXCLUDED.val;
-- MySQL 替代
INSERT INTO t (id, val) VALUES (1, 'x')
ON DUPLICATE KEY UPDATE val = VALUES(val);
-- SQL Server / PostgreSQL 15+ 标准 MERGE
MERGE INTO t USING (VALUES (1, 'x')) AS s(id, val) ON t.id = s.id
WHEN MATCHED THEN UPDATE SET val = s.val
WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val);
#
★★

51. PostgreSQL 对 SQL 标准的支持度(高度兼容)与 MySQL(弱兼容)的具体差异在哪些子句上体现?

PostgreSQL 对 SQL 标准的高度兼容与 MySQL 的弱兼容具体在哪些子句/特性上体现?

  • 标准子句的支持差异
  • 窗口函数、递归、外连接等标准特性
  • 行为差异(别名、类型、ONLY 等)

PostgreSQL 以标准兼容著称,差异主要体现在:标准特性齐全——窗口函数(FULL/PERCENT_RANK 等完整集)、递归 CTE(标准语法)、FILTER 子句(聚合过滤)、WITH TIES、FETCH FIRST 分页、布尔类型、域、CHECK 约束完全生效、外连接与 INTERSECT/EXCEPT 标准语义;MySQL 弱兼容表现——8.0 前无窗口函数与递归 CTE(8.0 补齐但语义细节有差)、无 INTERSECT/EXCEPT(8.0.31 补)、CHECK 8.0.16 才生效、无布尔类型(TINYINT(1) 模拟)、LIMIT 方言而非标准 FETCH、GROUP BY 对非分组列的宽松处理(ONLY_FULL_GROUP_BY 默认关闭时允许)、NULL 排序默认位置不同、INSERT 子句顺序等细节差异。

行为差异更值得注意:PostgreSQL 严格按标准拒绝 SELECT 非分组列(only_full_group_by 强制)、别名作用域严格(GROUP BY 不能用别名)、区分 '' 与 NULL、区分空字符串排序规则;MySQL 宽松(默认 sql_mode 允许非分组列任意取值、别名分组可用)。子句层面:PostgreSQL 支持标准 DELETE ... USING、UPDATE ... FROM、RETURNING(MySQL 8.0 才支持 UPDATE/INSERT ... RETURNING 需 8.0.19+ 且有限)、MERGE(15+);MySQL 的 REPLACE、INSERT IGNORE、LIMIT 更新/删除是方言扩展。跨库迁移时,标准特性差异集中体现在窗口函数、递归、布尔、CHECK、分页与分组严格性上。

答题按"标准特性齐全度(窗口、递归、布尔、CHECK、分页)+ 行为严格度(GROUP BY 分组列、别名作用域、NULL/空串)"两条线对比 PG 与 MySQL,再补方言扩展差异(RETURNING、LIMIT 更新),形成兼容性矩阵式回答。

#
★★

52. SQL/PSM(Persistent Stored Modules)与 SQL/CLI(Call Level Interface)在存储过程与客户端 API 标准化上的差异是什么?

SQL/PSM(Persistent Stored Modules)与 SQL/CLI(Call Level Interface)在存储过程与客户端 API 标准化上的差异是什么?

  • SQL/PSM 的存储过程语言标准
  • SQL/CLI 的客户端调用接口标准
  • 数据库实现与替代(PL/SQL、T-SQL、ODBC)

SQL/PSM(SQL:1999 的 Part 4)定义服务器端"持久化存储模块"的标准:存储过程/函数语言(变量、控制流、异常、游标)、CREATE PROCEDURE/FUNCTION 语法、权限与调用语义;SQL/CLI(SQL:1999 的 Part 3)定义客户端"调用级接口":客户端程序与数据库会话交互的 API(连接、语句、结果集、参数绑定、事务控制的标准函数集)。差异定位:PSM 是"服务器内如何写程序"的标准化,CLI 是"客户端如何调数据库"的标准化,一个面向过程语言,一个面向客户端编程接口。

实现现状:各库过程语言未完全遵循 PSM——Oracle 用 PL/SQL、SQL Server 用 T-SQL、PostgreSQL 用 PL/pgSQL(语法接近但非标准 PSM)、MySQL 用存储过程语法(8.0 前的部分支持),相互不可移植;CLI 的落地是 ODBC(Windows 生态通用)与 JDBC(Java 标准,借鉴 CLI 思想),主流驱动都实现了 CLI 风格 API(参数绑定、预处理语句、结果集游标),兼容性比 PSM 好。理解 PSM 与 CLI 的区分有助于把握"过程语言不可移植、客户端接口相对标准化"的生态现状,以及跨库存储过程重写时的成本。

答题先分别定义 PSM(服务器端过程语言标准)与 CLI(客户端调用接口标准)及其部件,再列各库实现(PL/SQL、T-SQL、PL/pgSQL 与 ODBC/JDBC),最后给出"过程语言碎片化、CLI 生态较统一"的结论。

#
★★

53. 三大主流数据库方言(PostgreSQL、MySQL、SQL Server、Oracle)在分页、字符串函数、日期函数、自增字段、布尔类型上的差异矩阵是什么?

PostgreSQL、MySQL、SQL Server、Oracle 在分页、字符串函数、日期函数、自增字段、布尔类型上的差异矩阵是什么?

  • 五类特性的方言矩阵
  • 等价函数映射
  • 跨库迁移要点

差异矩阵:分页——PostgreSQL/MySQL 用 LIMIT [OFFSET],SQL Server 用 OFFSET ... FETCH NEXT 或 TOP,Oracle 12c+ 用 OFFSET ... FETCH(老版本 ROWNUM);字符串函数——拼接 PG 用 || 或 CONCAT、MySQL 用 CONCAT(|| 默认不可用作拼接,需 PIPES_AS_CONCAT)、SQL Server 用 +(CONCAT 8+ 支持)、Oracle 用 ||;长度 PG length()/MySQL LENGTH(字节)/CHAR_LENGTH、SQL Server LEN(去尾空格)、Oracle LENGTH;日期函数——当前时间 PG NOW()/CURRENT_TIMESTAMP、MySQL NOW()、SQL Server GETDATE()、Oracle SYSDATE;日期加减 PG CURRENT_DATE + INTERVAL '1 day'、MySQL DATE_ADD/now()+INTERVAL 1 DAY、SQL Server DATEADD、Oracle SYSDATE + 1(天数为单位);自增——PG SERIAL/GENERATED AS IDENTITY(序列)、MySQL AUTO_INCREMENT、SQL Server IDENTITY(1,1)、Oracle 12c+ GENERATED AS IDENTITY(老版序列+触发器);布尔——PG 原生 BOOLEAN、MySQL TINYINT(1)、SQL Server BIT、Oracle NUMBER(1)(或 'Y'/'N' CHAR)。

迁移要点:这些差异是"方言翻译清单"的核心,跨库工具(如迁移工具、SQL 翻译器)按矩阵映射;函数语义细节(LEN 去尾空格、Oracle 日期默认天数单位、空串=NULL)比语法更难迁移;建议在 ORM/中间层统一抽象(如用标准函数 NOW()、CURRENT_TIMESTAMP、FETCH FIRST 等标准子集)降低迁移成本。

答题按五类特性逐一给出四库写法(矩阵式列出),再总结迁移要点(语义细节陷阱、标准子集抽象),展示对主流方言差异的系统掌握。

-- 分页等价
PG/MySQL:   SELECT ... LIMIT 10 OFFSET 20;
SQL Server: SELECT ... OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
Oracle:     SELECT ... OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- 自增等价
PG:     id SERIAL PRIMARY KEY;
MySQL:  id INT AUTO_INCREMENT PRIMARY KEY;
SQLServer: id INT IDENTITY(1,1) PRIMARY KEY;
Oracle: id NUMBER GENERATED AS IDENTITY PRIMARY KEY;
#
★★

54. 布尔类型(BOOLEAN)在 PostgreSQL 原生支持、SQL Server 用 BIT、MySQL 用 TINYINT(1)、Oracle 用 NUMBER(1) 的差异如何影响跨方言迁移?

布尔类型在 PostgreSQL(原生 BOOLEAN)、SQL Server(BIT)、MySQL(TINYINT(1))、Oracle(NUMBER(1))中的差异如何影响跨方言迁移?

  • 四库布尔类型的表示差异
  • 三值语义与存储差异
  • 迁移映射与查询改写

表示差异:PostgreSQL 原生 BOOLEAN(TRUE/FALSE/NULL 三值,内部 1 字节,支持布尔谓词直接比较、索引);SQL Server BIT(0/1/NULL,无 TRUE/FALSE 字面量,需 1=1 或 CAST);MySQL 无原生布尔,TINYINT(1) 模拟(TRUE/FALSE 是 1/0 的别名,存储仍是整数,且 TINYINT(1) 可存 -128..127 超范围值);Oracle 无布尔(SQL 层),NUMBER(1) 存 0/1 或 CHAR 'Y'/'N' 惯例,PL/SQL 中才有 BOOLEAN 类型。语义差异:PG 的布尔可与比较表达式混用(WHERE flag 即 WHERE flag = TRUE);MySQL 的 TINYINT 需显式 =1 判断;SQL Server 的 BIT 在 WHERE 直接使用。

迁移影响:其一,DDL 映射——BOOLEAN → BIT(SQL Server)/TINYINT(1)(MySQL)/NUMBER(1)(Oracle),迁移工具需转换类型并注意默认值(TRUE→1);其二,查询改写——WHERE is_active 在 PG 合法,迁到 MySQL/SQL Server 需改 WHERE is_active = 1 或 = 1;三值 NULL 语义——PG 布尔可空(NULL 表示未知),迁移到 MySQL TINYINT 后 NULL 判断用 IS NULL 相同但应用层取值类型变化(布尔↔整数);其三,驱动层——JDBC getBoolean 对 BIT/TINYINT 兼容但 NUMBER(1) 可能读为整数,ORM 布尔字段映射需显式配置;其四,索引与统计——PG 布尔列可用部分索引(WHERE is_active),MySQL 上等价于整数列索引。工程建议:ORM 层统一布尔语义(应用侧转换),DDL 用 GENERATED/CHECK 约束补强语义(如 TINYINT(1) CHECK (col IN (0,1)))。

答题先列四库表示(原生/模拟/惯例)与三值语义差异,再分 DDL 映射、查询改写(=1)、NULL 与驱动层四类迁移影响,最后给 ORM 统一与 CHECK 补强建议。

#
★★

55. 窗口函数(Window Functions)在 SQL:2003 中标准化,PostgreSQL 与 MySQL 8.0 的实现差异(命名、语法、聚合函数支持)是什么?

窗口函数(Window Functions)在 SQL:2003 标准化,PostgreSQL 与 MySQL 8.0 的实现差异(命名、语法、聚合函数支持)有哪些?

  • 窗口函数的标准语法(OVER 子句)
  • PG 与 MySQL 8.0 的实现差异
  • 专用窗口函数与聚合窗口化的支持度

标准语法:SELECT 聚合/窗口函数 OVER (PARTITION BY ... ORDER BY ... 框架子句) FROM ...,框架(ROWS/RANGE BETWEEN)控制窗口范围。实现差异:PostgreSQL 支持完整标准——全部聚合函数可作窗口函数、全部专用窗口函数(ROW_NUMBER、RANK、DENSE_RANK、NTILE、LAG/LEAD、FIRST_VALUE/LAST_VALUE/NTH_VALUE、PERCENT_RANK、CUME_DIST)、FILTER 子句(聚合窗口化加过滤)、GROUPS 框架模式、窗口命名(WINDOW 子句复用);MySQL 8.0 支持核心子集——常用窗口函数齐全(8.0 加入 ROW_NUMBER/RANK/DENSE_RANK/NTILE/LAG/LEAD/FIRST_VALUE/LAST_VALUE/NTH_VALUE、PERCENT_RANK/CUME_DIST 在 8.0.1+),聚合函数可作窗口函数,但差异:不支持 FILTER 子句、不支持 GROUPS 框架模式(只支持 ROWS 与部分 RANGE)、窗口定义不能引用其他窗口(WINDOW 子句缺失,MySQL 只能重复写 OVER 表达式)、RANGE 框架仅支持数字/日期默认范围、部分函数对框架的忽略规则细节不同。

性能与语义差异:PG 的窗口执行器支持并行与磁盘溢出,MySQL 8.0 窗口函数用临时表实现、大数据量下内存/磁盘压力更大;默认框架语义两库一致(RANGE UNBOUNDED PRECEDING AND CURRENT ROW)。迁移要点:把 WINDOW 子句复用展开为重复 OVER、FILTER 改写为 CASE WHEN 内聚合(COUNT(*) FILTER (WHERE x) → SUM(CASE WHEN x THEN 1 END))、GROUPS 框架改写为 ROWS 加连接条件。

答题先给窗口函数标准语法骨架,再按"专用函数、FILTER、WINDOW 子句、框架模式"四个维度对比 PG(完整)与 MySQL 8.0(核心子集),最后讲性能差异与迁移改写技巧。

-- PostgreSQL:WINDOW 复用 + FILTER
SELECT dept, emp_name, salary,
       SUM(salary) FILTER (WHERE salary > 5000) OVER w AS sum_high
FROM emp WINDOW w AS (PARTITION BY dept);
-- MySQL 8.0 等价改写
SELECT dept, emp_name, salary,
       SUM(CASE WHEN salary > 5000 THEN salary END) OVER (PARTITION BY dept) AS sum_high
FROM emp;
#
★★

56. MySQL 的 LIMIT、PostgreSQL 的 LIMIT/OFFSET、SQL Server 的 TOP、Oracle 的 ROWNUM 与 FETCH FIRST 四种分页方言的等价写法?

MySQL 的 LIMIT、PostgreSQL 的 LIMIT/OFFSET、SQL Server 的 TOP、Oracle 的 ROWNUM 与 FETCH FIRST 分页方言的等价写法是什么?

  • 四库分页语法
  • 等价改写(TOP、ROWNUM、FETCH)
  • Oracle 老版本 ROWNUM 技巧

等价写法(取第 21-30 行,按 id 排序):MySQL/PostgreSQL:SELECT ... ORDER BY id LIMIT 10 OFFSET 20;SQL Server:SELECT ... ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY(2005 前用 TOP 加子查询反转技巧);Oracle 12c+:SELECT ... ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;Oracle 老版本(11g 及以前)用 ROWNUM 三层嵌套:SELECT * FROM (SELECT a.*, ROWNUM rn FROM (SELECT * FROM t ORDER BY id) a WHERE ROWNUM <= 30) WHERE rn > 20——ROWNUM 在排序前赋值,必须最内层先排序、中层限制上限、外层过滤下限。

注意点:其一,SQL Server 的 TOP 本身不能直接实现 OFFSET(只取前 N),分页需 OFFSET/FETCH 或 2012 前的嵌套技巧;其二,Oracle 的 ROWNUM 陷阱——WHERE ROWNUM > 20 恒为空(ROWNUM 从 1 开始且过滤发生在赋值前),必须用三层嵌套写法;其三,FETCH FIRST n ROWS ONLY(标准)用于取前 N 行(SQL Server 可省略 OFFSET 0);其四,等价性要求 ORDER BY 稳定(加唯一列决胜),否则各库分页行集可能不同;其五,深分页性能问题各库相同(键集分页方案适用)。

答题按库给出"取第 21-30 行"的等价写法,重点展开 Oracle 老版本 ROWNUM 三层嵌套的原理与陷阱(ROWNUM 赋值时机),最后提醒 ORDER BY 稳定性与深分页优化,构成完整的分页方言知识。

-- MySQL / PostgreSQL
SELECT * FROM t ORDER BY id LIMIT 10 OFFSET 20;
-- SQL Server / Oracle 12c+
SELECT * FROM t ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Oracle 11g 及以前(ROWNUM 三层嵌套)
SELECT * FROM (
  SELECT a.*, ROWNUM rn FROM (
    SELECT * FROM t ORDER BY id
  ) a WHERE ROWNUM <= 30
) WHERE rn > 20;
#
★★

57. PostgreSQL 的 :: 类型转换、MySQL 的 CONVERT/CAST、Oracle 的 TO_CHAR/TO_DATE 函数差异如何统一为标准 CAST AS?

PostgreSQL 的 :: 类型转换、MySQL 的 CONVERT/CAST、Oracle 的 TO_CHAR/TO_DATE 在类型转换上的差异如何统一为标准 CAST AS?

  • 各库类型转换语法
  • 字符串/日期/数值转换的差异
  • 标准 CAST 的统一用法

语法差异:PostgreSQL 支持 :: 快捷转换(expr::type)、CAST(expr AS type) 标准写法、以及类型同名函数(int '42'、text 转换函数);MySQL 支持 CAST(expr AS type) 与 CONVERT(expr, type)(CONVERT 还可带 USING 字符集)、字符串转数字隐式转换;Oracle 的 CAST 存在但受限(不能转所有类型),字符串↔日期/数字主要用 TO_CHAR、TO_DATE、TO_NUMBER 函数(格式模型 TO_DATE('2024-01-01','YYYY-MM-DD')、TO_CHAR(dt,'YYYY-MM-DD'))。差异根源:Oracle 强制显式格式模型,PG/MySQL 默认 ISO 格式与隐式转换。

统一为标准 CAST:跨库代码优先用标准 CAST(expr AS type)——PG/MySQL/SQL Server 均完整支持;Oracle 的 CAST 可做基本转换(数字↔字符串、日期字符串转 DATE 用 CAST('2024-01-01' AS DATE) 依赖 NLS 格式,跨库需固定格式);日期格式化输出用标准函数替代:PG 的 to_char、MySQL 的 DATE_FORMAT、SQL Server 的 FORMAT、Oracle 的 TO_CHAR 是各自的"格式化输出函数",无标准统一体,需封装或按库分支;字符串转日期建议统一传 ISO 格式字符串('YYYY-MM-DD')再 CAST,避免格式模型依赖。工程建议:在 DAO/查询层封装"转换适配层",内部按方言选择实现,对外暴露标准语义。

答题先列四库语法(::、CONVERT、TO_CHAR/TO_DATE、CAST),说明 Oracle 格式模型与隐式转换的差异根源,再给"标准 CAST + ISO 格式字符串"的统一策略与格式化输出的封装建议。

-- 统一标准写法(PG/MySQL/SQL Server)
SELECT CAST(amount AS DECIMAL(10,2)), CAST('2024-01-01' AS DATE);
-- PostgreSQL 快捷写法
SELECT amount::DECIMAL(10,2), '2024-01-01'::DATE;
-- Oracle 格式模型(日期字符串依赖 NLS,跨库需固定)
SELECT TO_DATE('2024-01-01','YYYY-MM-DD'), TO_CHAR(hiredate,'YYYY-MM-DD') FROM emp;
#
★★

58. 方言检测方法,通过 INFORMATION_SCHEMA、版本函数(VERSION()、@@version)识别数据库类型的策略?

如何通过 INFORMATION_SCHEMA、版本函数(VERSION()、@@version)等识别当前数据库类型?有哪些策略?

  • 版本函数与变量(VERSION、@@version)
  • INFORMATION_SCHEMA 的特性探测
  • 能力探测(capability probing)

识别策略有三层:其一,版本/标识函数——PostgreSQL 用 SELECT version()(返回 "PostgreSQL ...")与 SHOW server_version;MySQL 用 SELECT VERSION()(返回 "8.0.x")与 SELECT @@version;SQL Server 用 SELECT @@VERSION(含 "Microsoft SQL Server");Oracle 用 SELECT * FROM v$version 或 BANNER。解析返回字符串即可判定厂商与主版本。其二,元数据差异探测——各库元数据特性不同:information_schema.tables 的字段差异(MySQL 有 TABLE_ROWS、ENGINE,PG 无)、查询 information_schema.schemata 的差异、PG 特有的 pg_catalog 表(pg_class)、MySQL 的 SHOW 命令可用性;写"探测查询"并捕获报错来区分。其三,能力探测(capability probing)——尝试执行代表性语句看是否报错:如 SELECT 1::text(PG 语法)、SELECT @@version(MySQL)、LIMIT 语法支持、CTE/WINDOW 函数可用性等,通过"成功/报错"判定能力集合(可探测版本演进,如 MySQL 8.0 是否支持递归 CTE)。

策略要点:探测结果缓存(连接建立时一次探测)、避免依赖易变细节(优先用官方版本函数,其次元数据,最后能力探测)、区分"厂商"与"版本"两层(迁移代码往往需要版本分支,如 MySQL 8.0.31 才有 INTERSECT)。工具化:DBA 脚本、迁移工具、ORM 方言选择器都采用"版本函数 + 能力探测"组合。

答题按三层策略展开:版本函数(VERSION()/@@version/v$version)、元数据差异(information_schema 与 pg_catalog/SHOW)、能力探测(试执行特性语句判报错),最后讲缓存与厂商/版本两层分支的工程要点。

-- PostgreSQL / MySQL / SQL Server / Oracle
SELECT version();        -- PG
SELECT VERSION(), @@version;  -- MySQL
SELECT @@VERSION;        -- SQL Server
SELECT * FROM v$version; -- Oracle
-- 能力探测示例(SQL 注释法或异常捕获)
SELECT 1::text;   -- PG 成功,MySQL 报语法错误
#
★★

59. 日期/时间函数 NOW()、CURRENT_TIMESTAMP、GETDATE()、SYSDATE 在四家数据库中的差异如何?

日期/时间函数 NOW()、CURRENT_TIMESTAMP、GETDATE()、SYSDATE 在 PostgreSQL、MySQL、SQL Server、Oracle 中的差异如何?

  • 各库"当前时间"函数
  • 标准 CURRENT_TIMESTAMP 与 CURRENT_DATE
  • 时区与精度差异

四库的"当前时间"获取:PostgreSQL 支持 NOW()(等价 CURRENT_TIMESTAMP,事务开始时间,非语句时间,精度微秒)、CURRENT_TIMESTAMP/CURRENT_DATE/CURRENT_TIME(标准)、clock_timestamp()(语句执行时刻);MySQL 支持 NOW()(语句开始时间)、CURRENT_TIMESTAMP(标准别名)、SYSDATE()(执行时刻,与 NOW 差异在长语句)、CURDATE();SQL Server 用 GETDATE()(本机时区)、GETUTCDATE()、SYSDATETIME()(更高精度,SQL Server 2008+);Oracle 用 SYSDATE(服务器时区,秒精度)、SYSTIMESTAMP(含时区与小数秒)、CURRENT_TIMESTAMP(会话时区,标准语义)。标准跨库可用 CURRENT_TIMESTAMP(四库都支持,语义为"当前事务/语句时间")。

关键差异:其一,语义——PostgreSQL 的 CURRENT_TIMESTAMP/NOW 是事务开始时间(同事务内不变),MySQL 的 NOW 是语句时间(同事务多语句会变)、SYSDATE 是函数执行时刻;其二,精度——Oracle SYSDATE 秒级、PG/MySQL 微秒级、SQL Server GETDATE 毫秒级(SYSDATETIME 100ns);其三,时区——SQL Server GETDATE 无时区信息(DATETIMEOFFSET 才有),Oracle CURRENT_TIMESTAMP 与会话时区绑定,PG timestamptz 存 UTC 转换显示;其四,历史兼容——Oracle 老代码习惯 SYSDATE + 1(天单位),PG 需 INTERVAL。迁移要点:统一用标准 CURRENT_TIMESTAMP 表达"当前时间",涉及时区用 TIMESTAMPTZ/DATETIMEOFFSET,精度敏感场景确认目标库精度并显式 CAST。

答题先列四库函数清单与标准 CURRENT_TIMESTAMP 的可用性,再对比三个关键差异(事务/语句时间语义、精度、时区绑定),最后给"统一 CURRENT_TIMESTAMP + 时区类型"的迁移建议。

#
★★

60. MySQL 与 PostgreSQL 在字符串拼接运算符上的差异是什么?

MySQL 与 PostgreSQL 在字符串拼接运算符上的差异是什么?如何写出可移植的拼接代码?

  • || 运算符在两库的语义差异
  • CONCAT 函数与 NULL 处理
  • 可移植写法

差异:PostgreSQL 用 || 做字符串拼接(标准行为),NULL 参与时结果 NULL('' || NULL 为 NULL);MySQL 默认把 || 当作"逻辑或"(与 SQL 标准不同,为兼容 C 风格),字符串拼接要用 CONCAT() 函数或开启 SQL_MODE=PIPES_AS_CONCAT 才让 || 变拼接;且 MySQL 的 CONCAT(NULL, 'a') 返回 NULL、CONCAT('a', NULL) 为 NULL,而 CONCAT_WS 会跳过 NULL(CONCAT_WS(',', 'a', NULL) = 'a')。PostgreSQL 的 CONCAT 函数(9.1+)与 MySQL 不同:CONCAT(NULL,'a') 中 PostgreSQL 的 concat() 会把 NULL 当空串处理(结果 'a'),而 || 仍传播 NULL——两个"拼接"语义并存。

可移植写法:其一,统一用 CONCAT() 函数(两库都有),但注意 NULL 语义差异(PG concat 忽略 NULL、MySQL CONCAT 传播 NULL)——需要"NULL 当空串"时 MySQL 用 CONCAT_WS 或 IFNULL、PG 用 concat();需要"NULL 传播"时 MySQL 用 CONCAT 且两库都避免 NULL 或先用 COALESCE;其二,避免 ||(MySQL 默认语义不同),或统一开启 PIPES_AS_CONCAT(不推荐,影响其他代码);其三,拼接多值建议用 CONCAT_WS(PG 9.1+、MySQL 都支持,统一分隔符);其四,纯字符串字面量拼接用相邻字面量或标准 concat。工程上 ORM/框架层统一封装拼接函数,应用层显式处理 NULL 语义。

答题先讲 || 在两库的语义差异(PG 拼接/MySQL 逻辑或)与 CONCAT 的 NULL 语义差异(PG concat 忽略 NULL、MySQL CONCAT 传播 NULL),再给 CONCAT/CONCAT_WS 统一写法与 NULL 处理规范。

-- PostgreSQL
SELECT 'a' || 'b', concat('a', NULL);        -- 'ab'、'a'
-- MySQL(默认 || 是逻辑或,用 CONCAT)
SELECT CONCAT('a', 'b');                      -- 'ab'
-- 可移植:CONCAT_WS 统一分隔符
SELECT CONCAT_WS('-', first_name, last_name);
#
★★

61. Oracle 中如何实现 PostgreSQL 的 LIMIT/OFFSET 分页?

Oracle 中如何实现 PostgreSQL 的 LIMIT/OFFSET 分页?各版本写法与注意点是什么?

  • Oracle 12c+ 的 OFFSET/FETCH 标准语法
  • 老版本 ROWNUM 嵌套实现
  • ROWNUM 的赋值时机陷阱

Oracle 12c+ 直接支持标准分页:SELECT * FROM t ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY,等价于 PostgreSQL 的 LIMIT 10 OFFSET 20(注意 Oracle 中 OFFSET/FETCH 先写,与 PG 的 LIMIT 顺序不同)。Oracle 11g 及以前没有 OFFSET/FETCH,必须用 ROWNUM 实现:三层嵌套——最内层先排序,中层用 ROWNUM <= 上限截断(取第 1 到 30 行并赋 rn 伪列),外层 WHERE rn > 20 过滤下限:SELECT * FROM (SELECT a.*, ROWNUM rn FROM (SELECT * FROM t ORDER BY id) a WHERE ROWNUM <= 30) WHERE rn > 20。

注意点:其一,ROWNUM 在"行被取出并赋值"时递增,WHERE ROWNUM > 20 恒为空(第一行 rn=1 就被过滤,永远无法推进)——必须先把 ROWNUM 物化为列(中间层)再过滤;其二,ROWNUM 在 ORDER BY 之前赋值,所以最内层必须先排序,否则分页顺序错误;其三,中层 ROWNUM <= 30 与排序在同一子查询内时,Oracle 会先排序再限行(子查询内 ORDER BY 后 ROWNUM 才稳定);其四,12c+ 的 OFFSET/FETCH 更清晰且可配 PERCENT/TIES,老代码迁移后注意 FETCH 的 NULLS 与稳定性差异。

答题先给 12c+ 标准写法,再重点展开 11g 的 ROWNUM 三层嵌套(排序层、限上层、过滤层)与两个陷阱(ROWNUM > n 恒空、ROWNUM 先于 ORDER BY 赋值),最后给迁移注意点。

-- Oracle 12c+(等价 LIMIT 10 OFFSET 20)
SELECT * FROM t ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Oracle 11g 及以前:ROWNUM 三层嵌套
SELECT * FROM (
  SELECT a.*, ROWNUM rn FROM (
    SELECT * FROM t ORDER BY id
  ) a WHERE ROWNUM <= 30
) WHERE rn > 20;
#
★★

62. PostgreSQL 中的 RETURNING 子句源自哪个 SQL 标准扩展?

PostgreSQL 中的 RETURNING 子句源自哪个 SQL 标准扩展?其语义与用法是什么?

  • RETURNING 的标准化来源(SQL:1999 的 OLE/扩展)
  • INSERT/UPDATE/DELETE 的返回语义
  • 与老式"先查后改"的对比

RETURNING 子句的语义是"DML 返回被影响行的值",它是 PostgreSQL 的扩展特性(PostgreSQL 文档在兼容性一节明确标注 "the RETURNING clause is a PostgreSQL extension",SQL 标准核心部分并未包含 RETURNING;对标 SQL Server 的 OUTPUT 子句、Oracle 的 RETURNING INTO),各库以扩展形式实现。PostgreSQL 的 RETURNING 用于 INSERT/UPDATE/DELETE:返回被修改行的列值(可含表达式),INSERT ... RETURNING id 直接拿到新生成的主键、UPDATE ... RETURNING * 返回更新后的行、DELETE ... RETURNING 返回被删行,配合 CTE 可做"修改后继续查询"的管道(WITH x AS (UPDATE ... RETURNING *) SELECT ... FROM x)。

对比其他实现:SQL Server 用 OUTPUT 子句(可输出到表变量)、Oracle 用 RETURNING INTO(PL/SQL 变量,SQL 层不可直接看行)、MySQL 8.0.19+ 才有有限的 RETURNING(仅用于 DELETE 的 ROW_COUNT 场景,INSERT/UPDATE 不支持标准 RETURNING;MariaDB 支持 INSERT ... RETURNING)。价值:避免"插入后按主键再查一次"的额外往返(原子拿到生成键与默认值)、支持原子"先删后查"的归档场景、与 CTE 组合实现复杂数据管道。

答题先回答来源(PostgreSQL 扩展特性,对标 SQL Server OUTPUT 与 Oracle RETURNING INTO),再讲三种 DML 的 RETURNING 语义与 CTE 管道用法,最后列各库对照与 MySQL 8.0 的支持缺口。

-- 插入并返回生成的主键与默认值
INSERT INTO orders (user_id, amount) VALUES (1, 99.5) RETURNING id, created_at;
-- 更新后返回受影响行
UPDATE orders SET status = 'PAID' WHERE id = 100 RETURNING *;
-- CTE 管道:删除并归档
WITH removed AS (DELETE FROM orders WHERE status = 'CANCELLED' RETURNING *)
INSERT INTO archive SELECT * FROM removed;
#
★★

63. SQL Server 的 TOP n 与 PostgreSQL 的 LIMIT n 语法差异如何?

SQL Server 的 TOP n 与 PostgreSQL 的 LIMIT n 语法差异是什么?等价写法如何?

  • TOP n 与 LIMIT n 的语法位置
  • 带排序的等价写法
  • TOP 的扩展(PERCENT、WITH TIES)

语法位置差异:PostgreSQL 的 LIMIT n 写在查询末尾(SELECT ... ORDER BY ... LIMIT n),SQL Server 的 TOP n 写在 SELECT 关键字后(SELECT TOP n ... ORDER BY ...),且 TOP n 可以用括号(TOP (n))与变量。无排序时两者都取"任意 n 行"(顺序不定);有排序时都先排序再取前 n:PostgreSQL SELECT * FROM t ORDER BY id LIMIT 10;SQL Server SELECT TOP 10 * FROM t ORDER BY id。取前 n 行(不带 OFFSET)时二者等价;带 OFFSET 时 SQL Server 用 OFFSET ... FETCH(2012+)而非 TOP。

TOP 的扩展能力:TOP n PERCENT 按百分比取行(PG 需手动计算 COUNT 后 LIMIT);TOP n WITH TIES 返回与第 n 行并列(同 ORDER BY 键)的行(PG 用 RANK/窗口函数模拟或 FETCH FIRST WITH TIES——标准语法,PG 13+ 支持 FETCH FIRST n ROWS WITH TIES);TOP (n) 支持变量与表达式(PG 的 LIMIT 也接受参数但语句内更受限)。性能与语义:两者都允许优化器提前终止(n 行后停止排序或使用 Top-N 堆排序),执行计划分别体现 Limit/Top 节点;分页场景 TOP 需要配合 OFFSET/FETCH,与 LIMIT/OFFSET 对应。

答题先对比语法位置(SELECT 后 vs 查询末尾)与取前 n 的等价,再讲 OFFSET 分页对应(OFFSET/FETCH)、TOP 的 PERCENT/WITH TIES 扩展及 PG 对应写法,最后提 Top-N 优化执行。

-- 等价:取前 10
PostgreSQL: SELECT * FROM t ORDER BY id LIMIT 10;
SQL Server: SELECT TOP 10 * FROM t ORDER BY id;
-- 分页等价
SQL Server: SELECT * FROM t ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
PostgreSQL: SELECT * FROM t ORDER BY id LIMIT 10 OFFSET 20;
-- WITH TIES 等价(PG 13+)
PostgreSQL: SELECT * FROM t ORDER BY score DESC FETCH FIRST 3 ROWS WITH TIES;
#
★★

64. SQL 方言迁移(MySQL → PostgreSQL)的常见语法替换清单有哪些?

MySQL 迁移到 PostgreSQL 的常见语法替换清单有哪些?请给出典型映射?

  • 自增、字符串、日期、布尔等映射
  • 引号与大小写规则
  • 语义差异提醒

常见替换清单:自增——MySQL AUTO_INCREMENT → PostgreSQL SERIAL 或 GENERATED AS IDENTITY(推荐 IDENTITY);主键/唯一——语法兼容但 PG 约束名规则不同;字符串拼接——MySQL CONCAT 保留(语义差异:PG concat 忽略 NULL),|| 在 MySQL 默认逻辑或需改 CONCAT;字符串函数——MySQL IFNULL → PG COALESCE、MySQL SUBSTRING_INDEX 无直接等价(用 split_part 等)、GROUP_CONCAT → PG 的 string_agg;日期——NOW() 兼容、MySQL DATE_FORMAT → PG to_char、DATE_ADD/INTERVAL 语法微调(PG INTERVAL '1 day');布尔——MySQL TINYINT(1) → PG BOOLEAN(查询改写 =1 → IS TRUE);分页 LIMIT 兼容;UPDATE ... LIMIT 不支持(PG 无)、REPLACE INTO → ON CONFLICT 或 DELETE+INSERT;INSERT IGNORE → ON CONFLICT DO NOTHING;DUPLICATE KEY UPDATE → ON CONFLICT DO UPDATE;SHOW CREATE TABLE → pg_dump/信息函数;反引号 ` → 双引号 ";表名大小写——MySQL 平台相关(lower_case_table_names)、PG 一律折叠小写。

语义差异提醒:空串——MySQL '' 是空串、PG '' 也是空串(可区分 NULL),Oracle 才等同;GROUP BY 宽松(MySQL 允许非分组列)→ PG 严格报错,需补全分组列或聚合;ORDER BY 别名可用但 GROUP BY 别名 PG 拒绝;LIMIT 无 OFFSET 顺序;外键 SET DEFAULT MySQL 不支持(PG 支持);事务 DDL(PG 支持事务内 DDL,MySQL 隐式提交)。迁移工具(如 pgloader、pg_chameleon)可自动处理大部分 DDL 与语法,但语义类差异(GROUP BY 严格性、NULL/空串、时区)需人工评审。

答题按"DDL/数据类型、函数、语法构造、行为差异"四类给出映射清单(AUTO_INCREMENT→IDENTITY、IFNULL→COALESCE、GROUP_CONCAT→string_agg、ON DUPLICATE→ON CONFLICT、反引号→双引号等),再重点提醒语义差异(GROUP BY 严格性、空串、事务 DDL),并建议迁移工具 + 人工评审结合。

-- MySQL → PostgreSQL 典型映射
AUTO_INCREMENT            → GENERATED AS IDENTITY
IFNULL(a, b)              → COALESCE(a, b)
GROUP_CONCAT(x)           → string_agg(x, ',')
REPLACE INTO t ...        → INSERT ... ON CONFLICT (...) DO UPDATE ...
INSERT IGNORE             → INSERT ... ON CONFLICT (...) DO NOTHING
`col` 反引号              → "col" 双引号(且标识符折叠小写)
DATE_FORMAT(d,'%Y-%m-%d') → to_char(d, 'YYYY-MM-DD')
#
★★

65. 如何在 MySQL 中实现 PostgreSQL 的 ILIKE(大小写不敏感匹配)?

如何在 MySQL 中实现 PostgreSQL 的 ILIKE(大小写不敏感匹配)?各方案的优缺点是什么?

  • ILIKE 的语义
  • MySQL 的 LOWER 改写与排序规则方案
  • 索引支持差异

PostgreSQL 的 ILIKE 做大小写不敏感的 LIKE 匹配(WHERE name ILIKE '%abc%');MySQL 无 ILIKE,实现方案:其一,LOWER 改写——WHERE LOWER(name) LIKE '%abc%'(或 LCASE),语义等价但函数包裹列导致无法走普通索引(需表达式索引,MySQL 8.0 支持函数索引/生成列索引:CREATE INDEX ... ON t ((LOWER(name))));其二,排序规则(Collation)方案——把列的排序规则设为大小写不敏感(如 utf8mb4_general_ci、utf8mb4_0900_ai_ci),LIKE 本身即大小写不敏感(比较按排序规则),无需改写且能利用列索引;若全库/全列统一 ci 排序规则,普通 LIKE 天然满足 ILIKE 语义;其三,正则——REGEXP_LIKE(name, 'abc', 'i')(8.0+,性能差于 LIKE)。

方案取舍:单条查询偶尔用 LOWER 改写(注意无索引);全局大小写不敏感需求优先选"列排序规则 ci"(索引友好、语义统一),代价是该列所有比较(含=、JOIN)都大小写不敏感,混合场景需小心;表达式索引适合"个别列个别场景"。注意:MySQL 的 LIKE 对大小写敏感性完全由排序规则决定(ci 不敏感、cs/bin 敏感),这是与 PostgreSQL(按 collation 由 locale 决定,ILIKE 强制不敏感)不同的机制——理解"排序规则驱动比较"是 MySQL 大小写问题的钥匙。

答题先定义 ILIKE 语义,再给三种 MySQL 实现(LOWER 改写+表达式索引、ci 排序规则、正则),对比索引友好性与全局影响,最后点出"MySQL 比较由排序规则驱动"的机制根源。

-- 方案一:LOWER 改写 + 表达式索引(8.0)
SELECT * FROM users WHERE LOWER(name) LIKE '%abc%';
CREATE INDEX idx_users_lower_name ON users ((LOWER(name)));
-- 方案二:大小写不敏感排序规则(比较层生效)
ALTER TABLE users MODIFY name VARCHAR(100) COLLATE utf8mb4_0900_ai_ci;
SELECT * FROM users WHERE name LIKE '%abc%';  -- ci 下天然不敏感
#
★★

66. 保留关键字(Reserved Words)与非保留关键字的差异,使用保留关键字作为标识符时的引号转义方法(""、``、[])在三种方言中如何?

保留关键字(Reserved Words)与非保留关键字有何差异?使用保留关键字作标识符时,双引号、反引号、方括号转义在三种方言中如何?

  • 保留/非保留关键字的定义
  • 三库标识符引号规则
  • 转义后的可移植性

保留关键字(Reserved)不能直接作标识符(表名/列名),需转义;非保留关键字可作标识符但语义可能歧义(如函数名)。转义规则:SQL 标准用双引号 "..."(引号内保留大小写,区分大小写标识符)——PostgreSQL 遵循标准("Order" 表示大写的 Order 标识符,未加引号的标识符折叠小写);MySQL 默认用反引号 ...(也可开 ANSI_QUOTES 模式用双引号,此时双引号不再表示字符串);SQL Server 用方括号 [...](也支持 ANSI 双引号,QUOTED_IDENTIFIER ON)。三库共同点:转义后标识符区分大小写(MySQL 表名还受 lower_case_table_names 影响)。

实践要点:其一,尽量避开保留关键字命名(列名 user、order、group、select 等),用带后缀或下划线命名(user_name、order_no),从源头消除转义与迁移差异;其二,迁移时引号风格必须转换(MySQL col → PG "col" → SQL Server [col]),且转义标识符的大小写敏感会在 PG("Order" 与 order 是两个对象)与 MySQL(Windows 表名大小写不敏感)产生差异;其三,标准兼容代码用双引号 + ANSI_QUOTES/QUOTED_IDENTIFIER 统一;其四,ORM/框架通常自动加引号(Hibernate 的 backtick 转义语法),底层生成各库引号。判断关键字列表:各库文档有保留关键字清单(MySQL 8.0 的 reserved words、PG 的 keywords 表),命名前查表。

答题先定义保留/非保留关键字差异,再给三库转义规则(PG/标准双引号、MySQL 反引号、SQL Server 方括号)与大小写语义,最后给"避免保留字命名 + 引号转换 + 标准双引号"的工程建议。

-- 三种方言的转义
PostgreSQL: SELECT "order", "select" FROM "table";   -- 双引号
MySQL:      SELECT `order`, `select` FROM `table`;    -- 反引号
SQL Server: SELECT [order], [select] FROM [table];    -- 方括号
-- 推荐:避免保留字,直接命名
SELECT order_no FROM orders;
#
★★

67. 命名空间(Schema、Database、Catalog)的三层结构在 PostgreSQL(Catalog/Schema/Table)、MySQL(Database/Table)、SQL Server(Database/Schema/Table)的差异?

命名空间的三层结构在 PostgreSQL(Catalog/Schema/Table)、MySQL(Database/Table)、SQL Server(Database/Schema/Table)中有何差异?

  • 标准 Catalog/Schema/对象模型
  • PG 与 SQL Server 的三层、MySQL 的两层
  • 对象解析与隔离语义

SQL 标准是 Catalog(目录)→ Schema(模式)→ 对象 三层。PostgreSQL 三层:实例(集群)含多个 Database(各库独立、隔离强、不能跨库 JOIN),Database 内含多个 Schema(public、业务 schema),对象全限定 database.schema.table,未限定名按 search_path 解析;MySQL 两层的简化:没有独立 Schema 层,CREATE DATABASE 与 CREATE SCHEMA 同义,对象限定 db.table,库之间隔离但同一实例内可跨库引用(db2.table);SQL Server 三层:实例 → Database → Schema(默认 dbo),对象限定 database.schema.table(跨库引用 database.schema.table,同实例可跨库),schema 是权限与对象分组的逻辑边界。

差异要点:其一,层数与隔离强度——PG 的 Database 隔离最彻底(不能跨库 JOIN、备份恢复按库),MySQL 的 Database 只是命名空间(可跨库查询)、SQL Server 的 Database 是物理隔离单元(日志/备份按库,跨库查询需同实例);其二,Schema 角色——PG/SQL Server 用 Schema 做应用分组与权限边界,MySQL 无此层(应用隔离靠 Database);其三,解析机制——PG 的 search_path(可动态切换默认 schema),MySQL 的 USE db(会话级默认库),SQL Server 的默认 schema(用户绑定);其四,迁移影响——MySQL 的 db.table 平铺结构迁到 PG/SQL Server 常映射为一个 Schema 或按业务拆 Schema。理解三层差异是设计多租户/多应用共享实例方案(每租户 Schema 隔离 vs 每租户 Database)的基础。

答题先给标准三层模型,再逐一对比 PG(实例→DB→Schema)、MySQL(DB 即 Schema 的两层)、SQL Server(实例→DB→Schema/dbo)的隔离强度与解析机制,最后讲多租户设计影响。

#
★★

68. 标识符(表名、列名、函数名)在 PostgreSQL、MySQL、SQL Server 中的最大长度限制与字符集支持差异是什么?

标识符(表名、列名、函数名)在 PostgreSQL、MySQL、SQL Server 中的最大长度限制与字符集支持有何差异?

  • 三库标识符长度上限
  • 字符集与多字节支持
  • 未加引号标识符的字符规则

长度限制:PostgreSQL 标识符最长 63 字节(NAMEDATALEN-1,注意是字节,中文名一个 UTF-8 字符 3 字节,约 21 个汉字),超长会被截断(静默截断到 63 字节,可能引起命名冲突);MySQL 标识符最长 64 字符(表名/列名/索引名),以字符计;SQL Server 标识符最长 128 字符(常规标识符),且不能以数字开头、不能含空格等特殊字符(转义后除外)。字符集支持:三库都支持 UTF-8 多字节标识符(中文表名列名),PostgreSQL 未加引号的标识符只能用字母、数字、下划线、$(不能以数字开头),非 ASCII 字符需加引号;MySQL 同样限制未加引号标识符字符集(8.0 支持更多 Unicode 字符,仍建议 ASCII),表名是否区分大小写受 lower_case_table_names 影响;SQL Server 常规标识符限 ASCII 字母数字下划线,Unicode 需方括号转义。

差异影响:其一,长度按"字节 vs 字符"计——PG 的中文名可容纳数少,命名策略(如外键约束名拼接表名列名)易超长被截断导致约束名不可读;其二,迁移时表名/约束名生成规则要按目标库长度重算(如 ORM 自动命名需截断策略);其三,字符集——建议跨库统一 ASCII 命名(拉丁字母+下划线),避免中文名在排序规则、转义、导出工具中的兼容问题;其四,超长截断的静默行为(PG 截断、SQL Server 报错)差异要在命名生成器里规避。

答题先给三库长度上限(PG 63 字节、MySQL 64 字符、SQL Server 128 字符)与"字节/字符"计量的差异,再讲字符集与未加引号规则,最后落到命名策略(ASCII、长度预算、截断行为)的工程建议。

#
★★

69. MySQL 的 lower_case_table_names 参数(0/1/2)对跨平台迁移的影响是什么?为何在 Linux 默认 0、Windows 默认 1?

MySQL 的 lower_case_table_names 参数(0/1/2)对跨平台迁移有何影响?为什么 Linux 默认 0、Windows 默认 1?

  • 三个取值的行为语义
  • 平台文件系统大小写敏感性差异
  • 参数不一致导致迁移问题

lower_case_table_names 控制表名(及数据库名)的大小写处理:0——表名按创建时大小写存储与比较(区分大小写,Linux 默认);1——表名一律转小写存储与比较(不区分,Windows/macOS 默认);2——表名按创建大小写存储但比较时转小写(macOS 特有折中)。Linux 默认 0 的原因:Linux 文件系统(ext4 等)本身区分大小写,表名与 .frm/.ibd 文件一一对应,保持原生语义;Windows 默认 1 的原因:Windows 文件系统(NTFS)不区分大小写,若按 0 存储,同一表名的不同大小写写法会映射到同一文件造成混乱,必须统一小写。注意:8.0 起该参数必须在初始化时确定(不可运行时改),且不同节点(主从)必须一致。

影响:其一,迁移时源库与目标库参数不一致导致表名找不到——从 Linux(0)导出的表 Users 迁到 Windows(1)后查询 users 可命中但反向不行,DBA 脚本、应用代码的大小写约定失效;其二,跨平台备份恢复、复制拓扑要求所有实例同参数;其三,MySQL 的列名/索引名不受此参数影响(列名永远不区分大小写,比较由排序规则决定),只影响库名与表名(Windows 下含库名);其四,编码规范:跨平台应用应约定"全小写表名"以规避平台差异(这也是多数团队的默认规范)。

答题先解释三个取值的语义与平台默认值(Linux 0、Windows 1)及其文件系统原因,再讲"初始化后不可改、主从需一致"与跨平台迁移的坑(表名找不到、备份恢复),最后给全小写命名规范。

#
★★

70. PostgreSQL 中标识符大小写折叠(Case Folding)规则,未加引号的标识符自动转为小写,加双引号保留原大小写。请举例说明查询时的陷阱。

PostgreSQL 的标识符大小写折叠规则是什么?未加引号的标识符自动转小写、加双引号保留大小写,请举例说明查询时的陷阱?

  • 折叠规则(未引号→小写、引号→保留)
  • 建表与查询的引号一致性
  • 大小写敏感列名的常见坑

PostgreSQL 对未加引号的标识符一律折叠为小写:CREATE TABLE Users 实际创建 users,SELECT * FROM Users 解析为 users(大小写写法都能命中);加双引号则完全保留大小写:CREATE TABLE "Users" 创建名为 Users(大写)的表,此时必须用 "Users" 引用,写 Users 或 users 都会报 "relation does not exist"(因为它们分别折叠为 users 与查找 users,而实际对象是 Users)。列名同理:CREATE TABLE t ("Name" text) 创建大写 Name 列,SELECT "Name" FROM t 才能取到,写 name 报 column does not exist。

陷阱场景:其一,建表用双引号、查询忘引号(或反之)——最常见报错来源,且错误信息只提示"不存在"难以排查;其二,ORM/迁移工具生成带引号的大小写混合标识符(如 "OrderId"),后续手写 SQL 极易拼错;其三,从 MySQL 迁移的表(MySQL 表名大小写不敏感)到 PG 后,应用代码大小写不一致即报错;其四,双引号内可含空格与保留字("order" 是合法列名),但引用必须处处带引号。工程规范:始终使用小写、下划线、不带引号的标识符(PG 官方推荐),让折叠规则成为优势(大小写写法都可命中);避免在 DDL 中使用双引号(除非明确需要大小写敏感命名,而这类命名应尽量避免)。

答题先讲折叠规则(未引号→小写、引号→保留)与建表/查询的一致性要求,再列四类陷阱(引号不一致、ORM 生成混合大小写、MySQL 迁移、双引号保留字)各配报错表现,最后给全小写命名规范。

CREATE TABLE "Users" (id INT, "Name" TEXT);   -- 创建大写 Users、Name
SELECT * FROM users;      -- 报错:折叠为 users 不存在
SELECT * FROM "Users";    -- 正确
SELECT "Name" FROM "Users";  -- 正确
#
★★

71. 对象名长度限制,PostgreSQL 默认 63 字节、MySQL 64 字符、Oracle 30 字节、SQL Server 128 字符的差异对业务命名的影响?

对象名长度限制(PostgreSQL 63 字节、MySQL 64 字符、Oracle 30 字节、SQL Server 128 字符)对业务命名有何影响?

  • 四库长度上限与计量单位
  • 自动命名(约束、索引、外键)的超长问题
  • 命名预算与截断策略

长度上限(按各自计量):PostgreSQL 63 字节(NAMEDATALEN,字节计,超长静默截断);MySQL 64 字符(表/列/索引名,字符计);Oracle 30 字节(老版本 30、12.2+ 可调至 128,默认仍常按 30 管理);SQL Server 128 字符。计量单位差异(字节 vs 字符)使多字节命名(中文)在 PG 中实际可容纳的"字符数"远少于 MySQL/SQL Server。

影响:其一,自动生成名超长——外键约束名、索引名、唯一约束名常由"表名_列名_类型"拼接(如 fk_orders_order_items_user_id_created_at),长表名+长列名+多层嵌套极易超限;PG 静默截断导致约束名不可读且可能与手工命名冲突(截断后撞名报错),Oracle 直接报错(ORA-00972),SQL Server 报错;其二,业务命名风格——驼峰/全拼长命名在 Oracle 30 字节下很快超限,建议短小单词+缩写约定(如 user_id 而非 user_identifier_code);其三,迁移与工具——DDL 生成器(ORM、迁移工具)需按目标库配置长度预算与截断规则,避免"源库合法、目标库报错";其四,审计/运维可读性——被截断的约束名无法从报错定位业务规则。工程建议:统一命名长度预算(如表名 ≤ 30、约束名 ≤ 55)、全 ASCII 短命名、由工具保证截断一致性与唯一性(截断后加哈希后缀),并在 CI 中做目标库 DDL 演练。

答题先列四库上限与计量单位差异(字节/字符),再重点分析自动命名(约束/索引拼接名)的超长场景与 PG 静默截断/Oracle 报错的行为差异,最后给长度预算与工具截断策略。

#
★★

72. MySQL 中反引号 ` 与 PostgreSQL 中双引号 " 的转义用途差异如何?

MySQL 中反引号 ` 与 PostgreSQL 中双引号 " 的转义用途差异是什么?

  • 两库标识符转义符号
  • 双引号在 MySQL 的默认语义(字符串)
  • 转义与大小写规则联动

差异核心:PostgreSQL 用双引号 " 转义标识符(标准行为,双引号内保留大小写,未加引号折叠小写),单引号 ' 表示字符串字面量;MySQL 默认用反引号 ` 转义标识符,双引号 " 默认表示字符串字面量(与单引号等价)——这是与 SQL 标准的重要偏离,标准中双引号应表示标识符。MySQL 可在 SQL_MODE 开启 ANSI_QUOTES 后让双引号表示标识符(此时字符串只能用单引号);SQL Server 类似地用 QUOTED_IDENTIFIER 控制双引号语义(默认 ON,双引号=标识符)。因此"双引号"在三库语义不同:PG 恒为标识符、MySQL 默认为字符串(可开关切换)、SQL Server 默认标识符。

联动差异:其一,字符串中的引号转义——MySQL 反引号标识符内可含反引号(双写),PG 双引号内双写双引号转义;其二,大小写——PG 双引号标识符保留大小写(敏感),MySQL 反引号标识符的表名大小写仍受 lower_case_table_names 影响(列名不区分);其三,迁移——MySQL 的 col 迁到 PG 改 "col",且若 MySQL 标识符原为大小写混合(反引号创建),迁到 PG 需保留引号否则折叠;若迁移工具生成的 PG 代码统一小写无引号则最稳;其四,SQL 注入面——标识符转义与字符串转义分离,ORM 参数化时两类转义都要处理。工程建议:MySQL 代码统一反引号转义(或全小写无保留字避免转义),PG 代码统一小写无引号,跨库代码避免依赖双引号语义差异。

答题先给出"MySQL 反引号转义标识符、双引号默认是字符串(ANSI_QUOTES 可切换);PG 双引号恒为标识符、单引号是字符串"的核心差异,再讲大小写联动、转义写法与迁移注意事项,最后给跨库命名规范。

-- MySQL:反引号转义标识符,双引号默认字符串
SELECT `order`, "abc" FROM `table`;
-- 开启 ANSI_QUOTES 后双引号变为标识符
SET sql_mode = 'ANSI_QUOTES';
SELECT "order" FROM "table";
-- PostgreSQL:双引号标识符、单引号字符串
SELECT "Order", 'abc' FROM "Table";
#
★★

73. PostgreSQL 中查询当前 Schema 的 search_path 默认值是什么?

PostgreSQL 中查询当前 Schema 与 search_path 的方法是什么?默认 search_path 的值是什么?

  • search_path 的默认值
  • current_schema() 与 SHOW search_path
  • 对象解析顺序

默认 search_path 是 "$user", public:先查与当前用户名同名的 schema(如用户 app 则找 app schema,不存在则跳过),再查 public schema;超级用户与普通用户相同($user 同名 schema 通常不存在,实际解析落点就是 public)。查询方法:SHOW search_path 查看当前值(返回如 '"$user", public');SELECT current_schema() 返回当前会话"第一个存在的 schema"(通常 public);SELECT current_schemas(true) 返回解析数组(含隐式 pg_catalog 与临时表 schema)。

解析规则细节:pg_catalog 与 pg_temp 总是优先(即使不在 search_path 中,pg_catalog 实际始终被隐式查询);未限定的对象名按 search_path 顺序查找第一个命中的 schema;CREATE TABLE 未限定模式时默认建在 search_path 的第一个 schema。运维影响:若默认 public 有同名对象被恶意创建(search_path 攻击),未限定引用会命中错误对象——安全最佳实践是把 public 从 search_path 移除并指定应用 schema(SET search_path TO app, public 或只留 app),多应用环境用 ALTER ROLE 设置专属 search_path。

答题先给默认值("$user", public)与三个查询命令(SHOW search_path、current_schema()、current_schemas(true)),再讲解析顺序(pg_catalog 隐式优先)与安全实践(移除 public、固定应用 schema)。

SHOW search_path;                 -- '"$user", public'
SELECT current_schema();          -- public
SELECT current_schemas(true);     -- {pg_catalog, public}
-- 固定应用 search_path
SET search_path TO app, public;
ALTER ROLE app_user SET search_path TO app, public;
#
★★

74. 如何在已有表上添加唯一约束?请给出 ALTER TABLE ADD CONSTRAINT 语法。

如何在已有表上添加唯一约束?请给出 ALTER TABLE ADD CONSTRAINT 语法与注意事项?

  • ALTER TABLE ADD CONSTRAINT UNIQUE 语法
  • 存量数据校验
  • 复合唯一与自动索引

语法:ALTER TABLE users ADD CONSTRAINT uk_users_email UNIQUE (email);复合唯一:ALTER TABLE t ADD CONSTRAINT uk_t_a_b UNIQUE (a, b);MySQL 也可用 ALTER TABLE t ADD UNIQUE INDEX uk (col)(约束与索引一体),PostgreSQL/SQL Server 用 ADD CONSTRAINT。执行语义:数据库先校验存量数据是否满足唯一(有重复则整个语句失败回滚、无表被改动),成功后自动创建唯一索引(PG 的约束与索引同名、MySQL 的约束名即索引名、SQL Server 默认唯一索引)并开始对新写入强制。

注意事项:其一,存量重复数据处理——先查重复(GROUP BY HAVING COUNT>1),决定删除/合并/加版本列后再加约束;其二,NULL 处理——约束列含 NULL 时按各库规则(PG/Oracle 允许多 NULL、SQL Server 唯一索引单 NULL),业务语义要确认;其三,加约束的锁与成本——大表加唯一约束会全表扫描校验并建索引,PostgreSQL 建议低峰执行(或用 CREATE UNIQUE INDEX CONCURRENTLY 先建唯一索引,再以索引为后盾添加约束,避免长时间锁表;MySQL 8.0 在线 DDL 支持 ALGORITHM=INPLACE 但校验仍扫描);其四,命名规范——唯一约束用 uk_ 前缀,避免匿名约束名;其五,删除语法差异——PG 用 ALTER TABLE DROP CONSTRAINT,MySQL 用 DROP INDEX(约束名即索引名)或 8.0 的 DROP CHECK 语法。

答题先给单列/复合唯一约束的 ALTER 语法与"先校验存量、自动建唯一索引"的执行语义,再列存量重复、NULL 语义、锁与并发建索引、命名与删除差异五类注意事项。

ALTER TABLE users ADD CONSTRAINT uk_users_email UNIQUE (email);
-- 复合唯一
ALTER TABLE orders ADD CONSTRAINT uk_orders_user_no UNIQUE (user_id, order_no);
-- 大表低锁方案(PostgreSQL):先并发建唯一索引,再挂约束
CREATE UNIQUE INDEX CONCURRENTLY uk_users_email ON users(email);
ALTER TABLE users ADD CONSTRAINT uk_users_email UNIQUE USING INDEX uk_users_email;
#

75. UNION、UNION ALL、INTERSECT、EXCEPT 的语义差异与去重代价如何?

UNION、UNION ALL、INTERSECT、EXCEPT 的语义差异是什么?各自的去重代价如何?

  • 四种集合运算的语义
  • 去重(UNION/INTERSECT/EXCEPT)与不去重(UNION ALL)
  • 去重实现与代价

语义:UNION 取两结果集的并集并去重;UNION ALL 取并集但保留所有重复行(不去重);INTERSECT 取交集(两边都有的行)并去重;EXCEPT(Oracle 为 MINUS)取差集(左侧有右侧无)并去重。四个运算都要求两侧列数相同、对应列类型兼容;列名取左侧。UNION ALL 与 UNION 是唯一"不去重 vs 去重"的对偶,INTERSECT/EXCEPT 天然去重(标准语义)。

去重代价:UNION、INTERSECT、EXCEPT 需要排序(Sort)或哈希(HashAggregate/哈希去重)消除重复,时间复杂度 O(n log n) 或 O(n) 并需额外内存/临时文件;UNION ALL 直接拼接两个结果集(Append 节点),无去重开销,通常比 UNION 快一个数量级。工程建议:确认业务不需要去重时一律用 UNION ALL(如日志合并、分区结果拼接);需要去重时若两集合本身无交集或已唯一,可用 UNION ALL 加前提注释;INTERSECT/EXCEPT 在数据量大时可改写为 JOIN/ANTI JOIN(EXCEPT 常用 NOT EXISTS 改写,可下推过滤、利用索引),性能通常更优;注意 MySQL 8.0.31+ 才有 INTERSECT/EXCEPT(之前用 JOIN/NOT EXISTS 模拟)。

答题先逐个定义四种运算(并去重/并不去重/交/差)与列数类型要求,再对比去重代价(排序或哈希 vs Append 直拼),最后给 UNION ALL 优先、EXCEPT 用 NOT EXISTS 改写的工程建议。

-- 去重 vs 不去重
SELECT city FROM a UNION SELECT city FROM b;        -- 去重
SELECT city FROM a UNION ALL SELECT city FROM b;    -- 不去重
-- EXCEPT 的 NOT EXISTS 等价改写
SELECT id FROM a EXCEPT SELECT id FROM b;
SELECT id FROM a WHERE NOT EXISTS (SELECT 1 FROM b WHERE b.id = a.id);
#

76. BETWEEN ... AND ... 是闭区间还是开区间?包含边界值吗?

BETWEEN ... AND ... 是闭区间还是开区间?边界值是否包含?

  • BETWEEN 的闭区间语义
  • 与 >= AND <= 的等价
  • NULL 与边界陷阱

BETWEEN ... AND ... 是闭区间(inclusive):col BETWEEN 10 AND 20 等价于 col >= 10 AND col <= 20,包含两个边界值 10 与 20。SQL 标准定义如此,四库一致。若需开区间(不包含边界),必须显式写 col > 10 AND col < 20,或用 col BETWEEN 11 AND 19(数值离散时近似,不通用)。注意与 NOT 组合的边界:col NOT BETWEEN 10 AND 20 等价于 col < 10 OR col > 20(不包含 10、20)。

陷阱:其一,边界类型与隐式转换——col BETWEEN '2024-01-01' AND '2024-01-31' 在日期时间列上是 [00:00:00, 00:00:00] 闭区间,2024-01-31 12:00 会被排除,实际常写成 AND '2024-01-31 23:59:59' 或 < 下月 1 日;其二,NULL 传播——col 为 NULL 时 BETWEEN 返回 UNKNOWN 被过滤;边界值为 NULL 时(BETWEEN NULL AND 20)结果为 UNKNOWN/空;其三,参数顺序——低值在前高值在后,反写(BETWEEN 20 AND 10)恒为假(MySQL 返回空,行为可配置);其四,字符串边界按排序规则比较(含大小写与尾随空格差异)。

答题先明确闭区间语义与 >= AND <= 等价(含边界),再讲 NOT BETWEEN 的边界,最后列日期时间边界、NULL、参数顺序、字符串排序规则四类陷阱。

-- 闭区间:包含 10 和 20
SELECT * FROM t WHERE col BETWEEN 10 AND 20;
-- 等价写法
SELECT * FROM t WHERE col >= 10 AND col <= 20;
-- 日期时间边界陷阱:要用 < 下月 1 日
SELECT * FROM t WHERE ts >= '2024-01-01' AND ts < '2024-02-01';
#

77. CASE WHEN 表达式的两种写法(搜索式、简单式)语法差异是什么?

CASE WHEN 表达式的搜索式与简单式两种写法的语法差异是什么?各自适用场景是什么?

  • 简单式(CASE expr WHEN value)与搜索式(CASE WHEN 条件)
  • 等值分派 vs 完整布尔
  • NULL 判定的差异

简单式:CASE col WHEN 1 THEN 'A' WHEN 2 THEN 'B' ELSE 'X' END——CASE 后跟表达式,WHEN 后跟"值",内部等价于逐项等值比较(col = 1、col = 2),适合"同一表达式的多值分派"(状态码映射、字典翻译);搜索式:CASE WHEN 条件 THEN 结果 WHEN 条件2 THEN 结果2 ELSE 兜底 END——WHEN 后跟完整布尔条件,可写范围、多列组合、NULL 判断等任意谓词,表达力更强。两者语法上可互相改写:简单式可展开为搜索式(WHEN col = 1),搜索式不能总是简化为简单式(条件不是等值)。

关键差异:NULL 处理——简单式 CASE col WHEN NULL THEN ... 等价于 col = NULL,恒不命中(NULL 判空必须用搜索式 WHEN col IS NULL);返回值与类型——所有 THEN/ELSE 结果类型需兼容(隐式提升,如数字与字符串混用时按类型转换规则),CASE 整体返回一个值;评估顺序——WHEN 自上而下短路求值,第一个命中即返回(THEN 之后不再评估),ELSE 兜底(无 ELSE 且都不命中返回 NULL)。场景:简单式用于枚举映射(可读性好),搜索式用于区间、逻辑组合与 NULL 处理。

答题先给两种语法的形式与等价关系(简单式是等值分派、可展开为搜索式),再对比 NULL 判定、类型兼容、短路求值与 ELSE 兜底四个差异点,最后给场景建议。

-- 简单式:等值分派
SELECT CASE status WHEN 'N' THEN '新建' WHEN 'P' THEN '已支付' ELSE '其他' END FROM orders;
-- 搜索式:范围与 NULL
SELECT CASE WHEN score >= 90 THEN '优秀' WHEN score IS NULL THEN '缺考' ELSE '一般' END FROM exam;
#

78. ORDER BY 1 与 ORDER BY column_name 的差异如何?数字 1 表示什么?

ORDER BY 1 与 ORDER BY column_name 有何差异?数字 1 表示什么?

  • 序数排序(ORDER BY 序号)
  • 与列名排序的差异
  • 方言支持与风险

ORDER BY 1 是"按 SELECT 列表中第 1 列排序"的序数写法(1-based 位置序号),ORDER BY 2 按第 2 列排序;ORDER BY column_name 按指定列名排序。两者在"排序键相同"时等价,但语义脆弱:其一,SELECT 列表列顺序变化时 ORDER BY 1 跟随的列变化,排序结果可能完全改变(可读性与稳定性差);其二,SELECT 列表中的表达式列(如 SUM(x))可用序号引用而无法用列名直接引用(除非用别名);其三,方言差异——PostgreSQL 支持 ORDER BY 序号与列名,且标准允许;MySQL 支持序号;SQL Server 支持序号;Oracle 支持序号但注意与 GROUP BY 序号区分;部分场景(如 UNION 结果排序、SELECT 列表含 DISTINCT)序号行为有细节差异。

风险与规范:其一,ORDER BY 1 不具自描述性,代码评审无法判断排序意图,属于"魔法数字";其二,SELECT 列表改动时静默改变排序语义(比报错更危险);其三,GROUP BY 中的序号(GROUP BY 1)同理,MySQL 8.0 已废弃 GROUP BY 序号。工程规范:一律写列名或别名(ORDER BY created_at DESC、ORDER BY total_amount),表达式排序用别名;序号写法仅用于一次性调试脚本。注意:ORDER BY 1 中的"1"是位置序号,不是"常量 1"(按常量排序无意义),且只在 ORDER BY/GROUP BY 语境中才有序号语义。

答题先解释序数语义(第 N 列)与列名的差异,再列表达式列引用、UNION、DISTINCT 等场景差异与方言支持,最后强调"魔法数字"风险与列名/别名规范。

SELECT name, salary FROM emp ORDER BY 2 DESC;   -- 按第 2 列 salary 排序
SELECT name, salary AS s FROM emp ORDER BY s DESC;  -- 推荐:别名
#

79. SQL 子句中能否使用列别名(alias)?在哪些子句中可用、哪些不可用?

SQL 子句中能否使用列别名?在哪些子句中可用、哪些不可用?原因是什么?

  • 别名作用域与逻辑执行顺序
  • 可用:ORDER BY、HAVING(部分)、GROUP BY(方言)
  • 不可用:WHERE、ON、JOIN 条件

列别名的可用性由逻辑执行顺序决定:SELECT 列表中的列别名在"SELECT 投影阶段"才生成,因此——不可用:WHERE(行过滤先于投影)、ON/JOIN 条件(连接先于投影)、GROUP BY(分组先于投影,但 MySQL/SQL Server 宽松允许)、HAVING(组过滤先于投影,MySQL 允许、PostgreSQL 拒绝);可用:ORDER BY(排序后于投影,标准允许引用别名)、以及同一 SELECT 之外的下层/外层查询(子查询输出别名供外层使用)、ORDER BY 中还可引用未选列(PostgreSQL 允许、MySQL 在 DISTINCT 时受限)。

细节:其一,FROM 子句中的表别名/派生表别名作用域更早(WHERE 可用);其二,SELECT 同层别名互相引用不可(同时求值),需嵌套子查询或重复表达式;其三,表达式别名(SELECT SUM(x) AS total)在 ORDER BY/HAVING(宽松库)可用;其四,方言差异表:MySQL 对 GROUP BY/HAVING 别名宽松(先扩展表达式)、PostgreSQL 严格按标准、SQL Server 允许 GROUP BY 别名。工程规范:跨库代码只在 ORDER BY 与子查询输出中使用别名,其余场景重复写表达式或包一层派生表/CTE,避免依赖方言行为。

答题按逻辑执行顺序推导别名可见性(WHERE/ON/GROUP BY/HAVING 不可用、ORDER BY 与下层/外层可用),再列方言宽松差异(MySQL/SQL Server 对 GROUP BY 别名宽松、PG 严格),最后给跨库规范。

#

80. 方言兼容性策略,跨数据库应用应避免使用哪些方言特定特性?请给出可移植 SQL 的编码规范清单。

跨数据库应用应避免使用哪些方言特定特性?请给出可移植 SQL 的编码规范清单?

  • 方言特定特性的识别
  • 可移植 SQL 规范清单
  • 抽象层(ORM/方言层)策略

应避免的方言特性:分页方言(LIMIT/TOP/ROWNUM → 标准 OFFSET/FETCH 或由 ORM 生成);字符串拼接运算符(|| vs + vs CONCAT → 统一 CONCAT 函数);布尔类型(BOOLEAN/TINYINT/BIT → 应用层布尔、DDL 由工具转换);自增语法(AUTO_INCREMENT/IDENTITY/SERIAL → GENERATED AS IDENTITY 标准或 ORM);日期函数(NOW/GETDATE/SYSDATE → 标准 CURRENT_TIMESTAMP);类型转换(::/CONVERT/TO_DATE → 标准 CAST);UPDATE...LIMIT、INSERT IGNORE、REPLACE、ON DUPLICATE KEY、RETURNING、ILIKE 等方言扩展;引号风格(` / " / [] → 由 ORM 处理);函数差异(GROUP_CONCAT/string_agg、DATE_FORMAT/to_char 等格式化函数无标准体);MySQL 宽松特性(GROUP BY 非分组列、别名分组)。

可移植 SQL 规范清单:其一,只用标准语法与标准函数(CURRENT_TIMESTAMP、CAST、COALESCE、CASE、标准 JOIN、EXISTS、子查询、CTE);其二,避免上述方言特性,格式化输出放应用层;其三,标识符全小写下划线、避免保留字;其四,NULL/空串语义显式化(COALESCE、IS NULL);其五,所有 DML/DDL 由 ORM 或迁移工具生成(Hibernate/JPA 方言、Flyway 多库脚本);其六,CI 中对所有目标库跑 DDL/查询回归(方言兼容测试);其七,性能敏感查询单独按库优化并注释(通用版本保证正确性、特化版本保证性能)。

答题先列"应避免的方言特性"清单(分页、拼接、布尔、自增、日期、类型转换、扩展语法、引号、函数),再给可移植 SQL 的七条规范(标准子集、应用层格式化、命名、NULL 显式化、ORM 生成、CI 回归、特化优化),形成可执行的兼容策略。

#

81. 多字节字符(中文、日文、Emoji)作为表名或列名时的兼容性如何?UTF-8 编码下字节长度与字符长度的差异?

多字节字符(中文、日文、Emoji)作为表名或列名的兼容性如何?UTF-8 编码下字节长度与字符长度的差异是什么?

  • 多字节标识符的支持差异
  • 字节 vs 字符的长度计量
  • 编码兼容与命名建议

兼容性:主流数据库(PostgreSQL、MySQL 8.0、SQL Server)的 UTF-8 客户端下都允许中文/日文标识符(列名、表名),但须满足各库规则——PostgreSQL 未加引号的非 ASCII 标识符需加双引号(折叠规则只针对 ASCII 字母,中文名通常直接可用但建议引号包裹)、MySQL 标识符支持 UTF-8 但受字符集与 lower_case_table_names 影响、SQL Server 的 Unicode 标识符需方括号/双引号转义。Emoji 等补充平面字符在部分库(如 Oracle 若 AL32UTF8 配置、MySQL utf8mb4 下)可用,但兼容面更窄。

字节 vs 字符:UTF-8 编码下 ASCII 字符 1 字节、中文 3 字节、Emoji 4 字节;因此"长度限制"差异显著——PostgreSQL 标识符上限 63 字节(非 63 字符),21 个中文(63/3)即超限;MySQL 64 字符(按字符计,可容纳 64 个中文);SQL Server 128 字符。影响:其一,约束名/索引名的自动拼接(表名+列名)在 PG 中叠加多字节后极易超 63 字节被截断;其二,CHAR(n)/VARCHAR(n) 的 n 计量各库不同——MySQL 的 VARCHAR(10) 按字符(可存 10 个中文)、Oracle VARCHAR2(10 CHAR) 需显式 CHAR、SQL Server nvarchar(10) 按字符、PostgreSQL varchar(10) 按字符(但 text 无限制);其三,排序规则与比较——多字节标识符的比较、大小写折叠行为因库而异。 工程建议:标识符一律 ASCII(小写+下划线),数据内容用 utf8mb4/UTF-8;长度预算按字节核算(PG 场景尤其);若必须用多字节标识符,全链路(DDL、应用代码、导出工具)保持引号与字符集一致。

答题先讲多字节标识符的支持现状(各库规则与 Emoji 兼容面),再重点分析字节 vs 字符的计量差异(PG 63 字节 vs MySQL 64 字符、中文 3 字节)及其对长度限制与自动命名的冲击,最后给 ASCII 命名建议。

#

82. SQL 标准中双引号表示什么?单引号又表示什么?

SQL 标准中双引号表示什么?单引号表示什么?各数据库实现有何差异?

  • 标准的引号语义
  • 标识符与字符串字面量的区分
  • 方言偏离(MySQL 双引号默认字符串)

SQL 标准语义:双引号 "..." 用于标识符(quoted identifier,表名/列名等对象名,且引号内保留大小写与特殊字符);单引号 '...' 用于字符串字面量(字符/日期等值)。两者职责分离:引号类型决定"对象名 vs 数据值",混用会导致语法错误或语义变化。转义规则:字符串内的单引号用双写 '' 转义(标准),标识符内的双引号用双写 "" 转义。

各库实现差异:PostgreSQL 完全遵循标准(双引号标识符、单引号字符串);SQL Server 遵循(QUOTED_IDENTIFIER ON 时双引号标识符、单引号字符串,OFF 时双引号变字符串);MySQL 偏离标准——默认单引号与双引号都表示字符串(双引号不是标识符),标识符用反引号 `,需开启 ANSI_QUOTES 模式才让双引号表示标识符;Oracle 遵循标准(双引号标识符,但日期等仍用字符串字面量写法)。迁移影响:跨库代码中"双引号"语义不统一(MySQL 默认字符串),标准可移植写法是"标识符用反引号/双引号由工具生成、字面量一律单引号",应用层代码(如 ORM 生成的 SQL)由框架按方言处理引号。

答题先给标准的职责划分(双引号标识符、单引号字符串、双写转义),再列四库实现差异(PG/Oracle 遵循、SQL Server 按 QUOTED_IDENTIFIER、MySQL 默认双引号是字符串需 ANSI_QUOTES),最后给跨库引号规范。

#

83. 标识符命名中的下划线命名法(snake_case)与驼峰命名法(camelCase)哪种更符合 SQL 惯例?

标识符命名中,下划线命名法(snake_case)与驼峰命名法(camelCase)哪种更符合 SQL 惯例?为什么?

  • snake_case 与 camelCase 的对比
  • 大小写折叠与跨库兼容
  • 命名惯例的工程依据

snake_case(小写+下划线,如 user_id、created_at)更符合 SQL 惯例,原因:其一,大小写折叠兼容——PostgreSQL 把未加引号的标识符折叠为小写,snake_case 全小写无需引号即可正确解析;驼峰(userId)在 PG 中若不加引号会被折叠成 userid(语义变化),加引号 "userId" 则处处要引号且大小写敏感,跨库行为不一致;MySQL(lower_case_table_names=1)会把驼峰表名转小写,SQL Server 大小写不敏感但对驼峰无天然优势;其二,可读性与历史——SQL 标准与主流规范(PostgreSQL、MySQL 官方命名建议、常见工具)都倾向小写+下划线,Oracle 的 30 字节限制下 snake_case 也便于缩写;其三,工具链——pg_dump、迁移工具、ORM 默认生成 snake_case(Hibernate 物理命名策略、Flyway 约定),Rails/Django 等框架的默认表名也全部 snake_case。

反例与注意:Java/JPA 实体字段常用驼峰,通过 ORM 命名策略映射到 snake_case 列名(显式 @Column(name) 或策略转换)是成熟做法;SQL Server 惯例历史上大小写不敏感(可接受驼峰),但统一 snake_case 仍是跨库最稳。工程结论:DDL 与手写 SQL 一律 snake_case;ORM 实体层的驼峰由映射层转换,避免把驼峰直接落到列名。

答题先给出结论(snake_case 更符合惯例),再从大小写折叠(PG 折叠小写、MySQL 转小写)、可读性与官方规范、工具链默认(ORM/迁移工具)三点论证,最后讲 ORM 驼峰与列名映射的实践。

#

84. ISO/IEC 9075 标准的五部分结构(框架、基础、调用级接口、持久存储模块、对象语言绑定)分别定义了什么?

ISO/IEC 9075 标准的五部分结构分别定义了什么?各部分的作用是什么?

  • 9075-1 框架
  • 9075-2 基础(核心 SQL)
  • 9075-3 调用级接口

ISO/IEC 9075(SQL 标准)是多部分文档:9075-1 框架(Framework)——总体结构、术语、概念模型(SQL 数据模型、对象模型、事务模型)与各部分的相互引用,是理解其他部分的总纲;9075-2 基础(Foundation)——SQL 语言核心:表/视图/约束/索引等模式对象、DML/DDL 语法、数据类型、表达式、谓词、事务、权限与目录(INFORMATION_SCHEMA),是数据库实现与应用的"主标准"(日常所说的 SQL 标准主要指这一部分);9075-3 调用级接口(Call Level Interface, CLI)——客户端与数据库交互的 API 标准(连接、语句、参数、结果集、事务控制函数),ODBC/JDBC 的思想源头;9075-4 持久存储模块(PSM)——服务器端存储过程/函数语言与模块的标准(CREATE PROCEDURE/FUNCTION、控制流、异常);9075-5 主机语言绑定(Host Language Bindings)——SQL 嵌入宿主语言(COBOL、Fortran、PL/I、Ada 等)的嵌入式 SQL 标准(EXEC SQL 嵌入声明)。

历史与现状:SQL-92 之前标准为单文档(ISO 9075:1989 等),SQL:1999 起拆分为多部分并引入 PSM/CLI/绑定;后续版本(SQL:2003+)又增加 SQL/XML、SQL/OLAP(窗口函数)、SQL/PGQ(属性图查询,SQL:2023)等部分,ISO/IEC 9075 现已有十余个部分。掌握五部分划分有助于理解"SQL 语言(基础)"与"过程语言(PSM)""客户端接口(CLI)""嵌入绑定"的边界,解释为何存储过程各库不兼容(PSM 落地不足)而 ODBC/JDBC 相对统一(CLI 思想被广泛采用)。

答题先逐一说明五部分的内容(框架/基础/CLI/PSM/绑定),再讲标准从单文档到多部分的演进与后续扩展部分(XML、OLAP、PGQ),最后联系"过程语言碎片化 vs CLI 生态统一"的现实。

#

85. SQL 标准的发展历程(SQL-86、SQL-89、SQL-92、SQL:1999、SQL:2003、SQL:2006、SQL:2008、SQL:2011、SQL:2016、SQL:2019、SQL:2023)每个版本引入了哪些核心特性?

SQL 标准的发展历程中,SQL-86、SQL-89、SQL-92、SQL:1999、SQL:2003、SQL:2006、SQL:2008、SQL:2011、SQL:2016、SQL:2019、SQL:2023 各引入了哪些核心特性?

  • 各版本的时间线与核心特性
  • 关键里程碑(SQL-92、SQL:1999、SQL:2003)
  • 最新版本方向(2023 的 PGQ 等)

发展历程与核心特性:SQL-86(1986,首个 ANSI 标准,基础 SELECT/DDL/DML);SQL-89(1989,ANSI 修订,补充完整性约束 INTEGRITY 增强、引用完整性选项);SQL-92(1992,ISO/ANSI 联合的重大里程碑:外连接(LEFT/RIGHT/FULL)、CASE 表达式、内连接语法(JOIN...ON)、集合运算 UNION/INTERSECT/EXCEPT、约束增强、三种符合级别(Entry/Intermediate/Full));SQL:1999(拆分多部分:递归 CTE(WITH RECURSIVE)、触发器等 PSM、CLI、对象关系特性(用户定义类型 UDT、继承、引用类型)、OLAP 初版);SQL:2003(窗口函数(OVER)、MERGE、XML 相关(SQL/XML)、序列、GENERATED 列、多行 INSERT);SQL:2006(SQL/XML 重大扩展:XML 类型与查询集成);SQL:2008(TRUNCATE、INSTEAD OF 触发器、FETCH FIRST 分页标准化、增强的 MERGE);SQL:2011(时态数据:PERIOD 有效时间/事务时间表、增强的窗口与 LATERAL);SQL:2016(JSON 支持(JSON 类型与函数)、行模式识别(MATCH_RECOGNIZE)、增强的多态表函数、ODBC/JDBC 更新);SQL:2019(SQL/JSON 增强、多态表函数 PTF 完善);SQL:2023(SQL/PGQ 属性图查询(GRAPH_TABLE)、增强 JSON、其他)。

理解要点:SQL-92 奠定现代 SQL 基础(此后核心语言稳定),SQL:1999 引入递归与对象特性,SQL:2003 引入窗口与 MERGE(现代分析查询的关键),SQL:2016 起转向 JSON/图等半结构化与新兴方向;厂商实现滞后标准若干年(如窗口函数 MySQL 8.0 才实现、MERGE PG 15 才实现),标准是"方向",落地看各库版本。

答题按时间线列各版本核心特性(重点标 SQL-92、1999、2003、2016、2023 五个里程碑),再讲"标准滞后落地"的现实(窗口/MERGE 的厂商实现时间),形成完整的演进认知。

#

86. SQL-92 标准是 ANSI 还是 ISO 标准化组织的产物?请给出标准号。

SQL-92 标准是 ANSI 还是 ISO 标准化组织的产物?请给出标准号?

  • SQL-92 的发布组织
  • ANSI/ISO 标准号
  • 后续标准编号

SQL-92 由 ANSI 与 ISO 联合制定并同时发布(两个组织同步采用,内容一致):ANSI 编号 X3.135-1992(美国国家标准,前身 SQL-86 为 X3.135-1986、SQL-89 为 X3.135-1989);ISO 编号 ISO/IEC 9075:1992(国际标准,是 ISO 9075 系列的首个完整 SQL 版本,前身为 ISO 9075:1989)。因此"SQL-92 是 ANSI 还是 ISO"的答案:两者都是——它是 ANSI X3.135-1992 同时也是 ISO/IEC 9075:1992,常被写作 ANSI SQL-92 或 ISO SQL-92。

后续:SQL:1999 起统一为 ISO/IEC 9075 多部分系列(9075-1 框架、9075-2 基础等),ANSI 直接采用 ISO 文本(美国作为 ISO 成员转正为国家标准 INCITS);SQL:2003、2006、2008、2011、2016、2019、2023 都是 ISO/IEC 9075:2003 等。理解编号有助于查证规范原文(ISO 官网/标准库按 ISO/IEC 9075-2 检索 Foundation 部分),以及区分"SQL-92 的 Entry/Intermediate/Full 三级符合度"(SQL-92 特有概念,后续版本不再沿用)。

答题直接回答"ANSI 与 ISO 联合发布",给出两个编号(ANSI X3.135-1992 与 ISO/IEC 9075:1992),再讲后续版本统一为 ISO/IEC 9075 系列与 ANSI 转正机制,最后提 SQL-92 的三级符合度概念。