3

Oracle Database 11gEnterpriseEditionリリース11.2.0.2.0を使用しています

私は次のようなテーブルを持っています:

Table1:
Name              Null     Type          
----------------- -------- ------------- 
NAME              NOT NULL VARCHAR2(64)  
VERSION           NOT NULL VARCHAR2(64) 


Table1
Name    Version
---------------
A         1
B         12.1.0.2
B         8.2.1.2
B         12.0.0
C         11.1.2
C         11.01.05

次のような出力が必要です。

Name    Version
---------------
A        1
B        12.1.0.2
C        11.01.05

基本的に、バージョンが最も高い名前ごとに行を取得したいと思います。このために、私は次のクエリを使用しています。

SELECT t1.NAME, 
       t1.VERSION
FROM TABLE1 t1 
LEFT OUTER JOIN TABLE1 t2
on (t1.NAME = t2.NAME and t1.VERSION < t2.VERSION)
where t2.NAME is null

現在、「t1.VERSION <t2.VERSION」は通常のバージョンの場合にのみ機能しますが、次のような場合にのみ機能します。

B         12.1.0.2
B         8.2.1.2

失敗します。バージョン文字列を正規化し、それらを比較してより高い値にするためのPL/SQLスクリプトが必要です。

4

4 に答える 4

6

これは、 REGEXP_SUBSTR()を適切に使用して行うことができます。PL/SQLを使用する必要はありません。

select *
  from ( select a.*
              , row_number() over ( 
                  partition by name
                      order by to_number(regexp_substr(version, '[^.]+', 1)) desc
                             , to_number(regexp_substr(version, '[^.]+', 2)) desc
                             , to_number(regexp_substr(version, '[^.]+', 3)) desc
                             , to_number(regexp_substr(version, '[^.]+', 4)) desc
                               ) as rnum
          from table1 a )
 where rnum = 1

これがデモンストレーションするSQLフィドルです。これを機能させるために、各部分を数値に変換する必要があることに注意してください。

ただし、これらをメジャーバージョン、マイナーバージョンなどの異なる列に分割すると、生活がどれほど楽になるかを強調することはできません。次に、これらをすべて連結してエクスポートが常に標準化されるようにする仮想列を作成できます。あなたが望むなら。

たとえば、次のようにテーブルを作成した場合:

create table table1 ( 
    name varchar2(64)
  , major number
  , minor number
  , build number
  , revision number
  , version varchar2(200) generated always as (
      to_char(major) || '.' || 
      to_char(minor) || '.' || 
      to_char(build) || '.' || 
      to_char(revision)
      )

クエリが理解しやすくなります。SQLフィドルでも

select name, version
  from ( select a.*
              , row_number() over ( 
                   partition by name
                       order by major desc
                              , minor desc
                              , build desc
                              , revision desc ) as rnum
           from table1 a )
 where rnum = 1
于 2013-01-28T20:18:36.797 に答える
3

このソリューションは、バージョンコード内にある数値部分の数に依存しません。
すべての数値部分が6桁以下で構成されていることを前提としています。

select 
  name,
  max(version) keep (dense_rank first order by version_norm desc) 
    as max_version
from (
    select 
      t.*,
      regexp_replace(
        regexp_replace('000000'||version, '\.', '.000000')||'.',
        '\d*(\d{6}\.)', '\1') 
        as version_norm
    from table1 t
  )
group by name

SQLフィドル

于 2013-01-29T02:30:09.460 に答える
1

どういうわけか、文字列値を数値に変換してから、適切な乗数でスケーリングする必要があります。例として、各バージョン値は0..99の間の数値でなければならないと仮定します。したがって、文字列が「8.2.1.2」の場合、文字列の数値をスケーリングします。「abcd」= d + c * 100 + b * 10000 + a * 1000000、= 2 + 100 + 20000 + 8000000 = 8020102 、次にその値を使用して注文できます。

区切られた文字列からトークンを解析するために使用できる関数を見つけました。

CREATE OR REPLACE FUNCTION get_token (the_list     VARCHAR2,
                                      the_index    NUMBER,
                                      delim        VARCHAR2 := ',')
   RETURN VARCHAR2
IS
   start_pos   NUMBER;
   end_pos     NUMBER;
BEGIN
   IF the_index = 1
   THEN
      start_pos := 1;
   ELSE
      start_pos :=
         INSTR (the_list,
                delim,
                1,
                the_index - 1);

      IF start_pos = 0
      THEN
         RETURN NULL;
      ELSE
         start_pos := start_pos + LENGTH (delim);
      END IF;
   END IF;

   end_pos :=
      INSTR (the_list,
             delim,
             start_pos,
             1);

   IF end_pos = 0
   THEN
      RETURN SUBSTR (the_list, start_pos);
   ELSE
      RETURN SUBSTR (the_list, start_pos, end_pos - start_pos);
   END IF;
END get_token;

だから何かを呼ぶ

select to_number(get_token(version,1,'.'))*1000000 +  to_number(get_token(version,2,'.'))*10000 + .. etc.
于 2013-01-28T19:54:23.133 に答える
0

タスクを実行するためのMySQLユーザー定義関数を作成しただけで、それをORACLE PL/SQLに簡単に移植できます。

DELIMITER $$

DROP FUNCTION IF EXISTS `VerCmp`$$

CREATE FUNCTION VerCmp (VerX VARCHAR(64), VerY VARCHAR(64), Delim CHAR(1))
RETURNS INT DETERMINISTIC
BEGIN
    DECLARE idx INT UNSIGNED DEFAULT 1;
    DECLARE xVer INT DEFAULT 0;
    DECLARE yVer INT DEFAULT 0;
    DECLARE xCount INT UNSIGNED DEFAULT 0;
    DECLARE yCount INT UNSIGNED DEFAULT 0;
    DECLARE counter INT UNSIGNED DEFAULT 0;

SET xCount = LENGTH(VerX) - LENGTH(REPLACE(VerX, Delim,'')) +1;
SET yCount = LENGTH(VerY) - LENGTH(REPLACE(VerY, Delim,'')) +1;

IF xCount > yCount THEN
    SET counter = xCount;
ELSE
    SET counter = yCount;
END IF;

WHILE (idx <= counter) DO

    IF (xCount >= idx) THEN
        SET xVer = SUBSTRING_INDEX(SUBSTRING_INDEX(VerX, Delim, idx), Delim, -1) +0;
    ELSE
        SET xVer =0;
    END IF;
    IF (yCount >= idx) THEN
        SET yVer = SUBSTRING_INDEX(SUBSTRING_INDEX(VerY, Delim, idx), Delim, -1) +0;
    ELSE 
        SET yVer = 0;
    END IF;

    IF (xVer > yVer) THEN
        RETURN 1;
    ELSEIF (xVer < yVer) THEN
        RETURN -1;
    END IF;

    SET idx = idx +1;
END WHILE;

RETURN 0;

END$$;

DELIMITER ;

私が実行したいくつかのテスト:

select vercmp('5.2.4','5.2.5','.');
+------------------------------+
| vercmp('5.2.4','5.2.5','.')  |
+------------------------------+
|                           -1 |
+------------------------------+

select vercmp('5.2.4','5.2.4','.');
+------------------------------+
| vercmp('5.2.4','5.2.4','.')  |
+------------------------------+
|                            0 |
+------------------------------+

select vercmp('5.2.4','5.2','.');
+----------------------------+
| vercmp('5.2.4','5.2','.')  |
+----------------------------+
|                          1 |
+----------------------------+

select vercmp('1,2,4','5,2',',');
+----------------------------+
| vercmp('1,2,4','5,2',',')  |
+----------------------------+
|                         -1 |
+----------------------------+
于 2013-01-28T22:16:47.637 に答える