编程后端开发者

如何在SQL中实现对可能是复杂并且允许NULL的业务键的唯一性控制,当标准的唯一约束无效时?

用 Hintsage AI 助手通过面试

答案。

问题历史

对业务键的唯一性控制(不仅按ID,还按“自然”字段)在企业应用中由来已久。这样的键往往是复杂的(多个列),并允许NULL值。标准的SQL工具——唯一约束或唯一索引——有其局限性:根据SQL标准,具有至少一个UNIQUE键组件为NULL的多行被视为唯一,并不违反约束。

问题

业务逻辑可能要求,某些列(包括允许NULL的列)的组合必须是唯一的,而SQL标准行为(NULL ≠ NULL)会破坏这一场景。例如,我们有一个包含 (passport_number, passport_series) 一对列的表,且其中至少一个可以为NULL,但如果值相同,我们必须禁止重复。

解决方案

在大多数流行的数据库中,解决方案是通过触发器实现唯一性检查,或者使用带条件的部分索引,或者使用允许将NULL视为相等值的函数(例如,ISNULL或COALESCE)。以下是使用表达式的PostgreSQL唯一索引示例:

CREATE UNIQUE INDEX idx_passport_unique ON persons ( COALESCE(passport_number, ''), COALESCE(passport_series, '') );

在应用程序逻辑层,往往需要重复检查以确保在插入时不遗漏重复。

关键特性:

  • 标准的唯一约束在键中包含至少一个NULL时不起作用。
  • 解决方案依赖于数据库:部分索引、计算列、触发器。
  • 如果无法创建正确的索引,唯一性检查可以转移到业务逻辑上。

反向问题。

如果列中有nullable列,唯一索引能否确保无法插入重复?

不能。在SQL中,NULL的行为是特殊的:具有相同值集的行,如果其中至少一个是NULL,索引会将它们视为不同的,并允许它们同时存储。

不同的数据库能否以不同方式实现NULL唯一性?

可以,它们之间存在差异。例如,在Oracle中,唯一索引允许多个NULL行,而在MS SQL中,则只允许一个。PostgreSQL可以通过表达式创建部分索引。

仅通过DDL级别而无需触发器是否可以绕过该问题?

在某些数据库中,可以通过表达式实现,例如COALESCE。但并不总是如此,如果需要复杂的行为(NULL应视为重复),则必须使用逻辑检查或触发器。

常见错误和反模式

  • 依赖标准唯一约束,当列中允许NULL时
  • 不在业务逻辑中检查重复
  • 创建触发器时不考虑性能

生活中的例子

负面案例

在一个不关键的数据库中,决定依赖(电子邮件, 部门代码)的唯一索引,其中两个字段都允许NULL。结果是多次出现重复,导致外部集成出现问题。

优点:

  • 简单的DDL

缺点:

  • 不明显的重复、不一致的数据、报告错误

正面案例

在PostgreSQL中,通过COALESCE索引实现了包含(护照号码,系列)的业务键,并额外在应用程序端添加了检查。

优点:

  • 确保唯一性,符合业务逻辑

缺点:

  • 多层控制,增加了维护复杂性