Ispired from @Sean answer, since I needed it for 4 parts, I wrote this (and it is easily modulable for more, comment on function in end of code):
CREATE OR REPLACE FUNCTION compareversions(v1 text,v2 text)
RETURNS smallint
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100
AS $$
declare res int;
-- Set parts into variables (for now part 1 to 4 are used)
-- IMPORTANT: if you want to add part(s) think to add:
-- - Setting of part(s) to 0 in "Convert all empty or null parts to 0" below
-- - Proper tests in select/case below
-- IMPORTANT: do not use CAST here since it will lead to syntax error if a version or part is empty
-- v1
declare v1_1 text := split_part(v1, '.', 1);
declare v1_2 text := split_part(v1, '.', 2);
declare v1_3 text := split_part(v1, '.', 3);
declare v1_4 text := split_part(v1, '.', 4);
-- v2
declare v2_1 text := split_part(v2, '.', 1);
declare v2_2 text := split_part(v2, '.', 2);
declare v2_3 text := split_part(v2, '.', 3);
declare v2_4 text := split_part(v2, '.', 4);
begin
-- Convert all empty or null parts to 0
-- v1
if v1_1 = '' or v1_1 is null then v1_1 = '0'; end if;
if v1_2 = '' or v1_2 is null then v1_2 = '0'; end if;
if v1_3 = '' or v1_3 is null then v1_3 = '0'; end if;
if v1_4 = '' or v1_4 is null then v1_4 = '0'; end if;
-- v2
if v2_1 = '' or v2_1 is null then v2_1 = '0'; end if;
if v2_2 = '' or v2_2 is null then v2_2 = '0'; end if;
if v2_3 = '' or v2_3 is null then v2_3 = '0'; end if;
if v2_4 = '' or v2_4 is null then v2_4 = '0'; end if;
select
case
-------------
-- Compare first part:
-- - If v1_1 is inferior to v2_1 return -1 (v1 < v2),
-- - If v1_1 is superior to v2_1 return 1 (v1 > v2).
when CAST(v1_1 as int) < cast(v2_1 as int) then -1
when CAST(v1_1 as int) > cast(v2_1 as int) then 1
-------------
-------------
-- v1_1 is equal to v2_1, compare second part:
-- - If v1_2 is inferior to v2_2 return -1 (v1 < v2),
-- - If v1_2 is superior to v2_2 return 1 (v1 > v2).
when CAST(v1_2 as int) < cast(v2_2 as int) then -1
when CAST(v1_2 as int) > cast(v2_2 as int) then 1
-------------
-------------
-- v1_1 is equal to v2_1 and v1_2 is equal to v2_2, compare third part:
-- - If v1_3 is inferior to v2_3 return -1 (v1 < v2),
-- - If v1_3 is superior to v2_3 return 1 (v1 > v2).
when CAST(v1_3 as int) < cast(v2_3 as int) then -1
when CAST(v1_3 as int) > cast(v2_3 as int) then 1
-------------
-------------
-- Etc..., continuing with fourth part:
when CAST(v1_4 as int) < cast(v2_4 as int) then -1
when CAST(v1_4 as int) > cast(v2_4 as int) then 1
-------------
-- All parts are equals, meaning v1 == v2, return 0
else 0
end
into res;
return res;
end;
$$;
;
COMMENT ON FUNCTION compareversions(v1 text,v2 text)
IS 'Function to compare 2 versions as strings, versions can have from 1 to 4 parts (e.g. "1", "2.3", "3.4.5", "5.6.78.9") but it is easy to add a part.
A version having less than 4 parts is considered having its last part(s) set to 0, i.e. "2.3" is considered as "2.3.0.0" so that comparing "1.2.3" to "1.2.3.0" returns "equal"). Indeed we consider first part is always major, second minor, etc ... whatever the number of part for any version.
Function returns:
- -1 when v1 < v2
- 1 when v1 > v2
- 0 when v1 = v2
And, according to return value:
- To compare if v1 < v2 check compareversions(v1, v2) == -1
- To compare if v1 > v2 check compareversions(v1, v2) == 1
- To compare if v1 == v2 check compareversions(v1, v2) == 0
- To compare if v1 <= v2 check compareversions(v1, v2) <= 0
- To compare if v1 >= v2 check compareversions(v1, v2) >= 0'
;
With this you can also for example compare a version "1.2" with "1.2.1" (will return -1, v1 < v2) as "1.2" will be considered as "1.2.0", it is not an usual check but in case during time a digit is added to version a "1.2" will actually be considered equal to "1.2.0".
And it's also easily modulable for another version format, for X.Y-Z for example, v1_1, etc... will be (not tested but you got the idea):
-- v1_1 = X
declare v1_1 text := split_part(v1, '.', 1);
-- tmp = Y-Z
declare tmp text := split_part(v1, '.', 2);
-- v1_2 = Y
declare v1_2 text := split_part(tmp, '-', 1);
-- v1_3 = Z
declare v1_3 text := split_part(tmp, '-', 2);
-- do the same for v2