I have the following 2 tables:
Bookings
- User ID
Shifts
- Booking ID
- Date
I'd like to make sure that a user isn't booked in more than one shift on the same date. In other words, a UNIQUE constraint for fields [Bookings.UserID, Shifts.date]
Can I do this in PostgreSQL?
You can define a UNIQUE constraint at the column or the table level. Only at the table level, you can define a UNIQUE constraint across multiple columns. Once a UNIQUE constraint is defined, if you attempt to insert or update a value that already exists in the column, SQLite will issue an error and abort the operation.
If you create a unique constraint then by default a non-clustered Index has been created. You can create a maximum of 999 (Non-Clustered Indexes) per table as per limitations. You can create 999 Unique constraints per table.
UNIQUE key does not allow duplicate values. UNIQUE key allows NULL values but does not allow NULL values multiple times. We can create multiple UNIQUE columns on one table however only one PRIMARY KEY for table. Defining primary key on a column has a UNIQUE constraint property by default.
A table can have more than one unique key unlike primary key. Unique key constraints can accept only one NULL value for column. Unique constraints are also referenced by the foreign key of another table.
Indexes in PostgreSQL are on one table and cannot span multiple tables.
But you can solve your problem with proper tables layout. What you probably need is an n:m relationship, implemented with 3 tables. Then there are various ways to enforce your condition.
You can store the date in booking redundantly, create the primary key on (usr_id, shift_date)
instead of (usr_id, shift_id)
and guarantee referential integrity with a multi-column foreign key on (shift_id, shift_date)
:
CREATE TABLE usr(
usr_id serial PRIMARY KEY
--, more?
);
CREATE TABLE shift(
shift_id serial PRIMARY KEY
, shift_date date
--, more?
);
This also requires a UNIQUE INDEX
on shift
first:
CREATE UNIQUE INDEX shift_date_idx ON shift (shift_id, shift_date);
CREATE TABLE booking(
usr_id int REFERENCES usr(usr_id)
, shift_id int
, shift_date date
--, more?
, CONSTRAINT booking_pkey PRIMARY KEY (usr_id, shift_date)
, CONSTRAINT booking_fkey FOREIGN KEY (shift_id, shift_date)
REFERENCES shift(shift_id, shift_date)
);
Another, less restrictive, but also less reliable way would be a trigger ON INSERT OR UPDATE
on the table booking
that checks if the user has a shift with the new date already. Could work without the redundant date column.
I would go with the first solution.
There are no indexes on views in PostgreSQL either.
You could create a function that returns shift_date
for a given shift_id
and declare it IMMUTABLE
(although, in fact, it isn't). With that you could create a multi-column index on an expression:
CREATE function f_shift_date (shift_id int)
RETURNS date
LANGUAGE sql IMMUTABLE AS
$func$
SELECT shift_date FROM shift WHERE shift_id = $1;
$func$;
CREATE UNIQUE INDEX shift_date_idx ON booking (shift_id, f_shift_date(shift_id));
This would enforce your condition. But honestly, this is pretty suicidal. The index relies on the condition that you get the same date for a shift_id
indefinitely. If shift_date
ever changes relative to its shift_id
, the index has to be recreated or it will yield incorrect results.
So, don't do that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With