status
should really be boolean
. Cheaper, cleaner.
Either way, you can impose your rule with a partial unique index.
To allow zero or one row with status = 'Active'
in the whole table:
CREATE UNIQUE INDEX tbl_active_uni ON tbl (status)
WHERE status = 'Active';
To allow zero or one row with status = 'Active'
per userid
, make userid
the indexed column:
CREATE UNIQUE INDEX tbl_userid_active_uni ON tbl (userid)
WHERE status = 'Active';
Null values in userid
do not trigger unique violations, because two null values are not considered equal - at all, or by default since Postgres 15 which added the NULLS NOT DISTINCT
clause. If you don't want that, set the column NOT NULL
, or see:
Why use an index and not a constraint?
Addressing your question in the comment: This is an index, not a CONSTRAINT
.
The index for the first case is tiny, holding one or no row.
The index for the second case holds one row per existing userid
, but it's the cheapest and fastest way, in addition to being clean and safe. You would need an index to check on other rows in any case to make this fast.
You cannot have a CHECK
constraint check on other rows - at least not in a clean, reliable fashion. There are ways I would certainly not recommend for this case:
If you use a UNIQUE
constraint on (userid, status)
(which is also implemented with a unique index internally!), you cannot make it partial, and all combinations are enforced to be unique. You could still use this if you work with status IS NULL
for all cases except the 'Active'
case. But that would create a much bigger index including all rows.
IsActive
of boolean type instead of storingVARCHAR
or if you have more statuses use INT type and add lookup table. – Sirloin