How to compare version string (“x.y.z”) in MySQL?

前端 未结 10 2118
南旧
南旧 2020-12-16 14:02

I have firmware version strings into my table (like \"4.2.2\" or \"4.2.16\")

How can I compare, select or sort them ?

I cannot use standard strings compariso

相关标签:
10条回答
  • 2020-12-16 14:41

    This is rather a complicated one, as SQL isn't designed to split out multiple values from a single field - this is a violation of First Normal Form. Assuming that you are not going to have more than three groups of numbers, each of which will not be more than three digits long, try:

    cast(substring_index(concat(X,'.0.0.'), '.', 1) as float) * 1000000 +
    cast(substring_index(substring_index(concat(X,'.0.0.'), '.', 2), '.', -1) as float) * 1000 +
    cast(substring_index(substring_index(concat(X,'.0.0.'), '.', 3), '.', -1) as float)
    
    0 讨论(0)
  • 2020-12-16 14:44
    /**
    function version_compare(version1, version2)
    
    parameters
    version1 first version number.
    version2 second version number.
    
    return values
    -1: if version1 is less than version2;
    1: if version1 is greater than version2,
    0: if version1 equal version2.
    
    example:
    select version_compare('4.2.2','4.2.16') from dual;
    version_compare('4.2.2','4.2.16')  
    -----------------------------------
        -1 
    
    */
    drop function if exists version_compare;
    delimiter @@
    
    create function version_compare(version1 varchar(100), version2 varchar(100))
      returns tinyint
      begin
        declare v_result tinyint;
        declare version1_sub_string varchar(100);
        declare version2_sub_string varchar(100);
        declare version1_sub_int int;
        declare version2_sub_int int;
    
        declare version1_sub_end tinyint;
        declare version2_sub_end tinyint;
    
    
        if version1 = version2 then
          set v_result = 0;
        else
    
          set version1_sub_string = version1;
          set version2_sub_string = version2;
    
          lp1 : loop
            set version1_sub_end = locate('.', version1_sub_string);
            set version2_sub_end = locate('.', version2_sub_string);
    
            if version1_sub_end <> 0 then
              set version1_sub_int = cast(substring(version1_sub_string, 1, version1_sub_end - 1) as signed);
              set version1_sub_string = substring(version1_sub_string, version1_sub_end +1 );
            else
              set version1_sub_int = cast(version1_sub_string as signed);
            end if;
    
            if version2_sub_end <> 0 then
              set version2_sub_int = cast(substring(version2_sub_string, 1, version2_sub_end - 1) as signed);
    
              set version2_sub_string = substring(version2_sub_string, version2_sub_end + 1);
            else
              set version2_sub_int = cast(version2_sub_string as signed);
            end if;
    
    
            if version1_sub_int > version2_sub_int then
              set v_result = 1;
              leave lp1;
    
            elseif version1_sub_int < version2_sub_int then
                set v_result = -1;
                leave lp1;
            else
              if version1_sub_end = 0 and version2_sub_end = 0 then
                set v_result = 0;
                leave lp1;
    
              elseif version1_sub_end = 0 then
                  set v_result = -1;
                  leave lp1;
    
              elseif version2_sub_end = 0 then
                  set v_result = 1;
                  leave lp1;
              end if;      
            end if;
    
          end loop;
        end if;
    
        return v_result;
    
     end@@
    delimiter ;
    
    0 讨论(0)
  • 2020-12-16 14:52

    Python can compare lists element-by-element in exactly the way you want the versions to be compared, so you can simply split on the ".", call int(x) on each element (with a list comprehension) to convert the string to an int, and then compare

        >>> v1_3 = [ int(x) for x in "1.3".split(".") ]
        >>> v1_2 = [ int(x) for x in "1.2".split(".") ]
        >>> v1_12 = [ int(x) for x in "1.12".split(".") ]
        >>> v1_3_0 = [ int(x) for x in "1.3.0".split(".") ]
        >>> v1_3_1 = [ int(x) for x in "1.3.1".split(".") ]
        >>> v1_3
        [1, 3]
        >>> v1_2
        [1, 2]
        >>> v1_12
        [1, 12]
        >>> v1_3_0
        [1, 3, 0]
        >>> v1_3_1
        [1, 3, 1]
        >>> v1_2 < v1_3
        True
        >>> v1_12 > v1_3
        True
        >>> v1_12 > v1_3_0
        True
        >>> v1_12 > v1_3_1
        True
        >>> v1_3_1 < v1_3
        False
        >>> v1_3_1 < v1_3_0
        False
        >>> v1_3_1 > v1_3_0
        True
        >>> v1_3_1 > v1_12
        False
        >>> v1_3_1 < v1_12
        True
        >>> 
    
    0 讨论(0)
  • 2020-12-16 14:53

    Finally, I found another way to sort version strings.

    I just justify the string before storing into de database in a way it is sortable. As I am using the python Django framework, I just have created a VersionField that 'encode' the version string while storing and 'decode' it while reading, so that it is totally transparent for the application :

    Here my code :

    The justify function :
    
    def vjust(str,level=5,delim='.',bitsize=6,fillchar=' '):
        """
        1.12 becomes : 1.    12
        1.1  becomes : 1.     1
        """
        nb = str.count(delim)
        if nb < level:
            str += (level-nb) * delim
        return delim.join([ v.rjust(bitsize,fillchar) for v in str.split(delim)[:level+1] ])
    
    The django VersionField :
    
    class VersionField(models.CharField) :
    
        description = 'Field to store version strings ("a.b.c.d") in a way it is sortable'
    
        __metaclass__ = models.SubfieldBase
    
        def get_prep_value(self, value):
            return vjust(value,fillchar=' ')
    
        def to_python(self, value):
            return re.sub('\.+$','',value.replace(' ',''))
    
    0 讨论(0)
  • 2020-12-16 14:54

    Lots of good solutions here, but I wanted a stored function that would work with ORDER BY

    CREATE FUNCTION standardize_version(version VARCHAR(255)) RETURNS varchar(255) CHARSET latin1 DETERMINISTIC NO SQL
    BEGIN
      DECLARE tail VARCHAR(255) DEFAULT version;
      DECLARE head, ret VARCHAR(255) DEFAULT NULL;
    
      WHILE tail IS NOT NULL DO 
        SET head = SUBSTRING_INDEX(tail, '.', 1);
        SET tail = NULLIF(SUBSTRING(tail, LOCATE('.', tail) + 1), tail);
        SET ret = CONCAT_WS('.', ret, CONCAT(REPEAT('0', 3 - LENGTH(CAST(head AS UNSIGNED))), head));
      END WHILE;
    
      RETURN ret;
    END|
    

    to test:

    SELECT standardize_version(version) FROM (SELECT '1.2.33.444.5b' AS version UNION SELECT '1' UNION SELECT NULL) AS t;
    

    renders:

    00001.00002.00033.00444.00005b
    00001
    (null)
    

    And allows for comparisons of almost any set of versions, even ones with letters.

    0 讨论(0)
  • 2020-12-16 15:00

    Assuming that the number of groups is 3 or less, you can treat the version number as two decimal numbers and sort it accordingly. Here is how:

    SELECT 
    ver,
    CAST(
        SUBSTRING_INDEX(ver, '.', 2)
        AS DECIMAL(6,3)
    ) AS ver1, -- ver1 = the string before 2nd dot
    CAST(
        CASE
            WHEN LOCATE('.', ver) = 0 THEN NULL
            WHEN LOCATE('.', ver, LOCATE('.', ver)+1) = 0 THEN SUBSTRING_INDEX(ver, '.', -1)
            ELSE SUBSTRING_INDEX(ver, '.', -2)
        END
        AS DECIMAL(6,3)
    ) AS ver2  -- ver2 = if there is no dot then 0.0
               --        else if there is no 2nd dot then the string after 1st dot
               --        else the string after 1st dot
    FROM
    (
    SELECT '1' AS ver UNION
    SELECT '1.1' UNION
    SELECT '1.01' UNION
    SELECT '1.01.03' UNION
    SELECT '1.01.04' UNION
    SELECT '1.01.1' UNION
    SELECT '1.11' UNION
    SELECT '1.2' UNION
    SELECT '1.2.0' UNION
    SELECT '1.2.1' UNION
    SELECT '1.2.11' UNION
    SELECT '1.2.2' UNION
    SELECT '2.0' UNION
    SELECT '2.0.1' UNION
    SELECT '11.1.1' 
    ) AS sample
    ORDER BY ver1, ver2
    

    Output:

    ver     ver1    ver2
    ======= ======  ======
    1        1.000  (NULL)
    1.01     1.010   1.000
    1.01.03  1.010   1.030
    1.01.04  1.010   1.040
    1.01.1   1.010   1.100
    1.1      1.100   1.000
    1.11     1.110  11.000
    1.2.0    1.200   2.000
    1.2      1.200   2.000
    1.2.1    1.200   2.100
    1.2.11   1.200   2.110
    1.2.2    1.200   2.200
    2.0      2.000   0.000
    2.0.1    2.000   0.100
    11.1.1  11.100   1.100
    

    Notes:

    1. You can extend this example for max 4 groups or more but the string functions will get more and more complicated.
    2. The datatype conversion DECIMAL(6,3) is used for illustration. If you expect more than 3 digits in minor version numbers then modify accordingly.
    0 讨论(0)
提交回复
热议问题