代码之家  ›  专栏  ›  技术社区  ›  user1777929

SQL Server:如何按输入的顺序排序?

  •  2
  • user1777929  · 技术社区  · 8 年前

    我正在使用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.
    
    5 回复  |  直到 7 年前
        1
  •  4
  •   John Cappelletti    8 年前

    下面是一个在线方法

    例子

    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
    

    编辑-子查询返回

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

    你可以替换 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
  •  3
  •   Gottfried Lesigang    8 年前

    我会再提供一个我刚刚发现的方法,但这需要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文本中元素的索引作为键。

        4
  •  1
  •   Gottfried Lesigang    8 年前

    这不是一个答案,只是一些测试代码来检查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
  •  1
  •   Gottfried Lesigang    8 年前

    好的,经过一些思考之后,我会提供最终的XML 类型安全 分类安全 拆分器:

    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:使用已知数量的元素,可以使用这个(性能更好)

    使用 函数 迭代字面上给定的列表:

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