Я думаю, что рекурсивный CTE - это хороший подход:
with t as (
select v.*
from (values (1, 1, 15),
(2, 1, 16),
(3, 1, 20),
(4, 2, 12),
(5, 2, 15),
(6, 3, 8)
) v( id, group_id, value )
),
cte as (
select t.group_id, min(t.value) as value, max(t.value) as maxvalue, convert(varchar(max), min(t.value)) as vals, 1 as lev
from t
group by t.group_id
union all
select cte.group_id, value + 1, maxvalue, concat(vals, ',', value + 1), lev + 2
from cte
where value < maxvalue
)
select cte.group_id, cte.vals
from (select cte.*, max(cte.lev) over (partition by cte.group_id) as maxlev
from cte
) cte
where lev = maxlev
order by group_id;
Здесь - это db <> скрипка.