我有这样的价值观:
1.1.2
9.1
2.2
4
1.2.3.4
3.2.14
3.2.1.4.2
.....
我需要使用 mysql 对这些值进行排序。该数据类型是 varbinary(300)。
所需的输出将类似于:
1.1.2
1.2.3.4
2.2
3.2.1.4.2
3.2.14
4
9.1
查询是:
select version_number from table order by version_number asc
它没有给出正确的排序顺序。
期望的输出是:
1.1.2
1.2.3.4
2.2
3.2.1.4.2
3.2.14
4
9.1
版本号最多 20 位(如 1.2.3.4.5.6.7.8.9.2.34)甚至更多。没有特定的最大尺寸,标准版本就像上面提到的那样。
尝试滥用
INET_ATON
函数进行排序,如下所示:
SELECT version_number FROM table ORDER BY INET_ATON(SUBSTRING_INDEX(CONCAT(version_number,'.0.0.0'),'.',4))
这个技巧最初发布在 mysql 邮件列表上,非常感谢原始发布者 Michael Stassen!
这是他必须说的话:
如果每个部分不大于255,您可以利用INET_ATON()来执行 你想要什么(直到第四部分)。诀窍是让每一个 首先使用 CONCAT 添加“0.0.0”来确保每个 IP 看起来像一个 IP row 至少有 4 个部分,然后 SUBSTRING_INDEX 只提取 前 4 部分。
现在,我必须指出,因为我们正在根据 列,而不是列本身,我们不能在列上使用索引 列来帮助排序。换句话说,排序将是 相对较慢。
在后一种情况下,他推荐了与@spanky(单独的列)发布的解决方案类似的解决方案。
我会将其存储在三个单独的列中,每个列对应版本号的每个部分。
将每一列设置为 TINYINT,甚至在这 3 列之间创建一个索引。这应该会让事情变得简单。
然后你可以这样做:
select CONCAT(v1,'.',v2,'.',v3) AS version_number FROM table ORDER BY v1 asc, v2 asc, v3 asc
如果您想支持像
1.1-beta
这样的版本或使用不带INTE_ATON
的旧MySql版本,您可以通过拆分版本并将每个部分作为整数和字符串进行排序来获得相同的排序:
SELECT
version,
REPLACE(SUBSTRING(SUBSTRING_INDEX(version, '.', 1), LENGTH(SUBSTRING_INDEX(version, '.', 1 - 1)) + 1), '.', '') v1,
REPLACE(SUBSTRING(SUBSTRING_INDEX(version, '.', 2), LENGTH(SUBSTRING_INDEX(version, '.', 2 - 1)) + 1), '.', '') v2,
REPLACE(SUBSTRING(SUBSTRING_INDEX(version, '.', 3), LENGTH(SUBSTRING_INDEX(version, '.', 3 - 1)) + 1), '.', '') v3,
REPLACE(SUBSTRING(SUBSTRING_INDEX(version, '.', 4), LENGTH(SUBSTRING_INDEX(version, '.', 4 - 1)) + 1), '.', '') v4
FROM
versions_table
ORDER BY
0+v1, v1 DESC, 0+v2, v2 DESC, 0+v3, v3 DESC, 0+v4, v4 DESC;
使用正则表达式。首先对值进行归一化:
SELECT REGEXP_REPLACE(
REGEXP_REPLACE(
REGEXP_REPLACE('v1.22.333', '^v', ''),
'(^|\\.)(\\d+)',
'\\100000\\2'
),
'0+(\\d{5})(\\.|$)',
'\\1\\2'
)
输出:
00001.00022.00333
然后就可以正常排序了。
该解决方案适用于任意数量的组件。您可以将组件长度从 5 缩放到任何固定长度。
尽管@TrinitronX 的答案非常优雅,但它不适用于各种可能的语义版本控制值。特别是那些由预发布部分组成的部分,例如
1.0.0-beta.1
。
另一种方法是在 MySQL 数据库表中保留版本的排序列表。让我们创建以下数据库表:
CREATE TABLE `versions` (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
version VARCHAR(255) COLLATE utf8mb4_bin NOT NULL,
sort_order BIGINT UNSIGNED NOT NULL,
UNIQUE KEY unique_version (version),
KEY idx_sort_order_version (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
每次我们向
versions
数据库表添加新版本时,我们都会在插入记录之前计算 sort_order
值。理论上这个操作的复杂度应该是O(log n)。有了这个,我们就可以使用下面的 SQL 查询来获取版本的顺序列表:
SELECT `version` FROM `versions` ORDER BY sort_order;
我创建了 https://github.com/recipe/mysql-sermver 项目,以在两个 MySQL 存储函数中实现这个想法:
VERSION_COMPARE(version1, version2)
此功能支持语义版本2以及大多数现实生活版本。它回来了
0
如果两个版本相同,1
如果 version1
大于 version2
,-1
如果 version2
大于 version1
。SELECT VERSION_COMPARE('1.0.0', '1.0.0');
0
SELECT VERSION_COMPARE('1.0.2-alpha', '1.0.2');
-1
SELECT VERSION_COMPARE('1.0.2-beta', '1.0.2-alpha');
1
GET_SORT_ORDER(version)
此函数计算新添加版本的
sort_order
值,并且可以在 BEFORE INSERT
数据库表上的 versions
数据库触发器内使用。
DELIMITER //
CREATE TRIGGER bi_versions_set_sort_order BEFORE INSERT ON versions
FOR EACH ROW
BEGIN
DECLARE v BIGINT UNSIGNED;
IF NEW.sort_order = 0 THEN
SELECT GET_SORT_ORDER(NEW.version) INTO v;
SET NEW.sort_order = v;
END IF;
END //
对于上面的问题,它是如何工作的?
您需要将 mysql-semver 项目中的两个存储函数安装到 MySQL 数据库。然后你 可以添加新版本并选择订购的版本。
INSERT versions (version, sort_order)
VALUES
('1.1.2', 0),
('9.1', 0),
('2.2', 0),
('4', 0),
('1.2.3.4', 0),
('3.2.14', 0),
('3.2.1.4.2', 0);
SELECT `version` FROM `versions` ORDER BY sort_order;
+---------+
|version |
+---------+
|1.1.2 |
|1.2.3.4 |
|2.2 |
|3.2.1.4.2|
|3.2.14 |
|4 |
|9.1 |
+---------+