使同一表中2列中的值相互唯一

时间:2019-06-21 15:55:07

标签: oracle constraints oracle12c

我想让同一张表的2列中的值唯一。我想建立一个规则,使任何值都不能再次出现在表的2列中。

例如,考虑表mail_address_book (pk_serial_no, address_a, address_b)address_aaddress_b是我要建立相互唯一性的2列。

如果有人尝试运行以下插入语句,则应为:

create table mail_address_book (pk_serial_no number, address_a varchar2(5), address_b  varchar2(5))
insert into mail_address_book(1,'A','B'); --Allow
insert into mail_address_book(2,'B','A'); --Error
insert into mail_address_book(3,'C','A'); --Error
insert into mail_address_book(4,'C','C'); --Error
insert into mail_address_book(5,'C',null); --Allow

3 个答案:

答案 0 :(得分:2)

如果要使同一表中2列的值唯一,那么数据模型似乎存在问题-两个或更多列包含相同类型的信息。也许最好的解决方案是重新分配DM并创建单独的表:

function addUser(event,rc,prc)          
{
    LOCAL.userBean = populateModel("userBean").init(5,prc.siteid,event.getValue('userid',0));
    rc.user = securityService.getUser(LOCAL.userBean);
    LOCAL.userBean.setMethod(3);
    rc.genderList=globalsService.getGlobals(LOCAL.userBean); 
    LOCAL.userBean.setMethod(7);
    rc.stateList=globalsService.getGlobals(LOCAL.userBean);
    event.setLayout("Window");
    event.setView("purchase/addUser");
}

作为解决方法,您可以将物理表转换为视图,然后将该视图而不是表用于所有查询和DML语句。考虑以下示例:

create table mail_address_book (serial_no number primary key /* maybe FK to somewhat */)
/
create table mail_address_entries (
    serial_no number, addrno number, address varchar2(5) unique,
    constraint pk_fk_mail_address_entries primary key(serial_no, addrno),
    constraint fk_mail_address_entries foreign key (serial_no) references mail_address_book (serial_no))
/

插入测试数据:

create table mail_address_entries (
    pk_serial_no number, addrno number, address varchar2(5) unique,
    constraint pk_mail_address_entries primary key (pk_serial_no, addrno)
)
/
create or replace view mail_address_book as
    select a.pk_serial_no, a.address address_a, b.address address_b
    from mail_address_entries a  
    join mail_address_entries b on (
        b.pk_serial_no = a.pk_serial_no and a.addrno = 1 and b.addrno = 2 
    );

create or replace trigger trig_mail_address_book
instead of insert on mail_address_book
begin
    if inserting then -- the same for updating, deleting 
        insert into mail_address_entries values (:new.pk_serial_no, 1, :new.address_a);
        insert into mail_address_entries values (:new.pk_serial_no, 2, :new.address_b);
    end if;
end;
/

结果:

create or replace type addrRow force is object (pk_serial_no number, address_a varchar2(5), address_b varchar2(5));  
/
create or replace type addrRows is table of addrRow;
/
exec dbms_errlog.create_error_log (dml_table_name => 'mail_address_book');

declare
    testdata addrRows; 
begin
    testdata := addrRows (
        addrRow (1, 'A', 'B'),
        addrRow (2, 'B', 'A'),
        addrRow (3, 'C', 'A'),
        addrRow (4, 'C', 'C'),
        addrRow (5, 'C', null),
        addrRow (6, null, null),
        addrRow (7, 'D', 'E'),
        addrRow (8, 'E', 'F')
    ); 
    for r in (select * from table (testdata)) loop 
        begin
            insert into mail_address_book values (r.pk_serial_no, r.address_a, r.address_b);
        exception when dup_val_on_index then 
            insert into err$_mail_address_book (pk_serial_no, address_a, address_b, ora_err_mesg$)
            values (r.pk_serial_no, r.address_a, r.address_b, 'error'); 
        end;
    end loop;
end;
/

db<>fiddle

答案 1 :(得分:2)

与OP提出的方法类似的方法,使用一个具有唯一性的伪表和一个触发器来管理合法动作:

create table hack_table (address varchar2(5) primary key);

create trigger hack_trigger
before insert or update or delete on mail_address_book
for each row
begin
  if inserting then
    if :new.address_a is not null then
      insert into hack_table (address) values (:new.address_a);
    end if;
    if :new.address_b is not null then
      insert into hack_table (address) values (:new.address_b);
    end if;
  elsif updating then
    -- maybe skip this is column values have swapped
    if :old.address_a is null and :new.address_a is not null then
      insert into hack_table (address) values (:new.address_a);
    elsif :old.address_a is not null and :new.address_a is null then
      delete from hack_table where address = :old.address_a;
    elsif :new.address_a != :old.address_b then
      update hack_table set address = :new.address_a where address = :old.address_a;
    end if;

    if :old.address_b is null and :new.address_b is not null then
      insert into hack_table (address) values (:new.address_b);
    elsif :old.address_b is not null and :new.address_b is null then
      delete from hack_table where address = :old.address_b;
    elsif :new.address_b != :old.address_a then
      update hack_table set address = :new.address_b where address = :old.address_b;
    end if;
  else
    delete from hack_table where address in (:old.address_a, :old.address_b);
  end if;
end;
/

显然选择了更合适的对象名称和列大小* 8-)

然后一些示例插入得到:

insert into mail_address_book (pk_serial_no, address_a, address_b) values (1,'A','B'); --Allow

1 row inserted.

insert into mail_address_book (pk_serial_no, address_a, address_b) values (2,'B','A'); --Error

ORA-00001: unique constraint (STACKOVERFLOW.SYS_C00141064) violated

insert into mail_address_book (pk_serial_no, address_a, address_b) values (3,'C','A'); --Error

ORA-00001: unique constraint (STACKOVERFLOW.SYS_C00141064) violated

insert into mail_address_book (pk_serial_no, address_a, address_b) values (4,'C','C'); --Error

ORA-00001: unique constraint (STACKOVERFLOW.SYS_C00141064) violated

insert into mail_address_book (pk_serial_no, address_a, address_b) values (5,'C',null); --Allow

1 row inserted.

insert into mail_address_book (pk_serial_no, address_a, address_b) values (6,'D','C'); --Error

ORA-00001: unique constraint (STACKOVERFLOW.SYS_C00141064) violated

insert into mail_address_book (pk_serial_no, address_a, address_b) values (7,'C','D'); --Error

ORA-00001: unique constraint (STACKOVERFLOW.SYS_C00141064) violated

insert into mail_address_book (pk_serial_no, address_a, address_b) values (8,'D','E'); --Allow

1 row inserted.

insert into mail_address_book (pk_serial_no, address_a, address_b) values (9,'E','F'); --Error

ORA-00001: unique constraint (STACKOVERFLOW.SYS_C00141064) violated

最终只能得到:

PK_SERIAL_NO ADDRE ADDRE
------------ ----- -----
           1 A     B    
           5 C          
           8 D     E    

db<>fiddle

答案 2 :(得分:1)

我想在不使用功能索引和触发器的情况下以某种方式创建查找表的解决方案,但是由于无法找到解决方案,并且由于缺乏完整的答案,请在下面找到我的方法:

请在下面找到代码流/算法。.我正在使用具有唯一索引的数据库查找表,但是该表对原始表的最终用户隐藏了。

create table distinct_add (address_code varchar2(25) not null);

create unique index distinct_add_uq1 on distinct_add(address_code);

Trigger before insert or update or delete on mail_address_book
------

IF INSERTING OR UPDATING
Then

   IF UPDATING
       IF(:old.address_a is not null and nvl(:old.address_a,'garbage') != nvl(:new.address_a,'garbage'))
       THEN

          delete :old.address_a from distinct_add

          catch exception raise error

       END IF

       IF(:old.address_b is not null and nvl(:old.address_b,'garbage') != nvl(:new.address_b,'garbage'))
       THEN

          delete :old.address_b from distinct_add

          catch exception raise error

       END IF

   END IF

   IF(:new.address_a is not null and nvl(:old.address_a,'garbage') != nvl(:new.address_a,'garbage'))
   THEN

       insert :new.address_a into distinct_add

       catch exception raise error

   END IF

   IF(:new.address_b is not null and nvl(:old.address_b,'garbage') != nvl(:new.address_b,'garbage'))
   THEN

       insert :new.address_b into distinct_add

       catch exception raise error

   END IF


END IF


IF DELETING
Then

    delete nvl(:new.address_a,'garbage') nvl(:new.address_b,'garbage') from distinct_add

    catch exception raise error

END IF