with elements as (
select id,
regexp_split_to_array(id, '(\.)') as id_elements,
name,
details
from the_table
), bounds as (
select id,
case
when strpos(id, '-') = 0 then 1
else split_part(id_elements[cardinality(id_elements)], '-', 1)::int
end as start_value,
case
when strpos(id, '-') = 0 then 1
else split_part(id_elements[cardinality(id_elements)], '-', 2)::int
end as end_value,
case
when strpos(id, '-') = 0 then id
else array_to_string(id_elements[1:cardinality(id_elements)-1], '.')
end as base_id,
name,
details
from elements
)
select b.base_id||'.'||c.cnt as new_id,
b.name,
b.details,
count(*) over (partition by b.base_id) as num_rows
from bounds b
cross join lateral generate_series(b.start_value, b.end_value) as c (cnt)
order by num_rows desc, c.cnt;
The first CTE simply splits the ID based on the .. The second CTE then calculates the start and end value for each ID and "strips" the range definition from the actual ID value to get the base that can be concatenated with the actual row index in the final select statement.
With this test data:
insert into the_table
values
('1.3.1-3', 'Jack', 'details 1'),
('5.4.1-2', 'John', 'details 2'),
('1.4.5', 'Alex', 'details 3'),
('10.11.12.1-5', 'Peter', 'details 4'),
('1.4.10-13', 'Arthur','details 5'),
('11.12.13.14.15.16.2-7','Zaphod','details 6');
The following result is returned:
new_id | name | details | num_rows
--------------------+--------+-----------+---------
11.12.13.14.15.16.2 | Zaphod | details 6 | 6
11.12.13.14.15.16.3 | Zaphod | details 6 | 6
11.12.13.14.15.16.4 | Zaphod | details 6 | 6
11.12.13.14.15.16.5 | Zaphod | details 6 | 6
11.12.13.14.15.16.6 | Zaphod | details 6 | 6
11.12.13.14.15.16.7 | Zaphod | details 6 | 6
10.11.12.1 | Peter | details 4 | 5
10.11.12.2 | Peter | details 4 | 5
10.11.12.3 | Peter | details 4 | 5
10.11.12.4 | Peter | details 4 | 5
10.11.12.5 | Peter | details 4 | 5
1.4.10 | Arthur | details 5 | 4
1.4.11 | Arthur | details 5 | 4
1.4.12 | Arthur | details 5 | 4
1.4.13 | Arthur | details 5 | 4
1.3.1 | Jack | details 1 | 3
1.3.2 | Jack | details 1 | 3
1.3.3 | Jack | details 1 | 3
5.4.1 | John | details 2 | 2
5.4.2 | John | details 2 | 2
1.4.5.1 | Alex | details 3 | 1
The use of cardinality(id_elements) requires Postgres 9.4. For earlier versions this needs to be replaced with array_length(id_elements, 1))
A final note:
This would be a lot easier if you stored the start and end value in separate (integer) columns, rather then appending them to the ID itself. This model violates basic database normalization (first normal form).
This solution (or any solution in the answers given) will fail badly if an is stored that contains e.g. 10.12.13.A-Z (non-numeric values) which can be prevented by properly normalizing the data.