coalesce(range_agg(r.period), tstzmultirange())) AS vacant_range FROM reservation_slot rs LEFT OUTER JOIN reservation r ON rs.id = r.reservation_slot_id AND r.period && tstzrange(:start_time, :end_time, '[)') WHERE rs.available && tstzrange(:start_time, :end_time, '[)') GROUP BY rs.id, rs.available ), calendar_item AS ( SELECT tstzrange(t, t + '30 minutes'::interval) AS period FROM generate_series(:start_time, :end_time, '30 minutes') t ) SELECT lower(c.period) AS start, EXISTS( SELECT 1 FROM vacancy v WHERE c.period <@ v.vacant_range ) AS vacant FROM calendar_item c 16