Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I constrain multiple columns to prevent duplicates, but ignore null values?

Here's a little experiment I ran in an Oracle database (10g). Aside from (Oracle's) implementation convenience, I can't figure out why some insertions are accepted and others rejected.

create table sandbox(a number(10,0), b number(10,0));
create unique index sandbox_idx on sandbox(a,b);

insert into sandbox values (1,1); -- accepted
insert into sandbox values (1,2); -- accepted
insert into sandbox values (1,1); -- rejected

insert into sandbox values (1,null); -- accepted
insert into sandbox values (2,null); -- accepted
insert into sandbox values (1,null); -- rejected

insert into sandbox values (null,1); -- accepted
insert into sandbox values (null,2); -- accepted
insert into sandbox values (null,1); -- rejected

insert into sandbox values (null,null); -- accepted
insert into sandbox values (null,null); -- accepted

Assuming that it makes sense to occasionally have some rows with some column values unknown, I can think of two possible use cases involving preventing duplicates:
1. I want to reject duplicates, but accept when any constrained column's value is unknown.
2. I want to reject duplicates, even in cases when a constrained column's value is unknown.

Apparently Oracle implements something different though:
3. Reject duplicates, but accept (only) when all constrained column values are unknown.

I can think of ways to make use of Oracle's implementation to get to use case (2) -- for example, have a special value for "unknown", and make the columns non-nullable. But I can't figure out how to get to use case (1).

In other words, how can I get Oracle to act like this?

create table sandbox(a number(10,0), b number(10,0));
create unique index sandbox_idx on sandbox(a,b);

insert into sandbox values (1,1); -- accepted
insert into sandbox values (1,2); -- accepted
insert into sandbox values (1,1); -- rejected

insert into sandbox values (1,null); -- accepted
insert into sandbox values (2,null); -- accepted
insert into sandbox values (1,null); -- accepted

insert into sandbox values (null,1); -- accepted
insert into sandbox values (null,2); -- accepted
insert into sandbox values (null,1); -- accepted

insert into sandbox values (null,null); -- accepted
insert into sandbox values (null,null); -- accepted
like image 402
Chris Avatar asked Mar 23 '09 22:03

Chris


People also ask

Can a columns with the unique constraint can contain null values?

You can insert NULL values into columns with the UNIQUE constraint because NULL is the absence of a value, so it is never equal to other NULL values and not considered a duplicate value. This means that it's possible to insert rows that appear to be duplicates if one of the values is NULL .

How do you avoid NULL values in a query?

To exclude the null values from the table we need to use IS NOT NULL operator with the WHERE clause. WHERE Clause: The WHERE clause is used to filter the records. It will extract those records that fulfill the condition.

HOW MANY NOT NULL values can we use in unique constraints MySQL?

In MySQL you can not have one UNIQUE NULL value, however you can have one UNIQUE empty value by inserting with the value of an empty string. Warning: Numeric and types other than string may default to 0 or another default value.

Can unique constraint hold null values a true b false?

Another difference is that the UNIQUE constraint allows for one NULL value, but the PRIMARY KEY does not allow NULL values.


2 Answers

Try a function-based index:

create unique index sandbox_idx on sandbox(CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE a||','||b END);

There are other ways to skin this cat, but this is one of them.

like image 103
DCookie Avatar answered Sep 23 '22 16:09

DCookie


create unique index sandbox_idx on sandbox
 (case when a is null or b is null then null else a end,
  case when a is null or b is null then null else b end);

A functional index! Basically I just needed to make sure all the tuples I want to ignore (ie - accept) get translated to all nulls. Ugly, but not butt ugly. Works as desired.

Figured it out with the help of a solution to another question: How to constrain a database table so only one row can have a particular value in a column?

So go there and give Tony Andrews points too. :)

like image 35
2 revs Avatar answered Sep 25 '22 16:09

2 revs