如何按照在 SQL 服务器中输入的顺序进行排序?
How to SORT in order as entered in SQL Server?
我正在使用 SQL 服务器,我正在尝试查找结果,但我希望获得结果的顺序与我输入条件时的顺序相同。
我的代码:
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.
这是一个内联方法
例子
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
您可以将 IN
替换为 JOIN
,并设置一个排序字段,如下所示:
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
这不是答案,只是一些测试代码来检查 John Cappelletti 的方法。
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;
至少在这个简单的例子中,我总是只得到一条无效的记录...
好吧,经过一些重新思考,我会提供最终的 XML 基于 type-safe 和 sort-safe分离器:
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;
诀窍是创建一个 运行 数字列表和合适的元素数量(数字的 table 更好)并根据它们的位置选择元素。
提示:这相当慢...
更新:更好:
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);
元素创建为
<x p="99">TheValue</x>
遗憾的是,XQuery
函数 position()
无法用于 检索 值。但是你可以使用这个技巧来计算给定节点之前的所有元素。这是严重的扩展,因为必须一遍又一遍地执行此计数。元素越多越糟...
UPDATE2:如果元素数量已知,可以使用它(性能更好)
使用XQuery
迭代字面上给定的列表:
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);
我将提供另一种我刚刚发现的方法,但这需要 v2016。遗憾的是,开发人员忘记将索引包含在 STRING_SPLIT()
的结果集中,但这会起作用并记录在案:
一个解决方案via FROM OPENJSON()
:
DECLARE @str VARCHAR(100) = 'val1,val2,val3';
SELECT *
FROM OPENJSON('["' + REPLACE(@str,',','","') + '"]');
结果
key value type
0 val1 1
1 val2 1
2 val3 1
文档说的很清楚:
When OPENJSON parses a JSON array, the function returns the indexes of the elements in the JSON text as keys.
在 Azure SQL 中,现在有 STRING_SPLIT
的扩展版本,如果第三个可选参数 enable_ordinal
设置为 1,它也可以 return 项目的顺序.
那么这个简单的任务终于简单了:
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
遗憾的是 SQL Server 2019 中不可用,目前仅在 Azure 中可用,希望它会在 SQL Server 2022 中可用。
我正在使用 SQL 服务器,我正在尝试查找结果,但我希望获得结果的顺序与我输入条件时的顺序相同。
我的代码:
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.
这是一个内联方法
例子
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
您可以将 IN
替换为 JOIN
,并设置一个排序字段,如下所示:
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
这不是答案,只是一些测试代码来检查 John Cappelletti 的方法。
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;
至少在这个简单的例子中,我总是只得到一条无效的记录...
好吧,经过一些重新思考,我会提供最终的 XML 基于 type-safe 和 sort-safe分离器:
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;
诀窍是创建一个 运行 数字列表和合适的元素数量(数字的 table 更好)并根据它们的位置选择元素。
提示:这相当慢...
更新:更好:
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);
元素创建为
<x p="99">TheValue</x>
遗憾的是,XQuery
函数 position()
无法用于 检索 值。但是你可以使用这个技巧来计算给定节点之前的所有元素。这是严重的扩展,因为必须一遍又一遍地执行此计数。元素越多越糟...
UPDATE2:如果元素数量已知,可以使用它(性能更好)
使用XQuery
迭代字面上给定的列表:
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);
我将提供另一种我刚刚发现的方法,但这需要 v2016。遗憾的是,开发人员忘记将索引包含在 STRING_SPLIT()
的结果集中,但这会起作用并记录在案:
一个解决方案via FROM OPENJSON()
:
DECLARE @str VARCHAR(100) = 'val1,val2,val3';
SELECT *
FROM OPENJSON('["' + REPLACE(@str,',','","') + '"]');
结果
key value type
0 val1 1
1 val2 1
2 val3 1
文档说的很清楚:
When OPENJSON parses a JSON array, the function returns the indexes of the elements in the JSON text as keys.
在 Azure SQL 中,现在有 STRING_SPLIT
的扩展版本,如果第三个可选参数 enable_ordinal
设置为 1,它也可以 return 项目的顺序.
那么这个简单的任务终于简单了:
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
遗憾的是 SQL Server 2019 中不可用,目前仅在 Azure 中可用,希望它会在 SQL Server 2022 中可用。