SQL存储过程中的dynamicsorting

这是我花了数小时研究的一个问题。 在我看来,现在的RDBMS解决scheme应该已经解决了这个问题,但是迄今为止,我还没有发现任何真正解决了在任何带有数据库后端的Web或Windows应用程序中令人难以置信的普遍需求的东西。

我谈到dynamicsorting。 在我的幻想世界里,它应该像下面这样简单:

ORDER BY @sortCol1, @sortCol2 

这是新手SQL和存储过程开发人员在整个互联网论坛上给出的典型例子。 “为什么这不可能?” 他们问。 总而言之,最终有人会讲述关于存储过程的编译本质,总体执行计划以及为什么不能直接将参数直接放入ORDER BY子句中的各种其他原因。


我知道你们中的一些人已经在想:“那就让客户做分类吧。” 当然,这将卸载数据库中的工作。 在我们的例子中,我们的数据库服务器甚至没有99%的时间都是汗streamand背,甚至没有多核心,或者每6个月发生一次系统架构的其他改进。 仅仅因为这个原因,让我们的数据库处理sorting不会是一个问题。 另外,数据库非常擅长分类。 他们已经为它做了优化,并且已经有好几年的时间了,它的语言是非常灵活,直观和简单的,最重要的是任何初学SQL编程人员都知道如何去做,更重要的是他们知道如何编辑它,进行修改,维护等。当你的数据库远没有被征税,你只是想简化(缩短!)开发时间,这似乎是一个明显的select。

然后是networking问题。 我已经使用JavaScript来处理HTML表格的客户端sorting,但是它们不可避免地不能满足我的需求,而且,由于我的数据库没有过度征税,并且可以容易地进行sorting,所以我很难有时间重新编写或推出我自己的JavaScript分类器。 服务器端sorting通常是一样的,尽pipe它已经可能比JavaScript更受欢迎了。 我不是特别喜欢DataSets的开销,所以告我。

但是这又带来了一个不可能 – 或者说不容易的地步。 我已经用先前的系统做了一个非常黑客的方式来获得dynamic分类。 这并不美观,也不直观,简单或灵活,初学者的SQL编写器会在几秒钟内丢失。 这已经不是一个“解决scheme”,而是一个“并发症”。


下面的例子并不意味着公开任何types的最佳实践或者良好的编码风格或者任何东西,也不意味着我作为T-SQL程序员的能力。 他们是他们是什么,我完全承认他们是混乱,不良forms,只是简单的黑客。

我们将一个整数值作为parameter passing给一个存储过程(让我们把这个参数称为“sort”),然后我们确定一堆其他variables。 例如…让我们说sorting是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 

你已经可以看到,如果我声明更多的@colXvariables来定义其他列,我可以真正得到创造性的列sorting基于“sorting”的价值…使用它,它通常最终看起来像下面令人难以置信的杂乱的条款:

 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 

显然这是一个非常简单的例子。 真正的东西,因为我们通常有四个或五个列支持sorting,每个可能的第二列甚至第三列进行sorting,除了(例如date降序,然后按名称升序二次sorting)有效地使案件数量翻倍。 是的,它真的很快。

这个想法是,可以“轻松”地改变sorting情况,使得在存储date之前将vehicleidsorting…但是至less在这个简单的例子中,伪灵活性真的在那里结束。 从本质上讲,每个testing失败的情况(因为我们的sorting方法不适用于此次)呈现一个NULL值。 因此,你最终得到一个如下function的条款:

 ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah 

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

但它确实有效。


那么我的问题是: 还有更好的办法吗?

除了存储过程的解决scheme,我还好,因为我意识到它可能不是要走的路。 最好,我想知道是否有人可以在存储过程中更好地做到这一点,但如果不是,你们如何处理让用户使用ASP.NETdynamic地对数据表进行sorting(双向的)?

并感谢您阅读(或至less略读)如此长的问题!

PS:很高兴我没有显示我的存储过程的例子,支持dynamicsorting,dynamic过滤/文本search的列,通过ROWNUMBER()OVER分页, 尝试…赶上错误的事务回滚… “庞然大物”甚至没有形容它们。


更新:

  • 我想避免dynamicSQL 。 一起parsing一个string并运行一个EXEC,这首先破坏了存储过程的许多目的。 有时候我想知道做这样的事情是不是值得,至less在这些特殊的dynamic分类案例中。 不过,每当我做这样的dynamicSQLstring时,我总是觉得自己很肮脏 – 就像我仍然生活在经典ASP世界中一样。
  • 我们首先需要存储过程的很多原因是为了安全 。 我不打算就安全问题发出呼吁,只提出解决scheme。 使用SQL Server 2005,我们可以在单个存储过程的架构级别上设置权限(如果需要的话,以每个用户为基础),然后直接拒绝任何对表的查询。 批评这种方法的利弊也许是另一个问题,但这不是我的决定。 我只是领头羊猴子。 🙂

是的,这是一个痛苦,你做的方式看起来与我所做的相似:

 order by case when @SortExpr = 'CustomerName' and @SortDir = 'ASC' then CustomerName end asc, case when @SortExpr = 'CustomerName' and @SortDir = 'DESC' then CustomerName end desc, ... 

对我来说,这比从代码构builddynamicSQL要好得多,这将变成DBA的可伸缩性和维护的噩梦。

我从代码做的是重构分页和sorting,所以我至less没有太多的重复,为@SortExpr和@SortDir填充值。

就SQL而言,在不同的存储过程之间保持相同的devise和格式化,所以当你进行修改时,它至less是整洁和可识别的。

这种方法使得可sorting的列不会被顺序重复两次,而且可读性更强一些:

 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 

dynamicSQL仍然是一个选项。 你只需要决定这个选项是否比你现在拥有的更可口。

这里是一篇文章,显示: http : //www.4guysfromrolla.com/webtech/010704-1.shtml 。

我的应用程序做了很多,但他们都dynamic地构buildSQL。 但是,当我处理存储过程时,我这样做:

  1. 使存储过程的函数返回一个值的表 – 没有sorting。
  2. 然后在你的应用程序代码中做一个select * from dbo.fn_myData() where ... order by ...所以你可以在那里dynamic指定sorting顺序。

那么至lessdynamic部分是在你的应用程序中,但数据库仍然在做重大的事情。

有几种不同的方法可以解决这个问题。

先决条件:

  1. sp中只有一个SELECT语句
  2. 省略任何sorting(或者有一个默认的)

然后插入一个临时表:

 create table #temp ( your columns ) insert #temp exec foobar select * from #temp order by whatever 

方法#2:build立一个链接服务器回自己,然后使用openquery从这里select: http : //www.sommarskog.se/share_data.html#OPENQUERY

可能有第三个选项,因为你的服务器有很多的备用周期 – 使用一个辅助程序来通过临时表进行sorting。 就像是

 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 

警告:我没有testing过这个,但它应该在SQL Server 2005中工作(它将从结果集中创build一个临时表,而不需要提前指定列)。

在某些情况下,移动存储过程并仅仅使用参数化查询来避免这种types的哈希值是否值得呢?

我同意,使用客户端。 但看来,这不是你想听到的答案。

所以,它是完美的。 我不知道你为什么要改变它,甚至问“有没有更好的办法”。 真的,它应该被称为“方式”。 此外,它似乎工作,并适应项目的需要,很可能会延长足够多年来。 既然你的数据库没有征税,sorting真的很容易,它应该保持多年的方式。

我不会汗stream</s>背

当您对分页结果进行分页时,dynamicSQL是一个不错的select。 如果你对SQL注入偏执,你可以使用列号而不是列名。 我已经做了这个之前使用负值降序。 像这样的东西…

 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 

那么你只需要确保数字在1到#列内。 你甚至可以将它扩展到列号列表,并使用这样的函数将其parsing为整数表。 那么你会像这样构buildorder by子句…

 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 

一个缺点是你必须记住客户端每列的顺序。 特别是,当你不显示所有的列或你以不同的顺序显示它们。 当客户想要sorting时,将列名映射到列顺序并生成整数列表。

存储过程技术(hack?)我曾经用来避免某些作业的dynamicSQL是有一个独特的sorting列。 也就是说,

 SELECT name_last, name_first, CASE @sortCol WHEN 'name_last' THEN [name_last] ELSE 0 END as mySort FROM table ORDER BY mySort 

这一个很容易击败提交 – 你可以在mySort列中连接字段,颠倒math或date函数等的顺序。

尽pipe如此,我使用我的asp.net gridviews或其他对象与内置sorting为我做后sorting从我的服务器检索数据。 或者即使它不是内置的 – 例如,asp.net中的数据表等。

反对在客户端进行sorting的论据是大量的数据和分页。 一旦你的行数超出了你可以很容易地显示的行数,你通常会将其作为skip / take的一部分进行sorting,你可能想用SQL来运行。

对于entity framework,您可以使用存储过程来处理文本search。 如果遇到同样的sorting问题,我所看到的解决scheme是使用存储的proc进行search,只返回匹配的id密钥。 接下来,使用列表(包含)中的id对数据库重新进行查询(使用sorting)。 EF处理得非常好,即使ID设置非常大。 是的,这是两次往返,但它可以让您始终保持在数据库中的sorting,这在某些情况下可能很重要,并且可以防止您在存储过程中编写大量的逻辑。

如何处理显示结果的东西sorting – 网格,报告等,而不是在SQL?

编辑:

为了澄清事情,因为这个答案早些时候投了票,我会详细说明一下。

你说你知道客户端的分类,但想避开它。 当然,这是你的电话。

然而,我想指出的是,通过在客户端执行操作,您可以将数据提取出来,然后按照您的要求进行操作 – 而不是每次都要多次来回访问服务器sorting得到改变。

你的SQL Server没有被征税,这真棒。 它不应该。 但仅仅因为它没有超载,并不意味着它会永远保持这样的状态。

如果你使用任何新的ASP.NET的东西来显示在网上,很多东西已经烘焙了。

值得为每个存储过程添加这么多的代码来处理sorting吗? 再次,你的电话。

我不是最终负责支持它的人。 但是要想一想,在存储过程所使用的各种数据集(需要修改CASE语句)中添加/删除列时,或者突然而不是按两列进行sorting时,用户决定需要三个列,要求您现在更新每个使用此方法的存储过程。

对于我来说,值得的是获得一个可行的客户端解决scheme,并将其应用于less数用户面对的数据显示,并用它来完成。 如果添加了新列,则已经处理。 如果用户想要按多列进行sorting,则可以按照二到二十个sorting。

这个解决scheme可能只能在.NET中工作,我不知道。

我使用SQL order by子句中的初始sorting顺序将数据提取到C#中,将这些数据放在DataView中,将其caching在Sessionvariables中,然后使用它构build页面。

当用户点击列标题进行sorting(或页面或筛选)时,我不回到数据库。 相反,我回到我的caching的DataView,并将其“Sort”属性设置为dynamic构build的expression式,就像dynamicSQL一样。 (我使用“RowFilter”属性以相同的方式进行过滤)。

您可以在我的应用程序BugTracker.NET的演示中看到/感受它的工作,url为http://ifdefined.com/btnet/bugs.aspx

您应该避免SQL Serversorting,除非有必要。 为什么不sorting在应用程序服务器或客户端? .NETgenerics也做出了卓越的分类