SQL存储过程中的动态排序

Sea*_*ley 126 sql t-sql sorting stored-procedures

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

我说的是动态排序.在我的幻想世界中,它应该像以下一样简单:

ORDER BY @sortCol1, @sortCol2
Run Code Online (Sandbox Code Playgroud)

这是遍布Internet 的新手SQL和存储过程开发人员给出的典型示例."为什么这不可能?" 他们问.总是有人最终会向他们讲述存储过程的编译性质,一般的执行计划,以及为什么不能将参数直接放入ORDER BY子句的各种其他原因.


我知道你们中有些人已经在想:"让客户进行排序." 当然,这会从数据库中卸载工作.然而,在我们的案例中,我们的数据库服务器甚至没有在99%的时间里出汗,它们甚至还没有多核,或者每6个月发生一次对系统架构的任何其他改进.仅仅因为这个原因,让我们的数据库处理排序不会有问题.另外,数据库非常好擅长排序.它们已针对它进行了优化,并且已经有多年时间才能做到正确,这样做的语言非常灵活,直观且简单,而且最重要的是,任何初学SQL编写者都知道如何去做,更重要的是他们知道如何编辑它,进行更改,进行维护等等.当您的数据库远非被征税而您只想简化(并缩短!)开发时间时,这似乎是一个明显的选择.

然后是网络问题.我已经使用了JavaScript来对HTML表进行客户端排序,但是它们不可避免地对我的需求不够灵活,而且,因为我的数据库没有过度征税而且可以非常容易地进行排序,我很难证明重写或滚动我自己的JavaScript分拣机所需的时间.服务器端排序通常也是如此,尽管它已经比JavaScript更受欢迎了.我不是特别喜欢DataSet的开销的人,所以起诉我.

但这又说明了这是不可能的 - 或者更确切地说,不容易.在先前的系统中,我已经完成了一种令人难以置信的黑客方式来获得动态排序.它不漂亮,不直观,简单或灵活,初学者SQL编写器会在几秒钟内丢失.这已经不仅仅是一个"解决方案",而是一个"复杂化".


以下示例并不意味着公开任何类型的最佳实践或良好的编码风格或任何内容,也不表示我作为T-SQL程序员的能力.他们就是这样,我完全承认他们是混乱,糟糕的形式,只是简单的黑客.

我们将一个整数值作为参数传递给存储过程(让我们调用参数只是"排序"),然后从中确定一堆其他变量.例如......让我们说sort是1(或默认值):

DECLARE @sortCol1 AS varchar(20)
DECLARE @sortCol2 AS varchar(20)
DECLARE @dir1 AS varchar(20)
DECLARE @dir2 AS varchar(20)
DECLARE @col1 AS varchar(20)
DECLARE @col2 AS varchar(20)

SET @col1 = 'storagedatetime';
SET @col2 = 'vehicleid';

IF @sort = 1                -- Default sort.
BEGIN
    SET @sortCol1 = @col1;
    SET @dir1 = 'asc';
    SET @sortCol2 = @col2;
    SET @dir2 = 'asc';
END
ELSE IF @sort = 2           -- Reversed order default sort.
BEGIN
    SET @sortCol1 = @col1;
    SET @dir1 = 'desc';
    SET @sortCol2 = @col2;
    SET @dir2 = 'desc';
END
Run Code Online (Sandbox Code Playgroud)

你已经可以看到,如果我宣布更多@colX变量来定义其他列我真的可以发挥创意与列基础上的"排序"值进行排序...使用它,它通常最终看起来像以下令人难以置信的凌乱条款:

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
Run Code Online (Sandbox Code Playgroud)

显然这是一个非常简单的例子.真实的东西,因为我们通常有四到五列支持排序,每个列都有可能的二级或甚至第三列除了它之外的排序(例如日期降序然后按名称升序排序)和每个支持双定向排序,有效地使案件数量增加一倍.是的......它很快变得毛茸茸.

我们的想法是,人们可以"轻松"改变排序情况,以便在存储日期之前对车辆进行排序......但至少在这个简单的例子中,伪灵活性确实在那里结束.基本上,每个未通过测试的情况(因为我们的排序方法此次不适用于它)会呈现NULL值.因此,您最终会得到一个类似于以下内容的子句:

ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah
Run Code Online (Sandbox Code Playgroud)

你明白了.它的工作原理是因为SQL Server有效地按子句顺序忽略空值.这非常难以维护,因为任何具有SQL基本工作知识的人都可以看到.如果我失去了你们中的任何一个,不要感到难过.我们花了很长时间才使它工作,我们仍然感到困惑,试图编辑它或创建类似它的新的.值得庆幸的是,它不需要经常更换,否则很快就会变得"不值得麻烦".

确实有效.


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

我对使用存储过程之外的其他解决方案感到满意,因为我意识到这可能不是最佳选择.最好,我想知道是否有人可以在存储过程中做得更好,但如果没有,你们如何处理让用户动态地对数据表进行排序(也是双向的)?

谢谢你阅读(或至少略读)如此长的问题!

PS:很高兴我没有展示我的存储过程示例,该存储过程支持动态排序,列的动态过滤/文本搜索,通过ROWNUMBER()OVER进行分页,尝试...捕获事务回滚错误... "庞然大小"甚至没有开始描述它们.


更新:

  • 我想避免动态SQL.将字符串解析在一起并在其上运行EXEC会破坏许多首先存储过程的目的.有时我想知道做这样的事情的缺点是否值得,至少在这些特殊的动态排序案例中.尽管如此,每当我做这样的动态SQL字符串时,我总是感觉很脏 - 就像我仍然生活在经典ASP世界中一样.
  • 我们首先想要存储过程的很多原因是为了安全.我不打算就安全问题发出呼吁,只建议解决方案.使用SQL Server 2005,我们可以在单个存储过程的模式级别设置权限(如果需要,基于每个用户),然后直接拒绝对表的任何查询.批评这种方法的利弊可能是另一个问题,但同样不是我的决定.我只是领导代码猴子.:)

Eri*_*ard 96

是的,这是一种痛苦,你做它的方式看起来与我的做法相似:

order by
case when @SortExpr = 'CustomerName' and @SortDir = 'ASC' 
    then CustomerName end asc, 
case when @SortExpr = 'CustomerName' and @SortDir = 'DESC' 
    then CustomerName end desc,
...
Run Code Online (Sandbox Code Playgroud)

对我而言,这仍然比从代码构建动态SQL要好得多,这会成为DBA的可扩展性和维护噩梦.

我从代码中做的是重构分页和排序所以我至少没有很多重复与填充值@SortExpr@SortDir.

就SQL而言,保持不同存储过程之间的设计和格式相同,因此当您进行更改时,它至少是整洁且可识别的.


Jas*_*tes 23

这种方法使得可排序列不会按顺序重复两次,并且更易于阅读IMO:

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
Run Code Online (Sandbox Code Playgroud)


jop*_*jop 6

动态SQL仍然是一个选项.你只需要决定这个选项是否比你现在的选择更可口.

这篇文章显示:http://www.4guysfromrolla.com/webtech/010704-1.shtml.


Ron*_*age 6

我的应用程序做了很多,但它们都是动态构建SQL.但是,当我处理存储过程时,我这样做:

  1. 使存储过程成为一个返回值的表的函数 - 没有排序.
  2. 然后在您的应用程序代码中执行操作,select * from dbo.fn_myData() where ... order by ...以便您可以在那里动态指定排序顺序.

然后至少动态部分在您的应用程序中,但数据库仍在进行繁重的工作.

  • 您可以使用本地表变量而不是返回数据的表格函数来实现相同的目的。我发现本地表比函数更灵活,因为您可以输出一些调试信息。 (2认同)

dav*_*ave 5

我用来避免某些作业使用动态SQL的存储过程技术(hack?)是具有唯一的排序列。即

SELECT
   name_last,
   name_first,
   CASE @sortCol WHEN 'name_last' THEN [name_last] ELSE 0 END as mySort
FROM
   table
ORDER BY 
    mySort
Run Code Online (Sandbox Code Playgroud)

这很容易提交-您可以在mySort列中合并字段,使用数学或日期函数反转顺序等。

不过,最好是,我将asp.net gridviews或其他对象与内置排序一起使用,以便在从Sql-Server检索数据之后为我进行排序。甚至它不是内置的-例如,asp.net中的数据表等。


Kev*_*ild 1

如何在显示结果的内容(网格、报告等)上而不是在 SQL 上处理排序?

编辑:

为了澄清一些事情,因为这个答案之前被否决了,我将详细说明一下......

您表示您了解客户端排序,但想避开它。当然,这是你的决定。

不过,我想指出的是,通过在客户端执行此操作,您可以一次提取数据,然后随心所欲地使用它,而不是每次都往返于服务器排序发生变化。

您的 SQL Server 现在没有被征税,这太棒了。不应该的。但仅仅因为它还没有超载并不意味着它会永远保持这种状态。

如果您使用任何较新的 ASP.NET 内容在 Web 上显示,那么很多内容已经内置。

仅仅为了处理排序就值得向每个存储过程添加这么多代码吗?再次,你的电话。

我不是最终负责支持它的人。但是请考虑一下,当在存储过程使用的各种数据集中添加/删除列(需要修改 CASE 语句)时,或者当用户突然决定他们需要三列而不是按两列排序时,会涉及到什么 -要求您现在更新每个使用此方法的存储过程。

对我来说,获得一个有效的客户端解决方案并将其应用于少数面向用户的数据显示并完成它是值得的。如果添加了新列,则它已被处理。如果用户想要按多列排序,他们可以按其中两列或二十列排序。

  • 等等,如果结果集有数万行呢?您无法将所有数据返回给客户端。您必须对数据库进行分页和排序。 (3认同)