我使用的是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脚本来规范化版本字符串并将它们进行比较以获得更高的值。
答案 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
答案 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)
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 |
+----------------------------+