A question came up on OTN today asking for suggestions on how to enforce uniqueness on a pair of columns only when the second column was not null. There’s an easy and obvious solution – but I decided to clone the OP’s example and check that I’d typed my definition up before posting it; and the result came as a bit of a surprise. Here’s a demo script (not using the OP’s table):
create table t1 ( col1 int not null, col2 varchar2(1) ); create unique index t1_i1 on t1( -- case col2 when null then cast(null as int) else col1 end, -- case when col2 is null then cast(null as int) else col1 end, case when col2 is not null then col1 end, col2 ) ; insert into t1 values(1,null); insert into t1 values(1,null); insert into t1 values(1,'x'); insert into t1 values(1,'y'); insert into t1 values(1,'y'); commit; column ind1_is format a5 column ind1_when format 9999 set null N/A select case when col2 is null then cast (null as int) else col1 end ind1_is, case col2 when null then cast (null as int) else col1 end ind1_when from t1 ;
The strategy is simple, you create a unique function-based index with two columns; the first column of the index id defined to show the first column of the table if the second column of the table is not null, the second column of the index is simply the second column of the table. So if the second column of the table is null, both columns in the index are null and there is no entry in the index; but if the second column of the table is not null then the index copies both columns from the table and a uniqueness test applies.
Based on the requirement and definition you would expect the first 4 of my insert statements to succeed and the last one to fail. The index will then have two entries, corresponding to my 3rd and 4th insertions.
I’ve actually shown three ways to use the case statement to produce the first column of the index. The last version is the cleanest, but the first option is the one I first thought of – it’s virtually a literal translation the original requirement. The trouble is, with my first definition the index acquired an entry it should not have got, and the second insert raised a “duplicate key” error; the error didn’t appear when I switched the syntax of the case statement to the second version.
That’s why the closing query of the demo is there – when you run it the two values reported should be the same as each other for all four rows in the table – but they’re not. This is what I got on 220.127.116.11:
IND1_IS IND1_WHEN ------- --------- N/A 1 N/A 1 1 1 1 1
I’m glad I did a quick test before suggesting my original answer.
Anyone who has other versions of Oracle available is welcome to repeat the test and report back which versions they finding working correctly (or not).
It’s not a bug (see note 2 below from Jason Bucata), it’s expected behaviour.