2012-01-03 10 views
12

テーブルにファームウェアのバージョン文字列があります(「4.2.2」または「4.2.16」など)MySQLでバージョン文字列( "x.y.z")を比較するには?

どのように比較、選択、並べ替えができますか?

私は、標準の文字列比較を使用することはできません:「4.2.2」バージョン文字列として、私はあることを4.2.16をしたいと思い、「4.2.16」

より大きいSQLで見られているよりも大きい4.2.2

私は、ファームウェアのバージョンがそれらに文字を持つことができると考えています:4.24a1、4.25b3 ...これに対して、通常、charsを持つサブフィールドは固定長です。

どのように進めますか?

+3

文字列を数字として文字列と文字列として保存する理由は? – zerkms

+0

バージョン番号には常に3つの数字のグループが含まれていますか? –

+0

@サルマン:いいえ4.2と4.2.1を比較する必要があるかもしれません。 – Eric

答えて

3

最後に、私は、バージョン文字列をソートする別の方法を発見しました。

データベースに格納する前に文字列を並べ替えることができます。 私はPythonのDjangoフレームワークを使用しているので、読み込み中にバージョン文字列を 'エンコード'して保存して 'デコード'する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以下であると仮定すると、バージョン番号を2つの10進数として扱い、それに応じて並べ替えることができます。ここでは、どのように:

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は1つのフィールドから複数の値を分割するようには設計されていないため、これはむしろ複雑です。これはFirst Normal Formという違反です。あなたは以上の3桁の長さではありませんそれぞれの数値以上の三つのグループを、持っているつもりされていないと仮定すると、試してみてください。

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

を修正します。しかし、float型としてキャストすると、mysql(?)にSQL構文エラーが発生します。したがって、私は少し変更を行います:CONCATを選択します(LPAD(substat_index(concat( "1.2.3"、 '.0。')、 '。'、1)、9、 '0')、LPAD(substring_index(substring_index(concat LPAD(substring_index(substring_index(concat( "1.2.3"、 "1.2.3"、 '.0。') '、'。 '、-1)、9、' 0 ' '。'、3)、 '。'、-1)、9、 '0')); – tangxinfa

+0

このソリューションは機能します。 – tangxinfa

14

をすべてのバージョン番号は、これらのいずれかのように見える場合:

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

Xは0〜255の整数で、INET_ATON()関数を使用して、文字列を比較のために整数に変換できます。

関数を適用する前に、必要な数量を'.0'に追加して、関数の引数がX.X.X.X形式であることを確認する必要があります。それを行うには、まず知る必要がありますどのようにこのように行うことができ、多くの.の文字列がすでに含まれている、:

ある
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の形式です。したがって、順番に、verと一緒にCONCAT()関数に渡されます。その結果、CONCAT()INET_ATON()に直接渡すことができます。結果は次のようになります。

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

これは1つの値にすぎません。 :)他の文字列に対しても同様の式を構築し、その後で結果を比較することができます。

参照:

+0

ありがとうございました。私はデータベースからバージョンの値を比較しなければならなかったこの問題を抱えていました。ですから、私はinet_atonに渡す前にMySQLでバージョン情報を消毒する方法が必要でした。あなたに+1 – RedBaron

0

私は同じことを探して、代わりにこれをやってしまった - が、MySQLに滞在:

  • のmysqlにこのudf libraryをインストールし、私はPCREの力を望んでいたので。

    • preg_replaceは、UDFライブラリが作成した関数である:私はそれが何を意味するのか壊すよこの文

      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 
      

    を使用して

  • 。それはUDFなので、あなただけの今の私は、バージョンにセパレータや伝統的な「点」として、これらの文字のすべてを検討している

  • preg_replace('/(?<=[0-9])([^".,\\/_()0-9-]+)/','.!$1',version)は、すべての非を交換することを意味し
  • ^".,\\/_()のような任意のユーザーまたはDB領域からそれを呼び出すことができます - 「ドット」と、数字の前に「ドット」と感嘆符が続く非数字。
  • preg_replace('/([^".,\\/_()-]+)([".,\\/_()-]*)/','000000000$1.', ...)は、すべての「ドット」を実際のドットでさらに置き換え、すべての数字を9個のゼロで埋め込むことを意味します。また、隣接するドットはすべて1に縮小されます。
  • preg_replace('/0*([^.]{10})[.]+/', '$1', ...)は、すべての数値ブロックをわずか10桁まで削除し、必要なだけ多くのブロックを保存することを意味します。私は6ブロックを64バイト以下に保つために強制したかったが、7ブロックを必要とすることは驚くほど一般的であり、したがって私の正確さのために必要であった。また、10のブロックが必要なので、9の7ブロックはオプションではありませんでした。しかし、可変長は私のためにうまくいきます。 - 文字列を覚えだから今、私はのようなバージョン扱うことができる権利

に委ねて比較している:それは(私はとにかく使っていることを)照合シーケンスにソートするため

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 

を、私は前に感嘆符を選びました0への相対ソートでは、bとaのような文字は、上記の数値のすぐ隣に使用すると、新しいセクションのように扱われ、0の前にソートされます。これは、私が使っているパディングです。

固定された3桁ブロックから変数1への移動のようなベンダーの間違いが私を噛まないように、埋め込みとして0を使用しています。

"2.11.0開発中(不安定)(2010-03-09)"のような愚かなバージョンを処理したい場合は、より多くのパディングを簡単に選択できます。文字列developmentは11バイトです。

最終的な交換でさらにブロックをリクエストできます。

私はもっと多くのことをすることができましたが、定期的にスキャンするために数百万のレコードがあるため、できるだけ多くのペースを高精度で実行しようとしていました。もし誰かが最適化を見ていれば、ご連絡ください。

キャストにコストがかかり、私たちが見たように文字も重要であるため、文字列にして数字にキャストしないことを選択しました。私が考えていたことの一つは、文字列のテストを行い、より洗練されたケースではそれほど多くのパスやより安価な関数ではないオプションを返すことでした。 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

ここで良いソリューションの多くが、私は望んでいましたwとなるストアド関数テストへ

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| 

BY ORDERでORK:

00001.00002.00033.00444.00005b 
00001 
(null) 

をとバージョン、文字でも、もののほぼすべてのセットの比較をすることができます:

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

レンダリングします。

+0

それは扱わない唯一のものは、いくつかのバージョン番号付けスキームの終わりにハッシュ値ですが、それらはとにかくソート可能ではありません。 – CSTobey