Alphanumeric Sort

限于喜欢 提交于 2019-11-30 21:38:53

问题


I need quick help in sorting data at SQL side. I am using Sqlserver 2012 (good if answer give with the new feature).

I already search some links as Sorting in alphanumeric , Alphanumeric string Sorting in Sqlserver - Code project. But does not give the desired result.

Still What I have try :

CREATE TABLE dbo.Section
    (
           Section varchar(50) NULL
    )
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsit no.43')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsit no.41')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite No. 11')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite No. 1')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite No. 12')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite No. 2')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite No. 3')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite No. 4')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite No. 40')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite No. 41')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite no.20')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Campsite no.41')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Cabin')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Group Tent Campsite')
    INSERT INTO dbo.Section (Section.Section) VALUES ('Tent Campsite')
    INSERT INTO dbo.Section (Section.Section) VALUES ('test1')
    INSERT INTO dbo.Section (Section.Section) VALUES ('test2')
    INSERT INTO dbo.Section (Section.Section) VALUES ('test11')
    SELECT Section
    FROM dbo.Section
    --Show normal Sort
    SELECT Section
    FROM dbo.Section
    ORDER BY Section
    --Show AlphaNumberic Sort
    SELECT Section
    FROM dbo.Section
    ORDER BY LEFT(Section,PATINDEX('%[0-9]%',Section)), -- alphabetical sort
             CONVERT(varchar(50),SUBSTRING(Section,PATINDEX('%[0-9]%',Section),LEN(Section))) -- numerical sort
    --cleanup our work
    --DROP Table dbo.Section

Now what I want is : if same string find in alphabet part sort on that first and then numeric (consider space also if possible or you may give the result without space like Campsite no.41 and Campsite No. 41 will give in same order)

Actual Result          Expected Result
Campsit no.41          Campsit no.41
Campsit no.43          Campsit no.43
Campsite No. 1         Campsite No. 1
Campsite No. 11        Campsite No. 2
Campsite No. 12        Campsite No. 3
Campsite No. 2         Campsite No. 4
Campsite No. 21        Campsite No. 11
Campsite No. 3         Campsite No. 12
Campsite No. 4         Campsite No. 21
Campsite No. 40        Campsite No. 40
Campsite No. 41        Campsite No. 41
Campsite no.20         Campsite no.20 --this will good to come here, if possible or if not, then remove space and set approriate
Campsite no.41         Campsite no.41 --this will good to come here, if possible or if not, then remove space and set approriate
Group Tent Campsite    Group Tent Campsite
Tent Campsite          Tent Campsite
test1                  test1
test11                 test2
test2                  test11

回答1:


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




回答2:


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



回答3:


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;



回答4:


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)



回答5:


 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


来源:https://stackoverflow.com/questions/29676432/alphanumeric-sort

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!