我的表中有固件版本字符串(如“4.2.2”或“4.2.16”)
如何比较,选择或排序?
我不能使用标准字符串比较:“4.2.2”是SQL看到的大于“4.2.16”
作为版本字符串,我希望4.2.16大于4.2.2
我想考虑一下固件版本中可能有chars:4.24a1,4.25b3 ......为此,通常情况下,带字符的子字段具有固定长度。
如何进行?
答案 0 :(得分:15)
如果您的所有版本号都像以下任何一个:
X
X.X
X.X.X
X.X.X.X
其中X是0到255(含)的整数,那么您可以使用INET_ATON()
函数将字符串转换为适合比较的整数。
在应用函数之前,您需要通过向其附加必要数量的X.X.X.X
来确保函数的参数是'.0'
形式。要做到这一点,首先需要找出字符串已经包含多少.
,这可以这样做:
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
格式。因此,它将与CONCAT()
一起传递给ver
函数。现在,CONCAT()
的结果可以直接传递给INET_ATON()
。所以这就是我们最终得到的结果:
INET_ATON(
CONCAT(
ver,
REPEAT(
'.0',
3 - CHAR_LENGTH(ver) + CHAR_LENGTH(REPLACE(ver, '.', ''))
)
)
)
这仅适用于一个值! :)应该为另一个字符串构造一个类似的表达式,然后你可以比较结果。
参考文献:
答案 1 :(得分:4)
假设组的数量为3或更少,您可以将版本号视为两个十进制数,并相应地对其进行排序。方法如下:
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
注意:
DECIMAL(6,3)
用于说明。如果您希望次要版本号超过3位数,则相应地进行修改。答案 2 :(得分:3)
最后,我找到了另一种对版本字符串进行排序的方法。
我只是在以可排序的方式存储到de数据库之前证明字符串是正确的。 当我使用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(' ',''))
答案 3 :(得分:2)
这是一个非常复杂的问题,因为SQL不是为了从单个字段中拆分出多个值 - 这违反了First Normal Form。假设您不会有超过三组数字,每组数字不会超过三位数,请尝试:
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)
答案 4 :(得分: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
>>>
答案 5 :(得分:1)
这里有很多好的解决方案,但我想要一个可以与ORDER BY一起使用的存储函数
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|
进行测试:
SELECT standardize_version(version) FROM (SELECT '1.2.33.444.5b' AS version UNION SELECT '1' UNION SELECT NULL) AS t;
呈现:
00001.00002.00033.00444.00005b
00001
(null)
允许比较几乎任何版本的版本,甚至是带字母的版本。
答案 6 :(得分:0)
我正在寻找同样的事情而不是最终做到这一点 - 但留在mysql:
使用此声明
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
我会打破这意味着什么:
preg_replace
是UDF库创建的函数。因为它是一个UDF,你可以从任何用户或数据库空间中调用它,就像那样^".,\\/_ ()
我现在正在考虑所有这些字符作为分隔符或版本中的传统“点”preg_replace('/(?<=[0-9])([^".,\\/_ ()0-9-]+)/','.!$1',version)
表示替换所有非“点”和非数字,前面有数字,前面有“点”和感叹号。 preg_replace('/([^".,\\/_ ()-]+)([".,\\/_ ()-]*)/','000000000$1.', ...)
表示用实际点替换所有“点”并用9个零填充所有数字。此外,任何相邻的点都将减少到1。preg_replace( '/0*([^.]{10})[.]+/', '$1', ... )
意味着另外将所有数字块删除至仅10位数,并根据需要保留尽可能多的块。我想强制使用6个块来保持64字节以下但是需要7个块是非常常见的,因此我的准确性是必需的。还需要10个块,所以7个块9个不是一个选项。但是变长对我来说效果很好。 - 记住字符串从左到右比较所以现在我可以处理以下版本:
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之前对归类序列(我正在使用)进行排序。它的相对排序为0允许像b和a这样的字母在紧邻上面的数字时使用,就像新的一样对待section并在0之前排序 - 这是我正在使用的填充。
我使用0作为填充,以便供应商的错误,如从固定的3位数块移动到变量块,不会咬我。
如果你想处理像“2.11.0 Under development(unstable)(2010-03-09)”这样的愚蠢版本,你可以轻松选择更多填充 - 字符串development
是11个字节。
您可以在最终替换中轻松请求更多块。
我本来可以做得更多,但我试图尽可能少地以高精度完成,因为我有数百万条记录要定期扫描。如果有人看到优化,请回复。
我选择将它保留为一个字符串,而不是强制转换为数字,因为演员阵容有成本,而且字母也很重要。我正在考虑的一件事是对字符串进行测试并返回一个选项,该选项没有那么多通过或更便宜的功能更整齐的情况。像11.1.1.3
这是一种非常常见的格式
答案 7 :(得分:0)
这是我的解决方案。这不取决于颠覆的数量。
例如:
select SF_OS_VERSION_COMPARE('2016.10.1712.58','2016.9.1712.58');
返回'HIGH'
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 //
答案 8 :(得分:0)
oraenv
答案 9 :(得分:0)
我已经基于上面的excellent answer of Salman A创建了一个灵活的仅SQL解决方案:
按照这种逻辑,我比较了前4个版本段。当版本字符串包含更多细分时,尾部的细分将被忽略。
代码从表中提取id
和ver
列,然后“清理” ver
值以始终包含3个点-{{1 }}字段。
然后将经过清理的版本分为4个整数值,每个整数值代表一个版本段。您可以根据这4个整数对结果进行比较或排序。
sane_ver
这是一个完整查询,其中包含一些示例数据和一个过滤器,该过滤器仅返回低于SELECT
id,
ver,
SUBSTRING_INDEX(sane_ver, '.', 1) + 0 AS ver1,
SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 2), '.', -1) + 0 AS ver2,
SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 3), '.', -1) + 0 AS ver3,
SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 4), '.', -1) + 0 AS ver4
FROM (
SELECT
id,
ver,
CONCAT(
ver,
REPEAT('.0', 3 - CHAR_LENGTH(ver) + CHAR_LENGTH(REPLACE(ver, '.', '')))
) AS sane_ver
FROM (
SELECT id, ver FROM some_table
) AS raw_data
) AS sane_data
的版本
1.2.3.4
请注意此逻辑与Salman A的原始代码有何不同:
original answer使用SELECT
id,
ver,
SUBSTRING_INDEX(sane_ver, '.', 1) + 0 AS ver1,
SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 2), '.', -1) + 0 AS ver2,
SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 3), '.', -1) + 0 AS ver3,
SUBSTRING_INDEX(SUBSTRING_INDEX(sane_ver, '.', 4), '.', -1) + 0 AS ver4
FROM (
SELECT
id,
ver,
CONCAT(
ver,
REPEAT('.0', 3 - CHAR_LENGTH(ver) + CHAR_LENGTH(REPLACE(ver, '.', '')))
) AS sane_ver
FROM (
SELECT 1 AS id, '1' AS ver UNION
SELECT 2, '1.1' UNION
SELECT 3, '1.2.3.4.5' UNION
SELECT 4, '1.01' UNION
SELECT 5, '1.01.03' UNION
SELECT 6, '1.01.04a' UNION
SELECT 7, '1.01.04' UNION
SELECT 8, '1.01.04b' UNION
SELECT 9, '1.01.1.9.2.1.0' UNION
SELECT 10, '1.11' UNION
SELECT 11, '1.2' UNION
SELECT 12, '1.2.0' UNION
SELECT 13, '1.2.1' UNION
SELECT 14, '1.2.11' UNION
SELECT 15, '1.2.2' UNION
SELECT 16, '2.0' UNION
SELECT 17, '2.0.1' UNION
SELECT 18, '11.1.1' UNION
SELECT 19, '2020.11.18.11'
) AS raw_data
) AS sane_data
HAVING
ver1 <= 1
AND (ver2 <= 2 OR ver1 < 1)
AND (ver3 <= 3 OR ver2 < 2 OR ver1 < 1)
AND (ver4 < 4 OR ver3 < 3 OR ver2 < 2 OR ver1 < 1)
将CAST AS DECIMAL()
转换为1.02
,并将1.020
转换为1.1.0
→比较 1.02.0低于1.1.0 (据我所知,这是错误的)
此答案中的代码将1.100
转换为整数1.02
,并将1, 2
转换为整数1.1
→比较 1.1.0低于1.02.0
此外,我们两个解决方案都完全忽略了任何非数字字符,将1, 1
等同于1.2-alpha
。