26

Is there a way to compare software version (e.g. X.Y.Z > A.B.C) in Postgres ? I'm searching for a function on string/varchar or a "version" type.

I found out that http://pgxn.org/dist/semver/doc/semver.html, but I'm looking for alternatives (not so easy to deploy..)

1
  • debian created an extension for this. debversion pgxn.org/dist/debversion for debian based systems deployment is easy. Commented Jan 9, 2017 at 20:30

6 Answers 6

48

Use string_to_array(). No need for expensive regular expressions.

SELECT string_to_array(v1, '.')::int[] AS v1
     , string_to_array(v2, '.')::int[] AS v2
     ,(string_to_array(v1, '.')::int[] > string_to_array(v2, '.')::int[]) AS cmp
FROM   versions;

fiddle
Old sqlfiddle

Assuming all parts of the version number are valid integer numbers, of course.

Sign up to request clarification or add additional context in comments.

4 Comments

I think this has the same drawback as the accepted answer. '1.9' > '1.10', but you want that reversed for semver.
@EricHu: You may have missed the cast to int[]. Hence 1.10 > 1.9.
@ErwinBrandstetter whats the best way to do this in version postgres 11!?
@ProgramCpp: This is the best way in any version.
12

You can split the version to array and then do array comparison.

select regexp_split_to_array(v1, '\.')::int[] v1, 
       regexp_split_to_array(v2, '\.')::int[] v2,
       regexp_split_to_array(v1, '\.')::int[] > regexp_split_to_array(v2, '\.')::int[] cmp
from versions;

demo

4 Comments

Comparing string arrays doesn't work any better than simply comparing the strings - you still get things like '1.9' > '1.10'. You probably want to cast your split output to int[].
@NickBarnes, great catch! The answer has been updated per your suggestion.
An issue that might occur is if the version has a postfix to it, i.e. 1.0.0-beta (which is valid for a semver). A workaround would be removing all non number or dots, and prefixing with "0" just in case. TLDR: This link is more robust : sqlfiddle.com/#!15/0d32c/2/0
@EdmondLafay-David Thanks this looks more robust compared to string_to_array wondering what is the performance cost, Also is it possible to sort just 1 column and get latest vs comparing 1 column to another ?
6

Going a bit further Erwin's answer, we can create a function to compare a software version with a requirement (like in ruby). I've written a function that does just that, to use it:

SELECT semver_match('4.2.0', '>= 4.0'); -- TRUE

Here's the code and tests:

CREATE OR REPLACE FUNCTION semver_match(version text, req text) RETURNS boolean
    LANGUAGE SQL
    IMMUTABLE
    RETURNS NULL ON NULL INPUT
    AS $$ 
    SELECT CASE
    WHEN req LIKE '~>%' THEN
        string_to_array(version, '.')::int[] >= string_to_array(substring(req from 4), '.')::int[]
        AND
        string_to_array(version, '.')::int[] <
        -- increment last item by one. (X.Y.Z => X.Y.(Z+1))
        array_append(
            (string_to_array(substring(req from 4), '.')::int[])[1:(array_length(string_to_array(req, '.'), 1) - 1)], -- X.Y
            (string_to_array(substring(req from 4), '.')::int[])[array_length(string_to_array(req, '.'), 1)] + 1 -- Z + 1
        )
 WHEN req LIKE '>=%' THEN string_to_array(version, '.')::int[] >= string_to_array(substring(req from 4), '.')::int[]
    WHEN req LIKE '<=%' THEN string_to_array(version, '.')::int[] <= string_to_array(substring(req from 4), '.')::int[]
    WHEN req LIKE '>%' THEN string_to_array(version, '.')::int[] > string_to_array(substring(req from 3), '.')::int[]
    WHEN req LIKE '<%' THEN string_to_array(version, '.')::int[] < string_to_array(substring(req from 3), '.')::int[]
   WHEN req LIKE '=%' THEN 
        (string_to_array(version, '.')::int[])[1:array_length(string_to_array(substring(req from 3), '.'), 1)] = 
        string_to_array(substring(req from 3), '.')::int[]
    ELSE NULL
    END $$;

-- tests.
SELECT 
    ver,
    req,
    CASE WHEN semver_match(ver, req) = expected
    THEN '✅' ELSE '❌' END AS test_passed
FROM (VALUES
    ('2.3',   '>= 2.3',   TRUE),
    ('2.3.1', '> 2.3',    TRUE),
    ('2.3.1', '< 2.3.2',  TRUE),
    ('2.3.1', '~> 2.3.2', FALSE),
    ('2.4.3', '~> 2.3.2', FALSE),
    ('2.3.2', '~> 2.3.2', TRUE),
    ('2.3.2', '= 2.3.2',  TRUE),
    ('2.3.2', '= 2.3',    TRUE),
    ('2.3.2', '= 2.4',    FALSE)
) AS _ (ver, req, expected)

See my blogpost on that topic for more implementation details.

3 Comments

Nice work. Should be the accepted answer.
Thanks, that helps a lot :) A small issue though on the >= and <= operators. They are conflicting with > and < and should be placed before. As it is, the = part is not done.
@TitouanFreville you are absolutely right, thanks for debugging. I've also added a test to avoid future regression. Let's update the blogpost now :)
5

As already suggested an easy way is to work with a numeric format of the version. The variable 'server_version_num' contains a numeric format of the version.

Eg.

  • version 9.5.2 => 90502
  • version 9.6.0 => 90600
  • version 10.5 => 100500

    select current_setting('server_version_num')

return a number that can be easily compared with another version number.

Comments

1

An alternative approach is to use

SHOW server_version_num;

This returns a version number that's simpler to compare. e.g. 90610 for 9.6.10.

Update

Clarifying this answer based on the comments below. The version number produced here is machine readable. It is designed to be unique and avoid clashes.

e.g. Postgres version 12.1 will produce 120001 while 12.10 will produce 120010, etc

https://database.guide/how-to-check-your-postgresql-version/

3 Comments

This must be used with to much care to be useful, since 9.61.0 would be equal to 9.6.10
@EdmondLafay-David I don't think so: 096100 ! <> 090610, maybe the answer lacks a bit of clarity though :/
indeed, the update to the comment proves me wrong , i'll upvote the answer and the comment (I can't edit my comment, only delete it, which would make the edit confusing)
0

Maybe you can add a pl function, in my case I have used python and distutils.version:

CREATE FUNCTION _is_major (a text, b text)
  RETURNS boolean
AS $$
    from distutils.version import LooseVersion 
    return LooseVersion(a) > LooseVersion(b)
$$ LANGUAGE PLPYTHONU;

You need the postgresql-plpython package.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.