(Image by Andrew Martin from Pixabay – modifications by Sam)

Ever tried to sort Version-Strings like this with SQL – for example when you want to find the highest Flyway version installed in your database (installed_rank doesn’t necessarily mean it’s the highest version)?

1.5.185.3
1.5.0
1.5
12.4.1
1.6
2

While there might be different approaches with PL/SQL that result in fewer code, it is achievable in pure SQL.

My (longish) solution with recursive WITH-clause

The core of this solution is to split the version-string into separate rows by “.” delimiter.

After that, we can calculate a comparison-string with leading zeros for each version part and use listagg to group it together again:

with
  input as (
    select '1.5.185.3' as version from dual union all
    select '1.5.0'     as version from dual union all
    select '1.5'       as version from dual union all
    select '12.4.1'    as version from dual union all
    select '1.6'       as version from dual union all
    select '2'         as version from dual
  ),
  -- Known Split-solution with fixed delimiter "."
  split_positions(version, depth, start_pos, end_pos) as (
    select
      version     as version,
      1           as depth,
      1           as start_pos,
      coalesce(
        nullif(instr(version, '.', 1, 1),0),
        length(version)+1
      )           as end_pos
    from input
    union all
    select
      version     as version,
      depth+1 	  as depth,
      end_pos+1   as start_pos,
      coalesce(
        nullif(instr(version, '.', 1, depth+1),0),
        length(version)+1
      )           as end_pos
    from split_positions
    where instr(version, '.', start_pos) > 0
  ),
  split as (
    select
      version,
      depth,
      substr(version, start_pos, end_pos-start_pos) as elem_value
    from split_positions
  ),
  /* Now we calculate a comparison-string for each depth
     The comparison string should have the char-length of the
     longest element in all of the versions, filled with 0 at
     the beginning */
  comparison_elments as (
    select
      version,
      depth,
      lpad(
        elem_value,
        max(length(elem_value)) over (),
        '0'
      ) as comparison_part
    from split
  ),
  /* Now we can listagg all the comparison-strings per version
     and order the strings by depth. That way we get a comparable
     and therefore sortable string */
  comparison_grouped as (
    select
      version,
      listagg(comparison_part) within group ( order by depth ) sort_value
      from comparison_elments
      group by version
  )
select version
  from comparison_grouped
  order by sort_value

You can try it out on LiveSQL as usual.

And if you want to know what the highest Flyway version in your Oracle database is, change the above example a tiny little bit:

with
  input as ( select "version" as version from "schema_version"),
  ...
select version
from comparison_grouped
order by sort_value desc
fetch first row only

Happy coding!

Regexp-Solution by Laurent Schneider

When you learn in public, there is always the chance for someone to come around with a more elegant or already known solution – and that is an immensely great thing!

Matthias Rogel shared this solution to the problem which was discussed in an AskTom thread 10 years ago by Laurent Schneider – and it’s very, very elegant:

with
	input as (
		select '1.5.185.3' as version from dual union all
		select '1.5.0'     as version from dual union all
		select '1.5'       as version from dual union all
		select '12.4.1'    as version from dual union all
		select '1.6'       as version from dual union all
		select '2'         as version from dual
	  )
select
    version
from input
order by
	regexp_replace(
	    replace('.'||version, '.', '.0000000'),
	    '\.0+([0-9]{8})',
	    '.\1'
	)


0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.