PostgreSQL multi-column unique constraint and NULL values
You can do that in pure SQL. Create a partial unique index in addition to the one you have:
CREATE UNIQUE INDEX ab_c_null_idx ON my_table (id_A, id_B) WHERE id_C IS NULL;
This way you can enter for (id_A, id_B, id_C)
in your table:
(1, 2, 1)
(1, 2, 2)
(1, 2, NULL)
But none of these a second time.
Or use two partial UNIQUE
indexes and no complete index (or constraint). The best solution depends on the details of your requirements. Compare:
- Create unique constraint with null columns
While this is elegant and efficient for a single nullable column in the UNIQUE
index, it gets out of hand quickly for more than one. Discussing this - and how to use UPSERT with partial indexes:
- PostgreSQL UPSERT issue with NULL values
Asides
No use for mixed case identifiers without double quotes in PostgreSQL.
You might consider a serial
column as primary key or an IDENTITY
column in Postgres 10 or later. Related:
- Auto increment table column
So:
CREATE TABLE my_table (
my_table_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY -- for pg 10+
-- my_table_id bigserial PRIMARY KEY -- for pg 9.6 or older
, id_a int8 NOT NULL
, id_b int8 NOT NULL
, id_c int8
, CONSTRAINT u_constraint UNIQUE (id_a, id_b, id_c)
);
If you don't expect more than 2 billion rows (> 2147483647) over the lifetime of your table (including waste and deleted rows), consider integer
(4 bytes) instead of bigint
(8 bytes).
I had the same problem and I found another way to have unique NULL into the table.
CREATE UNIQUE INDEX index_name ON table_name( COALESCE( foreign_key_field, -1) )
In my case, the field foreign_key_field
is a positive integer and will never be -1.
So, to answer Manual Leduc, another solution could be
CREATE UNIQUE INDEX u_constrainte (COALESCE(id_a, -1), COALESCE(id_b,-1),COALESCE(id_c, -1) )
I assume that ids won't be -1.
What is the advantage on creating a partial index ?
In case where you don't have the NOT NULL clause, id_a
, id_b
and id_c
can be NULL together only once.
With a partial index, the 3 fields could be NULL more than once.
A Null can mean that value is not known for that row at the moment but will be added, when known, in the future (example FinishDate
for a running Project
) or that no value can be applied for that row (example EscapeVelocity
for a black hole Star
).
In my opinion, it's usually better to normalize the tables by eliminating all Nulls.
In your case, you want to allow NULLs
in your column, yet you want only one NULL
to be allowed. Why? What kind of relationship is this between the two tables?
Perhaps you can simply change the column to NOT NULL
and store, instead of NULL
, a special value (like -1
) that is known never to appear. This will solve the uniqueness constraint problem (but may have other possibly unwanted side effects. For example, using -1
to mean "not known / does not apply" will skew any sum or average calculations on the column. Or all such calculations will have to take into account the special value and ignore it.)