对业务键的唯一性控制(不仅按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, '') );
在应用程序逻辑层,往往需要重复检查以确保在插入时不遗漏重复。
关键特性:
如果列中有nullable列,唯一索引能否确保无法插入重复?
不能。在SQL中,NULL的行为是特殊的:具有相同值集的行,如果其中至少一个是NULL,索引会将它们视为不同的,并允许它们同时存储。
不同的数据库能否以不同方式实现NULL唯一性?
可以,它们之间存在差异。例如,在Oracle中,唯一索引允许多个NULL行,而在MS SQL中,则只允许一个。PostgreSQL可以通过表达式创建部分索引。
仅通过DDL级别而无需触发器是否可以绕过该问题?
在某些数据库中,可以通过表达式实现,例如COALESCE。但并不总是如此,如果需要复杂的行为(NULL应视为重复),则必须使用逻辑检查或触发器。
在一个不关键的数据库中,决定依赖(电子邮件, 部门代码)的唯一索引,其中两个字段都允许NULL。结果是多次出现重复,导致外部集成出现问题。
优点:
缺点:
在PostgreSQL中,通过COALESCE索引实现了包含(护照号码,系列)的业务键,并额外在应用程序端添加了检查。
优点:
缺点: