在这种情况下我该如何处理唯一性?

How can I handle uniqueness in this situation?

我有一个 table 这样的:

create table my_table
(
type1 varchar2(10 char),
type2 varchar2(10 char) 
);

我想要这样的独特性;

  1. 如果 type1 列具有 'GENERIC' 值,则 type2 列对于 table 必须是唯一的。例如;
    • type1 列有 'GENERIC' 值,type2 列有 'value_x' 那么不能有任何 type2 列值等于 'value_x'.
  2. 但其他唯一性正在寻找这两列。我的意思是它应该在 type1 和 type2 列中是唯一的。(当然第一条规则是不变的)

我尝试用触发器实现;

CREATE OR REPLACE trigger my_trigger
BEFORE INSERT OR UPDATE 
ON my_table
FOR EACH ROW 
DECLARE 
       lvn_count NUMBER :=0;
       lvn_count2 NUMBER :=0;
       errormessage clob;
       MUST_ACCUR_ONE EXCEPTION;
    --   PRAGMA AUTONOMOUS_TRANSACTION; --without this it gives mutating error but I cant use this because it will conflict on simultaneous connections
BEGIN

      IF :NEW.type1 = 'GENERIC' THEN

       SELECT count(1) INTO lvn_count FROM my_table
       WHERE type2= :NEW.type2;
      
      ELSE 
      
       SELECT count(1) INTO lvn_count2 FROM my_table
       WHERE type1= :NEW.type1 and type2= :NEW.type2;
 
      END IF;     
 

       IF (lvn_count >= 1 or lvn_count2 >= 1) THEN
             RAISE MUST_ACCUR_ONE; 
       END IF;

END;

但是它在没有 pragma 的情况下给出了 mutating 错误。由于同时连接发生冲突,我不想使用它。 (错误是因为我在触发器上使用了相同的 table)

我试着用唯一索引来做,但我做不到。

CREATE UNIQUE INDEX my_table_unique_ix 
ON my_table (case when type1= 'GENERIC' then 'some_logic_here' else type1 end, type2);  -- I know it does not make sense but maybe there is something different that I can use in here.

示例;

**Example 1**

insert into my_table (type1,type2) values ('a','b'); -- its ok no problem

insert into my_table (type1,type2) values ('a','c'); -- its ok no problem

insert into my_table (type1,type2) values ('c','b'); -- its ok no problem

insert into my_table (type1,type2) values ('GENERIC','b'); -- it should be error because b is exist before (i look just second column because first column value is 'GENERIC')


EXAMPLE 2:

insert into my_table (type1,type2) values ('GENERIC','b'); -- its ok no problem

insert into my_table (type1,type2) values ('a','c'); -- its ok no problem

insert into my_table (type1,type2) values ('d','c'); -- its ok no problem

insert into my_table (type1,type2) values ('d','b'); -- it should be error because second column can not be same as the second column value that first column value is 'GENERIC' 

试试这样:

CREATE TABLE my_table (
    type1 VARCHAR2(10 CHAR),
    type2 VARCHAR2(10 CHAR),
    type1_unique VARCHAR2(10 CHAR) GENERATED ALWAYS AS ( NULLIF(type1, 'GENERIC') ) VIRTUAL
);

ALTER TABLE MY_TABLE ADD (CONSTRAINT my_table_unique_ix UNIQUE (type1_unique, type2) USING INDEX)

或者像这样的索引也应该有效:

CREATE UNIQUE INDEX my_table_unique_ix ON MY_TABLE (NULLIF(type1, 'GENERIC'), type2);

或者按照你的风格去做(你只错过了END):

CREATE UNIQUE INDEX my_table_unique_ix ON my_table (case when type1= 'GENERIC' then null else type1 end, type2);

您尝试做的事情在 Oracle 中并不是很简单。一种可能的(虽然有些麻烦)方法是结合使用

  • 带有 refresh (on commit)
  • 的附加物化视图
  • 用于计算每组不同值数量的窗口函数
  • 计算每组 GENERIC 行数的窗口函数
  • 一个检查约束,以确保我们要么只有一个 DISTINCT 值,要么在同一组中没有 GENERIC

这应该有效:

create materialized view mv_my_table 
refresh on commit
as
select 
 type1,
 type2,
 count(distinct type1) over (partition by type2) as distinct_type1_cnt,
 count(case when type1 = 'GENERIC' then 1 else null end) 
     over (partition by type2) as generic_cnt
from my_table;

alter table mv_my_table add constraint chk_type1 
  CHECK (distinct_Type1_cnt = 1 or generic_cnt = 0);

现在,INSERT 复制不会立即失败,但随后的 COMMIT 会失败,因为它会触发物化视图刷新,这会导致检查约束触发。

缺点

  • 重复的 INSERT 不会立即失败(使调试更加痛苦)
  • 根据 table 的大小,MView 刷新可能会显着降低 COMMIT 速度

链接

有关此方法的更详细讨论,请参阅AskTom on cross-row constraints

除非我遗漏了一些明显的东西,否则@Frank Schmitt 的答案中的逻辑也可以使用语句级触发器来实现。它实施起来简单得多,并且没有 Frank 提到的缺点。

create or replace TRIGGER my_table_t
    AFTER INSERT OR UPDATE OR DELETE 
    ON my_table
DECLARE
  l_dummy NUMBER;
  MUST_ACCUR_ONE EXCEPTION;  
BEGIN
  WITH constraint_violated AS
  (
  select 
   type1,
   type2,
   count(distinct type1) over (partition by type2) as distinct_type1_cnt,
   count(case when type1 = 'GENERIC' then 1 else null end) 
       over (partition by type2) as generic_cnt
  from my_table
  )
  SELECT 1 INTO l_dummy 
    FROM constraint_violated
   WHERE NOT (distinct_type1_cnt = 1 or generic_cnt = 0) FETCH FIRST 1 ROWS ONLY;
  RAISE MUST_ACCUR_ONE; 
EXCEPTION WHEN NO_DATA_FOUND THEN
  NULL;
END;
/