如何在空列上创建唯一索引?

113

我正在使用SQL Server 2005。我想将列中的值约束为唯一,同时允许NULLS。

我的当前解决方案涉及在视图上创建一个唯一索引,如下所示:

CREATE VIEW vw_unq WITH SCHEMABINDING AS
    SELECT Column1
      FROM MyTable
     WHERE Column1 IS NOT NULL

CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)

还有更好的想法吗?


16
不能使用 SQL 2008?可以使用“where”创建一个筛选索引。 - Simon_Weaver
4
您似乎并不是想表达“唯一但允许 NULL 值”,而是想表达“唯一但包括多个 NULL 值”。否则,NULL 值会像其他任何值一样被索引,并且唯一性约束将按预期工作——只是不符合 SQL 标准,如下面 @pst 在评论中提到的那样。 - Suncat2000
5个回答

117

在使用SQL Server 2008时,您可以创建筛选索引

CREATE UNIQUE INDEX AK_MyTable_Column1 ON MyTable (Column1) WHERE Column1 IS NOT NULL

另一个选项是使用触发器来检查唯一性,但这可能会影响性能。


87
在MyTable表的Column1列上创建唯一索引UIX,其中Column1不为空。 - Jørn Schou-Rode
1
注意:目前SQL Server Management Studio似乎不知道如何创建这样的索引,因此如果您稍后修改表格,它会变得混乱并尝试删除它,因此请记得重新创建它。 - Simon_Weaver
4
看起来微软已经更新了SSMS以支持这一功能。我有SSMS 10.50.1617版本,在索引属性对话框中,您可以选择筛选器页面来编辑筛选器。例如,“([Column1] IS NOT NULL)”。 - Phil Haselden
5
允许索引中存在多个空值和从索引中过滤空值是两个不同的概念。过滤索引实际上会排除索引中的记录,而其他解决方案则将空值转换为有用的唯一值。请注意区别。 - Suncat2000
如果您正在使用带有过滤索引的存储过程,那么请确保 ANSI_NULLS 设置为 ON,否则在尝试插入数据时会出现错误。 - Arne
其他解決方案將 null 轉換為有用的唯一值。請注意差別。是的,它們被排除在外,但如果列中包含一些整數,主鍵是其他一些整數。如果你正在搜索可為空的列=某些整數的位置,那麼 nullbuster 技術是活躍危險的-存在一定的機會,在可為空的列中會有一個整數值,該值也存在於 pk 列中,這可能允許您找到完全錯誤的行!排除您不想查找的行更加安全。 - Brian White

76
计算列技巧被广泛称为“nullbuster”; 我的笔记归功于Steve Kass:
CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X  int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)

这看起来像是一个很酷的技巧。奇怪的是搜索nullbuster并没有带来太多的东西。我在想这是否有助于加速搜索 - 而不是使用仅为1和0的计算列来表示null或非null,如果使用PK,则索引会得到更多的工作?这个周末打算在一张大表上进行测试并观察结果。 - David Storfer
@DavidStorfer,你不能这样做,因为两个不同表的ID可能会发生冲突。 - Matt Sgarlata
改进:ISNULL(X,CONVERT(VARCHAR(10),pk)) - Faiz
7
@Faiz:“改进在观察者的眼中。我更喜欢原来的外观。” - onedaywhen
@NunoG,这应该是被接受的答案,因为它提供了符合您要求的良好解决方案,而不仅仅是链接到可能会消失的外部网站。 - Frédéric

25

2
似乎你提供的链接内容实际上是(部分地)未经归属复制自这里:http://decipherinfosys.wordpress.com/2007/11/30/multiple-null-values-in-a-unique-index-in-sql-serverdb2-luw/ - Tom Juergens
85
我不同意“违反唯一性的目的”的说法--在SQL中,NULL是一个特殊的值(类似于NaN),需要相应地处理。实际上,这是SQL Server未能遵守各种SQL规范的失败:以下是一个请求“正确实现”的链接,供参考:http://connect.microsoft.com/SQLServer/feedback/details/299229/change-unique-constraint-to-allow-multiple-null-values。 - user166390
6
参考资料:从2008年开始,您可以执行CREATE UNIQUE INDEX foo ON dbo.bar(key) WHERE key IS NOT NULL; 这将创建一个唯一索引,其中键列不允许NULL值。 - niico
2
我也不同意“违反唯一性的目的”,NULL 不等于 NULL,因此您可以在可空列上创建唯一索引并插入多个 NULL。 - Wodzu
SQL不同于编程语言,其中null是有意义的值,且null==null。在SQL中,null表示__没有值__。请注意:(a)即使在Microsoft中,约束(如外键和检查约束)也不适用于null,(b) 即使在Microsoft中,WHERE null=null也为false,以及(c)大多数其他DBMS不将null应用于UNIQUE约束,MSSQL确实是与众不同的。 - Manngo
显示剩余2条评论

1

可以使用过滤谓词来指定在索引中包含哪些行。

来自文档:

WHERE <filter_predicate> 通过指定要在索引中包含哪些行来创建过滤索引。过滤索引必须是表上的非聚集索引。为过滤索引中的数据行创建过滤统计信息。

示例:

CREATE TABLE Table1 (
  NullableCol int NULL
)

CREATE UNIQUE INDEX IX_Table1 ON Table1 (NullableCol) WHERE NullableCol IS NOT NULL;

2
过滤索引是在SQL Server 2008中引入的。OP表示他正在使用2005年版本(问题已有12.5年之久,因此版本号已过时)。 - SchmitzIT
@SchmitzIT 谢谢您的解释。 - Martin Staufcik

-3

严格来说,一个唯一可空列(或一组列)只能为 NULL(或一条记录的 NULLs),因为具有相同的值(包括 NULL)超过一次明显违反了唯一限制。

但是,这并不意味着“唯一可空列”的概念无效;要在任何关系数据库中实际实现它,我们只需记住这种类型的数据库旨在归一化以正常工作,并且归一化通常涉及添加几个(非实体)额外表来建立实体之间的关系。

让我们考虑一个仅涉及一个“唯一可空列”的基本示例,很容易将其扩展到更多这样的列。

假设我们使用如下所示的表表示的信息:

create table the_entity_incorrect
(
  id integer,
  uniqnull integer null, /* we want this to be "unique and nullable" */
  primary key (id)
);

我们可以通过将uniqnull分离出来并添加第二个表来建立uniqnull值与the_entity之间的关系(而不是将uniqnull“内置”于the_entity中)来实现它:

create table the_entity
(
  id integer,
  primary key(id)
);

create table the_relation
(
  the_entity_id integer not null,
  uniqnull integer not null,

  unique(the_entity_id),
  unique(uniqnull),
  /* primary key can be both or either of the_entity_id or uniqnull */
  primary key (the_entity_id, uniqnull), 
  foreign key (the_entity_id) references the_entity(id)
);

为了将uniqnull的值与the_entity中的一行关联起来,我们还需要在the_relation中添加一行。

对于在the_entity中没有关联uniqnull值的行(即我们会在the_entity_incorrect中放置NULL的行),我们只需不在the_relation中添加一行。

请注意,uniqnull的值对于所有the_relation都是唯一的,并且请注意,对于the_entity中的每个值,最多只能有一个值与the_relation相关联,因为它上面的主键和外键强制执行此操作。

因此,如果要将uniqnull的值5与the_entity id 3关联起来,我们需要:

start transaction;
insert into the_entity (id) values (3); 
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;

如果 the_entity 的 id 值为 10 没有唯一的对应项,我们只需要执行以下操作:

start transaction;
insert into the_entity (id) values (10); 
commit;

为了去规范化这个信息并得到一个像 the_entity_incorrect 表那样的数据表,我们需要进行以下步骤:
select
  id, uniqnull
from
  the_entity left outer join the_relation
on
  the_entity.id = the_relation.the_entity_id
;

"左外连接"运算符确保结果中出现来自the_entity的所有行,在没有匹配列的情况下,将NULL放入uniqnull列。

请记住,花费几天(或几周或几个月)设计规范化的数据库(以及相应的反规范化视图和过程)将为您节省数年(甚至数十年)的痛苦和浪费资源。


7
正如已经在被接受的答案评论中指出并获得了50个赞同,MS Sql Server 应该支持将列中的多个空值建索引为唯一。不允许这样做是没有实现SQL标准的失败。Null 不是一个值,Null 不等于 Null,这是多年来基本的 SQL 规则。因此,你的第一句话是错误的,并且大多数读者都不会继续阅读。 - Frédéric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接