我有一个包含代表发布“版本”的字符串的列,例如:
5.14.0
5.10.1
5.1.8
5.1.87
5.1.357
26.3.0
24.1.1
如何在 Snowflake 中对这些进行排序?请注意,数字排序(我们想要的)与简单字符串排序不同。
使用
SPLIT_PART
:
SELECT *
FROM data
ORDER BY SPLIT_PART(version_number, '.', 1)::INT,
SPLIT_PART(version_number, '.', 2)::INT,
SPLIT_PART(version_number, '.', 3)::INT;
输出:
VERSION_NUMBER
5.1.8
5.1.87
5.1.357
5.10.1
5.14.0
24.1.1
26.3.0
使用
natsort
Python 包:
create or replace function natural_sort(arr ARRAY)
returns array
language python
runtime_version = '3.8'
packages = ('natsort')
handler = 'sort'
as
$$
import natsort
def sort(arr):
return natsort.natsorted(arr)
$$;
输入数据:
create table data as
select value version_number
from (table(split_to_table(
$$5.14.0
5.10.1
5.1.8
5.1.87
5.1.357
26.3.0
24.1.1$$, '\n'
)))
;
输出:
SELECT NATURAL_SORT(SELECT ARRAY_AGG(VERSION_NUMBER) FROM data);
-- ["5.1.8", "5.1.87", "5.1.357", "5.10.1", "5.14.0", "24.1.1", "26.3.0" ]
或:
SELECT VALUE::TEXT AS VERSION_NUMBER
FROM TABLE(FLATTEN(NATURAL_SORT(SELECT ARRAY_AGG(VERSION_NUMBER) FROM data)));
/*
VERSION_NUMBER
5.1.8
5.1.87
5.1.357
5.10.1
5.14.0
24.1.1
26.3.0
*/
编辑 - 版本 2:
create or replace function natural_sort(str TEXT)
returns array
language python
runtime_version = '3.8'
packages = ('natsort')
handler = 'sort'
as
$$
import natsort
def sort(str):
return list(natsort.natsort_key(str))
$$;
致电:
SELECT *
FROM data
ORDER BY natural_sort(version_number);
输出:
VERSION_NUMBER
5.1.8
5.1.87
5.1.357
5.10.1
5.14.0
24.1.1
26.3.0
我们在 dbt Slack 上讨论了这个问题 - 我的建议将排序转换移动到
order by
- 通过分割字符串并将其转换为整数数组。 Snowflake 本身就知道如何以正确的顺序对这些数组进行排序:
select *
from data
order by (
select [y[0]::int, y[1]::int, y[2]::int]
from(
select split(version_number, '.') y
)
);
样本数据:
create table data as
select value version_number
from (table(split_to_table(
$$5.14.0
5.10.1
5.1.8
5.1.87
5.1.357
26.3.0
24.1.1$$, '\n'
)))
;
我们还可以创建一个 UDF 以实现可重用性:
create function string_to_int_array_3(version_number string)
returns array
as
$$
select [y[0]::int, y[1]::int, y[2]::int]
from(
select split(version_number, '.') y
)
$$
;
select *
from data
order by string_to_int_array_3(version_number);
想法:假设数字只构成正整数,排序可以使用
REGEXP_EXTRACT_ALL
将输入分组为连续数字和其他字符:
伪代码:
SELECT *
FROM data
ORDER BY REGEXP_EXTRACT_ALL(version_number,'(\\d+|[^\\d]+)');
输入:
CREATE OR REPLACE TABLE data(version_number TEXT) AS
SELECT '5.14.0'
UNION SELECT '5.10.1'
UNION SELECT '5.1.8'
UNION SELECT '5.1a'
UNION SELECT '5.2b'
UNION SELECT '5.1.87'
UNION SELECT '2.10'
UNION SELECT '5.1.357'
UNION SELECT '26.3.0'
UNION SELECT '24.1.1';
SELECT *, REGEXP_EXTRACT_ALL(version_number,'(\\d+|[^\\d]+)')
FROM data;
棘手的部分是数组的每个元素都具有
TEXT
的数据类型,并且要对其进行正确排序,需要显式转换为 INT
。
SELECT *
FROM data
ORDER BY TRANSFORM(REGEXP_EXTRACT_ALL(version_number,'(\\d+|[^\\d]+)')
,elem -> CASE WHEN elem RLIKE '\\d+' THEN elem::INT ELSE elem END);
将
ARRAY
的每个数字元素转换为INT
:
SELECT version_number,
ARRAY_AGG(CASE WHEN g.VALUE::TEXT RLIKE '\\d+' THEN g.value::INT ELSE g.value END)
WITHIN GROUP(ORDER BY g.INDEX) AS arr_ver
FROM data
,LATERAL FLATTEN(REGEXP_EXTRACT_ALL(version_number,'(\\d+|[^\\d]+)')) g
GROUP BY version_number
ORDER BY arr_ver;
输出: