Please give this a try:
with trans as (
select c1,
case when lag(c1) over (order by c1) = c1 - 1 then 0 else 1 end as new
from tt8
), groups as (
select c1, sum(new) over (order by c1) as grpnum
from trans
), ranges as (
select grpnum, min(c1) as low, max(c1) as high
from groups
group by grpnum
), texts as (
select grpnum,
case
when low = high then low::text
else low::text||'-'||high::text
end as txt
from ranges
)
select string_agg(txt, ',' order by grpnum) as answer
from texts;
answer
---------------
1-3,6-9,12,14
(1 row)
You can change the last query to bring back the results of each CTE to see what is happening.
trans uses the lag() window function to mark rows that start groups:
c1 | new
----+-----
1 | 1
2 | 0
3 | 0
6 | 1
7 | 0
8 | 0
9 | 0
12 | 1
14 | 1
(9 rows)
groups uses the sum() window function with the implicit unbounded preceding to assign each row a grpnum:
c1 | grpnum
----+--------
1 | 1
2 | 1
3 | 1
6 | 2
7 | 2
8 | 2
9 | 2
12 | 3
14 | 4
(9 rows)
ranges collapses each groupnum to its min() and max():
grpnum | low | high
--------+-----+------
3 | 12 | 12
4 | 14 | 14
2 | 6 | 9
1 | 1 | 3
(4 rows)
texts translates the low and high ranges into text representations:
grpnum | txt
--------+-----
3 | 12
4 | 14
2 | 6-9
1 | 1-3
(4 rows)
The string_agg() turns the txt values into a comma-separated list.