SQL存储过程中的动态排序

SQL存储过程中的动态排序,sql,tsql,stored-procedures,sorting,Sql,Tsql,Stored Procedures,Sorting,这是我过去花了几个小时研究的问题。在我看来,这似乎是现代解决方案应该解决的问题,但到目前为止,我还没有发现任何东西能够真正解决我认为在任何带有数据库后端的Web或Windows应用程序中的一个非常普遍的需求 我说的是动态排序。在我的幻想世界里,它应该像这样简单: ORDER BY @sortCol1, @sortCol2 这是一个典型的例子,由新手SQL和互联网上所有论坛的开发人员给出。“为什么这不可能?”他们问道。最终总会有人来告诉他们存储过程的编译性质,执行计划的一般性质,以及为什么不能将

这是我过去花了几个小时研究的问题。在我看来,这似乎是现代解决方案应该解决的问题,但到目前为止,我还没有发现任何东西能够真正解决我认为在任何带有数据库后端的Web或Windows应用程序中的一个非常普遍的需求

我说的是动态排序。在我的幻想世界里,它应该像这样简单:

ORDER BY @sortCol1, @sortCol2
这是一个典型的例子,由新手SQL和互联网上所有论坛的开发人员给出。“为什么这不可能?”他们问道。最终总会有人来告诉他们存储过程的编译性质,执行计划的一般性质,以及为什么不能将参数直接放入
ORDER BY
子句中的各种其他原因


我知道你们中的一些人已经在想:“那就让客户机来排序吧。”很自然,这会减轻数据库中的工作量。然而,在我们的例子中,我们的数据库服务器99%的时间都不会流汗,它们甚至还不是多核的,也不是每6个月对系统架构进行的任何其他无数改进。仅出于这个原因,让我们的数据库处理排序不会是一个问题。此外,数据库非常擅长排序。他们为此进行了优化,多年来一直在做正确的事情,做这件事的语言非常灵活、直观、简单,最重要的是,任何SQL初学者都知道如何做,更重要的是,他们知道如何编辑、更改、维护,当您的数据库远没有被征税,并且您只想简化(并缩短!)开发时间时,这似乎是一个显而易见的选择

然后是网络问题。我曾经尝试过使用JavaScript对HTML表进行客户端排序,但它们不可避免地不够灵活,无法满足我的需要。再说一次,由于我的数据库没有过多的负担,而且可以非常轻松地进行排序,我很难证明重新编写或运行自己的JavaScript排序程序所需的时间。服务器端排序通常也是如此,尽管它可能已经比JavaScript更受欢迎。我不是一个特别喜欢数据集开销的人,所以起诉我吧

但这让我们想起了一点,那就是这是不可能的——或者更确切地说,是不容易的。我用以前的系统做了一个难以置信的黑客方式来获得动态排序。它既不美观,也不直观、简单,也不灵活,一个SQL初学者几秒钟之内就会迷失方向。这已经不是一个“解决方案”,而是一个“复杂问题”


下面的例子并不是为了展示任何类型的最佳实践、良好的编码风格或任何东西,也不是为了表明我作为T-SQL程序员的能力。他们就是他们,我完全承认他们是混乱的,糟糕的形式,只是简单的黑客

我们将一个整数值作为参数传递给一个存储过程(我们将该参数称为“sort”),并从中确定一组其他变量。例如假设sort为1(或默认值):

您已经可以看到,如果我声明更多@colX变量来定义其他列,我将如何根据“sort”的值对列进行排序。。。要使用它,它通常看起来像以下令人难以置信的混乱条款:

ORDER BY
    CASE @dir1
        WHEN 'desc' THEN
            CASE @sortCol1
                WHEN @col1 THEN [storagedatetime]
                WHEN @col2 THEN [vehicleid]
            END
    END DESC,
    CASE @dir1
        WHEN 'asc' THEN
            CASE @sortCol1
                WHEN @col1 THEN [storagedatetime]
                WHEN @col2 THEN [vehicleid]
            END
    END,
    CASE @dir2
        WHEN 'desc' THEN
            CASE @sortCol2
                WHEN @col1 THEN [storagedatetime]
                WHEN @col2 THEN [vehicleid]
            END
    END DESC,
    CASE @dir2
        WHEN 'asc' THEN
            CASE @sortCol2
                WHEN @col1 THEN [storagedatetime]
                WHEN @col2 THEN [vehicleid]
            END
    END
显然,这是一个非常简单的例子。真正的东西,因为我们通常有四到五列来支持排序,每个列都可能有第二列甚至第三列来进行排序(例如,日期递减,然后按名称升序进行第二次排序),并且每个列都支持双向排序,这有效地将案例数量增加了一倍。是 啊它很快就有毛了

其想法是可以“轻松”更改排序情况,以便vehicleid在storagedatetime之前得到排序。。。但伪灵活性,至少在这个简单的例子中,实际上到此为止。本质上,每个测试失败的情况(因为我们的排序方法这次不适用于它)都会呈现一个空值。因此,您将得到一个子句,其功能如下:

ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah
你明白了。它之所以能够工作,是因为SQL Server实际上会按order by子句忽略空值。这是难以置信的难以维护,任何具有SQL基本工作知识的人都可能看到这一点。如果我失去了你们,别难过。我们花了很长时间才让它工作起来,但我们仍然对编辑它或创建类似的新文件感到困惑。谢天谢地,它不需要经常改变,否则它很快就会变得“不值得麻烦”

但它确实起了作用


我的问题是:有更好的方法吗?

我对存储过程以外的解决方案没有意见,因为我意识到这可能不是解决方案。最好是,我想知道是否有人可以在存储过程中做得更好,但如果没有,您如何处理让用户使用ASP.NET对数据表进行动态排序(也是双向排序)

谢谢你阅读(或至少略读)这么长的问题

PS:很高兴我没有展示我的存储过程示例,它支持动态排序、列的动态筛选/文本搜索、通过ROWNUMBER()进行分页,并尝试…捕获错误时的事务回滚。。。“庞然大物大小”甚至没有开始描述它们


更新:

  • 我希望避免使用动态SQL。将字符串解析在一起并在其上运行EXEC会破坏一开始就有一个存储过程的许多目的。有时我想知道,至少在这些特殊的动态排序情况下,这样做的缺点是否值得。尽管如此,每当我这样做动态SQL字符串时,我总是感到脏兮兮的——就像我仍然生活在
    ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah
    
    order by
    case when @SortExpr = 'CustomerName' and @SortDir = 'ASC' 
        then CustomerName end asc, 
    case when @SortExpr = 'CustomerName' and @SortDir = 'DESC' 
        then CustomerName end desc,
    ...
    
    create procedure uspCallAndSort
    (
        @sql varchar(2048),        --exec dbo.uspSomeProcedure arg1,'arg2',etc.
        @sortClause varchar(512)    --comma-delimited field list
    )
    AS
    insert into #tmp EXEC(@sql)
    declare @msql varchar(3000)
    set @msql = 'select * from #tmp order by ' + @sortClause
    EXEC(@msql)
    drop table #tmp
    GO
    
    create table #temp ( your columns )
    
    insert #temp
    exec foobar
    
    select * from #temp order by whatever
    
    SELECT
      s.*
    FROM
      (SELECT
        CASE @SortCol1
          WHEN 'Foo' THEN t.Foo
          WHEN 'Bar' THEN t.Bar
          ELSE null
        END as SortCol1,
        CASE @SortCol2
          WHEN 'Foo' THEN t.Foo
          WHEN 'Bar' THEN t.Bar
          ELSE null
        END as SortCol2,
        t.*
      FROM
        MyTable t) as s
    ORDER BY
      CASE WHEN @dir1 = 'ASC'  THEN SortCol1 END ASC,
      CASE WHEN @dir1 = 'DESC' THEN SortCol1 END DESC,
      CASE WHEN @dir2 = 'ASC'  THEN SortCol2 END ASC,
      CASE WHEN @dir2 = 'DESC' THEN SortCol2 END DESC
    
    declare @o int;
    set @o = -1;
    
    declare @sql nvarchar(2000);
    set @sql = N'select * from table order by ' + 
        cast(abs(@o) as varchar) + case when @o < 0 then ' desc' else ' asc' end + ';'
    
    exec sp_executesql @sql
    
    declare @cols varchar(100);
    set @cols = '1 -2 3 6';
    
    declare @order_by varchar(200)
    
    select @order_by = isnull(@order_by + ', ', '') + 
            cast(abs(number) as varchar) + 
            case when number < 0 then ' desc' else '' end
    from dbo.iter_intlist_to_tbl(@cols) order by listpos
    
    print @order_by
    
    SELECT
       name_last,
       name_first,
       CASE @sortCol WHEN 'name_last' THEN [name_last] ELSE 0 END as mySort
    FROM
       table
    ORDER BY 
        mySort