Search code examples
sqloracleunique-constraintoracle19c

Imposing complex unique constraint in Oracle


I have following Oracle 19c table:

create table t (
  id number primary key,
  pid number not null,
  tech varchar2(1)
);

and I want to impose following constraint on columns pid and tech:

Within group of records of same pid, following situations of tech values are not allowed:

  • multiple same nonnull values
  • multiple null values
  • null and nonnull value

In other words, for null value of tech there must not exist any other null or nonnull value. For nonnull value there must not exist other null or same value.

My questin is if this task is solvable using plain unique index or other simple database feature. Application-based guard is the fallback if other solutions are impossible or hard to understand.

Test data

(with expected result of insertion of all rows of given pid, all testcases with pid<10 are succeeding, pid>=90 are failing)

with t(pid, tech) as (
  select 1  , null from dual union all
  select 2  , 'A'  from dual union all
  select 3  , 'A'  from dual union all
  select 3  , 'B'  from dual union all
  select 90 , null from dual union all
  select 90 , null from dual union all
  select 91 , 'A'  from dual union all  
  select 91 , 'A'  from dual union all
  select 92 , null from dual union all  
  select 92 , null from dual union all  
  select 92 , 'A'  from dual union all  
  select 92 , 'B'  from dual union all
  select 93 , null from dual union all  
  select 93 , null from dual union all  
  select 93 , 'A'  from dual union all  
  select 93 , 'A'  from dual union all
  select 94 , null from dual union all  
  select 94 , 'A'  from dual union all  
  select 94 , 'A'  from dual union all
  select 95 , null from dual union all  
  select 95 , null from dual union all  
  select 95 , 'A'  from dual union all
  select 96 , null from dual union all
  select 96 , 'A'  from dual union all
  select 97 , null from dual union all
  select 97 , 'A'  from dual union all
  select 97 , 'B'  from dual
)
select pid--, count(distinct tech) as d, count(tech) as t, count(*) as c
     , case when count(*) = 1 or count(*) = count(tech) and count(*) = count(distinct tech) then 'OK' else 'ERROR' end as count_check
from t
group by pid
order by pid;

PID COUNT_CHECK
1 OK
2 OK
3 OK
90 ERROR
91 ERROR
92 ERROR
93 ERROR
94 ERROR
95 ERROR
96 ERROR
97 ERROR

fiddle

Attempts

I have tried couple but with no or partial success:

  • unique index based on auxiliary value (db fiddle). The value is computed as virtual column (vc) so that nulls are mapped to other nonnull existing value within group or to certainly-nonexistent value (which is not problem to set and rely on). Unique index is then imposed on the (pid,vc) tuple. In case of two nulls or null together with nonnull, the uniqueness of such index would be violated. The problem is, the value of vc columns needs to be computed using function operating on same table and ORA-04091: table is mutating error appears on attempt of insert. (If window functions were allowed in virtual column expression the function would not be needed but unfortunately this is not possible.)
  • table-scope constraint. Essentially what I need is no row with count_check='ERROR' as defined by counts in test data sample. It would be great to impose such constraint on the table as a whole. I played with solution based on materialized views but ran into privilege issues and two new db objects don't seem too convincing anyway.

Solution

  • The problem was finally solved at application level. We made a decision that benefits of guarding constraint at lower level are no worth adding and maintaining new database object. Thank you all for advices. The answer to my question is simply no.