Preventing adjacent/overlapping entries with EXCLUDE in PostgreSQL

You can rewrite the exclude with the range type introduced in 9.2. Better yet, you could replace the two fields with a range. See "Constraints on Ranges" here, with an example that basically amounts to your use case:

http://www.postgresql.org/docs/current/static/rangetypes.html


Range types consist of lower and upper bound, and each can be included or excluded.
The canonical form (and default for range types) is to include the lower and exclude the upper bound.

Inclusive bounds '[]'

You could include lower and upper bound ([]), and enforce it with a CHECK constraint using range functions.

Then "adjacent" ranges overlap. Excluding overlapping ranges seems clear. There is a code example in the manual.

CREATE TABLE tbl (
   tbl_id serial PRIMARY KEY
 , tsr tsrange
 , CONSTRAINT tsr_no_overlap EXCLUDE USING gist (tsr WITH &&)
 , CONSTRAINT tsr_enforce_incl_bounds CHECK (lower_inc(tsr) AND upper_inc(tsr))  -- all bounds inclusive!
);

Only ranges with inclusive bounds are allowed:

INSERT INTO tbl(tsr) VALUES ('[2013-10-22 00:00, 2013-10-22 01:00]');

db<>fiddle here

Canonical bounds '[)'

Enforce [) bounds (including lower and excluding upper).

In addition, create another exclusion constraint employing the adjacent operator -|- to also exclude adjacent entries. Both are based on GiST indexes as GIN is currently not supported for this.

CREATE TABLE tbl (
   tbl_id serial PRIMARY KEY
 , tsr tsrange
 , CONSTRAINT tsr_no_overlap  EXCLUDE USING gist (tsr WITH &&)
 , CONSTRAINT tsr_no_adjacent EXCLUDE USING gist (tsr WITH -|-)
 , CONSTRAINT tsr_enforce_bounds CHECK (lower_inc(tsr) AND NOT upper_inc(tsr))
);

db<>fiddle here
Old sqlfiddle

Unfortunately, this creates two identical GiST indexes to implement both exclusion constraints, where one would suffice, logically.