简体   繁体   English

SQL大于自定义排序

[英]SQL greater than custom ordering

I've got a database that contains release versions of software and I want to be able to pull back all versions that are greater than the current version, ordered by version number. 我有一个数据库,其中包含软件的发行版本,并且我希望能够按版本号将所有大于当前版本的版本拉回。 However, the releases are sorted in a custom (but standard) way - from alpha version to beta version to main release to patch. 但是,这些发布以自定义(但标准)的方式排序-从Alpha版本到Beta版本再到主要版本再到补丁。 So here's an example of the ordering: 因此,这是排序的示例:

100a1
100a4
100b1
100
100p1
101
101p3
etc.

Is it possible to form an SQL query that pulls back this data given the custom ordering or does > only work for given orderings like integers and dates? 是否可以形成一个SQL查询,以给定自定义顺序来拉回此数据,或者>仅适用于给定顺序(如整数和日期)? I'm working with MSSQL if that makes any difference. 如果这有任何区别,我正在使用MSSQL。

As long as you can actually describe how the ordering is supposed to work, sure. 当然,只要您能真正描述订购的工作原理即可。

The two basic approaches are: 两种基本方法是:

  • Convert the value into something ordinal. 将值转换为序数。 For example, you could use something like order by left([Version] + '__', 5) . 例如,您可以使用诸如order by left([Version] + '__', 5) Making a single integer out of the more complex value also works. 用更复杂的值制作一个整数也可以。
  • Separate the value into multiple values that are each ordinal, and use all of those in the order by , in any order you want. 将值分成每个序数的多个值,然后order by的任意顺序按order by使用所有值。 This is the more idiomatic way of handling this in SQL - basically, why are you using one value 101p1 when you're logically working with 101, p, 1 ? 这是在SQL中处理此问题的更惯用的方式-基本上,为什么在逻辑上使用101, p, 1时为什么要使用一个值101p1

Parsing is a bit tricky to handle in SQL, because SQL really is designed for normalized data sets - and you're effectively storing multiple values in one column. 在SQL中处理解析有些棘手,因为SQL实际上是为规范化数据集设计的-而且您实际上将多个值存储在一个列中。 If your rules aren't too complicated, though, this should still be doable. 但是,如果您的规则不太复杂,这仍然可行。 It's not going to be awfully pretty, though :D 虽然不会很漂亮,但:D

For fixed length values, this is pretty simple, of course - that's the equivalent of using eg 001p01 as filenames in the file system - the alphabetical ordering is the correct ordering. 对于固定长度的值,这当然非常简单-相当于在文件系统中使用例如001p01作为文件名-字母顺序正确的顺序。 You could then simply use order by on the whole value, or split it into parts based on substring s. 然后,您可以简单地对整个值使用order by ,或根据substring s将其拆分为多个部分。 For values with separators, it's a bit uglier, but still pretty easy - 1.p.1 can be split relatively easily, and then you can order by each of the parts in sequence. 对于带有分隔符的值,它有点1.p.1 ,但仍然很简单1.p.1可以相对容易地拆分,然后可以按顺序按每个部分排序。

However, your system seems to be a better fit for humans than machines - there's no real hints to follow. 但是,您的系统似乎比机器更适合人类-没有真正的提示可循。 Basically, it seems that you're looking at a pattern of "numbers, letters, numbers... treat numbers as numbers, and letters as letters". 基本上,您似乎正在看“数字,字母,数字...将数字视为数字,而将字母视为字母”的模式。 This is actually quite tricky to handle in T-SQL. 在T-SQL中处理这实际上非常棘手。 It might be worth it to bring in the help of the CLR, and regular expressions in particular - I'm not sure if you'll be able to handle this in general for an unlimited amount of number/letter groups anyway, though. 引入CLR(尤其是正则表达式)的帮助可能是值得的-但是我不确定您是否总能为数量不限的数字/字母组进行处理。

The simplest way by far seems to be to simply separate the version column into multiple columns, each with just one value - something like MajorVersion, Level, Revision or something like that, corresponding to 101, Alpha, 3 . 到目前为止,最简单的方法似乎是将version列简单地分成多个列,每个列只有一个值-类似于MajorVersion, Level, Revision或类似的值,对应于101, Alpha, 3

我认为前3个是数字。

select * from tablename order by convert(int,left(Columnname,3))

Here is my code example. 这是我的代码示例。 Not the shortest one but it holds many demo input/output and can be further simplified if you understand what I want. 它不是最短的,但它拥有许多演示输入/输出,如果您了解我想要的内容,可以进一步简化。

CREATE TABLE #versions(version nvarchar(10))

INSERT INTO #versions(version)
VALUES(N'100a1'),(N'100a4'),(N'100b1'),(N'100p1'),(N'100'),(N'101'),(N'101p3')

-- Just an example using substrings etc. how to get the 
SELECT version,
    SUBSTRING(version,1,
        CASE 
            WHEN PATINDEX(N'%[a-z]%',version) > 0 
            THEN PATINDEX(N'%[a-z]%',version)-1 
            ELSE LEN(version) 
        END
    ) as version_number,
    SUBSTRING(version,
        CASE 
            WHEN PATINDEX(N'%[a-z]%',version) > 0 
            THEN PATINDEX(N'%[a-z]%',version)
            ELSE 0
        END, PATINDEX(N'%[0-9]%',
            SUBSTRING(version,1,
                CASE 
                    WHEN PATINDEX(N'%[a-z]%',version) > 0 
                    THEN PATINDEX(N'%[a-z]%',version)-1 
                    ELSE LEN(version) 
                END
            )
        )
    ) as version_suffix,
    SUBSTRING(version,
        PATINDEX(N'%[a-z]%',
            SUBSTRING(version,
                CASE 
                    WHEN PATINDEX(N'%[a-z]%',version) > 0 
                    THEN PATINDEX(N'%[a-z]%',version)
                    ELSE LEN(version) 
                END, LEN(version)
            )
        ),
        PATINDEX(N'%[0-9]%',
            SUBSTRING(version,1,
                CASE 
                    WHEN PATINDEX(N'%[a-z]%',version) > 0 
                    THEN PATINDEX(N'%[a-z]%',version)-1 
                    ELSE LEN(version) 
                END
            )
        )
    ) as version_sub
FROM #versions

-- Now your code:
;WITH vNumber AS(
    SELECT version,SUBSTRING(version,1,
        CASE 
            WHEN PATINDEX(N'%[a-z]%',version) > 0 
            THEN PATINDEX(N'%[a-z]%',version)-1 
            ELSE LEN(version) 
        END
    ) as version_number
    FROM #versions
), vSuffix AS(
    SELECT version, SUBSTRING(version,
                CASE 
                    WHEN PATINDEX(N'%[a-z]%',version) > 0 
                    THEN PATINDEX(N'%[a-z]%',version)
                    ELSE LEN(version) 
                END, LEN(version)
            ) as version_suffix
    FROM #versions
)
SELECT dat.version
FROM (
    SELECT vn.version, vn.version_number,
        CASE 
            SUBSTRING(vn.version,
                CASE 
                    WHEN PATINDEX(N'%[a-z]%',vn.version) > 0 
                    THEN PATINDEX(N'%[a-z]%',vn.version)
                    ELSE 0
                END, 1
            )
            WHEN N'a' THEN 1
            WHEN N'b' THEN 2
            WHEN N'' THEN 3
            WHEN N'p' THEN 4
        END as version_suffix,
        SUBSTRING(vn.version,
            PATINDEX(N'%[a-z]%',
                vs.version_suffix
            ),
            PATINDEX(N'%[0-9]%',
                SUBSTRING(vn.version,1,
                    CASE 
                        WHEN PATINDEX(N'%[a-z]%',vn.version) > 0 
                        THEN PATINDEX(N'%[a-z]%',vn.version)-1 
                        ELSE LEN(vn.version) 
                    END
                )
            )
        ) as version_sub
    FROM vNumber as vn
    INNER JOIN vSuffix as vs
            ON vn.version = vs.version
) AS dat
ORDER BY dat.version_number, dat.version_suffix, dat.version_sub

DROP TABLE #versions

This is my input: 这是我的输入:

version
----------
100a1
100a4
100b1
100p1
100
101
101p3

And this is the result: 结果如下:

version
----------
100a1
100a4
100b1
100
100p1
101
101p3

Anyway. 无论如何。 I would suggest to split those values into separate columns. 我建议将这些值分成单独的列。 It will make your live much easier. 这将使您的生活更加轻松。 :-) :-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM