Объединить непрерывные строки с Postgresql

У меня есть таблица slots, как это:

Column | Type |
------------+-----------------------------+
 id | integer |
 begin_at | timestamp without time zone |
 end_at | timestamp without time zone |
 user_id | integer |

и мне нравится выбирать объединенные строки для непрерывного времени. Скажем, у меня есть (упрощенные) данные вроде:

(1, 5:15, 5:30, 1)
(2, 5:15, 5:30, 2)
(3, 5:30, 5:45, 2)
(4, 5:45, 6:00, 2)
(5, 8:15, 8:30, 2)
(6, 8:30, 8:45, 2)

Я хотел бы знать, можно ли выбрать строки, отформатированные так:

(5:15, 5:30, 1)
(5:15, 6:00, 2) // <======= rows id 2,3 and 4 merged
(8:15, 8:45, 2) // <======= rows id 5 and 6 merged

EDIT: Здесь SQLfiddle

Я использую Postgresql, версия 9.3!

Спасибо!

2 ответа

Вот один из способов решения этой проблемы. Создайте флаг, который определяет, не перекрывается ли одна запись с предыдущей. Это начало группы. Затем возьмите кумулятивную сумму этого флага и используйте его для группировки:

select user_id, min(begin_at) as begin_at, max(end_at) as end_at
from (select s.*, sum(startflag) over (partition by user_id order by begin_at) as grp
 from (select s.*,
 (case when lag(end_at) over (partition by user_id order by begin_at) >= begin_at
 then 0 else 1
 end) as startflag
 from slots s
 ) s
 ) s
group by user_id, grp;

Здесь - скрипт SQL.


Гордон Линофф уже предоставил ответ (я сохранил).

Я использовал тот же подход, но хотел иметь дело с tsrange type. Поэтому я придумал эту конструкцию:

SELECT min(id) b_id, min(begin_at) b_at, max(end_at) e_at, grp, user_id
 FROM (
 SELECT t.*, sum(g) OVER (ORDER BY id) grp
 FROM (
 SELECT s.*, (NOT r -|- lag(r,1,r)
 OVER (PARTITION BY user_id ORDER BY id))::int g
 FROM (SELECT id,begin_at,end_at,user_id,
 tsrange(begin_at,end_at,'[)') r FROM slots) s
 ) t
 ) u
 GROUP BY grp, user_id
 ORDER BY grp;

К сожалению, на верхнем уровне нужно использовать min(begin_at) и max(end_at), так как нет никаких агрегатных функций для оператора объединения на основе диапазона +.

Я создаю диапазоны с эксклюзивными верхними границами, это позволяет мне использовать "рядом с" (-|-). Я сравниваю текущий tsrange с тем, который был в предыдущей строке, по умолчанию для текущего, если нет предыдущего. Затем я отрицаю сравнение и отбрасываю integer, что дает мне 1 в случаях, когда начинается новая группа.

licensed under cc by-sa 3.0 with attribution.