如何在sql(oracle)中进行跨列唯一约束

wkyowqbh  于 2021-07-24  发布在  Java
关注(0)|答案(4)|浏览(361)

如何在具有两列的oracledb中具有唯一约束,以便其中一列中不能出现重复。
假设这张table

|id | A | B |
|---|---|---|
| 1 | 1 | 2 |
| 2 | 3 | 4 |

一个新的行不允许在列“a”中有一个与列“a”或“b”中的值重复的值。
在上面的例子中:我可以在列“a”中添加5,但不能添加1、2、3或4。
我的想法是:

CREATE UNIQUE INDEX crossTest ON test (
    SELECT t.A AS x FROM test t
    UNION ALL
    SELECT t.B AS x FROM test t
    )

但它不起作用,因为oracle不接受这种语法。
两种经典方法:
有两个唯一的约束 CREATE UNIQUE INDEX uidxA ON test A 以及 CREATE UNIQUE INDEX uidxB ON test B 不起作用,因为我可以在“a”列中添加2和4
具有两列的唯一约束 CREATE UNIQUE INDEX uidxB ON test (A, B) 因为这只检查现有的对。
(附加问题:应该允许同一行的“a”和“b”可以相等)
示例的sql脚本

CREATE TABLE test (id NUMBER (10) NOT NULL, a VARCHAR2(12), b VARCHAR2(12));
INSERT INTO test (id,a,b) VALUES(1, '1', '2');
INSERT INTO test (id,a,b) VALUES(2, '3', '4');

INSERT INTO test (id,a,b) VALUES(3, '4', 'x'); -> should fail
INSERT INTO test (id,a,b) VALUES(3, '5', 'x'); -> should work
jhdbpxl9

jhdbpxl91#

@tejash的回答给了我一个避免锁定或序列化的想法。您可以创建一个辅助表 duet_index 生成包含所有行的扩展数据集。然后一个简单的触发器就可以了,包括你的奖金问题。
例如:

create table duet_index (
  n number,
  constraint unique uq1 (n)
);

然后触发:

create or replace trigger test_trg
before insert on test
for each row
begin
  insert into duet_index (n) values (:new.a);
  if (:new.a <> :new.b) then
    insert into duet_index (n) values (:new.b);
  end if;
end;

请认为我不擅长编写oracle触发器。语法可能是错误的,但想法应该是正确的。

8ftvxx2r

8ftvxx2r2#

我在甲骨文公司工作了几十年,我不记得有过这样的要求。你的数据模型让我很紧张。
你想做的事不能用一个索引来完成。在所有多用户情况下,基于触发器的方法都很难正常工作。物化视图方法似乎很有前途。
我的建议是创建一个在提交时刷新并包含连接的物化视图( UNION ALL )列a和列b的值。
以下是我的意思(更多细节请参见代码中的注解):

create table test1 ( id number not null primary key, a number, b number );

insert into test1 values ( 1, 1, 2);
insert into test1 values ( 2, 3, 4);
commit;
-- Create a snapshot to allow us to create a REFRESH FAST ON COMMIT snapshot...
create snapshot log on test1 with primary key, rowid;

-- And create that snapshot...  this will be updated when any changes to TEST1 are committed   
create materialized view test1_concat 
refresh fast on commit
as
select t1.rowid row_id, 1 as marker, t1.a concatenation from test1 t1
union all 
select t2.rowid row_id, 2 as marker, t2.b concatenation from test1 t2
-- this next bit allows a = b in single rows (i.e., bonus question)
where t2.a != t2.b;

-- Now, enforce the constraint on our snapshot to prevent cross-column duplicates
create unique index test1_concat_u1 on test1_concat ( concatenation );

-- Test #1  -- column a may equal column b without error (bonus!)
insert into test1 values ( 3, 5, 5);
commit;

-- Test #2 uniqueness enforced    
insert into test1 values ( 4, 6, 1);
-- (no error at this point)
commit;

> ORA-12008: error in materialized view refresh path  ORA-00001: unique
> constraint (APPS.TEST1_CONCAT_U1) violated

缺点

这里有一个可伸缩性问题。oracle将在提交时同步。我相信,解决你问题的每一个有效办法都会有这个缺点
在事务尝试提交之前,不会出现错误,此时无法更正和恢复事务。我相信在任何解决方案中,您都无法解决这个缺点,而不会使缺点#1变得更糟(即,您的表上没有更广泛和更持久的锁)。

a0zr77ik

a0zr77ik3#

我建议修复我们的数据模型,使值以行而不是列的形式出现:

CREATE TABLE test (
    id NUMBER (10) NOT NULL,
    type varchar2(1) check (type in ('A', 'B'),
    value varchar2(12),
    unique (value),
    unique (id, type)
);

唯一的约束很容易。

db2dz4w8

db2dz4w84#

不可能使用 INDEX 或者 CONSTRAINT . 你需要一个 trigger ,类似于:

CREATE OR REPLACE TRIGGER TEST_TRG
BEFORE INSERT ON TEST
FOR EACH ROW
DECLARE
CNT NUMBER := 0;
BEGIN
SELECT COUNT(1) INTO CNT from TEST
WHERE A = :NEW.A OR B = :NEW.A OR A = :NEW.B OR B = :NEW.B;
IF CNT > 0 THEN
raise_application_error(-20111,'This value is not allowed');
END IF;
END;

相关问题