constraint on overlapping intervals in postgres records - sql

So I am making a booking system and I have the following table for reservations.
CREATE TABLE reservations (
user_id INT REFERENCES users (id),
booth_number INT REFERENCES booths (booth_number),
starts DATE NOT NULL,
ends DATE NOT NULL,
PRIMARY KEY (user_id, booth_number)
);
and I would like to know if it is possible to put a constraint on a database level that disallows insertion on overlapping reservations on the same booth?
e.g
INSERT INTO reservations( user_id, booth_number, starts, ends)
VALUES
(1, 0, CURRENT_DATE, CURRENT_DATE + 7),
(2, 0, CURRENT_DATE + 4, CURRENT_DATE + 9),

Almost the exact scenario(room instead of booth) is in the docs:
https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-CONSTRAINT
"
You can use the btree_gist extension to define exclusion constraints on plain scalar data types, which can then be combined with range exclusions for maximum flexibility. For example, after btree_gist is installed, the following constraint will reject overlapping ranges only if the meeting room numbers are equal:"
CREATE EXTENSION btree_gist;
CREATE TABLE room_reservation (
room text,
during tsrange,
EXCLUDE USING GIST (room WITH =, during WITH &&)
);
To modify for your use:
EXCLUDE USING GIST (booth_number WITH =, daterange(starts, ends, '[]') WITH &&))

Related

CUSTOM UNIQUE CHECK POSTGRES

I am working on a task where I need to store the interviewer's time slot in the table INTERVIEW_SLOT. The table schema is like this:
CREATE TABLE INTERVIEW_SLOT (
ID SERIAL PRIMARY KEY NOT NULL,
INTERVIEWER INTEGER REFERENCES USERS(ID) NOT NULL,
START_TIME TIMESTAMP NOT NULL, -- start time of interview
END_TIME TIMESTAMP NOT NULL, -- end time of interview
IS_BOOKED BOOL NOT NULL DEFAULT 'F', -- slot is booked by any candidate or not
CREATED_ON TIMESTAMP,
-- interviewer can't give the same slot twice
CONSTRAINT UNIQUE_INTERVIEW_SLOT UNIQUE (start_time, INTERVIEWER)
);
We want to ensure that the interviewer can not give the same slot twice but the problem is with second and millisecond values of start_time. I want the UNIQUE_INTERVIEW_SLOT constant like this:
UNIQUE_INTERVIEW_SLOT UNIQUE(TO_TIMESTAMP(start_time::text, 'YYYY-MM-DD HH24:MI'), INTERVIEWER)
Is there any way to add a unique constraint that ignores the second and millisecond value?
You are looking for an exclusion constraint
create table interview_slot
(
id integer primary key generated always as identity,
interviewer integer references users(id) not null,
start_time timestamp not null, -- start time of interview
end_time timestamp not null, -- end time of interview
is_booked bool not null default 'f', -- slot is booked by any candidate or not
created_on timestamp,
constraint unique_interview_slot
exclude using gist (interviewer with =,
tsrange(date_trunc('minute', start_time), date_trunc('minute', end_time), '[]') with &&)
);
This prevents rows with overlapping start/end ranges for the same interviewer. The timestamps are "rounded" to the full minute. You need the extension btree_gist in order to create that constraint.
You can use an UNIQUE INDEX to make this check for you and truncate the timestamp to minutes:
CREATE UNIQUE INDEX idx_interview_slot_ts
ON interview_slot (interviewer, date_trunc('minutes',start_time));
Demo: db<>fiddle

Postgres stored procedure(function) confusion

I'm pretty new to Postgres and SQL as a whole and could use a bit of help with a function here.
I have this table:
CREATE TABLE car_rentals(
plate varchar(10) NOT NULL,
start_date date NOT NULL,
end_date date NOT NULL,
license_nr varchar(10) NOT NULL,
CONSTRAINT unq_car_rentals_start UNIQUE (plate, start_date),
CONSTRAINT unq_car_rentals_end UNIQUE (plate, end_date));
What i need is for a function to take as input plate, start date and end date and
throw an error if the table contains any row with the same plate where the rental period is different
from but overlaps with the given period.
I found this document on reporting errors but I'm not sure how to properly format the function in order to implement what I need.
https://www.postgresql.org/docs/9.6/plpgsql-errors-and-messages.html
You don't need a trigger to achieve this. Postgres supports so called exclusion constraints exactly for this purpose:
create table car_rentals
(
plate varchar(10) NOT NULL,
start_date date NOT NULL,
end_date date NOT NULL,
license_nr varchar(10) NOT NULL,
constraint no_overlapping_ranges
exclude using gist (plate with =, daterange(start_date, end_date, '[]') with &&)
);
The constraint definition in details:
plate with = for rows with the same plate number
daterange(start_date, end_date, '[]') with && - fail if there are rows with an overlapping range.
As the constraint includes the = operator, you need to install the extension btree_gist in order to create the above constraint:
As a superuser run:
create extension btree_gist;
in the database where you want to create the car_rentals database (you only need to do this once).
Online example
The WHERE condition of a query you could use in a trigger could be:
WHERE daterange(start_date, end_date, '[]') && daterange($1, $2, '[]') -- overlaps
AND (start_date <> $1 OR end_date <> $2) -- is not equal
AND plate = $3
Here $1, $2 and $3 are placeholders for the data against which you want to check, and the query becomes simple by using the "overlaps" range operator &&.

SQL - How do you use a user defined function to constrain a value between 2 tables

First here's the relevant code:
create table customer(
customer_mail_address varchar(255) not null,
subscription_start date not null,
subscription_end date, check (subscription_end !< subcription start)
constraint pk_customer primary key (customer_mail_address)
)
create table watchhistory(
customer_mail_address varchar(255) not null,
watch_date date not null,
constraint pk_watchhistory primary key (movie_id, customer_mail_address, watch_date)
)
alter table watchhistory
add constraint fk_watchhistory_ref_customer foreign key (customer_mail_address)
references customer (customer_mail_address)
on update cascade
on delete no action
go
So i want to use a UDF to constrain the watch_date in watchhistory between the subscription_start and subscription_end in customer. I can't seem to figure it out.
Check constraints can't validate data against other tables, the docs say (emphasis mine):
[ CONSTRAINT constraint_name ]
{
...
CHECK [ NOT FOR REPLICATION ] ( logical_expression )
}
logical_expression
Is a logical expression used in a CHECK constraint and returns TRUE or
FALSE. logical_expression used with CHECK constraints cannot
reference another table but can reference other columns in the same
table for the same row. The expression cannot reference an alias data
type.
That being said, you can create a scalar function that validates your date, and use the scalar function on the check condition instead:
CREATE FUNCTION dbo.ufnValidateWatchDate (
#WatchDate DATE,
#CustomerMailAddress VARCHAR(255))
RETURNS BIT
AS
BEGIN
IF EXISTS (
SELECT
'supplied watch date is between subscription start and end'
FROM
customer AS C
WHERE
C.customer_mail_address = #CustomerMailAddress AND
#WatchDate BETWEEN C.subscription_start AND C.subscription_end)
BEGIN
RETURN 1
END
RETURN 0
END
Now add your check constraint so it validates that the result of the function is 1:
ALTER TABLE watchhistory
ADD CONSTRAINT CHK_watchhistory_ValidWatchDate
CHECK (dbo.ufnValidateWatchDate(watch_date, customer_mail_address) = 1)
This is not a direct link to the other table, but a workaround you can do to validate the date. Keep in mind that if you update the customer dates after the watchdate insert, dates will be inconsistent. The only way to ensure full consistency in this case would be with a few triggers.

Postgres - range for 'time without time zone' and exclude constraint

I have the following table:
create table booking (
identifier integer not null primary key,
room uuid not null,
start_time time without time zone not null,
end_time time without time zone not null
);
I want to create an exclude constraint to enforce that there are no overlapping appointments for the same room.
I tried the following:
alter table booking add constraint overlapping_times
exclude using gist
(
cast(room as text) with =,
period(start_time, end_time) with &&)
);
This has two problems:
Casting room to text is not enough, it gives: ERROR: data type text has no default operator class for access method "gist". I know in v10 there is btree_gist, but I am using v9.5 and v9.6, so I have to manually cast the uuid to a text afaik.
period(...) is wrong, but I have no idea how to construct a range of time without time zone type.
After installing btree_gist, you can do the following:
create type timerange as range (subtype = time);
alter table booking add constraint overlapping_times
exclude using gist
(
(room::text) with =,
timerange(start_time, end_time) with &&
);
If you want an expression in the constraint you need to put that into parentheses. So either (room::text) or (cast(room as text))

Oracle Check Constraint Issue with to_date

So I'm new to Oracle, trying to create a table as follows:
create table Movies (
Title varchar2 primary key,
Rating NUMBER CONSTRAINT Rating_CHK CHECK (Rating BETWEEN 0 AND 10),
Length NUMBER CONSTRAINT Length_CHK CHECK (Length > 0),
ReleaseDate DATE CONSTRAINT RDATE_CHK
CHECK (ReleaseDate > to_date('1/1/1900', 'DD/Month/YYYY')),
CONSTRAINT title_pk PRIMARY KEY (Title)
)
Per my assignment, the ReleaseDate must have a constraint enforcing only dates after 1/1/1900. The input my professor has given us for dates is as follows: 13 August 2010
Can one of you experts see where my issue lies?
The spec for Title column is incorrect, as well as the date string/format model combination in your to_date function call. Specify a column length for TITLE, and fix the date string to match the format model.
Try this:
create table Movies (
Title varchar2(100),
Rating NUMBER CONSTRAINT Rating_CHK CHECK (Rating BETWEEN 0 AND 10),
Length NUMBER CONSTRAINT Length_CHK CHECK (Length > 0),
ReleaseDate date CONSTRAINT RDATE_CHK CHECK (ReleaseDate > to_date('1/January/1900', 'DD/Month/YYYY')),
CONSTRAINT title_pk PRIMARY KEY (Title)
)
Update:
As an aside, Title is a lousy primary key. Ever hear of two different movies with the same title? Can you say "remake"?
Another edit. I guess since your prof gave you the date format, you should make the date string match the format model. I've updated my answer.
I think 'Month' in TO_DATE is looking for a month name - not a number.
Either change the second 1 to January or change Month to MM.