找到最好的版本

时间:2013-01-28 19:30:15

标签: oracle plsql oracle11g

我使用的是Oracle Database 11g企业版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&lt; t2.VERSION'仅适用于正常版本的情况,但在例如:

的情况下
B         12.1.0.2
B         8.2.1.2

它失败了,我需要一个PL / SQL脚本来规范化版本字符串并将它们进行比较以获得更高的值。

4 个答案:

答案 0 :(得分: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

答案 1 :(得分: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 Fiddle

答案 2 :(得分:1)

您需要将字符串值转换为数值,然后通过一些适当的乘数来缩放它们。假设每个版本值必须是介于0..99之间的数字作为示例。所以,如果你的字符串是“8.2.1.2”,你会缩放字符串的数值,“a.b.c.d”= 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.

答案 3 :(得分: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 |
+----------------------------+