Suppose you have the following rows in a
table:
ID ADATE
---------- ---------
1
1 04-JUN-04
1 04-JUN-04
1 05-JUN-04
2
2 04-JUN-04
2 05-JUN-04
Now, you are looking for a constraint, which allows duplicate rows,
such as ...
ID ADATE
---------- ---------
1 04-JUN-04
1 04-JUN-04
... but not with NULL values for adate, not
allowed is therefore:
ID ADATE
---------- ---------
1 NULL
1 NULL
The first idea will be to write a
trigger, a constraint cannot be found (CHECK, NOT NULL, UNIQUE). A trigger will do the job, but if you have 100 million
rows, this will be a performance problem. The answer is - use a Function Based Index, here is the
complete example:
CREATE TABLE demo (id NUMBER, adate
DATE);
INSERT INTO DEMO VALUES (1 , NULL);
INSERT INTO DEMO VALUES (2 , NULL);
INSERT INTO DEMO VALUES (1 , SYSDATE);
INSERT INTO DEMO VALUES (1 , SYSDATE);
INSERT INTO DEMO VALUES (2 , SYSDATE);
INSERT INTO DEMO VALUES (1 , SYSDATE+1);
INSERT INTO DEMO VALUES (2 , SYSDATE+1);
Now create the Function Based Index which will do
the job:
CREATE UNIQUE INDEX demo_idx
ON demo (DECODE(adate, NULL, id, NULL));
This is a unique index on id when adate is NULL, the index is only indexing rows
where by the adate is NULL.
If you have 100 rows in the table, 50 of which have a non-null
adate's, the index will have 50 entries. Entirely NULL entries are not made in
B*Tree indexes -- hence the DECODE only indexes the rows that have NULL (active)
adate's.
DECODE is an IF-THEN-ELSE, row by row:
IF (adate IS NULL) THEN
Return the Value of ID
ELSE
Return NULL
END IF
Make a Test:
INSERT INTO DEMO VALUES (1 , NULL);
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.DEMO_IDX) violated
UPDATE demo SET adate = NULL WHERE id = 2;
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.DEMO_IDX) violated