Alphanumeric Sort

谁说我不能喝 提交于 2019-12-01 01:24:33

Here's a tip: Whenever you are having problems with the sort, add order by items to your select clause. this will enable you to see if what you are sorting by is actually what you want to sort by:

SELECT Section,
        CASE WHEN PATINDEX('%[0-9]%',Section) > 1 THEN
          LEFT(Section,PATINDEX('%[0-9]%',Section)-1)
        ELSE 
          Section
        END As alphabetical_sort, -- alphabetical sort
        CASE WHEN PATINDEX('%[0-9]%',Section) > 1 THEN
          CAST(SUBSTRING(Section,PATINDEX('%[0-9]%',Section),LEN(Section)) as float)
        ELSE
          NULL
        END As Numeric_Sort
FROM dbo.Section
ORDER BY alphabetical_sort, Numeric_Sort

After I've got the sort correctly, All I had to do is move the case statements to the order by clause:

SELECT Section
FROM dbo.Section
ORDER BY 
    CASE WHEN PATINDEX('%[0-9]%',Section) > 1 THEN
        LEFT(Section,PATINDEX('%[0-9]%',Section)-1)
    ELSE 
        Section
    END , -- Alphabetical sort
    CASE WHEN PATINDEX('%[0-9]%',Section) > 1 THEN
        CAST(SUBSTRING(Section,PATINDEX('%[0-9]%',Section),LEN(Section)) as float)
    ELSE
        NULL
    END  -- Numeric sort

Basically, You had 4 major problems:

  • Your alphabetical sort expression assumed that every row have numbers in it.
  • Your alphabetical sort expression contained the numbers as well as the text.
  • Your numeric sort expression had both numeric and alphabetical values.
  • Because of article 3, you couldn't cast your numeric sort expression to a numeric type, and this is why you would get you a string sort.

See this sql fiddle

Here try this. Note: Cabin is in your data, but not your expected results. Also if you want something changed with the spaces, just let me know.

SELECT  Section,
        FormatSection
FROM dbo.Section
CROSS APPLY (SELECT CASE 
                        WHEN PATINDEX('%[0-9]%',Section) != 0 
                            THEN SUBSTRING(Section,0,PATINDEX('%[0-9]%',Section)) + FORMAT(CAST(SUBSTRING(Section,PATINDEX('%[0-9]%',Section),5) AS INT),'0#')
                        ELSE Section
                    END
            ) AS CA(FormatSection)
ORDER BY FormatSection

Results:

Section                                            FormatSection
-------------------------------------------------- ---------------------
Cabin                                              Cabin
Campsit no.41                                      Campsit no.41
Campsit no.43                                      Campsit no.43
Campsite No. 1                                     Campsite No. 01
Campsite No. 2                                     Campsite No. 02
Campsite No. 3                                     Campsite No. 03
Campsite No. 4                                     Campsite No. 04
Campsite No. 11                                    Campsite No. 11
Campsite No. 12                                    Campsite No. 12
Campsite No. 40                                    Campsite No. 40
Campsite No. 41                                    Campsite No. 41
Campsite no.20                                     Campsite no.20
Campsite no.41                                     Campsite no.41
Group Tent Campsite                                Group Tent Campsite
Tent Campsite                                      Tent Campsite
test1                                              test01
test2                                              test02
test11                                             test11

The below gives the result you are after, but I doubt it is fool proof. I think you are going to struggle to get a solution that is infallible and still performs well.

The first part is to get the first number (where it occurs after a space so that Campsite no.41 is treated differently to Campsite no. 40), I have put this into an APPLY to make it easier to re-use the result.

The next stage is to find the first non-numeric character after the first number, i.e. where the number ends, so that we can extract the full number using substring, then finally use TRY_CONVERT(INT to get this extract into a sortable type.

SELECT  s.Section, 
        TextPart = SUBSTRING(s.Section, 1, ISNULL(fn.FirstNumber, LEN(s.Section))),
        Number = CASE WHEN FirstNumber IS NULL THEN NULL
                    ELSE TRY_CONVERT(INT, SUBSTRING(s.section, fn.FirstNumber + 1, ISNULL(ln.LastNumber, LEN(s.Section)))) 
                END
FROM    dbo.Section AS s
    -- GET FIRST NUMBER (WHERE PRECEDING CHARACTER IS A SPACE
    CROSS APPLY (SELECT NULLIF(PATINDEX('% [0-9]%', s.section), 0)) AS fn (FirstNumber)

    -- GET FIRST NON NUMERIC CHARACTER AFTER FIRST NUMBER
    CROSS APPLY (SELECT NULLIF(PATINDEX('%[^0-9]%', SUBSTRING(s.section, fn.FirstNumber + 1, LEN(s.Section))), 0)) AS ln (LastNumber)
ORDER BY TextPart, Number;

n.b. You would need to move the expressions in the select into the order by rather than the column alias, but I have left it in this format to make it more clear what is going on

I have tried to comment the solution, but there is a fair bit going on so a full explanation of each bit will be quite hard. Sorry, if anything is not clear


EDIT

Sorry, missed the update where you switched from wanting (test1, test11, test2) to (test1, test2, test11). This just changes your logic to look for the first letter, but now it is where the previous character is not a fullstup (PATINDEX('%[^.][0-9]%', s.section)) rather than where the previous character was a space as before (This ensures that Campsite no.20 is sorted after Campsite no. 40)

SELECT  s.Section, 
        TextPart = SUBSTRING(s.Section, 1, ISNULL(fn.FirstNumber, LEN(s.Section))),
        Number = CASE WHEN FirstNumber IS NULL THEN NULL
                    ELSE TRY_CONVERT(INT, SUBSTRING(s.section, fn.FirstNumber + 1, ISNULL(ln.LastNumber, LEN(s.Section)))) 
                END
FROM    #Section AS s
    -- GET FIRST NUMBER (WHERE PRECEDING CHARACTER IS NOT A FULL STOP
    CROSS APPLY (SELECT NULLIF(PATINDEX('%[^.][0-9]%', s.section), 0)) AS fn (FirstNumber)

    -- GET FIRST NON NUMERIC CHARACTER AFTER FIRST NUMBER
    CROSS APPLY (SELECT NULLIF(PATINDEX('%[^0-9]%', SUBSTRING(s.section, fn.FirstNumber + 1, LEN(s.Section))), 0)) AS ln (LastNumber)
ORDER BY TextPart, Number;

I found the following alternative.

Create this function and just use this function in your query "ORDER BY fnGetNumericFromString([columnNm])".

CREATE FUNCTION fnGetNumericFromString (@InString VARCHAR(20), @OutStrType VARCHAR(3))
    RETURNS VarChar(20)
    AS
    BEGIN
    -- declare variables
    DECLARE @pos INT
    DECLARE @strLength INT
    DECLARE @NumericString VarChar(20)
    DECLARE @CharString VarChar(20)
    DECLARE @ReturnValue VarChar(20)
    -- set values
    SET @NumericString = ''
    SET @CharString = ''
    SET @pos= 1
    SET @strLength = LEN(@InString)
    SET @InString = UPPER(@InString)
    SET @OutStrType = UPPER(@OutStrType)

    --start looping
    WHILE @pos <= @strLength 
    BEGIN
    -- number codes are 48 to 57
    IF ASCII(SUBSTRING(@InString, @pos, 1))BETWEEN 48 AND 57
    SET @NumericString = @NumericString + SUBSTRING(@InString, @pos, 1)
    else
    SET @CharString = @CharString + SUBSTRING(@InString, @pos, 1)

    --increment to next character
    SET @pos = @pos + 1
    END

    IF @OutStrType = 'STR'
    SET @ReturnValue = @CharString
    ELSE
    SET @ReturnValue = @NumericString

    RETURN @ReturnValue
    END


select section from Section
order by dbo.fnGetNumericFromString(section, 'str'), CAST(dbo.fnGetNumericFromString(section, 'int') AS INT)
Param Yadav
 SELECT *,
       ROW_NUMBER()OVER(ORDER BY CASE WHEN ISNUMERIC (ID)=1 THEN CONVERT(NUMERIC(20,2),SUBSTRING(Id, PATINDEX('%[0-9]%', Id), LEN(Id)))END DESC)Rn ---- numerical
FROM
(
SELECT '1'Id UNION ALL
SELECT '25.20' Id UNION ALL
SELECT 'A115' Id UNION ALL
SELECT '2541' Id UNION ALL
SELECT '571.50' Id UNION ALL
SELECT '67' Id UNION ALL
SELECT 'B48' Id UNION ALL
SELECT '500' Id UNION ALL
SELECT '147.54' Id UNION ALL
SELECT 'A-100' Id
)A
ORDER BY 
CASE WHEN ISNUMERIC (ID)=0                                /* alphabetical sort */ 
     THEN CASE WHEN PATINDEX('%[0-9]%', Id)=0
               THEN LEFT(Id,PATINDEX('%[0-9]%',Id))
               ELSE LEFT(Id,PATINDEX('%[0-9]%',Id)-1)
          END
END DESC
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!