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

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))

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

constraint on overlapping intervals in postgres records

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 &&))

Postgresql constraint no overlap between timestamps

I'm trying to create a table containing opening hours (for cafeteria's)
However I am having some issues with implementing the constraint. For each resto, the openinghours cannot overlap.
CREATE table openinghours(
id serial PRIMARY key,
start timestamp NOT NULL,
duration interval NOT NULL,
resto_naam varchar REFERENCES resto(naam),
constraint openingstijden_duplicate_check EXCLUDE USING GIST (
resto_naam WITH =,
/* Check wether there is no overlap between the dates */
tsrange(start, start + duration) WITH &&
)
);
when I try to execute this SQL I get the following error:
ERROR: data type character varying has no default operator class for access method "gist"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
SQL state: 42704
Any help would be appreciated!
You need the btree_gist extension for this to work. Execute create btree_gist and try again.
Best regards,
Bjarni

How do I create an updated "date" field using SQLITE 3?

I am writing a program to help me keep track of my day to day life, and I want one of the fields to be a "date" field that will automatically update. What specifically do I do in SQLITE 3? Something like....
create table day_to_day(
date field
miles_ran INTEGER
food_eaten TEXT
)
How about:
CREATE TABLE day_to_day(
id INTEGER PRIMARY KEY AUTOINCREMENT,
t TIMESTAMP DEFAULT CURRENT_TIMESTAMP
miles_ran INTEGER
food_eaten TEXT
);
which would give you a column called t with the type TIMESTAMP, as a alternative you could also use this:
CREATE TABLE day_to_day(
id INTEGER PRIMARY KEY AUTOINCREMENT,
t DATE DEFAULT (datetime('now','localtime')),
miles_ran INTEGER
food_eaten TEXT
);
Maybe you can use this :
CREATE TABLE table_test (
...
date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
or
CREATE TABLE table_test (
...
date DATE DEFAULT (datetime('now','localtime')),
);
This is a good reference : sqlite database default time value 'now'
You can read what the docs have to say about it: SQLite datatypes, scroll to section 1.2.
The gist of it is, you can either use TEXT, REAL, or INTEGER, and then use the corresponding Date/Time function to access it.

Postgres date overlapping constraint

I have a table like this:
date_start date_end account_id product_id
2001-01-01 2001-01-31 1 1
2001-02-01 2001-02-20 1 1
2001-04-01 2001-05-20 1 1
I want to disallow overlapping intervals a given (account_id, product_id)
EDIT: I found something:
CREATE TABLE test (
from_ts TIMESTAMPTZ,
to_ts TIMESTAMPTZ,
account_id INTEGER,
product_id INTEGER,
CHECK ( from_ts < to_ts ),
CONSTRAINT overlapping_times EXCLUDE USING GIST (
account_id WITH =,
product_id WITH =,
box(
point( extract(epoch FROM from_ts at time zone 'UTC'), extract(epoch FROM from_ts at time zone 'UTC') ),
point( extract(epoch FROM to_ts at time zone 'UTC') , extract(epoch FROM to_ts at time zone 'UTC') )
) WITH &&
)
);
If you want to know more about this http://www.depesz.com/2010/01/03/waiting-for-8-5-exclusion-constraints/
My only problem is that it doesn't work with null values as a ending timestamp, I thought of replace it with infinite values but does not work as well.
Ok i ended up doing this :
CREATE TABLE test (
from_ts TIMESTAMPTZ,
to_ts TIMESTAMPTZ,
account_id INTEGER DEFAULT 1,
product_id INTEGER DEFAULT 1,
CHECK ( from_ts < to_ts ),
CONSTRAINT overlapping_times EXCLUDE USING GIST (
account_id WITH =,
product_id WITH =,
period(from_ts, CASE WHEN to_ts IS NULL THEN 'infinity' ELSE to_ts END) WITH &&
)
);
Works perfectly with infinity, transaction proof.
I just had to install temporal extension which is going to be native in postgres 9.2 and btree_gist available as an extension in 9.1 CREATE EXTENSION btree_gist;
nb : if you don't have null timestamp there is no need to use the temporal extension you could go with the box method as specified in my question.
In up to date postgres versions (I tested it in 9.6 but I assume it's working in >=9.2) you can use the build in function tstzrange() as mentioned in some other comments. Null values will be treated as positive or negative infinity by default and the CHECK contraint is then not explicitly needed anymore (if you are fine that the check is only <= and a range can start and end with the same date). Only the extension btree_gist is still needed:
CREATE EXTENSION btree_gist;
CREATE TABLE test (
from_ts TIMESTAMPTZ,
to_ts TIMESTAMPTZ,
account_id INTEGER DEFAULT 1,
product_id INTEGER DEFAULT 1,
CONSTRAINT overlapping_times EXCLUDE USING GIST (
account_id WITH =,
product_id WITH =,
TSTZRANGE(from_ts, to_ts) WITH &&
)
);
This is a difficult problem because constraints can only reference the "current row", and may not contain subqueries. (otherwise the trivial solution would be to add some NOT EXISTS() subquery in the check)
A check constraint specified as a column constraint should reference that column's value only, while an expression appearing in a table constraint can reference multiple columns.
Currently, CHECK expressions cannot contain subqueries nor refer to variables other than columns of the current row.
Popular work-arounds are: use a trigger function which does the dirty work (or use the rule system, which is deprecated by most people)
Because most people favor triggers, I'll repost a rule-system hack here... (it does not have the extra "id" key element, but that's a minor detail)
-- Implementation of A CONSTRAINT on non-overlapping datetime ranges
-- , using the Postgres rulesystem.
-- We need a shadow-table for the ranges only to avoid recursion in the rulesystem.
-- This shadow table has a canary variable with a CONSTRAINT (value=0) on it
-- , and on changes to the basetable (that overlap with an existing interval)
-- an attempt is made to modify this variable. (which of course fails)
-- CREATE SCHEMA tmp;
DROP table tmp.dates_shadow CASCADE;
CREATE table tmp.dates_shadow
( time_begin timestamp with time zone
, time_end timestamp with time zone
, overlap_canary INTEGER NOT NULL DEFAULT '0' CHECK (overlap_canary=0)
)
;
ALTER table tmp.dates_shadow
ADD PRIMARY KEY (time_begin,time_end)
;
DROP table tmp.dates CASCADE;
CREATE table tmp.dates
( time_begin timestamp with time zone
, time_end timestamp with time zone
, payload varchar
)
;
ALTER table tmp.dates
ADD PRIMARY KEY (time_begin,time_end)
;
CREATE RULE dates_i AS
ON INSERT TO tmp.dates
DO ALSO (
-- verify shadow
UPDATE tmp.dates_shadow ds
SET overlap_canary= 1
WHERE (ds.time_begin, ds.time_end) OVERLAPS ( NEW.time_begin, NEW.time_end)
;
-- insert shadow
INSERT INTO tmp.dates_shadow (time_begin,time_end)
VALUES (NEW.time_begin, NEW.time_end)
;
);
CREATE RULE dates_d AS
ON DELETE TO tmp.dates
DO ALSO (
DELETE FROM tmp.dates_shadow ds
WHERE ds.time_begin = OLD.time_begin
AND ds.time_end = OLD.time_end
;
);
CREATE RULE dates_u AS
ON UPDATE TO tmp.dates
WHERE NEW.time_begin <> OLD.time_begin
AND NEW.time_end <> OLD.time_end
DO ALSO (
-- delete shadow
DELETE FROM tmp.dates_shadow ds
WHERE ds.time_begin = OLD.time_begin
AND ds.time_end = OLD.time_end
;
-- verify shadow
UPDATE tmp.dates_shadow ds
SET overlap_canary= 1
WHERE (ds.time_begin, ds.time_end) OVERLAPS ( NEW.time_begin, NEW.time_end)
;
-- insert shadow
INSERT INTO tmp.dates_shadow (time_begin,time_end)
VALUES (NEW.time_begin, NEW.time_end)
;
);
INSERT INTO tmp.dates(time_begin,time_end) VALUES
('2011-09-01', '2011-09-10')
, ('2011-09-10', '2011-09-20')
, ('2011-09-20', '2011-09-30')
;
SELECT * FROM tmp.dates;
EXPLAIN ANALYZE
INSERT INTO tmp.dates(time_begin,time_end) VALUES ('2011-09-30', '2011-10-04')
;
INSERT INTO tmp.dates(time_begin,time_end) VALUES ('2011-09-02', '2011-09-04')
;
SELECT * FROM tmp.dates;
SELECT * FROM tmp.dates_shadow;
How to create unique constraint to a group of columns :
CREATE TABLE table (
date_start date,
date_end date,
account_id integer,
UNIQUE (account_id , date_start ,date_end) );
in your case you will need to ALTER TABLE if the table already exists, check the documentation it would be helpful for you :
- DDL Constraints
- ALTER Table