Postgres conditional unique constraint - postgresql

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 person1#example.com
2 1 person2#example.com
This user is allowed because despite a duplicate e-mail, they are at a different tenant:
3 2 person1#example.com
This user is prevented because the e-mail is a duplicate at the same tenant:
4 2 person1#example.com <--- 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!

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.

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, 'person1#example.com'),
(2, 1, 'person2#example.com');
insert into myUsers values
(3, 2, 'person1#example.com');
Next insert will throw an error:
insert into myUsers values
(4, 2, 'person1#example.com');
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.

Related

Obfuscating data for other roles in Postgres

To comply with privacy guidelines, I want to binary obfuscate the contents of certain columns to other roles, including the administrative role/developers.
A table could look like this:
create table customer (
id serial primary key,
role text not null default session_user,
name text not null,
address text not null,
phone bigint default null
);
create policy customer_policy on customer
for all to public using (role = session_user);
In this example, the column contents such as name, address and phone should not be visible to other roles.
the policy only guarantees that roles of other database users cannot see the records, but the administrator with higher privileges, for example, can still see the data.
my idea is that a password is stored in another table that is created and changed by the respective role. the relevant columns are then encrypted and decrypted using this password.
How could this be implemented or does PostgreSQL already offer solutions for this?

duplicate key value violates unique constraint "core_user_phone_number_client_key" DETAIL: Key (phone_number, clientid)=(b9e507949695) already exists

I am using Cognito to authenticate users on the sign-up and once a user click create an account it should be directed to the verification screen(number and email) but instead I user is facing this error
duplicate key value violates unique constraint "core_user_phone_number_client_key" DETAIL: Key (phone_number, clientid)=(b9e507949695) already exists.
I am not quite sure if this error is related only to Cognito or to the database (Postgres) as I can't see the record on the table but when I try to create an account with the same rejected email is says user already exist but when to try to sign up its say user does not exist( its so tricky)
That certainly looks like a PostgreSQL error, except the part where the key has two columns, but the data only shows one column.
What would usually cause this error is that one transaction tries to insert the same data twice, and so conflicts with itself. Since the transaction rolls back, both rows are gone. So an outside observer can never spot the offending data as it never exists in a committed (visible) form.
from a colleague, so this will solve the issue
operations = [
migrations.RunSQL('CREATE UNIQUE INDEX IF NOT EXISTS core_user_username_key ON core_user (username);'),
# Make sure we take into account related client's id for unique index, to limit uniqueness
# verification by client profiles set (several clients may have profiles with the same email & phone number)
migrations.RunSQL('CREATE UNIQUE INDEX IF NOT EXISTS core_user_email_client_key '
'ON core_user (email, client_id) WHERE is_active = TRUE;'),
migrations.RunSQL('CREATE UNIQUE INDEX IF NOT EXISTS core_user_phone_number_client_key '
'ON core_user (phone_number, client_id) WHERE is_active = TRUE;')
]

Role-based DB design. Role as user attribute or role-specific tables

I'm developing a web application with several roles for its users. The DB in use is Postgres 10. The roles are very different and have much non-overlapping data so that oftentimes a specific table should belong only to one role and not to another.
In the examples below, I'll use two roles: seller and buyer. The first role is assigned to a user which has some company and sells goods. The second role is assigned to a user who buys goods and can receive gifts. Seller cannot receive gifts and buyer cannot have a company. Both seller and buyer can have an avatar.
I'm considering the following four designs with their cons and pros.
Case 1
Here we have a role reference table which enumerates all the roles in the system (seller or buyer), user table with password and email of the user which references the role table, avatar, company and gifts_recieved tables reference user table.
PROs
No tables have NULL-able attributes
CONs
Trigger should be used to check referential integrity. For example, if a user with id=1 is a buyer, we should prevent insertion of a row to the company table with user_id=1. Thus, we should create a trigger on every role-specific table which references user table to guard referential integrity.
Case 2
Here, user table contains nullable role attributes: seller_id and buyer_id (or other roles which are not in the example). Only one of them is not NULL and it defines user's role. All role-specific tables reference either seller or buyer table. Avatar table which is common for both roles continues to reference the user table.
PROs
No need for triggers to guard referential integrity of role-specific tables: they all reference either seller or buyer table.
CONs
NULL-able role attributes.
A single trigger (not triggers for all tables as in case 1) is required to ensure only one of role attributes (seller_id or buyer_id) is not NULL and all other are NULL.
Case 3
To avoid NULL-able role attributes, we'll use Postgres' inheritance for tables (this feature is not supported by MySQL). Here, user table is 'swallowed' into seller and buyer tables. Avatar table has got duplicated and prefixed with role name.
PROs
No NULL-able role attributes.
No triggers at all.
CONs
Tables which are common for different roles are duplicated - a pure waste of resources.
Inheritance is Postgres-specific feature.
Case 4
To solve the duplication problem from Case 3, avatar_id was moved to user table from which seller and buyer inherit. Thus, for every table which is common to seller and buyer we'll add an attribute to their parent table (user).
PROs
No triggers for referential integrity
No table duplication
No NULL-able atrrributes
CONs
Postgres inheritance has its caveats: https://www.postgresql.org/docs/10/ddl-inherit.html
Question
What solution of the above fit the best DB design practices (if any)? Should user role be an attribute of user table or be a specific table(s)?
Clarifications
A seller can have only one company.
A user identified by email/password pair can be either a seller or a buyer.
I am partial to Case 2.
I would not consider Cases 3 and 4 because of the use of inheritance. I have not visited this topic in many years, but my biggest concern about it was whether developers using a database implementing inheritance would be able to wrap their minds around how to use it.
The trigger is the sticking point for Case 1 in my mind.
Are you aware that you do not need a trigger in Case 2?
create table exclusion_constraint (
user_id int,
seller_id int,
buyer_id int,
check (case
when coalesce(seller_id, buyer_id) is null then false
when seller_id is not null and buyer_id is not null then false
else true
end)
);
CREATE TABLE
insert into exclusion_constraint values (1, 100, 200);
ERROR: new row for relation "exclusion_constraint" violates check constraint "exclusion_constraint_check"
DETAIL: Failing row contains (1, 100, 200).
insert into exclusion_constraint values (2, null, 200);
INSERT 0 1
insert into exclusion_constraint values (3, 100, null);
INSERT 0 1
insert into exclusion_constraint values (4, null, null);
ERROR: new row for relation "exclusion_constraint" violates check constraint "exclusion_constraint_check"
DETAIL: Failing row contains (4, null, null).

What suitable locking technique to prevent insertion of data? [PostgreSQL]

I want to ensure that one worker has exactly one manager
CREATE TABLE IF NOT EXISTS user_relationships (
object_id SERIAL PRIMARY KEY NOT NULL UNIQUE,
manager_id INT REFERENCES users (object_id) NOT NULL,
worker_id INT REFERENCES users (object_id) NOT NULL,
CHECK (manager_id != worker_id),
UNIQUE (manager_id, worker_id)
);
I have series of SQL statements using Read Committed level of transaction isolation,
BEGIN
SELECT * FROM users WHERE id=manager_id AND acc_type="manager" FOR UPDATE;
SELECT * FROM users WHERE id=worker_id AND acc_type="worker" FOR UPDATE;
SELECT * FROM relationships WHERE id=worker;
INSERT INTO relationships (m, w) VALUES (manager_id, worker_id)
COMMIT
I have figured out the first two FOR UPDATE to prevent other
concurrent transactions from changing the users account type mid
transaction
I could not figure out what kind of "trick" to use for third query. Third query should return empty list to ensure that the worker has not yet been owned by any manager.
Third query FOR UPDATE does not work because I am expecting an empty row.
Due to third query, I run the risk of concurrent transaction adding duplicate worker to different managers.
What can I do to enforce one worker to one manager?
Probably you need to add UNIQUE to constraint to worker_id:
CREATE TABLE IF NOT EXISTS user_relationships (
object_id SERIAL PRIMARY KEY NOT NULL UNIQUE,
manager_id INT REFERENCES users (object_id) NOT NULL,
worker_id INT REFERENCES users (object_id) UNIQUE NOT NULL,
CHECK (manager_id != worker_id)
);
But is better to add field manager_id INT REFERENCES users (object_id) NOT NULL to table users and do not use user_relationships.

Multiple ID's in a field. Postgresql

So, i'm currently working with a database system where a user can register, login and update his/her details whenever.
The database includes 5 roles:
1. Public
2. Member
3. Moderator
4. Coordinator
5. Admin
I want to be able to assign multiple roles to my users. For example, the Admin can also be a member. Therefore in the database it should show:
User_id | Role_ID
------------------------
user1 | 2, 5
^ is it possible to add multivalued id's in postgresql?
You can use filed of type array to store list of values.
However I think that there is much better way to organize what you want.
Make one table: role_names and another roles, like that:
CREATE TABLE role_names
(
id serial NOT NULL,
name text NOT NULL,
CONSTRAINT role_names_pkey PRIMARY KEY (id),
CONSTRAINT role_names_name_key UNIQUE (name)
);
CREATE TABLE roles
(
user_id bigint NOT NULL,
role_id bigint NOT NULL,
CONSTRAINT roles_pkey PRIMARY KEY (user_id, role_id),
CONSTRAINT roles_role_id_fkey FOREIGN KEY (role_id)
REFERENCES role_names (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE RESTRICT
);
Now in the table role_names you put all the roles that you want to have.
In the table roles, you can assign or delete any number of roles to any user.
Also you can search in table roles for specific users or specific roles - much neat and faster than searching into arrays I think.
Feel free to add FK constraint for the user_id field too.
Yes, you can use int array to store list of roles.
Here's related question -Junction tables vs foreign key arrays?