如何限制多列以防止重复,但忽略空值?

发布于 2024-07-15 19:44:24 字数 1746 浏览 13 评论 0原文

这是我在 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

Here's a little experiment I ran in an Oracle database (10g). Aside from (Oracle's) implementation convenience, I can't figure out why some insertions are accepted and others rejected.

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

Assuming that it makes sense to occasionally have some rows with some column values unknown, I can think of two possible use cases involving preventing duplicates:
1. I want to reject duplicates, but accept when any constrained column's value is unknown.
2. I want to reject duplicates, even in cases when a constrained column's value is unknown.

Apparently Oracle implements something different though:
3. Reject duplicates, but accept (only) when all constrained column values are unknown.

I can think of ways to make use of Oracle's implementation to get to use case (2) -- for example, have a special value for "unknown", and make the columns non-nullable. But I can't figure out how to get to use case (1).

In other words, how can I get Oracle to act like this?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

用心笑 2024-07-22 19:44:24

尝试基于函数的索引:

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

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

Try a function-based index:

create unique index sandbox_idx on sandbox(CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE a||','||b END);

There are other ways to skin this cat, but this is one of them.

深海里的那抹蓝 2024-07-22 19:44:24
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);

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

在另一个问题的解决方案的帮助下找到了答案:如何约束数据库表,使某一列中只有一行可以有特定值?

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

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);

A functional index! Basically I just needed to make sure all the tuples I want to ignore (ie - accept) get translated to all nulls. Ugly, but not butt ugly. Works as desired.

Figured it out with the help of a solution to another question: How to constrain a database table so only one row can have a particular value in a column?

So go there and give Tony Andrews points too. :)

兮颜 2024-07-22 19:44:24

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

向表(以及 UNIQUE 索引)中添加一个附加列,计算如下:如果 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);

I'm not an Oracle guy, but here's an idea that should work, if you can include a computed column in an index in Oracle.

Add an additional column to your table (and your UNIQUE index) that is computed as follows: it's NULL if both a and b are non-NULL, and it's the table's primary key otherwise. I call this additional column "nullbuster" for obvious reasons.

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);

I gave this example a number of times around 2002 or so in the Usenet group microsoft.public.sqlserver.programming. You can find the discussions if you search groups.google.com for the word "nullbuster". The fact that you're using Oracle shouldn't matter much.

P.S. In SQL Server, this solution is pretty much superseded by filtered indexes:

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

The thread you referenced suggests that Oracle doesn't give you this option. Does it also not have the possibility of an indexed view, which is another alternative?

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);
眼前雾蒙蒙 2024-07-22 19:44:24

我想你可以。

不过,为了记录,我留下我的段落来解释为什么如果在两列上有一个简单的唯一索引,Oracle 会表现得像这样:

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

一对 1 和 null 被视为“可索引”对。 一对两个 null 无法建立索引,这就是为什么它允许您插入任意数量的 null,null 对。

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

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

I guess you can then.

Just for the record though, I leave my paragraph to explain why Oracle behaves like that if you have a simple unique index on two columns:

Oracle will never accept two (1, null) pairs if the columns are uniquely indexed.

A pair of 1 and a null, is considered an "indexable" pair. A pair of two nulls cannot be indexed, that's why it lets you insert as many null,null pairs as you like.

(1, null) gets indexed because 1 can be indexed. Next time you try to insert (1, null) again, 1 is picked up by the index and the unique constraint is violated.

(null,null) isn't indexed because there is no value to be indexed. That's why it doesn't violate the unique constraint.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文