8

这是我在 Oracle 数据库 (10g) 中运行的一个小实验。除了(Oracle 的)实现方便之外,我无法弄清楚为什么一些插入被接受而另一些被拒绝。

create table sandbox(a number(10,0), b number(10,0));
create unique index sandbox_idx on sandbox(a,b);

insert into sandbox values (1,1); -- accepted
insert into sandbox values (1,2); -- accepted
insert into sandbox values (1,1); -- rejected

insert into sandbox values (1,null); -- accepted
insert into sandbox values (2,null); -- accepted
insert into sandbox values (1,null); -- rejected

insert into sandbox values (null,1); -- accepted
insert into sandbox values (null,2); -- accepted
insert into sandbox values (null,1); -- rejected

insert into sandbox values (null,null); -- accepted
insert into sandbox values (null,null); -- accepted

假设偶尔有一些列值未知的行是有意义的,我可以想到两个可能的涉及防止重复的用例:
1. 我想拒绝重复,但在任何受约束的列的值未知时接受。
2. 我想拒绝重复,即使在约束列的值未知的情况下。

显然,Oracle 实现了一些不同的东西:
3. 拒绝重复,但(仅)在所有受约束的列值都未知时接受。

我可以想办法利用 Oracle 的实现来处理用例 (2)——例如,为“未知”设置一个特殊值,并使列不可为空。但我不知道如何使用用例(1)。

换句话说,我怎样才能让 Oracle 这样做呢?

create table sandbox(a number(10,0), b number(10,0));
create unique index sandbox_idx on sandbox(a,b);

insert into sandbox values (1,1); -- accepted
insert into sandbox values (1,2); -- accepted
insert into sandbox values (1,1); -- rejected

insert into sandbox values (1,null); -- accepted
insert into sandbox values (2,null); -- accepted
insert into sandbox values (1,null); -- accepted

insert into sandbox values (null,1); -- accepted
insert into sandbox values (null,2); -- accepted
insert into sandbox values (null,1); -- accepted

insert into sandbox values (null,null); -- accepted
insert into sandbox values (null,null); -- accepted
4

4 回答 4

7

尝试基于函数的索引:

在沙盒上创建唯一索引 sandbox_idx(CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE a||','||b END);

还有其他方法可以给这只猫剥皮,但这是其中之一。

于 2009-03-23T22:28:37.683 回答
7
create unique index sandbox_idx on sandbox
 (case when a is null or b is null then null else a end,
  case when a is null or b is null then null else b end);

功能指标!基本上我只需要确保我想忽略(即接受)的所有元组都被转换为所有空值。丑,但不丑。根据需要工作。

在另一个问题的解决方案的帮助下弄清楚了:如何约束数据库表,以便在一列中只有一行可以具有特定值?

所以去那里也给托尼安德鲁斯积分。:)

于 2009-03-23T22:29:36.870 回答
2

我不是 Oracle 人,但如果您可以在 Oracle 的索引中包含计算列,那么这个想法应该可行。

向您的表(和您的唯一索引)添加一个附加列,计算如下:如果 a 和 b 都不是 NULL,则为 NULL,否则为表的主键。出于显而易见的原因,我将此附加列称为“nullbuster”。

alter table sandbox add nullbuster as 
  case when a is null or b is null then pk else null end;
create unique index sandbox_idx on sandbox(a,b,pk);

我在 2002 年左右的 Usenet 组 microsoft.public.sqlserver.programming 中多次给出了这个例子。如果您在 groups.google.com 中搜索“nullbuster”一词,您可以找到讨论。您使用 Oracle 的事实并不重要。

PS在 SQL Server 中,这个解决方案几乎被过滤索引所取代:

create unique index sandbox_idx on sandbox(a,b)
(where a is not null and b is not null);

您引用的线程表明 Oracle 没有为您提供此选项。它是否也没有索引视图的可能性,这是另一种选择?

create view sandbox_for_unique as
select a, b from sandbox
where a is not null and b is not null;

create index sandbox_for_unique_idx on sandbox_for_unique(a,b);
于 2009-08-10T02:34:10.350 回答
1

我想你可以。

只是为了记录,我留下我的段落来解释如果你在两列上有一个简单的唯一索引,为什么 Oracle 会这样:

如果列是唯一索引的,Oracle 将永远不会接受两个 (1, null) 对。

一对 1 和一个 null,被认为是“可索引”对。一对两个空值不能被索引,这就是为什么它可以让你插入尽可能多的空值,你喜欢的空值对。

(1, null) 被索引,因为 1 可以被索引。下次您尝试再次插入 (1, null) 时,索引会拾取 1 并违反唯一约束。

(null,null) 未编入索引,因为没有要编入索引的值。这就是为什么它不违反唯一约束。

于 2009-03-23T22:23:56.273 回答