在Postgres中按时间段计算资源可用性

时间:2014-05-01 20:15:58

标签: sql postgresql

假设我有一个reservations表,用于存储有关特定资源何时被保留的信息。

CREATE TABLE reservations (
  start_time timestamp not null,
  finish_time timestamp not null,
  id serial primary key
);

它包含数据:

id    start_time               finish_time
1    "2014-05-01 09:30:00"    "2014-05-01 10:00:00"
2    "2014-05-01 10:15:00"    "2014-05-01 11:00:00"
3    "2014-05-01 11:30:00"    "2014-05-01 11:45:00"

如果我生成一系列连续时间戳,间隔15分钟,如下:

SELECT timestamp as start_time, (timestamp + '15 minutes') AS finish_time FROM generate_series('2014-05-01 09:00'::timestamp, '2014-05-01 11:45'::timestamp, interval '15 minutes') AS timestamp;

会给我:

 start_time             finish_time
"2014-05-01 09:00:00"  "2014-05-01 09:15:00"
"2014-05-01 09:15:00"  "2014-05-01 09:30:00"
"2014-05-01 09:30:00"  "2014-05-01 09:45:00"
...
"2014-05-01 11:15:00"  "2014-05-01 11:30:00"
"2014-05-01 11:30:00"  "2014-05-01 11:45:00"
"2014-05-01 11:45:00"  "2014-05-01 12:00:00"

如何找到没有预订的时间间隔?所以我正在寻找的数据是:

 start_time             finish_time
"2014-05-01 09:00:00"  "2014-05-01 09:15:00"
"2014-05-01 09:15:00"  "2014-05-01 09:30:00"
"2014-05-01 10:00:00"  "2014-05-01 10:15:00"
"2014-05-01 11:00:00"  "2014-05-01 11:15:00"
"2014-05-01 11:15:00"  "2014-05-01 11:30:00"
"2014-05-01 11:45:00"  "2014-05-01 12:00:00"

N.B - 我不必使用generate_series,但我觉得它看起来很有用!我唯一的要求是找到未预订资源的时间间隔列表。

2 个答案:

答案 0 :(得分:2)

我建议将generate_seriesOVERLAPS运算符结合使用,并对临时表进行一些创造性使用。

我相信这应该可以解决这个问题(需要调整时间段以适应更大的时间范围,并且可能需要对更大的数据集进行一些性能调整):

INSERT INTO reservations (start_time, finish_time)
VALUES ('2014-05-01 09:30:00',  '2014-05-01 10:00:00'),
       ('2014-05-01 10:15:00',  '2014-05-01 11:00:00'),
       ('2014-05-01 11:30:00',  '2014-05-01 11:45:00');

CREATE TABLE timeslots AS
SELECT timestamp as start_time, (timestamp + '15 minutes') AS finish_time
FROM generate_series('2014-05-01 09:00'::timestamp,
                     '2014-05-01 11:45'::timestamp,
                     interval '15 minutes') AS timestamp;


CREATE TEMP TABLE slots_in_use AS
SELECT DISTINCT t.start_time, t.finish_time
FROM timeslots AS t
JOIN reservations AS r ON ( ((t.start_time, t.finish_time) OVERLAPS (r.start_time, r.finish_time)))
ORDER BY t.start_time, t.finish_time;

CREATE TEMP TABLE free_timeslots AS
SELECT *
FROM timeslots;

DELETE FROM free_timeslots as x
WHERE EXISTS (SELECT 1
              FROM slots_in_use AS s
              WHERE x.start_time = s.start_time
                AND x.finish_time = s.finish_time
             );

SELECT *
FROM free_timeslots
ORDER BY start_time, finish_time;

这产生以下结果(11-11:15和11:15-11:30作为2个单独的间隔而不是1,这是15分钟的块将产生的):

START_TIME  FINISH_TIME
May, 01 2014 09:00:00+0000  May, 01 2014 09:15:00+0000
May, 01 2014 09:15:00+0000  May, 01 2014 09:30:00+0000
May, 01 2014 10:00:00+0000  May, 01 2014 10:15:00+0000
May, 01 2014 11:00:00+0000  May, 01 2014 11:15:00+0000
May, 01 2014 11:15:00+0000  May, 01 2014 11:30:00+0000
May, 01 2014 11:45:00+0000  May, 01 2014 12:00:00+0000

答案 1 :(得分:1)

SELECT slots.slot_start, slots.slot_end
FROM (
        SELECT tick as slot_start, tick + '15 minutes'::interval AS slot_end
        FROM generate_series('2014-05-01 09:00'::timestamp
                           , '2014-05-01 11:45'::timestamp, interval '15 minutes') AS tick
        ) slots
WHERE NOT EXISTS (
        SELECT * FROM reservations rx
        WHERE (rx.start_time, rx.finish_time) OVERLAPS (slots.slot_start, slots.slot_end)
        )
        ;