问题
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