Assumptions
The decision is largely dependent on the definition of the exact table, including all restrictions. Due to the lack of information in the question, I will take this table:
CREATE TABLE booking (
booking_id serial PRIMARY KEY
, check_in timestamptz NOT NULL
, check_out timestamptz NOT NULL
, CONSTRAINT valid_range CHECK (check_out > check_in)
);
So, no NULL values, only valid ranges with inclusive lower and exclusive upper bounds, and we don’t care who checks.
Also assuming the current version of Postgres is at least 9.2 .
Query
One way to do this is with SQL only UNION ALLand with window functions:
SELECT ts AS check_id, next_ts As check_out
FROM (
SELECT *, lead(ts) OVER (ORDER BY ts) AS next_ts
FROM (
SELECT *, lag(people_ct, 1 , 0) OVER (ORDER BY ts) AS prev_ct
FROM (
SELECT ts, sum(sum(change)) OVER (ORDER BY ts)::int AS people_ct
FROM (
SELECT check_in AS ts, 1 AS change FROM booking
UNION ALL
SELECT check_out, -1 FROM booking
) sub1
GROUP BY 1
) sub2
) sub3
WHERE people_ct > 1 AND prev_ct < 2 OR
people_ct < 2 AND prev_ct > 1
) sub4
WHERE people_ct > 1 AND prev_ct < 2;
SQL Fiddle
Explanation
, plpgsql, dba.SE: