【问题标题】:How to SORT in order as entered in SQL Server?如何按在 SQL Server 中输入的顺序排序?
【发布时间】:2018-06-25 19:37:46
【问题描述】:

我正在使用 SQL Server,我正在尝试查找结果,但我希望以与输入条件相同的顺序获取结果。

我的代码:

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.

【问题讨论】:

  • stackoverflow.com/a/19209883/61305 - SELECT a.AccountNumber, a.EndDate FROM dbo.Accounts AS a INNER JOIN dbo.SplitStrings_Ordered('212345, 312345, 145687, 658975, 256987, 365874, 568974, 124578, 125689') AS s ORDER BY s.[Index];
  • 我不确定这是否可能在不产生某种可怕的字符串/XML 转换混乱的情况下实现。 IN 语句本质上是“WHERE column = 'value1' OR column = 'value2') - 对结果进行排序完全取决于 SQL Server,除非您指定排序顺序。让您退后一步 - 有什么东西在驱动那个列表?还是用户输入的?

标签: sql sql-server sql-order-by


【解决方案1】:

这是一种内联方法

示例

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 - 子查询返回

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

【讨论】:

  • 约翰,我刚刚用我刚刚偶然发现的一种新方法在这个线程中发布了一个答案FROM OPENJSON。可能有用...
【解决方案2】:

您可以将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

【讨论】:

    【解决方案3】:

    我将提供我刚刚发现的另一种方法,但这需要 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
    

    文档说得很清楚:

    OPENJSON 解析 JSON 数组时,函数返回 JSON 文本中元素的索引作为键。

    【讨论】:

    • 非常干净优雅。应该是 2016+ 的新标准 像你一样,我不明白为什么他们省略了 split_string() 中的序列。您是否执行过任何压力测试/基准测试。
    • @JohnCappelletti,是的,非常有前途...stackoverflow.com/a/51401153/5089204
    • @JohnCappelletti 我刚刚在那里添加了一个综合测试场景。
    【解决方案4】:

    这不是答案,只是一些测试代码来检查 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;
    

    至少在这种简单的情况下,我总是只得到一条无效的记录...

    【讨论】:

    • 还没有运行我的测试,但这看起来很有希望
    【解决方案5】:

    好的,经过重新思考后,我将提供基于 XML 的终极 type-safesort-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;
    

    诀窍是用合适的元素数量创建一个运行数字列表(数字表更好),并根据它们的位置选择元素。

    提示:这相当慢...

    更新:更好:

    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);
    

    【讨论】:

    • 一如既往...最令人印象深刻。
    【解决方案6】:

    在 Azure SQL 中,现在有 STRING_SPLIT 的扩展版本,如果第三个可选参数 enable_ordinal 设置为 1,它还可以返回项目的顺序。

    那么这个简单的任务终于简单了:

    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 中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-03
      • 2016-08-16
      • 1970-01-01
      • 2017-12-16
      • 1970-01-01
      • 2018-06-20
      • 2021-09-03
      相关资源
      最近更新 更多