PostgreSQL: Unique Constraint With Null Values

By default, PostgreSQL's UNIQUE constraint treats NULL values as distinct values. Same behaviour is applicable to indexes with null values as well. The implication is that PostgreSQL will store multiple rows for cases where we may not expect it to do so, if one of the columns included to the UNIQUE constraint is NULL. Apparently this behavior is aligned with the SQL standard definition regarding NULL values uniqueness.

If you rely on a DB to validate uniqueness constraint for a set of columns when some/all of the columns are nullable, you need to properly declare the constraint.

For a quick example, let's create a books table with state column that can be nullable and adding a unique constraint to reflect our business rule to allow only a single record for the case when books.state = NULL:

CREATE TABLE books (
    book_id text NOT NULL,
    state text NULL,
    quantity integer NOT NULL
);

ALTER TABLE ONLY books ADD CONSTRAINT unique_book UNIQUE (book_id, state);        

Adding few records - notice that we successfully persist several records that have the same value of book_id with a status equal NULL, because by default NULL values are considered unique (that is, null != null):

INSERT INTO books (book_id, state, quantity)
    VALUES('1234', 'new', 12);

INSERT INTO books (book_id, state, quantity)
    VALUES('1234', 'used', 9);
   
INSERT INTO books (book_id, state, quantity)
    VALUES('1234', NULL, 3);   

INSERT INTO books (book_id, state, quantity)
    VALUES('1234', NULL, 15);        

Selecting from the table:

SELECT * FROM books;

-- output:
book_id|state|quantity|
-------+-----+--------+
1234   |new  |      12|
1234   |used |       9|
1234   |     |       3|
1234   |     |      15|        

Starting from PostgreSQL 15, there is a new clause, NULLS NOT DISTINCT, that we can use to change the default behavior and to prevent PostgreSQL from storing multiple records with NULL values:

CREATE TABLE books (
    book_id text NOT NULL,
    state text NULL,
    quantity integer NOT NULL
);

ALTER TABLE ONLY books ADD CONSTRAINT unique_book UNIQUE NULLS NOT DISTINCT (book_id, state);        

Now if we try to insert two records with status = NULL, the DB will throw an error, as we expect:

INSERT INTO books (book_id, state, quantity)
    VALUES('1234', NULL, 3);

INSERT INTO books (book_id, state, quantity)
    VALUES('1234', NULL, 15);

ERROR: duplicate key value violates unique constraint "unique_book"
  Detail: Key (book_id, state)=(1234, null) already exists.        

References:

John James

Software Engineer - Java/Java EE/Spring Developer - Docker - AWS

1y

Useful information. Well written. Bravo! 👏🏻

To view or add a comment, sign in

More articles by Mykhaylo Symulyk

Insights from the community

Others also viewed

Explore topics