Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Postgres unique constraint not enforcing uniqueness

Tags:

postgresql

Here is my constraint:

CREATE UNIQUE INDEX index_subscriptions_on_user_id_and_class_type_id_and_deleted_at
  ON subscriptions
  USING btree
  (user_id, class_type_id, deleted_at);

This query proves the constraint is not actually working:

SELECT id, user_id, class_type_id,deleted_at
FROM subscriptions;

Here is the output:

enter image description here

Why is uniqueness not being enforced?

like image 712
Lee Avatar asked May 03 '14 20:05

Lee


People also ask

What is unique constraint in PostgreSQL?

Summary: in this tutorial, you will learn about PostgreSQL UNIQUE constraint to make sure that values stored in a column or a group of columns are unique across rows in a table. Sometimes, you want to ensure that values stored in a column or a group of columns are unique across the whole table such as email addresses or usernames.

How does PostgreSQL implement the concept of partial uniqueness?

So, the way Postgresql implements it, is very logical: first, you declare that a column should be unique, then, Postgresql adds the implementation of adding an unique index for you. "So I think it is what you call "partial uniqueness" by adding a constraint."

How do I create a unique index in PostgreSQL?

When you add a UNIQUE constraint to a column or a group of columns, PostgreSQL will automatically create a unique index on the column or the group of columns. The following statement creates a new table named person with a UNIQUE constraint for the email column.

Can a unique constraint have a unique index?

Since various people have provided advantages of unique indexes over unique constraints, here's a drawback: a unique constraint can be deferred (only checked at the end of the transaction), a unique index can not be. How can this be, given that all unique constraints have a unique index?


2 Answers

Unique indexes in Postgres are based on values being equal, but NULL is never equal to anything, including other NULLs. Therefore any row with a NULL deleted_at value is distinct from any other possible row - so you can insert any number of them.

One way around this is to create partial indexes, applying different rules to rows with and without NULLs:

 CREATE UNIQUE INDEX ... ON subscriptions
 (user_id, class_type_id) WHERE deleted_at IS NULL;

 CREATE UNIQUE INDEX ... ON subscriptions
 (user_id, class_type_id, deleted_at) WHERE deleted_at IS NOT NULL;
like image 96
IMSoP Avatar answered Sep 16 '22 14:09

IMSoP


This happens because of the NULL value in the created_at column. A unique index (or constraint) allows multiple rows with NULL in them.

The only way you can prevent that is to either declare the column as NOT NULL in order to force a value for it, or reduce the unique columns to (user_id, class_type_id)

like image 30
a_horse_with_no_name Avatar answered Sep 17 '22 14:09

a_horse_with_no_name