2012-01-03 8 views
12

У меня есть строки прошивки в моей таблице (например, «4.2.2» или «4.2.16»)Как сравнить строку версии ("x.y.z") в MySQL?

Как я могу их сравнить, выбрать или отсортировать?

Я не могу использовать стандартные сравнения строк: «4.2.2» является видели SQL больше, чем «4.2.16»

В строках версий, я хотел 4.2.16, чтобы быть больше, чем 4.2.2

Я хотел бы рассмотреть, что версия прошивки может содержать в себе символы: 4.24a1, 4.25b3 ... для этого, как правило, подполе с символами имеет фиксированную длину.

Как продолжить?

+3

Вот почему вы должны хранить строки как строки и цифры в виде чисел – zerkms

+0

. Номера версий всегда содержат 3 группы чисел? –

+0

@Salman: Нет Мне, возможно, придется сравнивать 4.2 и 4.2.1 – Eric

ответ

3

Наконец, я нашел еще один способ сортировки версий строк.

Я просто оправдываю строку перед хранением в базе данных так, как она сортируется. Поскольку я использую фреймворк Django python, я просто создал VersionField, который «кодирует» строку версии при хранении и «декодирует» ее во время чтения, так что он полностью прозрачен для приложения:

Здесь мой код :

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(' ','')) 
4

Предполагая, что число групп равно 3 или меньше, вы можете рассматривать номер версии как два десятичных числа и сортировать ее соответствующим образом. Вот как:

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 

Выход:

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 

Примечание:

  1. Вы можете расширить этот пример для макс 4 или более групп, но строковые функции будут становиться все более и более сложными.
  2. Преобразование типа данных DECIMAL(6,3) используется для иллюстрации. Если вы ожидаете более 3-х цифр в младших версиях номеров, измените их соответствующим образом.
2

Это довольно сложный вопрос, поскольку SQL не предназначен для разделения нескольких значений из одного поля - это является нарушением First Normal Form. Предполагая, что вы не будете иметь более трех групп чисел, каждое из которых не будет более чем из трех цифр, попробуйте:

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

Это решение работает. Но cast как float вызывает синтаксическую ошибку sql в mysql (?). Поэтому я немного модифицирую: выберите CONCAT (LPAD (substring_index (concat ("1.2.3", '. 0.0.'), '.', 1), 9, '0'), LPAD (substring_index (substring_index (concat («1.2.3», «0.0.»), «.», 2), «.», -1), 9, «0»), LPAD (substring_index (substring_index (concat («1.2.3», '.0.0.'), '.', 3), '.', -1), 9, '0')); – tangxinfa

+0

Это решение работает. – tangxinfa

14

Если все ваши номера версии выглядеть как любой из них:

X 
X.X 
X.X.X 
X.X.X.X 

где X - целое число от 0 до 255 (включительно), то вы можете использовать функцию INET_ATON(), чтобы преобразовать строки в целые числа, подходящие для сравнения.

Прежде чем применить эту функцию, вам необходимо убедиться, что аргумент функции имеет форму X.X.X.X, добавив к ней необходимое количество '.0'. Чтобы сделать это, сначала нужно выяснить, сколько . «ы строка уже содержит, что может быть сделано, как это:

CHAR_LENGTH(ver) - CHAR_LENGTH(REPLACE(ver, '.', '') 

То есть, число периодов в строке длина из строка минус ее длина после удаления периодов.

Полученный результат должен затем быть вычтена из 3 и, вместе с '.0', передается REPEAT() функции:

REPEAT('.0', 3 - CHAR_LENGTH(ver) + CHAR_LENGTH(REPLACE(ver, '.', '')) 

Это даст нам подстроки, которые должны быть приложены к первоначальному ver значение, чтобы соответствовать с форматом X.X.X.X. Таким образом, он, в свою очередь, будет передан функции CONCAT() вместе с ver. И результат этого CONCAT() теперь может быть напрямую передан INET_ATON(). Итак, вот что получилось:

INET_ATON(
    CONCAT(
    ver, 
    REPEAT(
     '.0', 
     3 - CHAR_LENGTH(ver) + CHAR_LENGTH(REPLACE(ver, '.', '')) 
    ) 
) 
) 

И это только за одно значение! :) Аналогичное выражение должно быть построено для другой строки, после чего вы можете сравнить результаты.

Список литературы:

+0

Большое вам спасибо. У меня была эта проблема, когда мне приходилось сравнивать значения версий из базы данных. Таким образом, мне нужен был способ дезактивировать информацию о версии в MySQL, прежде чем перейти к inet_aton. +1 к вам – RedBaron

0

Я искал то же самое, и вместо того, чтобы в конечном итоге делает это - но оставаясь в MySQL:

  • Установка этого udf library в MySQL, потому что я хотел силу PCRE.
  • используя эту инструкцию

    case when version is null then null 
    when '' then 0 
    else 
    preg_replace('/[^.]*([^.]{10})[.]+/', '$1', 
        preg_replace('/([^".,\\/_()-]+)([".,\\/_()-]*)/','000000000$1.', 
         preg_replace('/(?<=[0-9])([^".,\\/_()0-9-]+)/','.!$1',version 
    ))) 
    end 
    

Я сломаться, что это значит:

  • preg_replace является функцией, что библиотека UDF создана.Потому что это UDF, вы можете просто вызвать из любого пользователя или пространства базы, как тот
  • ^".,\\/_() прямо сейчас я рассматриваю все эти символы в качестве разделителей или традиционных «точек» в версии
  • preg_replace('/(?<=[0-9])([^".,\\/_()0-9-]+)/','.!$1',version) означает заменить все не - «точки» и не номера, которым предшествует число, которому предшествует «точка» и восклицательный знак.
  • preg_replace('/([^".,\\/_()-]+)([".,\\/_()-]*)/','000000000$1.', ...) означает дополнительно заменить все «точки» фактическими точками и заполнить все цифры с помощью 9 нулей. Также любые смежные точки будут уменьшены до 1.
  • preg_replace('/0*([^.]{10})[.]+/', '$1', ...) означает, что дополнительно блокировать все числовые блоки до 10 цифр и сохранять как можно больше блоков. Я хотел заставить 6 блоков сохранить его под 64-байтами, но 7 блоков были на удивление распространены и, следовательно, были необходимы для моей точности. Также необходимы блоки из 10, поэтому 7 блоков из 9 не были вариантом. Но переменная длина работает хорошо для меня. - помните строки сравниваются слева направо

Так что теперь я могу справиться с версией как:

1.2 < 1.10 
1.2b < 1.2.0 
1.2a < 1.2b 
1.2 = 1.2.0 
1.020 = 1.20 
11.1.1.3.0.100806.0408.000 < 11.1.1.3.0.100806.0408.001 
5.03.2600.2180 (xpsp_sp2_rtm.040803-2158) 
A.B.C.D = a.B.C.D 
A.A < A.B 

Я выбрал восклицательный знак, потому что он сортирует в последовательностях Collations (что я использую в любом случае) перед 0. Это относительная сортировка 0 позволяет использовать буквы, такие как b и a, когда они используются сразу же рядом с числом выше, чтобы обрабатываться как новый раздел и быть сортировкой до 0 - это заполнение, которое я использую.

Я использую 0 в качестве дополнения, так что ошибки поставщика, такие как перемещение с фиксированного 3-значного блока на переменную, не укусят меня.

Вы можете легко выбрать дополнительное дополнение, если хотите обрабатывать глупые версии, такие как «2.11.0 В стадии разработки (неустойчивый) (2010-03-09)» - строка development составляет 11 байт.

Вы можете запросить дополнительные блоки в окончательной замене.

Я мог бы сделать больше, но я старался сделать как можно меньше шагов с высокой степенью точности, так как у меня есть несколько миллионов записей для сканирования регулярно. Если кто-нибудь увидит оптимизацию, пожалуйста, repsond.

Я решил сохранить его в виде строки, а не вставить в число, потому что у литья есть стоимость, а также буквы важны, как мы видели. Одна вещь, о которой я думал, - это тест на строку и возврат опции, которая не так много, или менее дорогая функция для более аккуратных случаев. 11.1.1.3 - очень распространенный формат

1

Python может сравнивать списки по элементам точно так, как вы хотите сравнить версии, поэтому вы можете просто разделить на «.», вызвать int (x) на каждом элемент (со списком), чтобы преобразовать строку в int, а затем сравнить

>>> 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

Это мое решение. Это не зависит от количества подрывных действий.

Например:

select SF_OS_VERSION_COMPARE('2016.10.1712.58','2016.9.1712.58');

возвращает 'высокий'

select SF_OS_VERSION_COMPARE('2016.10.1712.58','2016.10.1712.58');

возвращает 'Equal'

delimiter // 

DROP FUNCTION IF EXISTS SF_OS_VERSION_COMPARE // 

CREATE FUNCTION SF_OS_VERSION_COMPARE(ver_1 VARCHAR(50), ver_2 VARCHAR(50)) RETURNS VARCHAR(5) 
    DETERMINISTIC 
    COMMENT 'Return "HIGH", "LOW" OR "EQUAL" comparing VER_1 with VER_2' 
BEGIN 
    DECLARE v_ver1 VARCHAR(50); 
    DECLARE v_ver2 VARCHAR(50); 
    DECLARE v_ver1_num INT; 
    DECLARE v_ver2_num INT; 

    SET v_ver1 = ver_1; 
    SET v_ver2 = ver_2; 

    WHILE (v_ver1 <> v_ver2 AND (v_ver1 IS NOT NULL OR v_ver2 IS NOT NULL)) DO 

    SET v_ver1_num = CAST(SUBSTRING_INDEX(v_ver1, '.', 1) AS UNSIGNED INTEGER); 
    SET v_ver2_num = CAST(SUBSTRING_INDEX(v_ver2, '.', 1) AS UNSIGNED INTEGER); 

    IF (v_ver1_num > v_ver2_num) 
    THEN 
     return 'HIGH'; 
    ELSEIF (v_ver1_num < v_ver2_num) 
    THEN 
     RETURN 'LOW'; 
    ELSE 
     SET v_ver1 = SUBSTRING(v_ver1,LOCATE('.', v_ver1)+1); 
     SET v_ver2 = SUBSTRING(v_ver2,LOCATE('.', v_ver2)+1); 
    END IF; 

    END WHILE; 

    RETURN 'EQUAL'; 

END // 
1

Много хороших решений, но я хотел хранимую функцию, которая орк с 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| 

для теста:

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

оказывает:

00001.00002.00033.00444.00005b 
00001 
(null) 

и позволяет сравнивать практически любого множества версий, даже те, с буквами.

+0

Единственное, что он не обрабатывает, это хеш-значения в конце какой-либо схемы нумерации версий, но они не предназначены для сортировки в любом случае. – CSTobey