Create a PostgreSQL constraint to prevent unique combination rows
You can use an exclusion constraint with btree_gist
,
-- This is needed
CREATE EXTENSION btree_gist;
Then we add a constraint that says:
"We can't have 2 rows that have the same name
and different is_active
":
ALTER TABLE table_name
ADD CONSTRAINT only_one_is_active_value_per_name
EXCLUDE USING gist
( name WITH =,
is_active WITH <> -- if boolean, use instead:
-- (is_active::int) WITH <>
);
Some notes:
is_active
can be integer or boolean, makes no difference for the exclusion constraint. (actually it does, if the column is boolean you need to use(is_active::int) WITH <>
.)- Rows where
name
oris_active
is null will be ignored by the constraint and thus allowed. - The constraint makes sense only if the table has more columns. Otherwise, if the table has only these 2 columns, a
UNIQUE
constraint on(name)
alone would be easier and more appropriate. I don't see any reason for storing multiple identical rows. - The design violates 2NF. While the exclusion constraint will save us from update anomalies, it may not from performance issues. If you have for example 1000 rows with
name = 'A'
and you want to to update is_active status from 0 to 3, all 1000 will have to be updated. You should examine whether normalizing the design would be more efficient. (Normalizing meaning in this case to remove is_active status from the table and add a 2-column table with name, is_active and a unique constraint on(name)
. Ifis_active
is boolean, it could be totally stripped and the extra table just a single column table, storing only the "active" names.)
This is not a case where you can use a unique index. You can test the condition in a trigger, e.g.:
create or replace function a_table_trigger()
returns trigger language plpgsql as $$
declare
active int;
begin
select is_active into active
from a_table
where name = new.name;
if found and active is distinct from new.is_active then
raise exception 'The value of is_active for "%" should be %', new.name, active;
end if;
return new;
end $$;
Test it here.