I have problem with Postgres Unique constraint with multiple columns that may contain NULL value.
Let's assume this situation:
CREATE TEMP TABLE test (
foo TEXT,
bar TEXT,
UNIQUE (foo, bar)
);
INSERT INTO test
VALUES
('foo', NULL),
('foo', NULL),
('foo', 'bar'),
('foo', 'bar')
ON CONFLICT (foo, bar) DO NOTHING;
Insert will insert ('foo', 'bar') once and ('foo', NULL) twice (even though the intuition says it should insert once).
In this scenario solution is pretty straightforward. I could just add unique index
CREATE UNIQUE INDEX indx ON test (foo) WHERE bar IS NULL;
But the problem starts when there is more columns and with different types (not only text).
Let's say we have 10 columns and 9 of them can have NULL
value.
Maybe I could solve it with big amount of constraints, but it's not convenient at all.
Is there easier way to keep uniqueness for a row like that?
PostgreSQL unique constraint. null: Allowing only one Null. While the SQL standard allows multiple nulls in a unique column, and that is how Postgres behaves, some database systems (e.g. MS SQL) allow only a single null in such cases.
While the SQL standard allows multiple nulls in a unique column, and that is how Postgres behaves, some database systems (e.g. MS SQL) allow only a single null in such cases. Users migrating from other database systems sometimes want to emulate this behavior in Postgres.
There is a long discussion on why nullable columns with a UNIQUE constraint can contain multiple NULL values. The short version is that NULL represents missing information and comparing a field with missing information with another makes no sense.
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.
An alternative to the good solution of forbidding NULLs is to create a unique index.
All you need is a value that is guaranteed not to occur in your data set (in my example '@@'
):
CREATE UNIQUE INDEX ON test (
coalesce(foo, '@@'),
coalesce(bar, '@@')
);
You might consider the column definition in general, as both are stated as texts, you can just let them be NOT NULLABLE and provide a DEFAULT value as ' ' (empty string). This way you can be sure, that foo does not get saved twice. Also NULL values are not that good in practice, because its just a sign that there is a MISSING value.
CREATE TEMP TABLE test (
foo TEXT DEFAULT '' NOT NULL,
bar TEXT DEFAULT '' NOT NULL,
UNIQUE (foo, bar)
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With