基于字符串变量内容对结果进行排序

时间:2012-06-16 18:51:29

标签: sql-server-2008 tsql sql-server-2008-r2

我正在使用SQL Server 2008 R2。

我想按照我作为字符串

保存的特定订单对结果进行排序

(字符串是以编程方式创建的。)

考虑下表:

Col1    Col2    Col3
1       Jon     a
2       Joan    b
3       John    a
4       Jonnie  b
5       Jonny   a

我有nvarchar变量声明为@myOrderString,其中包含我想要选择的行的顺序。

让我们说@myOrderString = '213'(已编辑)

所以,我想做那样的事情:

SELECT 
    ROW_NUMBER() OVER (ORDER BY @mySortString) AS Row, 
    ( Col2 + '(' + Col1 + ')' ) AS Outcome
FROM 
    myTable 
WHERE 
    Col3 = 'a' 
ORDER BY 
    @mySortString

为了得到结果:(已编辑)

Row Outcome
1   John (3)
2   Jon (1)
3   Jonny (5)

我如何开始解决这个问题?

P.S。

如果@myOrderString中的值应该分开,我可以@myOrderString = '2,1,3'(已编辑)


编辑问题的原因:

  1. 字符串内容出错。 [标记为(已编辑)]
  2. 结果出错。感谢Andriy M. [标记为(编辑)]
  3. 我添加了化妆品更改并更正了一些语法和拼写错误。

  4. 澄清:(部分澄清基于Aaron Bertrand的评论)

    1. 字符串中的值的数量始终与数量匹配 结果中的行。
    2. 字符串中的值始终是行数的数字1。
    3. 第一个结果的行标记为1;第二个被标记为2和 第三个为3.现在,字符串意味着重新排序结果。什么时候 字符串是2,1,3,这意味着选择结果被重新排序: 第二行现在将作为第一行,第一行将结果 显示为秒,第3行将保持为第3行
    4. 最大行数为15,因此字符串可以是123456789ABCDEF形式,或者如果具有此缩减形式的解决方案不简单,则字符串可以是1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

    5. 感谢很多人在这里,我已经达成了以下解决方案:

      (不需要函数或循环)

      -- creating the original table and filling it
      DECLARE @t TABLE(Col1 INT, Col2 VARCHAR(22), Col3 CHAR(1));
      
      INSERT @t VALUES
      (1,'Jon',   'a'),
      (2,'Joan',  'b'),
      (3,'John',  'a'),
      (4,'Jonnie','b'),
      (5,'Jonny', 'a');
      
      -- this is the required order of the results
      DECLARE @myOrderString VARCHAR(32) = '213';
      
      -- this is my current solution
      SELECT 
          ROW_NUMBER()OVER (ORDER BY CHARINDEX(CAST(rr AS NVARCHAR(MAX)), @myOrderString)) As [Row],
          Outcome 
      FROM 
          (
              SELECT 
                  ROW_NUMBER()OVER (ORDER BY Col1) AS rr, 
                  Col2 + ' (' + CONVERT(NVARCHAR(22),Col1)+ ')' AS Outcome 
              FROM @t 
              WHERE  Col3 = 'a'
          ) as r
      ORDER BY 
          [Row]
      

      但是,它适用于最多9行的结果,而我最多有15行表示123456789ABCDEF形式。

      我尝试使用CONVERT(CHAR(1),CONVERT(VARBINARY(1),@Dec))对结果的行号使用Dec到十六进制转换,但没有运气。

      是否有简单更正可用于此工作?


      长例(超过9行)

      为了测试超过9行的示例,我使用:

      -- creating the original table and filling it
      DECLARE @t TABLE(Col1 INT, Col2 VARCHAR(22), Col3 CHAR(1));
      
      INSERT @t VALUES
      (1,'Jon',   'a'),
      (2,'Joan',  'b'),
      (3,'John',  'a'),
      (4,'Jonnie','b'),
      (5,'Jonny', 'a'),
      (6,'Don',   'a'),
      (7,'Doan',  'b'),
      (8,'Dohn',  'a'),
      (9,'Donnie','b'),
      (10,'Donny', 'a'),
      (11,'Gon',   'a'),
      (12,'Goan',  'a'),
      (13,'Gohn',  'a'),
      (14,'Gonnie','a'),
      (15,'Gonny', 'a');
      
      -- this is the required order of the results
      DECLARE @myOrderString VARCHAR(32) = '456B213A789';
      

      完整的解决方案:

      我将在答案部分使用循环提供我的完整解决方案,而不会将其视为答案。

5 个答案:

答案 0 :(得分:3)

使用逗号分隔的字符串会更好。你可以使用这样的分割函数:

CREATE FUNCTION dbo.SplitInts
(
    @List       NVARCHAR(MAX),
    @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
AS
    RETURN (SELECT Number = ROW_NUMBER() OVER (ORDER BY Number),
        Item FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(@List, Number, 
        CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)))
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
      FROM sys.all_objects) AS n(Number)
      WHERE Number <= CONVERT(INT, LEN(@List))
      AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
    ) AS y
    WHERE ISNUMERIC(Item) = 1
  );
GO

现在你可以这样做:

DECLARE @myOrderString VARCHAR(32) = '2,1,3';

DECLARE @t TABLE(col1 INT, col2 VARCHAR(32), col3 CHAR(1));

INSERT @t VALUES
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(3,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a');

SELECT 
  [Row] = s.Number, 
  Outcome = t.col2 + ' (' + CONVERT(VARCHAR(12), t.col1) + ')' 
FROM 
(
  SELECT col1, col2, rn = ROW_NUMBER() OVER (ORDER BY col1)
  FROM @t WHERE col3 = 'a'
) AS t 
INNER JOIN 
  dbo.SplitInts(@myOrderString, ',') AS s
  ON s.Item = t.rn 
ORDER BY s.Number;

再次,结果:

Row  Outcome
--   ----------
1    John (3)
2    Jon (1)
3    Jonny (5)

编辑

这是一个不使用函数的版本(虽然我不确定为什么在这种情况下会如此令人讨厌),但不要求列表以逗号分隔(一个新的“硬”要求,根据OP的自我答案),并且不必在继续之前手动填充循环中的表变量(这导致整体计划更便宜,即使OP的答案中的最终查询在您忽略时看起来更便宜循环+插入)。这将返回与OP的自答案相同的结果(假设添加到问题中的新的“长示例”样本数据),但同样限于15个最大排序值。

;WITH n(n,c) AS 
(
  SELECT CASE WHEN n < 10 THEN n ELSE n -7 END, CHAR(n+48)
  FROM 
  (
    SELECT TOP (21) n = ROW_NUMBER() OVER (ORDER BY [object_id])
    FROM sys.objects ORDER BY [object_id]
  ) AS x WHERE n BETWEEN 1 AND 9 OR n BETWEEN 17 AND 21
),
x(Outcome, n) AS 
(
  SELECT col2 + ' (' + CONVERT(VARCHAR(12), col1) + ')', 
  ROW_NUMBER() OVER (ORDER BY col1)
  FROM (SELECT col1, col2 FROM @t WHERE col3 = 'a') AS y
)
SELECT [Row] = ROW_NUMBER() OVER (ORDER BY 
    COALESCE(NULLIF(CHARINDEX(n.c, @myOrderString), 0), 16)), 
  Outcome FROM x LEFT OUTER JOIN n ON x.n = n.n
ORDER BY [Row], Outcome;

答案 1 :(得分:2)

我是这个问题的作者。作为答案,我不赞成这个答案。

此解决方案基于循环每个@myOrderString字符。

这里给出了这个解决方案的灵感,希望找到一个基于我在问题中描述的解决方案的解决方案。

创建原始表并填写它:

DECLARE @t TABLE(Col1 INT, Col2 VARCHAR(32), Col3 CHAR(1));

INSERT @t VALUES
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(3,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a'),
(6,'Don',   'a'),
(7,'Doan',  'b'),
(8,'Dohn',  'a'),
(9,'Donnie','b'),
(10,'Donny', 'a'),
(11,'Gon',   'a'),
(12,'Goan',  'a'),
(13,'Gohn',  'a'),
(14,'Gonnie','a'),
(15,'Gonny', 'a')

这是结果的必需顺序

DECLARE @myOrderString VARCHAR(32) = '456B213A789'

将字符串转换为具有Order Colomn(OC)

的表

我希望有一个内置函数可以将[分隔]字符串转换为执行此类操作的表。

DECLARE @ot TABLE (PK INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, OC INT)
DECLARE @i int
SET @i = 0
WHILE @i < LEN(@myOrderString) 
    BEGIN
        SET @i = @i + 1
        IF ASCII(UPPER(SUBSTRING(@myOrderString,@i,1))) < 65
            INSERT @ot VALUES (SUBSTRING(@myOrderString,@i,1))
        ELSE
            INSERT @ot VALUES (ASCII(UPPER(SUBSTRING(@myOrderString,@i,1)))-55)
    END

以所需顺序显示结果:

SELECT 
    ROW_NUMBER() OVER (ORDER BY PK) AS [Row], 
    r.Outcome 
FROM @ot 
INNER JOIN (
    SELECT 
        ROW_NUMBER() OVER (ORDER BY Col1) AS [RRow], 
        Col2 +' (' + CONVERT(varchar(11), Col1) + ')' AS Outcome
    FROM @t 
    WHERE Col3 = 'a') AS r
ON OC=RRow

附录

要获得有趣的结果(选择结果行 - 不需要),请尝试设置@myOrderString = '333222111'

答案 2 :(得分:1)

如果你让你的字符串是commasepearated,你将它插入临时表并在该表上连接,那么临时表可以包含一个序列号,每次从逗号分隔的字符串中插入一个整数时,该序列号会递增,这就是你订购的

这是一个非常快的例子(可以提供优化的空间)

DECLARE @string varchar(max),
    @delimiter char(1),
    @xml xml

SELECT @string = '3,1,4',
    @delimiter= ','

SELECT @xml = CONVERT(xml,'<root><s>' + REPLACE(@string,@delimiter,'</s><s>') + '</s></root>')

create table #values
(
    seq integer identity(1, 1),
    value integer
)

insert into #values (value)
SELECT [Value] = T.c.value('.','varchar(20)')
FROM @xml.nodes('/root/s') T(c)

select
    v.seq,
    m.Col2,
    m.Col1
from dbo.myTable m
inner join #values v on m.Col1 = v.value
order by v.seq

drop table #values

答案 3 :(得分:1)

根据要求,下面提到的是解决方案......

Select col1, Col2 + ' (' + Convert(Varchar, col1) + ')'
From #T
Where CHARINDEX(CAST(Col1 AS NVARCHAR(MAX)), '3,1,4') <> 0
order by CHARINDEX(CAST(Col1 AS NVARCHAR(MAX)), '3,1,4')

编辑-2 UDF中忽略非数字值。

编辑 - 1支持大于9

的值
declare @myTable table(col1 int, col2 varchar(10), col3 varchar(1))

insert @myTable values
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(322,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a')

Select col1, Col2 + ' (' + Convert(Varchar, col1) + ')'
From @myTable T
INNER JOIN 
(
    Select * From dbo.Split('322,1,4', ',')
)K
ON K.val = T.col1
Order by K.id

用户定义的功能

CREATE FUNCTION [dbo].[Split](@String varchar(8000), @Delimiter char(1))       
returns @temptable TABLE (id int IDENTITY(1,1), Val Int)       
as       
begin       
    declare @idx int       
    declare @slice varchar(8000)       

    select @idx = 1       
        if len(@String)<1 or @String is null  return       

    while @idx!= 0       
    begin       
        set @idx = charindex(@Delimiter,@String)       
        if @idx!=0       
            set @slice = left(@String,@idx - 1)       
        else       
            set @slice = @String       

        if(isnumeric(@slice) = 0) 
        Set @slice = '';

        if(len(@slice)>0)  
            insert into @temptable(Val) values(@slice)       

        set @String = right(@String,len(@String) - @idx)       
        if len(@String) = 0 break       
    end   
return       
end  

答案 4 :(得分:0)

根据提供的数据,您的输出表是不可能的,因为Jonnie是'b'而不是'a'。此外,在下面的示例中,'Jonny'是'a'但未返回,因为5不在提供的排序字符串中。关于如何处理这样的行,需要一些指导。

declare @sortTable table (sortId int identity, sortOrder varchar(1))
declare @sortString nvarchar(100) = '314'

declare @position int = 1
declare @sortOrder varchar(1) = substring(@sortString,@position,1)
while @sortOrder != ''
begin
    insert @sortTable (sortOrder) values (@sortOrder)
    set @position = @position + 1
    set @sortOrder = SUBSTRING(@sortString,@position,1)
end


declare @myTable table(col1 varchar(1), col2 varchar(10), col3 varchar(1))

insert @myTable values
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(3,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a')

SELECT ROW_NUMBER() OVER (ORDER BY rsSort.SortID) AS Row, ( Col2 + '(' + Col1 + ')' ) AS Outcome
FROM @myTable rsMain
inner join @sortTable rsSort on rsSort.sortOrder = rsMain.col1
WHERE Col3 = 'a'

Working Example