【发布时间】:2015-07-21 06:10:34
【问题描述】:
我们有一个遗留数据库架构,其中包含一些有趣的设计决策。直到最近,我们还只支持 Oracle 和 SQL Server,但我们正在尝试添加对 PostgreSQL 的支持,这带来了一个有趣的问题。我已经搜索了 Stack Overflow 和互联网的其他部分,我不认为这种特殊情况是重复的。
Oracle 和 SQL Server 在唯一约束中的可空列的行为相同,即在执行唯一检查时基本上忽略为 NULL 的列。
假设我有以下表格和约束:
CREATE TABLE EXAMPLE
(
ID TEXT NOT NULL PRIMARY KEY,
FIELD1 TEXT NULL,
FIELD2 TEXT NULL,
FIELD3 TEXT NULL,
FIELD4 TEXT NULL,
FIELD5 TEXT NULL,
...
);
CREATE UNIQUE INDEX EXAMPLE_INDEX ON EXAMPLE
(
FIELD1 ASC,
FIELD2 ASC,
FIELD3 ASC,
FIELD4 ASC,
FIELD5 ASC
);
在 Oracle 和 SQL Server 上,保留任何可空列 NULL 将导致仅对非空列执行唯一性检查。所以下面的插入只能做一次:
INSERT INTO EXAMPLE VALUES ('1','FIELD1_DATA', NULL, NULL, NULL, NULL );
INSERT INTO EXAMPLE VALUES ('2','FIELD1_DATA','FIELD2_DATA', NULL, NULL,'FIELD5_DATA');
-- These will succeed when they should violate the unique constraint:
INSERT INTO EXAMPLE VALUES ('3','FIELD1_DATA', NULL, NULL, NULL, NULL );
INSERT INTO EXAMPLE VALUES ('4','FIELD1_DATA','FIELD2_DATA', NULL, NULL,'FIELD5_DATA');
但是,因为 PostgreSQL(正确地)遵守 SQL 标准,所以那些插入(以及任何其他值的组合,只要其中一个为 NULL)不会抛出错误并且正确插入没有问题。不幸的是,由于我们的旧架构和支持代码,我们需要 PostgreSQL 的行为与 SQL Server 和 Oracle 相同。
我知道以下 Stack Overflow 问题及其答案:Create unique constraint with null columns。据我了解,解决这个问题有两种策略:
- 在可空列同时为
NULL和NOT NULL的情况下创建描述索引的部分索引(这会导致部分索引的数量呈指数增长) - 将
COAELSCE与索引中可为空的列上的标记值一起使用。
(1) 的问题在于,我们需要创建的部分索引的数量随着我们想添加到约束中的每个额外的可为空列而呈指数增长(如果我没记错的话,2^N)。 (2) 的问题是标记值减少了该列的可用值数量以及所有潜在的性能问题。
我的问题:这是解决这个问题的唯一两种方法吗?如果是这样,对于这个特定的用例,它们之间的权衡是什么?一个好的答案将讨论每个解决方案的性能、可维护性、PostgreSQL 如何在简单的SELECT 语句中利用这些索引,以及任何其他“陷阱”或需要注意的事情。请记住,5 个可为空的列仅作为示例;我们的架构中有一些表,最多 10 个(是的,我每次看到它都会哭,但它就是这样)。
【问题讨论】:
-
还有第三种选择。您可以使用基于函数的索引,而不是实际插入“标记值”:
CREATE UNIQUE INDEX u ON example(COALESCE(field1, '~~~~NULL'), COALESCE(field2, '~~~~NULL'))。会更糟,但值得考虑 -
是的,这就是我所说的第二个选项的意思。使用
COALESCE作为功能索引。似乎这种情况下的性能会很差,并且如果另一行已经是该列的NULL,则实际上无法插入使用的哨兵值。 -
好的,我明白了。那么,选项 3 是实际插入标记值(例如,通过触发器或通过访问应用程序中的转换器)。那会很快,但会很难看....顺便说一句,您还有另一个兼容性问题。在 Oracle 中,
'' IS NULL为真 :) -
您的示例可能有问题(除了缺少分号和字符串常量的
"-quoting)。如果我在 PG 中两次提交上述插入,则第二对失败。 (应该发生什么?) -
我将编辑问题,但在第二次插入时使用不同的键值。它会在不应该成功的时候成功。
标签: sql postgresql database-design null unique-constraint