2012-01-03 21 views
12

Tengo cadenas de versión de firmware en mi tabla (como "4.2.2" o "4.2.16")¿Cómo comparar la cadena de versión ("x.y.z") en MySQL?

¿Cómo puedo comparar, seleccionar o clasificar?

no puedo usar la comparación cuerdas estándar: "4.2.2" es un visto por SQL mayor que "4.2.16"

Como cadenas de versión, me gustaría 4.2.16 a ser mayor que 4.2.2

Me gustaría considerar que la versión de firmware puede tener caracteres en ellos: 4.24a1, 4.25b3 ... para esto, generalmente, el subcampo con caracteres tiene una longitud fija.

cómo proceder?

+3

Es por eso que debe almacenar cadenas como cadenas y números como números – zerkms

+0

¿Los números de versión siempre contienen 3 grupos de números? –

+0

@Salman: No, tendré que comparar 4.2 y 4.2.1 – Eric

Respuesta

3

Por último, he encontrado otra manera de ordenar cadenas de versión.

Justifico la cadena antes de almacenarla en la base de datos de una forma que se pueda ordenar. Como uso el framework Django de python, acabo de crear un VersionField que 'codifica' la cadena de versión mientras la almacena y la decodifica durante la lectura, para que sea totalmente transparente para la aplicación:

Aquí mi código :

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

Suponiendo que el número de grupos es 3 o menos, puede tratar el número de versión como dos números decimales y ordenarlo en consecuencia. Aquí es cómo:

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 

Salida:

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 

Notas:

  1. Puede ampliar este ejemplo para un máximo de 4 grupos o más, pero las funciones de cadena obtendrá más y más complicado.
  2. La conversión de tipo de datos DECIMAL(6,3) se usa a modo de ilustración. Si espera más de 3 dígitos en números de versiones menores, modifíquelos en consecuencia.
2

Esto es bastante complicada, ya que SQL no está diseñado para dividir a cabo múltiples valores de un solo campo - esto es una violación de First Normal Form. Suponiendo que usted no va a tener más de tres grupos de números, cada uno de los cuales no serán más de tres dígitos, intente:

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

Esta solución funciona. Pero emitir como float causa un error de sintaxis sql en mysql (?). Así que hago una pequeña modificación: seleccione CONCAT (LPAD (subcadena_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

Esta solución funciona. – tangxinfa

14

Si todos los números de versión se parece a ninguna de estas:

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

donde X es un número entero de 0 a 255 (inclusive), entonces podría usar la función INET_ATON() para transformar las cadenas en enteros adecuados para la comparación.

Antes de aplicar la función, deberá asegurarse de que el argumento de la función sea del formulario X.X.X.X al agregarle la cantidad necesaria de '.0'. Para ello, primero tendrá que averiguar cuántas . 's de la cadena ya contiene, lo que se puede hacer así:

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

Es decir, el número de períodos en la cadena es la longitud de la cadena menos su longitud después de eliminar los puntos.

El resultado obtenido a continuación debe restarse de 3 y, junto con '.0', pasa a la función REPEAT():

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

Esto nos dará la subcadena que debe ser anexa al valor original ver, para ajustarse con el formato X.X.X.X. Por lo tanto, a su vez, se pasará a la función CONCAT() junto con ver. Y el resultado de ese CONCAT() ahora se puede pasar directamente al INET_ATON(). Así que esto es lo que obtenemos con el tiempo:

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

Y esto es solo por un valor! :) Se debe construir una expresión similar para la otra cadena, luego se pueden comparar los resultados.

Referencias:

+0

Muchas gracias. Tuve este problema donde tuve que comparar los valores de versión de la base de datos. Así que necesitaba una forma de desinfectar la información de la versión en MySQL antes de pasar a inet_aton. +1 a usted – RedBaron

0

que estaba buscando lo mismo y en su lugar terminó haciendo esto - pero su estancia en MySQL:

  • la instalación de este udf library en MySQL porque quería el poder de PCRE.
  • usando esta declaración

    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 
    

voy a romper lo que eso significa:

  • preg_replace es una función que crea la biblioteca de UDF.Debido a que es una UDF que sólo se puede llamar desde cualquier usuario o dbspace así
  • ^".,\\/_() En este momento estoy teniendo en cuenta todos estos caracteres como separadores o "puntos" tradicionales en una versión
  • preg_replace('/(?<=[0-9])([^".,\\/_()0-9-]+)/','.!$1',version) significa para reemplazar todos los no - "puntos" y no números que van precedidos por un número que va precedido por un "punto" y un signo de exclamación.
  • preg_replace('/([^".,\\/_()-]+)([".,\\/_()-]*)/','000000000$1.', ...) significa reemplazar adicionalmente todos los "puntos" con puntos reales y rellenar todos los números con 9 ceros. También cualquier punto adyacente se reduciría a 1.
  • preg_replace('/0*([^.]{10})[.]+/', '$1', ...) significa pelar adicionalmente todos los bloques de números de solo 10 dígitos y conservar tantos bloques como sea necesario. Quería forzar 6 bloques para mantenerlo por debajo de 64 bytes pero necesitar 7 bloques era sorprendentemente común y por lo tanto necesario para mi precisión. También se necesitaban bloques de 10, por lo que 7 bloques de 9 no eran una opción. Pero la longitud variable funciona bien para mí. - recordar cadenas se comparan de izquierda a derecha

Así que ahora puedo manejar versiones como:

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 

Elegí signo de exclamación, ya que ordena en las secuencias intercalaciones (que estoy usando de todos modos) antes 0. Su clasificación relativa a 0 permite que las letras como by a cuando se usen inmediatamente adyacentes a un número anterior se traten como una nueva sección y sean ordenadas antes de 0, que es el relleno que estoy usando.

Estoy usando 0 como relleno para que los errores del vendedor como pasar de un bloque fijo de 3 dígitos a uno variable no me muerdan.

Puede elegir fácilmente más relleno si desea manejar versiones tontas como "2.11.0 En desarrollo (inestable) (2010-03-09)" - la cadena development tiene 11 bytes.

Puede solicitar fácilmente más bloques en la sustitución final.

Pude haber hecho más, pero estaba tratando de hacer la menor cantidad de pasos posible con un alto nivel de precisión ya que tengo varios millones de registros para escanear regularmente. Si alguien ve una optimización por favor repsond.

Elegí mantenerlo como una cuerda y no lanzar en un número porque el yeso tiene un costo y también las letras son importantes como vimos. Una cosa en la que estaba pensando es en hacer una prueba en la cadena y devolver una opción que no tenga tantos pases o funciones menos costosas para casos más discretos. como 11.1.1.3 es un formato muy común

1

Python puede comparar listas elemento por elemento exactamente de la manera que desea que se comparen las versiones, por lo que simplemente puede dividir el ".", invocar int (x) en cada elemento (con una lista de comprensión) para convertir la cadena a un int, y luego comparar

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

Esta es mi solución. No depende del número de subversión.

Por ejemplo:

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

retornos 'alto'

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

devuelve 'igual'

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

Un montón de buenas soluciones aquí, pero quería una función almacenada que w ork con 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| 

a prueba:

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) 

y permite comparaciones de casi cualquier conjunto de versiones, aunque sean con letras.

+0

lo único que no maneja son los valores hash al final de algún esquema de numeración de versión, pero esos no están destinados a ser ordenables de todos modos. – CSTobey

Cuestiones relacionadas