问题描述
如果给定条件为真,有没有办法忽略唯一约束?
例如,我的数据库中有 3 列构成唯一约束:
create table example_table
(
column_primarykey RAW(16) default NULL not null,column_a number(8) not null,column_b number(8) not null,column_c number(8) not null,constraint constraint_1
unique(column_a,column_b,column_c)
constraint constraint_2
primary key (column_primarykey)
现在我添加第四列:
alter table example_table
add column_d number(8) not null,
我想要实现的是,如果 column_d 的值已存在于表中,则唯一约束将被忽略。如果 column_d 在表中不是唯一的,则唯一约束将被忽略,您可以将该行添加到表中。例如,这是我表中的现有数据(忽略不相关的主键原因):
column_a | column_a | column_c | column_d |
---|---|---|---|
1 | 2 | 3 | 1 |
3 | 4 | 5 | 2 |
我想要的是您可以添加例如 (1,2,3,1) 但不能添加 (1,2) 因为已经有一行包含前三个值。仅当 column_d 中的值已存在且其他值等于现有行时才可能。
更多帮助理解的例子:
示例插入 | 结果 | 原因 |
---|---|---|
(1,1) | 接受 | d 不是唯一的,a、b、c 的值与 column_d 的值为 1 的现有行相同 |
(1,4) | 拒绝 | a,b,c 已经存在于表中 |
(5,6,7,1) | 拒绝 | 1 存在,但 a b 和 c 的值不同 |
(3,4,5,2) | 接受 | d 存在且 a、b、c 具有相同的值 |
(7,8,9,3) | 接受 | a、b、c 是唯一的,d 不存在 |
解决方法
听起来您好像试图将两张或多张桌子挤成一张桌子。
- 没有更多背景很难判断
例如,如果你制作了一个大的平面文件,你可能会有这个?
一个 | b | c | d | x | y | z | ||
---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 1 | 1 | 3 | 1 | ||
1 | 2 | 3 | 1 | 2 | 8 | 7 | ||
1 | 2 | 3 | 1 | 5 | 9 | 2 | ||
4 | 5 | 6 | 2 | 9 | 8 | 7 | ||
4 | 5 | 6 | 2 | 4 | 5 | 6 | ||
4 | 5 | 6 | 2 | 3 | 2 | 1 | ||
4 | 5 | 6 | 2 | 2 | 1 | 0 | ||
不过,数据库不是电子表格或平面文件,它们是关系结构。
上面的文件在数据库中可能会更好地表示为两个表...
一个 | b | c | d | |
---|---|---|---|---|
1 | 2 | 3 | 1 | |
4 | 5 | 6 | 2 |
d | x | y | z | |
---|---|---|---|---|
1 | 1 | 3 | 1 | |
1 | 2 | 8 | 7 | |
1 | 5 | 9 | 2 | |
2 | 9 | 8 | 7 | |
2 | 4 | 5 | 6 | |
2 | 3 | 2 | 1 | |
2 | 2 | 1 | 0 | |
如果你想要一个新的“数据”行,你可以在第二个表中添加一行。
如果要在 (a,b,c)
和 (d)
之间创建新关系,请向第一个表中添加一行。
可以按如下方式实施和执行...
CREATE TABLE map (
column_a NUMBER(8) NOT NULL,column_b NUMBER(8) NOT NULL,column_c NUMBER(8) NOT NULL,column_d NUMBER(8) NOT NULL,UNIQUE(column_a,column_b,column_c),UNIQUE(column_d)
)
CREATE TABLE fact (
column_pk RAW(16) NOT NULL,column_x NUMBER(8) NOT NULL,column_y NUMBER(8) NOT NULL,column_z NUMBER(8) NOT NULL,PRIMARY KEY (column_pk),FOREIGN KEY (column_d) REFERENCES map(column_d)
)
据我所知,这个结构可以包含您想要允许的所有内容,也可以禁止您想要禁止的所有内容。
,据我所知,您有两个独特的约束(在 A,B,C
和 D
上),如果您在 A,C,D
上有重复,您想抑制这两个约束。
AFAIK 无法在一张桌子上完成此操作,因此您必须将设置拆分为两张桌子。
第一个 ABCD
检查约束,但不允许重复,第二个 TAB
存储实际数据并引用第一个表。
在插入前触发器中,A,D
上的唯一值合并在第一个表中。
create table abcd
(a int,b int,c int,d int,constraint abcd unique (a,c,d),constraint abc unique (a,c),constraint d unique(d));
drop table tab;
create table tab
(pk int,a int,primary key(pk),foreign key(a,d) references abcd(a,d)
);
CREATE OR REPLACE TRIGGER tab_trigger
BEFORE INSERT ON tab
FOR EACH ROW
BEGIN
merge into abcd using
(select :new.a a,:new.b b,:new.c c,:new.d d from dual) src
on (abcd.a = src.a and abcd.b = src.b and abcd.c = src.c and abcd.d = src.d)
when not matched then insert (a,d) values (src.a,src.b,src.c,src.d)
;
END;
/
测试按预期运行
insert into tab(PK,A,D) values (1,1,2,3,1);
1 row inserted.
insert into tab(PK,D) values (2,4,5,2);
1 row inserted.
insert into tab(PK,D) values (3,D) values (4,4);
ORA-00001: unique constraint (ZZZ.ABC) violated
insert into tab(PK,D) values (5,6,7,1);
ORA-00001: unique constraint (ZZZ.D) violated
insert into tab(PK,D) values (6,D) values (7,8,9,3);
1 row inserted.
无论如何我必须承认我不喜欢基于触发器的解决方案,我更喜欢带有检查视图的延迟验证。
即您可以插入任何行,但在视图中您会看到哪些行是无效的,并且您可以处理它。
验证查询相当简单,它使用分析函数来获取键的重复计数。
with abcd as (
select PK,D,count(*) over (partition by A,C order by PK) cnt_abc,count(*) over (partition by D order by PK) cnt_d,D order by PK) cnt_abcd
from tab)
select
PK,CNT_ABC,CNT_D,CNT_ABCD,case when (CNT_ABC > 1 or CNT_D > 1) and CNT_ABCD = 1 then 'rejected'
else 'accepted' end as status
from abcd
order by PK;
ID A B C D CNT_ABC CNT_D CNT_ABCD STATUS
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- --------
1 1 2 3 1 1 1 1 accepted
2 3 4 5 2 1 1 1 accepted
3 1 2 3 1 2 2 2 accepted
4 1 2 3 4 3 1 1 rejected
5 5 6 7 1 1 3 1 rejected
6 3 4 5 2 2 2 2 accepted
7 7 8 9 3 1 1 1 accepted