Reputation: 11687
I have table booking with such looking records:
id | from | to
------+---------------------+---------------------
101 | 2015-09-24 08:00:00 | 2015-09-24 09:30:00
2261 | 2015-09-24 09:00:00 | 2015-09-24 10:00:00
4061 | 2015-09-24 10:00:00 | 2015-09-24 10:30:00
204 | 2015-09-24 12:00:00 | 2015-09-24 13:30:00
2400 | 2015-09-24 13:30:00 | 2015-09-24 14:00:00
4224 | 2015-09-24 14:00:00 | 2015-09-24 14:30:00
309 | 2015-09-24 16:00:00 | 2015-09-24 17:30:00
2541 | 2015-09-24 17:00:00 | 2015-09-24 18:00:00
I am looking for optimal query to find anwser to question:
Is this possible to find a timeslot with duration x (ie. 30 minutes) in above records?
I have ideas to use postgres arrays or time ranges, but still looking for better ideas....
EDIT: I will provide 'fake' bookings as boundaries, but if you have ideas how to do it better, please write :)
Upvotes: 3
Views: 1200
Reputation: 456
Generate your slots and then left join them. http://sqlfiddle.com/#!15/12bfa
create table t (id integer, "from" timestamp, "to" timestamp);
insert into t values
(101 , '2015-09-24 08:00:00' , '2015-09-24 09:30:00' ),
(2261 , '2015-09-24 09:00:00' , '2015-09-24 10:00:00' ),
(4061 , '2015-09-24 10:00:00' , '2015-09-24 10:30:00' ),
( 204 , '2015-09-24 12:00:00' , '2015-09-24 13:30:00' ),
(2400 , '2015-09-24 13:30:00' , '2015-09-24 14:00:00' ),
(4224 , '2015-09-24 14:00:00' , '2015-09-24 14:30:00' ),
( 309 , '2015-09-24 16:00:00' , '2015-09-24 17:30:00' ),
(2541 , '2015-09-24 17:00:00' , '2015-09-24 18:00:00' );
SELECT time_slots.t,
time_slots.t + interval '30 minutes'
FROM generate_series(date'2015-09-24',date'2015-09-25' - interval '30 minutes' ,interval '30 minutes') AS time_slots(t)
LEFT JOIN t ON (time_slots.t BETWEEN t."from" AND t."to")
WHERE t.id IS NULL;
SELECT time_slots.t,
time_slots.t + interval '30 minutes'
FROM generate_series(date'2015-09-24',date'2015-09-25' - interval '30 minutes',interval '30 minutes') AS time_slots(t)
LEFT JOIN t ON ((time_slots.t,
time_slots.t + interval '30 minutes') OVERLAPS (t."from",
t."to"))
WHERE t.id IS NULL;
Upvotes: 0
Reputation: 44250
Non-standard self-join:
SELECT
ll.ts_to AS ts_from
, hh.ts_from AS ts_to
FROM bookings ll
JOIN bookings hh
-- enough space
ON hh.ts_from >= ll.ts_to + '30 min'::interval
-- and nothing in between
AND NOT EXISTS (
SELECT * FROM bookings nx
WHERE nx.ts_from >= ll.ts_to
AND nx.ts_to <= hh.ts_from
)
UNION ALL -- before the first
SELECT '-infinity'::timestamp AS ts_from
, MIN(ts_from) AS ts_to
FROM bookings
UNION ALL -- after the last
SELECT MAX(ts_to) AS ts_from
, 'infinity'::timestamp AS ts_to
FROM bookings
ORDER BY 1,2
;
Upvotes: 0
Reputation: 10693
Here's one solution using analytical functions, it provides all windows with no bookings:
SELECT null as ts_from, min(ts_from) as ts_to
FROM bookings
UNION ALL
SELECT ts as ts_from, next_ts as ts_to
FROM (SELECT ts, lead(ts, 1) over (order by ts) as next_ts, sum(bk) over (order by ts) as bksum
FROM (SELECT ts_from as ts, 1 as bk
FROM bookings
UNION ALL
SELECT ts_to as ts, -1 as bk
FROM bookings) as t) as tt
WHERE bksum = 0
ORDER BY 1 NULLS FIRST;
SQL Fiddle here.
Upvotes: 1
Reputation: 121604
You can use lag() function:
select *, book_start- previous_book_end timeslot
from (
select id, "from" book_start, "to" book_end,
lag("to") over (order by "to") previous_book_end
from test
) sub
order by book_end
id | book_start | book_end | previous_book_end | timeslot
------+---------------------+---------------------+---------------------+-----------
101 | 2015-09-24 08:00:00 | 2015-09-24 09:30:00 | |
2261 | 2015-09-24 09:00:00 | 2015-09-24 10:00:00 | 2015-09-24 09:30:00 | -00:30:00
4061 | 2015-09-24 10:00:00 | 2015-09-24 10:30:00 | 2015-09-24 10:00:00 | 00:00:00
204 | 2015-09-24 12:00:00 | 2015-09-24 13:30:00 | 2015-09-24 10:30:00 | 01:30:00
2400 | 2015-09-24 13:30:00 | 2015-09-24 14:00:00 | 2015-09-24 13:30:00 | 00:00:00
4224 | 2015-09-24 14:00:00 | 2015-09-24 14:30:00 | 2015-09-24 14:00:00 | 00:00:00
309 | 2015-09-24 16:00:00 | 2015-09-24 17:30:00 | 2015-09-24 14:30:00 | 01:30:00
2541 | 2015-09-24 17:00:00 | 2015-09-24 18:00:00 | 2015-09-24 17:30:00 | -00:30:00
(8 rows)
Select rows with timeslots >= '30m'::interval
:
select *, book_start- previous_book_end timeslot
from (
select id, "from" book_start, "to" book_end,
lag("to") over (order by "to") previous_book_end
from test
) sub
where book_start- previous_book_end >= '30m'::interval
order by book_end
id | book_start | book_end | previous_book_end | timeslot
-----+---------------------+---------------------+---------------------+----------
204 | 2015-09-24 12:00:00 | 2015-09-24 13:30:00 | 2015-09-24 10:30:00 | 01:30:00
309 | 2015-09-24 16:00:00 | 2015-09-24 17:30:00 | 2015-09-24 14:30:00 | 01:30:00
(2 rows)
Upvotes: 0
Reputation: 44766
Something like this:
select t1.*
from tablename t1
where (select min("from") from tablename t2
where t2."from" > t1."from") >= t1."to" + interval '30' minute
I.e. return a row if the gap to the following row is >= 30 minutes.
Note: from
and to
are reserved words in ANSI SQL, that's why they are delimited as "from"
and "to"
.
Upvotes: 1