with recursive
data as (
select * from (
values ('foo', 2, 3),
('foo', 3, 4),
('foo', 4, 5),
('foo', 10, 11),
('foo', 11, 13),
('foo', 13, 15),
('bar', 1, 2),
('bar', 2, 4),
('bar', 7, 8)
) as baz (name, first, last)
),
recur (name, first, last) as (
select name, first, last, last-first as span from data
union all
select name, data.first, data.last, recur.span+data.last-data.first as span
from data join recur using (name)
where data.first = recur.last
)
select name, start, start + span as end, span from (
select name, (last-span) as start, max(span) as span from (
select name, first, last, max(span) as span
from recur
group by name, first, last
) as z
group by name, (last-span)
) as z
┌──────┬───────┬─────┬──────┐
│ name │ start │ end │ span │
├──────┼───────┼─────┼──────┤
│ bar │ 1 │ 4 │ 3 │
│ bar │ 7 │ 8 │ 1 │
│ foo │ 10 │ 15 │ 5 │
│ foo │ 2 │ 5 │ 3 │
└──────┴───────┴─────┴──────┘
(4 rows)
Or if we do not have a "last" column.
with recursive
data as (
select * from (
values ('foo', 2),
('foo', 3),
('foo', 4),
('foo', 10),
('foo', 11),
('foo', 13),
('bar', 1),
('bar', 2),
('bar', 7)
) as baz (name, first)
),
recur (name, first) as (
select name, first, 1 as span from data
union all
select name, data.first, recur.span+1 as span
from data
join recur using (name)
where data.first = recur.first + 1
)
select name, start, start + span - 1 as end, span from (
select name, (first+1-span) as start, max(span) as span from (
select name, first, max(span) as span
from recur
group by name, first
) as z
group by name, start
) as z
order by name, start
┌──────┬───────┬─────┬──────┐
│ name │ start │ end │ span │
├──────┼───────┼─────┼──────┤
│ bar │ 1 │ 2 │ 2 │
│ bar │ 7 │ 7 │ 1 │
│ foo │ 2 │ 4 │ 3 │
│ foo │ 10 │ 11 │ 2 │
│ foo │ 13 │ 13 │ 1 │
└──────┴───────┴─────┴──────┘
(5 rows)
The problem is that with recursive tends to be slow when dealing with millions of rows.
How to find the boundaries of groups of contiguous sequential numbers? proposes a faster approach.
with
data as (
select * from (
values ('foo', 2),
('foo', 3),
('foo', 4),
('foo', 10),
('foo', 11),
('foo', 13),
('bar', 1),
('bar', 2),
('bar', 7)
) as baz (name, first)
),
island as (
select first - row_number() over (order by name, first) as grp,
name, first
from data
)
select name, min(first) as start, max(first) as end
from island
group by name, grp
order by name, start
┌──────┬───────┬─────┐
│ name │ start │ end │
├──────┼───────┼─────┤
│ bar │ 1 │ 2 │
│ bar │ 7 │ 7 │
│ foo │ 2 │ 4 │
│ foo │ 10 │ 11 │
│ foo │ 13 │ 13 │
└──────┴───────┴─────┘
(5 rows)










