SQL Server 中多列的动态排序

Posted

技术标签:

【中文标题】SQL Server 中多列的动态排序【英文标题】:Dynamic order by multiple columns in SQL Server 【发布时间】:2017-02-17 19:14:30 【问题描述】:

我使用 SQL Server 2012。我有一个名为“Table1”的示例表,包含七列。

        CREATE TABLE TABLE1 
            (
                Field1 INT , 
                Field2 INT , 
                Field3 INT , 
                Field4 INT , 
                Field5 INT , 
                Field6 INT , 
                Field7 INT 
            )
            GO

        INSERT INTO TABLE1 VALUES (1,2,9,5,1,5,85)
        INSERT INTO TABLE1 VALUES (2,6,8,4,1,4,45)
        INSERT INTO TABLE1 VALUES (3,5,7,3,5,6,1)
        INSERT INTO TABLE1 VALUES (4,4,6,1,51,4,1)
        INSERT INTO TABLE1 VALUES (5,5,5,4,7,2,7)
        INSERT INTO TABLE1 VALUES (6,5,4,6,4,7,8)
        INSERT INTO TABLE1 VALUES (7,12,5,3,2,5,3)
        INSERT INTO TABLE1 VALUES (8,1,6,5,9,5,1)
        INSERT INTO TABLE1 VALUES (9,1,13,2,1,7,3)
        INSERT INTO TABLE1 VALUES (10,6,9,3,6,2,6)
        INSERT INTO TABLE1 VALUES (11,2,1,2,8,7,7)
        INSERT INTO TABLE1 VALUES (12,7,6,1,3,3,2)
        INSERT INTO TABLE1 VALUES (13,7,2,6,4,7,1)
        GO

我已经创建了下面的存储过程,这个 SP 能够考虑按列询问的顺序查询数据。 我可以通过(ASC 或 Desc)查询我的表,其中包含单列和排序类型的每种可能性。

    CREATE Procedure ProceName
    (
        @OrderByField INT = 1,
        @OrderDirection INT = 0 -- 0 = Asc , 1 = Desc
    )  
    As  
    Begin  
        SELECT
                *  
        FROM Table1
        ORDER BY
                CASE WHEN @OrderDirection=0 AND @OrderByField=1 THEN Field1 END ASC,
                CASE WHEN @OrderDirection=0 AND @OrderByField=2 THEN Field2 END ASC,
                CASE WHEN @OrderDirection=0 AND @OrderByField=3 THEN Field3 END ASC,
                CASE WHEN @OrderDirection=0 AND @OrderByField=4 THEN Field4 END ASC,
                CASE WHEN @OrderDirection=0 AND @OrderByField=5 THEN Field5 END ASC,
                CASE WHEN @OrderDirection=0 AND @OrderByField=6 THEN Field6 END ASC,
                CASE WHEN @OrderDirection=0 AND @OrderByField=7 THEN Field7 END ASC,

                CASE WHEN @OrderDirection=1 AND @OrderByField=1 THEN Field1 END DESC,
                CASE WHEN @OrderDirection=1 AND @OrderByField=2 THEN Field2 END DESC,
                CASE WHEN @OrderDirection=1 AND @OrderByField=3 THEN Field3 END DESC,
                CASE WHEN @OrderDirection=1 AND @OrderByField=4 THEN Field4 END DESC,
                CASE WHEN @OrderDirection=1 AND @OrderByField=5 THEN Field5 END DESC,
                CASE WHEN @OrderDirection=1 AND @OrderByField=6 THEN Field6 END DESC,
                CASE WHEN @OrderDirection=1 AND @OrderByField=7 THEN Field7 END DESC        End  
    GO



EXECUTE ProceName  @OrderByField=1, @OrderDirection=0
EXECUTE ProceName  @OrderByField=6, @OrderDirection=1

现在我需要更改此 sp 以接受多列作为按系列列的顺序。它们可以按名称或按列的顺序传递。 在这种情况下,我应该能够像下面的命令一样执行我的 SP:

    EXECUTE ProceName  @OrderByField='6,7,2', @OrderDirection='0,1,1'

如何在不使用 sp_executesql(动态查询)的情况下实现这一目标?

【问题讨论】:

sqlperformance.com/2012/08/t-sql-queries/conditional-order-by 和 blogs.sentryone.com/aaronbertrand/sql-variant-use-case 【参考方案1】:

好吧,除非您尝试为每列选择 asc 和 desc,否则您的升序和降序在您的情况下并不重要。这里使用最后一列。

DECLARE @OrderByField VARCHAR(64) = '1,3,7'

DECLARE @DynamicColumns VARCHAR(256) = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@OrderByField,1,'FIELD1'),2,'FIELD2'),3,'FIELD3'),4,'FIELD4'),5,'FIELD5'),6,'FIELD6'),7,'FIELD7')

DECLARE @OrderDirection INT = 1
DECLARE @order varchar(4)
SET @order = CASE WHEN @OrderDirection = 1 THEN 'DESC' ELSE 'ASC' END

--uncomment this line of code to add the sort to every column which only matters for DESC since ASC is default
--SET @DynamicColumns = REPLACE(@DynamicColumns,',', ' ' + @order + ',')

DECLARE @sql VARCHAR(MAX) = (
        'SELECT
                *  
        FROM Table1
        ORDER BY ' + @DynamicColumns + ' ' + @order)

SELECT @sql

EXEC(@sql)

【讨论】:

我认为操作试图实现的是为每个列字段指定顺序 @LONG 那么必须有更多的参数,每列一个.... 是的......即使是简单的输入也会很复杂【参考方案2】:
 CREATE Procedure ProceName
    (
        @OrderByField VARCHAR(100),
        @OrderDirection VARCHAR(100),

    )  
    As  
    Begin  

    Declare @SQL VARCHAR(MAX)

    if OBJECT_ID('Example1') is not null
    begin
    drop table Example
    end

    if OBJECT_ID('Example2') is not null
    begin
    drop table Example2
    end

    Create table Example1
    (
    id int identity(1,1),
    Field varchar(20)
    )

    Create table Example2
    (
    id int identity(1,1),
    OrderNumber varchar(10)
    )

    --iterate each element for both @OrderByField and @OrderDirection
    Declare @separator char(1)=','
    Declare @position int = 0
    Declare @name varchar(20)
    Set @OrderByField = @OrderByField + @separator 
---------------------------------------------------------------------------
         /*iterate each for @OrderByField */
---------------------------------------------------------------------------

    While CHARINDEX (@separator,@OrderByField,@position) != 0 
    BEGIN

    SET @name = SUBSTRING (@OrderByField,@position,CHARINDEX (@separator,@OrderByField,@position)-@position)

    SET @SQL = 'Insert into Example1([Field]) Values(' + char(39) + @name + char(39) + ')'
    EXEC(@SQL)

    SET @position = CHARINDEX(@separator,@OrderByField,@position)+1

    END


---------------------------------------------------------------------------
         /*iterate each for @OrderDirection */
---------------------------------------------------------------------------

    SET @position = 0 --do not forget to reset the position number

    Set @OrderDirection = @OrderDirection + @separator

    While CHARINDEX (@separator,@OrderDirection,@position) != 0 
    BEGIN

    SET @name = SUBSTRING (@OrderDirection,@position,CHARINDEX (@separator,@OrderDirection,@position)-@position)

    SET @SQL = 'Insert into Example2([OrderNumber]) Values(' + char(39) + @name + char(39)+ ')'
    EXEC(@SQL)

    SET @position = CHARINDEX(@separator,@OrderDirection,@position)+1

    END




Set @name = '' --reset the @name for the use of Cursor
declare @NewName varchar(500) =''


Declare row_cursor CURSOR
FOR
select 'Field'+a.Field + ' '+ case when b.OrderNumber = 0 then 'ASC' else 'DESC' end +',' as command
from Example1 as a 
inner join Example2 as b
on b.id = a.id


OPEN row_cursor
FETCH NEXT FROM row_cursor into @name

WHILE (@@FETCH_STATUS =0)
begin
Set @NewName = @NewName + @name

FETCH NEXT FROM row_cursor into @name
end
close row_cursor
deallocate row_cursor

SET @NewName = REVERSE(STUFF(REVERSE(@NewName),1,1,''))


SET @SQL = 'Select * From Table1 Order by ' + @NewName

--Print(@SQL)
EXEC (@SQL)

END

【讨论】:

逻辑是创建两个表来存储两个输入数据,使用CHARINDEX来存储每个输入中的每个元素;使用表游标从连接结果中选择数据生成每一对,然后使用动态sql得到最终结果 我一直在寻找我们不使用动态查询的方式。 (如果不使用 sp_executesql(动态查询),我怎样才能实现这个目标?)

以上是关于SQL Server 中多列的动态排序的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL Server 中将多行动态组合成多列

vue表格多选、vue动态显示隐藏表格列、vue动态多列排序

SQL 动态列和更新多列

具有不同排序方向的多列上的Sql server聚集索引

动态 SQL 使用多列交叉应用来反透视数据

sql动态创建多列