3

我正在使用 Oracle Database 11g Enterprise Edition Release 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 Fiddle 。请注意我必须如何将每个部分转换为数字才能使其正常工作。

但是,如果您将它们分成不同的列,主要版本,次要版本等,我不能强调您的生活会变得多么容易。然后您可以有一个虚拟列将它们连接在一起以确保您的导出始终是标准化的,如果你愿意。

例如,如果您创建了一个表,如下所示:

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 Fiddle

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