简体   繁体   中英

How to SORT in order as entered in SQL Server?

I'm using SQL Server and I'm trying to find results but I would like to get the results in the same order as I had input the conditions.

My code:

SELECT 
    AccountNumber, EndDate
FROM 
    Accounts
WHERE 
    AccountNumber IN (212345, 312345, 145687, 658975, 256987, 365874, 568974, 124578, 125689)   -- I would like the results to be in the same order as these numbers.

Here is an in-line approach

Example

Declare @List varchar(max)='212345, 312345, 145687, 658975, 256987, 365874, 568974, 124578, 125689'

Select A.AccountNumber 
      ,A.EndDate
 From  Accounts A
 Join (
        Select RetSeq = Row_Number() over (Order By (Select null))
              ,RetVal = v.value('(./text())[1]', 'int')
        From  (values (convert(xml,'<x>' + replace(@List,',','</x><x>')+'</x>'))) x(n)
        Cross Apply n.nodes('x') node(v)
      ) B on A.AccountNumber = B.RetVal
 Order By B.RetSeq

EDIT - the subquery Returns

RetSeq  RetVal
1       212345
2       312345
3       145687
4       658975
5       256987
6       365874
7       568974
8       124578
9       125689

You can replace IN with a JOIN , and set a field for ordering, like this:

SELECT AccountNumber , EndDate
FROM Accounts a
JOIN (
    SELECT 212345 AS Number, 1 AS SeqOrder
UNION ALL
    SELECT 312345 AS Number, 2 AS SeqOrder
UNION ALL
    SELECT 145687 AS Number, 3 AS SeqOrder
UNION ALL
    ... -- and so on
) AS inlist ON inlist.Number = a.AccountNumber
ORDER BY inlist.SeqOrder

I will offer one more approach I just found out, but this needs v2016. Regrettfully the developers forgot to include the index into the resultset of STRING_SPLIT() , but this would work and is documented:

A solution via FROM OPENJSON() :

DECLARE @str VARCHAR(100) = 'val1,val2,val3';

SELECT *
FROM OPENJSON('["' +  REPLACE(@str,',','","') + '"]');

The result

key value   type
0   val1    1
1   val2    1
2   val3    1

The documentation tells clearly:

When OPENJSON parses a JSON array, the function returns the indexes of the elements in the JSON text as keys.

This is not an answer, just some test-code to check John Cappelletti's approach.

DECLARE @tbl TABLE(ID INT IDENTITY,SomeGuid UNIQUEIDENTIFIER);


--Create more than 6 mio rows with an running number and a changing Guid
WITH tally AS (SELECT ROW_NUMBER()OVER(ORDER BY (SELECT NULL)) AS Nmbr 
               FROM master..spt_values v1 
               CROSS JOIN master..spt_values v2)
INSERT INTO @tbl 
SELECT NEWID() from tally;

SELECT COUNT(*) FROM @tbl; --6.325.225 on my machine

--Create an XML with nothing more than a list of GUIDs in the order of the table's ID
DECLARE @xml XML=
(SELECT SomeGuid FRom @tbl ORDER BY ID FOR XML PATH(''),ROOT('root'),TYPE);

--Create one invalid entry
UPDATE @tbl SET SomeGuid = NEWID() WHERE ID=10000;

--Read all GUIDs out of the XML and number them
DECLARE @tbl2 TABLE(Position INT,TheGuid UNIQUEIDENTIFIER);
INSERT INTO @tbl2(Position,TheGuid)
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
      ,g.value(N'text()[1]',N'uniqueidentifier')
FROM @xml.nodes(N'/root/SomeGuid') AS A(g);

--then JOIN them via "Position" and check, 
--if there are rows, where not the same values get into the same row.
SELECT *
FROM @tbl t
INNER JOIN @tbl2 t2 ON t2.Position=t.ID
WHERE t.SomeGuid<>t2.TheGuid;

At least in this simple case I always get exactly only the one record back which was invalidated...

Okay, after some re-thinking I'll offer the ultimative XML based type-safe and sort-safe splitter:

Declare @List varchar(max)='212345, 312345, 145687, 658975, 256987, 365874, 568974, 124578, 125689';
DECLARE @delimiter VARCHAR(10)=', ';

WITH Casted AS
(
    SELECT (LEN(@List)-LEN(REPLACE(@List,@delimiter,'')))/LEN(REPLACE(@delimiter,' ','.')) + 1 AS ElementCount
           ,CAST('<x>' + REPLACE((SELECT @List AS [*] FOR XML PATH('')),@delimiter,'</x><x>')+'</x>' AS XML) AS ListXml
)
,Tally(Nmbr) As
(
    SELECT TOP((SELECT ElementCount FROM Casted)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values v1 CROSS JOIN master..spt_values v2
)
SELECT Tally.Nmbr AS Position
      ,(SELECT ListXml.value('(/x[sql:column("Tally.Nmbr")])[1]','int') FROM Casted) AS Item 
FROM Tally;

The trick is to create a list of running numbers with the fitting number of element (a number's table was even better) and to pick the elements according to their position.

Hint: This is rather slow...

UPDATE: even better:

WITH Casted AS
(
    SELECT (LEN(@List)-LEN(REPLACE(@List,@delimiter,'')))/LEN(REPLACE(@delimiter,' ','.')) + 1 AS ElementCount
           ,CAST('<x>' + REPLACE((SELECT @List AS [*] FOR XML PATH('')),@delimiter,'</x><x>')+'</x>' AS XML)
           .query('
                   for $x in /x
                   return <x p="{count(/x[. << $x])}">{$x/text()[1]}</x>
                  ') AS ListXml
)
SELECT x.value('@p','int') AS Position
      ,x.value('text()[1]','int') AS Item 
FROM Casted
CROSS APPLY Casted.ListXml.nodes('/x') AS A(x);

Elements are create as

<x p="99">TheValue</x>

Regrettfully the XQuery function position() is not available to retrieve the value. But you can use the trick to count all elements before a given node. this is scaling badly, as this count must be performed over and over. The more elements the worse it goes...

UPDATE2: With a known count of elements one might use this (much better performance)

Use XQuery to iterate a literally given list:

WITH Casted AS
(
    SELECT (LEN(@List)-LEN(REPLACE(@List,@delimiter,'')))/LEN(REPLACE(@delimiter,' ','.')) + 1 AS ElementCount
           ,CAST('<x>' + REPLACE((SELECT @List AS [*] FOR XML PATH('')),@delimiter,'</x><x>')+'</x>' AS XML)
           .query('
                   for $i in (1,2,3,4,5,6,7,8,9)
                   return <x p="{$i}">{/x[$i]/text()[1]}</x>
                  ') AS ListXml
)
SELECT x.value('@p','int') AS Position
      ,x.value('text()[1]','int') AS Item 
FROM Casted
CROSS APPLY Casted.ListXml.nodes('/x') AS A(x);

In Azure SQL, there is now extended version of STRING_SPLIT which also can return the order of items if the third optional argument enable_ordinal is set to 1.

Then this simple task is finally easy:

DECLARE @string AS varchar(200) = 'a/b/c/d/e'
DECLARE @position AS int = 3

SELECT value FROM STRING_SPLIT(@string, '/', 1) WHERE ordinal = @position

Unfortunately not available in SQL Server 2019, only in Azure for now, lets hope it will be in SQL Server 2022.

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