The following select gives a list of existing table that do not possess a unique constraint
select table_name from user_tables u1 where not exists (select null from user_constraints u2 where u2.table_name = u1.table_name and u2.constraint_type = 'U');
However, the above select doesn’t mean that there isn’t any other uniqueness enforcement implemented without a dedicated unique constraint.
Recently, I have been confronted to a business rule to be implemented in a Data Vault model which specifies that each Satellite or Satellite of Link table should have a unique key enforcement satisfying the following rule
For each couple of (col1, col2) I could have at most a unique record for a not null dv_load_end_date column.
A picture being worth a thousand words let’s see this through a real example
SQL> create table t1 (t1_sk number ,dv_load_date date ,seq_nbr number ,n1 number ,n2 number ,status varchar2(10) ,identification varchar2(30) ,dv_load_end_date date); SQL> alter table t1 add constraint t1_pk primary key (t1_sk,dv_load_date, seq_nbr);
The unique business rule would be described as follows: for each couple (t1_sk, n2) I could have at most one record for a null dv_load_end_date column
Is this doable via a unique constraint? No it is not.
And here where function based index comes to the rescue
SQL> create unique index ind_t1_uk on t1 (case when dv_load_end_date is null then t1_sk else null end ,case when dv_load_end_date is null then n2 else null end);
And here how the inserts go
SQL> insert into t1 (t1_sk ,dv_load_date ,seq_nbr,n1,n2,dv_load_end_date) values(1, sysdate, 1, 1, 100, null); 1 row created. SQL> insert into t1 (t1_sk ,dv_load_date ,seq_nbr,n1,n2,dv_load_end_date) values(1, sysdate, 1, 1, 100, sysdate); 1 row created. SQL> insert into t1 (t1_sk ,dv_load_date ,seq_nbr,n1,n2,dv_load_end_date) values(1, sysdate, 1, 1, 100, null); insert into t1 (t1_sk ,dv_load_date ,seq_nbr,n1,n2,dv_load_end_date) * ERROR at line 1: ORA-00001: unique constraint (XXX.IND_T1_UK) violated SQL> insert into t1 (t1_sk ,dv_load_date ,seq_nbr,n1,n2,dv_load_end_date) values(1, sysdate, 1, 1, 200, null); 1 row created.
This post is the second one in a set of small scripts (blog articles) I decided to collect in my blog for my documentation and reuse purposes
