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

Posted

技术标签:

【中文标题】如何在 NULL 列上创建唯一索引?【英文标题】:How to create a unique index on a NULL column? 【发布时间】:2010-09-16 12:37:35 【问题描述】:

我使用的是 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)

有更好的想法吗?

【问题讨论】:

没有机会使用 sql 2008?您可以使用“where”创建过滤索引 你的意思不是唯一的,允许NULLs,你的意思似乎是唯一的,但包括多个NULLs。否则,NULL 像任何其他值一样被索引,并且唯一性约束按预期工作 - 只是不符合 SQL 标准,正如@pst 在下面的评论中提到的那样。 【参考方案1】:

很确定你不能这样做,因为它违反了唯一性的目的。

但是,这个人似乎有一个体面的工作: http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html

【讨论】:

看来您提供的链接的内容实际上是(部分)从这里复制而没有署名:decipherinfosys.wordpress.com/2007/11/30/… 我不同意它“违反了唯一性的目的”——NULL 是 SQL 中的一个特殊值(在许多方面类似于 NaN),需要相应地处理。实际上,SQL Server 未能遵守各种 SQL 规范:这是一个请求“正确实现”的链接:connect.microsoft.com/SQLServer/feedback/details/299229/…。 供 2008 年参考,您可以在 dbo.bar(key) WHERE key IS NOT NULL 上执行 CREATE UNIQUE INDEX foo; 我也不同意“违反唯一性的目的”,NULL 不等于 NULL,因此您应该能够在可为空的列上创建唯一索引并插入多个空值。 Null not equal null 是迂腐的。 null == null -> IS NULL AND IS NULL,没有理由不限制唯一性【参考方案2】:

计算列技巧被广泛称为“nullbuster”;我的笔记归功于史蒂夫·卡斯:

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,如果使用 PK 为索引提供了更多可使用的东西?这个周末打算在一张大桌子上测试一下。 @DavidStorfer,您不能这样做,因为您可能会在两个不同表的 ID 之间发生冲突。 改进:ISNULL(X, CONVERT(VARCHAR(10),pk)) @Faiz:改进是在旁观者的眼中。我更喜欢原版的外观。 @NunoG,这应该是公认的答案,因为它提供了符合您要求的良好解决方案,而不仅仅是链接可能会消失的外部站点。【参考方案3】:

使用 SQL Server 2008,您可以创建过滤索引:http://msdn.microsoft.com/en-us/library/cc280372.aspx。 (我看到 Simon 将此作为评论添加,但认为它应该得到自己的答案,因为评论很容易被遗漏。)

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

【讨论】:

create unique index UIX on MyTable (Column1) where Column1 is not null 注意:目前 SQL Server Management Studio 似乎不知道如何创建此类索引,因此如果您稍后修改表,它会混淆并尝试删除它,因此请记住重新创建它跨度> 微软似乎已经更新了 SSMS 来支持这一点。我有 SSMS 10.50.1617,在“索引属性”对话框中,您可以选择“过滤器”页面来编辑过滤器。例如“([Column1] 不为空)” 在索引中允许多个空值和从索引中过滤空值是分开的事情。过滤索引实际上会从索引中排除记录,而其他解决方案会将空值转换为有用的唯一值。注意区别。 如果您在具有类似过滤索引的表上使用存储过程,请确保ANSI_NULLSON,否则在尝试插入数据时会出错。 【参考方案4】:

严格来说,一个唯一的可空列(或一组列)只能为 NULL(或 NULL 的记录)一次,因为多次具有相同的值(包括 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 与 3 的 the_entity id 相关联,我们需要:

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 没有 uniqnull 对应项,我们只做:

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 中的所有行都出现在结果中,当 the_relation 中不存在匹配列时,将 NULL 放在 uniqnull 列中。

请记住,花几天(或几周或几个月)设计一个良好规范化的数据库(以及相应的非规范化视图和过程)的任何努力都将为您节省数年(或数十年)的痛苦和浪费的资源。

【讨论】:

正如在接受的答案评论中已经说过,有 50 个赞成票,MS Sql Server 应该支持在索引为唯一的列中有多个 null。不允许这样做是执行 SQL 标准的失败。 Null 不是值,null 不等于 null,这是多年来的基本 SQL 规则。所以你的第一句话是错误的,大多数读者不会费心继续阅读。【参考方案5】:

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

来自documentation:

WHERE 通过指定哪个 要包含在索引中的行。过滤后的索引必须是 表上的非聚集索引。为 过滤索引中的数据行。

例子:

CREATE TABLE Table1 (
  NullableCol int NULL
)

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

【讨论】:

过滤索引是在 SQL Server 2008 中引入的。OP 声明他使用的是 2005 年(问题是 12.5 年,因此是过时的版本号)。 @SchmitzIT 谢谢你的解释。

以上是关于如何在 NULL 列上创建唯一索引?的主要内容,如果未能解决你的问题,请参考以下文章

在已经存在主键或唯一键约束的列上创建索引

MYSQL中唯一约束和唯一索引的区别

组合唯一约束

如何创建唯一索引

「进阶」MySQL中如何使用索引

使用SQL创建唯一索引