简体   繁体   中英

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. 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? I'm working with MSSQL if that makes any difference.

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) . 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. 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 ?

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. If your rules aren't too complicated, though, this should still be doable. It's not going to be awfully pretty, though :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. You could then simply use order by on the whole value, or split it into parts based on 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.

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. 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.

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 .

我认为前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. :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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