This is a perfect use case for PostgreSQL's (fantastic) [`RANGE`][1] type. All of the code below is available on the fiddle [here][2]. CREATE TABLE test ( business_id INT NOT NULL, intval TSTZRANGE, EXCLUDE USING GIST (business_id WITH =, intval WITH &&) ); So, this tells us that no `intval` can overlap with another with the **same** `business_id` (person, resource...). To test: INSERT INTO test VALUES (1, '[2022-01-01 11:30, 2022-01-01 15:00)'); and then: -- -- Non-overlapping -- INSERT INTO test VALUES (1, '[2022-01-01 16:30, 2022-01-01 18:00)'); No problem! But! -- -- Overlapping! -- INSERT INTO test VALUES (1, '[2022-01-01 10:30, 2022-01-01 13:00)'); and we get (correctly): > ERROR: conflicting key value violates exclusion constraint > "test_business_id_intval_excl" DETAIL: Key (business_id, intval)=(1, > ["2022-01-01 10:30:00+00","2022-01-01 13:00:00+00")) conflicts with > existing key (business_id, intval)=(1, ["2022-01-01 > 11:30:00+00","2022-01-01 15:00:00+00")). and now with a different `business_id`, but overlaps with preceding schedules: -- -- Interval overlaps, but business_id (person, other resource -- doesn't - no problem! -- INSERT INTO test VALUES (2, '[2022-01-01 11:30, 2022-01-01 15:00)'); Resutlt (as expected): INSERT 0 1 No need to worry about Sundays, Mondays or intervals going over day, week, month or year boundaries. These range types are incredibly powerful and well worth the effort of getting to know. See [here][3] for a list of functions and operators. [1]: https://www.postgresql.org/docs/current/rangetypes.html [2]: https://dbfiddle.uk/gr270OSB [3]: https://www.postgresql.org/docs/15/functions-range.html