2012-01-03 15 views
12

Nella mia tabella sono presenti stringhe della versione del firmware (come "4.2.2" o "4.2.16")Come confrontare la stringa di versione ("x.y.z") in MySQL?

Come posso confrontarle, selezionarle o ordinarle?

non posso usare il confronto stringhe di serie: "4.2.2" è un visto da SQL maggiore di "4.2.16"

Come stringhe di versione, vorrei 4.2.16 essere superiore a 4.2.2

Mi piacerebbe considerare che la versione del firmware può avere caratteri in essi: 4.24a1, 4.25b3 ... per questo, di solito, il sottocampo con caratteri ha una lunghezza fissa.

come procedere?

+3

Ecco perché è necessario memorizzare stringhe come stringhe e numeri come numeri – zerkms

+0

I numeri di versione contengono sempre 3 gruppi di numeri? –

+0

@ Salman: No Potrei dover confrontare 4.2 e 4.2.1 – Eric

risposta

3

Infine, ho trovato un altro modo per ordinare stringhe di versione.

Ho appena giustificato la stringa prima di memorizzarla nel database in modo che sia ordinabile. Mentre sto usando il framework Python di Django, ho appena creato un VersionField che "codifica" la stringa di versione durante l'archiviazione e la "decodifica" durante la lettura, in modo che sia totalmente trasparente per l'applicazione:

Qui il mio codice :

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

Supponendo che il numero di gruppi sia 3 o inferiore, è possibile considerare il numero di versione come due numeri decimali e ordinarlo di conseguenza. Ecco come:

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 

uscita:

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 

Note:

  1. È possibile estendere questo esempio per max 4 gruppi o più, ma le funzioni di stringa otterrete sempre più complicata.
  2. La conversione del tipo di dati DECIMAL(6,3) viene utilizzata per l'illustrazione. Se si prevedono più di 3 cifre in numeri di versione minori, modificare di conseguenza.
2

Questo è piuttosto complicato, in quanto SQL non è progettato per dividere più valori da un singolo campo: si tratta di una violazione di First Normal Form. Supponendo che non si ha intenzione di avere più di tre gruppi di numeri, ognuno dei quali non sarà lungo più di tre cifre, provare:

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

Questa soluzione funziona. Ma il cast come float causa un errore di sintassi sql in mysql (?). Così faccio una piccola modifica: selezionare 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

Questa soluzione funziona. – tangxinfa

14

Se tutti i numeri di versione assomigliano a uno di questi:

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

dove X è un numero intero compreso tra 0 e 255 (incluso), quindi è possibile utilizzare la funzione INET_ATON() per trasformare le stringhe in numeri interi adatti per il confronto.

Prima di applicare la funzione, tuttavia, è necessario assicurarsi che l'argomento della funzione sia del modulo X.X.X.X aggiungendo la quantità necessaria di '.0' ad esso. Per fare questo, è necessario prima di scoprire come s' la stringa contiene già, molti . che può essere fatto in questo modo:

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

Cioè, il numero di periodi nella stringa è la lunghezza del stringa meno la sua lunghezza dopo aver rimosso i punti.

Il risultato ottenuto deve poi essere sottratta dal 3 e, insieme con '.0', passato alla funzione REPEAT():

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

Questo ci darà la stringa che deve essere aggiunto al valore originale ver, di conformarsi con il formato X.X.X.X. Quindi, a sua volta, passerà alla funzione CONCAT() insieme a ver. E il risultato di quello CONCAT() può ora essere passato direttamente a INET_ATON(). Quindi ecco cosa otteniamo alla fine:

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

E questo è solo per un valore! :) Un'espressione simile dovrebbe essere costruita per l'altra stringa, in seguito è possibile confrontare i risultati.

Riferimenti:

+0

Grazie mille. Ho avuto questo problema in cui dovevo confrontare i valori di versione dal database. Quindi avevo bisogno di un modo per disinfettare le informazioni sulla versione in MySQL prima di passare a inet_aton. +1 a voi – RedBaron

0

ero alla ricerca per la stessa cosa e invece finito per fare questo - ma stare in MySQL:

  • l'installazione di questo udf library in MySQL perché volevo il potere di PCRE.
  • utilizzando questa dichiarazione

    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 
    

ti rompo giù che cosa significa:

  • preg_replace è una funzione che la libreria UDF creato.Perché è un UDF si può chiamare da qualsiasi utente o dbspace come quello
  • ^".,\\/_() in questo momento sto valutando tutti questi caratteri come separatori o "punti" tradizionali in versione
  • preg_replace('/(?<=[0-9])([^".,\\/_()0-9-]+)/','.!$1',version) significa per sostituire tutti i non - "punti" e non numeri preceduti da un numero preceduti da un "punto" e un punto esclamativo.
  • preg_replace('/([^".,\\/_()-]+)([".,\\/_()-]*)/','000000000$1.', ...) significa sostituire anche tutti i "punti" con punti reali e riempire tutti i numeri con 9 zero. Anche eventuali punti adiacenti si ridurre a 1.
  • preg_replace('/0*([^.]{10})[.]+/', '$1', ...) significa inoltre rimuovere tutti i blocchi numerici fino a sole 10 cifre e conservare tutti i blocchi necessari. Volevo forzare 6 blocchi per tenerlo sotto 64-byte ma necessitare di 7 blocchi era sorprendentemente comune e quindi necessario per la mia precisione. Necessari anche blocchi di 10 quindi 7 blocchi di 9 non erano un'opzione. Ma la lunghezza variabile sta funzionando bene per me. - ricorda le stringhe vengono confrontate da sinistra a destra

Così ora posso gestire versioni come:

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 

Ho scelto punto esclamativo perché ordina nelle sequenze regole di confronto (che sto usando in ogni caso) prima di 0. Il suo ordinamento relativo a 0 consente alle lettere come b e a quando viene utilizzato immediatamente adiacente a un numero sopra per essere trattato come una nuova sezione ed essere ordinato prima di 0 - che è il padding che sto usando.

Sto usando 0 come padding in modo che gli errori del venditore come passare da un blocco fisso a 3 cifre a uno variabile non mi mordino.

Puoi facilmente scegliere più padding se vuoi gestire versioni stupide come "2.11.0 In fase di sviluppo (unstable) (2010-03-09)" - la stringa development è di 11 byte.

È possibile richiedere più blocchi nella sostituzione finale.

Avrei potuto fare di più, ma stavo cercando di fare il minor tempo possibile con un alto livello di precisione, dal momento che ho diversi milioni di dischi da scansionare regolarmente. Se qualcuno vede un'ottimizzazione, per favore rispondi.

Ho scelto di mantenerlo come una stringa e non inserire un numero perché il cast ha un costo e anche le lettere sono importanti come abbiamo visto. Una cosa a cui stavo pensando è fare un test sulla stringa e restituire un'opzione che non è il numero di passaggi o la funzione meno costosa per i casi più ordinati. come 11.1.1.3 è un formato molto comune

1

Python può confrontare gli elenchi elemento per elemento esattamente nel modo in cui si desidera confrontare le versioni, in modo da poter semplicemente dividere il ".", chiamare int (x) su ogni elemento (con una lista di comprensione) per convertire la stringa in un int, e quindi confrontare

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

Questa è la mia soluzione. Non dipende dal numero di sovversione.

Ad esempio:

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

ritorna 'alta'

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

restituisce 'PARI'

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 sacco di buone soluzioni qui, ma volevo un funzione memorizzata che 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| 

alla prova:

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

rende:

00001.00002.00033.00444.00005b 
00001 
(null) 

e permette per i confronti di quasi tutte le serie di versioni, anche quelli con le lettere.

+0

l'unica cosa che non gestisce sono i valori hash alla fine di alcuni schemi di numerazione delle versioni, ma questi non sono pensati per essere ordinabili comunque. – CSTobey

Problemi correlati