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

前端 未结 10 2155
南旧
南旧 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 15:01

    I've created a flexible SQL-only solution based on the excellent answer of Salman A above:

    In this logic, I compare the first 4 version-segments. When the version string has more segments, the tailing ones are ignored.

    The code fetches the id and ver columns from a table and then "sanitizes" the ver value to always contain 3 dots - this sanitized version is returned by the sane_ver field.

    That sanitized version is then split into 4 integer values, each representing one version segment. You can compare or sort the results based on those 4 integers.

    The Code

    SELECT
        id,
        ver,
        SUBSTRING_INDEX(sane_ver, '.', 1) + 0 AS ver1,
        SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 2), '.', -1) + 0 AS ver2,
        SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 3), '.', -1) + 0 AS ver3,
        SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 4), '.', -1) + 0 AS ver4
    FROM (
        SELECT
            id,
            ver,
            CONCAT(
                ver,
                REPEAT('.0', 3 - CHAR_LENGTH(ver) + CHAR_LENGTH(REPLACE(ver, '.', '')))
            ) AS sane_ver
        FROM (
            SELECT id, ver FROM some_table
        ) AS raw_data 
    ) AS sane_data
    

    Sample

    Here's a full query with some sample data and a filter that returns only versions that are lower than 1.2.3.4

    SELECT
        id,
        ver,
        SUBSTRING_INDEX(sane_ver, '.', 1) + 0 AS ver1,
        SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 2), '.', -1) + 0 AS ver2,
        SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 3), '.', -1) + 0 AS ver3,
        SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 4), '.', -1) + 0 AS ver4
    FROM (
        SELECT
            id,
            ver,
            CONCAT(
                ver,
                REPEAT('.0', 3 - CHAR_LENGTH(ver) + CHAR_LENGTH(REPLACE(ver, '.', '')))
            ) AS sane_ver
        FROM (
            SELECT 1 AS id, '1' AS ver UNION
            SELECT 2,  '1.1' UNION
            SELECT 3,  '1.2.3.4.5' UNION
            SELECT 4,  '1.01' UNION
            SELECT 5,  '1.01.03' UNION
            SELECT 6,  '1.01.04a' UNION
            SELECT 7,  '1.01.04' UNION
            SELECT 8,  '1.01.04b' UNION
            SELECT 9,  '1.01.1.9.2.1.0' UNION
            SELECT 10, '1.11' UNION
            SELECT 11, '1.2' UNION
            SELECT 12, '1.2.0' UNION
            SELECT 13, '1.2.1' UNION
            SELECT 14, '1.2.11' UNION
            SELECT 15, '1.2.2' UNION
            SELECT 16, '2.0' UNION
            SELECT 17, '2.0.1' UNION
            SELECT 18, '11.1.1' UNION
            SELECT 19, '2020.11.18.11'
        ) AS raw_data 
    ) AS sane_data
    HAVING 
        ver1 <= 1
        AND (ver2 <= 2 OR ver1 < 1) 
        AND (ver3 <= 3 OR ver2 < 2 OR ver1 < 1) 
        AND (ver4 <  4 OR ver3 < 3 OR ver2 < 2 OR ver1 < 1)
    

    Notes

    Note how this logic is different than the original code by Salman A:

    The original answer uses CAST AS DECIMAL() which converts 1.02 to 1.020, and 1.1.0 to 1.100
    → That compares 1.02.0 to be lower than 1.1.0 (which is wrong, in my understanding)

    The code in this answer converts 1.02 to the integers 1, 2, and 1.1 to the integers 1, 1
    → That compares 1.1.0 to be lower than 1.02.0

    Also, both our solutions completely ignore any non-numeric characters, treating 1.2-alpha to be equal to 1.2.0.

提交回复
热议问题