Constrain Values per Overlying Group - postgresql

Each organisation has a unique name.
Within each organisation can be multiple teams, whose names should be unique, but only within their own organisation.
Also, each team can have projects, which names again should be unique per team.
Overall, expect for the organisation, names should be unique per their own "group", but can be duplicates otherwise.
I think I would need to create a constraint that CHECKs whether a given name already exists in the "overlying group", but I struggle to do this with the complex SQL (exclusion) constraints.
Would it be possible to write such a constraint ?

Assuming this is for PostgreSQL based on the two other tags (Postgresql & Supabase).
You can do this directly in the primary keys. Here's how the bare bones of such a structure would look like:
CREATE TABLE organization (
org_name text NOT NULL,
PRIMARY KEY (org_name)
);
CREATE TABLE teams (
org_name text NOT NULL,
team_name text NOT NULL,
PRIMARY KEY (org_name, team_name),
FOREIGN KEY (org_name)
REFERENCES organization (org_name)
);
CREATE TABLE projects (
org_name text NOT NULL,
team_name text NOT NULL,
project_name text NOT NULL,
PRIMARY KEY (org_name, team_name, project_name),
FOREIGN KEY (org_name)
REFERENCES organization (org_name),
FOREIGN KEY (org_name, team_name)
REFERENCES teams (org_name, team_name)
);
Notes, this will allow things like ORg Contoso & Org CONTOSO you may want to pick citext instead of text as the type to avoid such names.

Related

Composite FK referencing atomic PK + non unique attribute

I am trying to create the following tables in Postgres 13.3:
CREATE TABLE IF NOT EXISTS accounts (
account_id Integer PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS users (
user_id Integer PRIMARY KEY NOT NULL,
account_id Integer NOT NULL REFERENCES accounts(account_id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS calendars (
calendar_id Integer PRIMARY KEY NOT NULL,
user_id Integer NOT NULL,
account_id Integer NOT NULL,
FOREIGN KEY (user_id, account_id) REFERENCES users(user_id, account_id) ON DELETE CASCADE
);
But I get the following error when creating the calendars table:
ERROR: there is no unique constraint matching given keys for referenced table "users"
Which does not make much sense to me since the foreign key contains the user_id which is the PK of the users table and therefore also has a uniqueness constraint. If I add an explicit uniqueness constraint on the combined user_id and account_id like so:
ALTER TABLE users ADD UNIQUE (user_id, account_id);
Then I am able to create the calendars table. This unique constraint seems unnecessary to me as user_id is already unique. Can someone please explain to me what I am missing here?
Postgres is so smart/dumb that it doesn't assume the designer to do stupid things.
The Postgres designers could have taken different strategies:
Detect the transitivity, and make the FK not only depend on users.id, but also on users.account_id -> accounts.id. This is doable but costly. It also involves adding multiple dependency-records in the catalogs for a single FK-constraint. When imposing the constraint(UPDATE or DELETE in any of the two referred tables), it could get very complex.
Detect the transitivity, and silently ignore the redundant column reference. This implies: lying to the programmer. It would also need to be represented in the catalogs.
cascading DDL operations would get more complex, too. (remember: DDL is already very hard w.r.t. concurrency/versioning)
From the execution/performance point of view: imposing the constraints currently involves "pseudo triggers" on the referred table's indexes. (except DEFERRED, which has to be handled specially)
So, IMHO the Postgres developers made the sane choice of refusing to do stupid complex things.

Citus: How can I add self referencing table in distributed tables list

I'm trying to run create_distributed_table for tables which i need to shard and almost all of the tables have self relation ( parent child )
but when I run SELECT create_distributed_table('table-name','id');
it throws error cannot create foreign key constraint
simple steps to reproduce
CREATE TABLE TEST (
ID TEXT NOT NULL,
NAME CHARACTER VARYING(255) NOT NULL,
PARENT_ID TEXT
);
ALTER TABLE TEST ADD CONSTRAINT TEST_PK PRIMARY KEY (ID);
ALTER TABLE TEST ADD CONSTRAINT TEST_PARENT_FK FOREIGN KEY (PARENT_ID) REFERENCES TEST (ID);
ERROR
citus=> SELECT create_distributed_table('test','id');
ERROR: cannot create foreign key constraint
DETAIL: Foreign keys are supported in two cases, either in between two colocated tables including partition column in the same ordinal in the both tables or from distributed to reference tables
For the time being, it is not possible to shard a table on PostgreSQL without dropping the self referencing foreign key constraints, or altering them to include a separate and new distribution column.
Citus places records into shards based on the hash values of the distribution column values. It is most likely the case that the hashes of parent and child id values are different and hence the records should be stored in different shards, and possibly on different worker nodes. PostgreSQL does not have a mechanism to create foreign key constraints that reference records on different PostgreSQL clusters.
Consider adding a new column tenant_id and adding this column to the primary key and foreign key constraints.
CREATE TABLE TEST (
tenant_id INT NOT NULL,
id TEXT NOT NULL,
name CHARACTER VARYING(255) NOT NULL,
parent_id TEXT NOT NULL,
FOREIGN KEY (tenant_id, parent_id) REFERENCES test(tenant_id, id),
PRIMARY KEY (tenant_id, id)
);
SELECT create_distributed_table('test','tenant_id');
Note that parent and child should always be in the same tenant for this to work.

PSQL enforce uniqueness, but within a subset of a table, not through the entire thing

I'm creating a database of assessments for courses using PostgreSQL.
I'd like assessment names to be unique within the course, but two courses can have assessments with the same name.
-- assessment contains the different assignments & labs that
-- students may submit their code to.
CREATE TABLE assessment (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
comments TEXT NOT NULL,
type ASSESSMENT_TYPE NOT NULL,
course_id SERIAL NOT NULL,
FOREIGN KEY (course_id) REFERENCES courses(id)
);
-- courses contains the information about a course. Since
-- the same course can run multiple times, a single course
-- is uniquely identified by (course_code, year, period)
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL, -- Unique within all courses. Wrong!
course_code VARCHAR(20) NOT NULL,
period PERIOD NOT NULL,
year INTEGER NOT NULL
);
Two main points:
Can I do this without changing the schema?
If so, is there a more idiomatic solution that may include schema changes?
1. Can I do this without changing the schema?
No, since you have multiple issues here.
Your assessments are globally unique by name and not within a course.
assessment.course_id has its own sequence which is useless (SERIAL is just INTEGER + SEQUENCE)
Table courses defines a column data type that does not exist: PERIOD (at least not up to version 11)
2. If so, is there a more idiomatic solution that may include schema changes?
A modified schema that should do what you want would look like this following:
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
course_code VARCHAR(20) NOT NULL,
period tstzrange NOT NULL
);
-- the following is required to build the proper unique constraint...
CREATE EXTENSION IF NOT EXISTS btree_gist;
-- the unique constraint: no two courses with same name at any point in time
ALTER TABLE courses
ADD CONSTRAINT idx_unique_courses
EXCLUDE USING GIST (name WITH =, period WITH &&);
CREATE TABLE assessment (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
comments TEXT NOT NULL,
type ASSESSMENT_TYPE NOT NULL,
course_id INTEGER NOT NULL REFERENCES courses(id),
UNIQUE (course_id, name)
);

PostgreSQL audit table design with Multiple "User types"

I'm trying to implement an Audit table design in PostgreSQL, where I have different types of user id's that can be audited.
Let's say I have a table named admins (which belong to an organization), and table superadmins (which don't).
CREATE TABLE example.organizations (
id SERIAL UNIQUE,
company_name varchar(50) NOT NULL UNIQUE,
phone varchar(20) NOT NULL check (phone ~ '^[0-9]+$')
);
and an example of a potential admin design
CREATE TABLE example.admins (
id serial primary_key,
admin_type varchar not null,
#... shared data
check constraint admin_type in ("super_admins", "regular_admins")
);
CREATE TABLE example.regular_admins (
id integer primary key,
admin_type varchar not null default "regular_admins"
organization_id integer references example.organizations(id),
#... other regular admin fields
foreign key (id, admin_type) references example.admins (id, admin_type),
check constraint admin_type = "regular_admins"
);
CREATE TABLE example.super_admins (
id integer primary key,
admin_type varchar not null default "super_admins"
#... other super admin fields
foreign key (id, admin_type) references example.admins (id, admin_type),
check constraint admin_type = "super_admins"
);
Now an audit table
CREATE TABLE audit.organizations (
audit_timestamp timestamp not null default now(),
operation text,
admin_id integer primary key,
before jsonb,
after jsonb,
);
This calls for inheritance or polymorphism at some level, but I'm curious about how to design it. I've heard that using PostgreSQL's inheritance functionality is not always a great way to go, although I'm finding it to fit this use case.
I'll need to be able to reference a single admin id in the trigger that updates the audit table, and it would be nice to be able to get the admin information when selecting from the audit table without using multiple queries.
Would it be better to use PostgreSQL inheritance or are there other ideas I haven't considered?
I wouldn't say that it calls for inheritance or polymorphism. Admins and superadmins are both types of user, whose only difference is that the former belong to an organization. You can represent this with a single table and a nullable foreign key. No need to overcomplicate matters. Especially if you're using a serial as your primary key type: bad things happen if you confuse admin #2 for superadmin #2.

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?