Reputation: 959
Pretend I have a users table where users are members of a specific tenant, and their e-mails are uniquely indexed to their tenant, like this:
User
id | tenant_id | email
1 1 [email protected]
2 1 [email protected]
This user is allowed because despite a duplicate e-mail, they are at a different tenant:
3 2 [email protected]
This user is prevented because the e-mail is a duplicate at the same tenant:
4 2 [email protected] <--- will throw an error
We have this much covered with a unique index -- that part is easy.
Now pretend that I want to be able to add a global user that can access all tenants, but only if the e-mail doesn't already exist in the table at all. Additionally, once the record exists, nobody else -- whether tenanted or not -- will be able to use the same e-mail.
For clarity, the global users could simply have a null tenant ID but we would likely also add a global
boolean.
Is there a way to write constraints for this logic? You can't simply make e-mails globally uniquely constrained because they won't be able to be repeated across tenants, and if you index with a null tenant ID, postgres will allow an untenanted user if there are tenanted users with the same e-mail.
I've looked at exclusion constraints and checks but couldn't figure out how to combine them (uniquely constrain e-mail globally if tenant_id is null, and check for records with null tenant ID and matching e-mail when inserting any record).
Please don't ask why I'm doing things this way -- my table isn't actually users and we've considered and dismissed other architectures :)
Thanks in advance!
Upvotes: 4
Views: 4029
Reputation: 133
According to PostgreSQL Documentation you can create unique partial index which will be effectively the same as creating unique partial constraint on your table:
CREATE UNIQUE INDEX some_index ON some_table (col_a) WHERE (col_b is null);
Using this technique you can create 2 separate unique indexes for admin and non-admin users.
Upvotes: 3
Reputation: 10807
You can use a UNIQUE constraint for both fields:
create table myUsers
(
id int not null,
tenant int not null,
email varchar(200) not null,
UNIQUE(email, tenant)
);
insert into myUsers values
(1, 1, '[email protected]'),
(2, 1, '[email protected]');
insert into myUsers values
(3, 2, '[email protected]');
Next insert will throw an error:
insert into myUsers values
(4, 2, '[email protected]');
Error(s), warning(s):
23505: duplicate key value violates unique constraint "myusers_email_tenant_key"
Check it here: http://rextester.com/AJZVI34616
For the second part of the question:
Now pretend that I want to be able to add a global user that can access all tenants, but only if the e-mail doesn't already exist in the table at all.
One solution could be to reserve a tenant for admin users:
tenant = 0 <-- admin users
But the UNIQUE constraint allow duplicated emails, I recommend you to add a rol
field to this table, or have another table of admin users for this purpose.
In my case, we use two tables, and both have a rol
field.
Upvotes: 0