我过去花了数小时研究这个问题。在我看来,现代RDBMS解决方案应该解决此问题,但是到目前为止,我还没有发现任何能够真正解决我认为在任何具有数据库后端的Web或Windows应用程序中非常普遍的需求的东西。
我说的是动态排序。在我的幻想世界中,它应该像下面这样简单:
ORDER BY @sortCol1, @sortCol2
这是新手SQL和存储过程开发人员在整个Internet论坛上给出的规范示例。“为什么这不可能?” 他们问。最终,总是有人总会向他们讲授存储过程的编译性质,一般的执行计划以及无法将参数直接放入ORDER BY
子句的各种其他原因。
我知道你们中有些人已经在想:“那么,让客户来进行分类。” 自然,这会减轻数据库的工作量。在我们的案例中,我们的数据库服务器甚至在99%的时间内都不会汗水,甚至还不是多核的,也不是每6个月进行一次其他的系统架构改进。仅出于这个原因,让我们的数据库处理排序将不是问题。此外,数据库非常擅长分类。他们为此进行了优化,并且已经花了好几年的时间来实现它,其实现语言令人难以置信的灵活,直观和简单,最重要的是,任何初学者SQL编写者都知道如何做,更重要的是,他们知道如何进行编辑,进行更改,进行维护等。当您的数据库需要负担很多费用而您只想简化(并缩短!)开发时间时,这似乎是一个显而易见的选择。
然后是网络问题。我一直在使用JavaScript进行HTML表格的客户端排序,但是它们不可避免地不够灵活,无法满足我的需求,而且,由于我的数据库没有负担过多,并且可以非常轻松地进行排序,因此我很难证明重新编写或自己拥有JavaScript分选器所需的时间。服务器端排序通常也是如此,尽管它可能已经比JavaScript更受青睐。我不是特别喜欢DataSet开销的人,所以起诉我。
但这又回到了不可能的点,或者说不容易。对于以前的系统,我已经完成了一种获得动态排序的骇人听闻的破解方法。它不是很漂亮,也不是直观,简单或灵活的,初学者SQL编写器将在几秒钟内丢失。看起来这已经不是“解决方案”,而是“复杂性”。
以下示例无意提供任何种类的最佳实践或良好的编码风格,也不表示我作为T-SQL程序员的能力。它们就是它们的样子,我完全承认它们是令人困惑的,糟糕的形式,而且仅仅是简单的破解。
我们将一个整数值作为参数传递给存储过程(我们称该参数为“ sort”),然后从中确定一堆其他变量。例如...假设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
您已经知道如果我声明更多的@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
显然,这是一个非常简单的例子。真正的东西,因为我们通常有4到5列支持排序,每列除此以外还可能有第二列或什至是第三列(例如,日期递减,然后按名称升序进行第二排序),并且每一个都支持双向定向排序可有效地将案件数量加倍。是的...真的很快就长了毛。
这种想法是,人们可以“轻松”地更改排序条件,以便在存储日期时间之前对车辆ID进行排序……但是至少在此简单示例中,伪柔韧性才真正到此结束。本质上,每个未通过测试的案例(因为这次我们的sort方法都不适用)会呈现NULL值。因此,您最终得到了一个功能如下的子句:
ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah
你明白了。之所以起作用,是因为SQL Server有效地忽略了by by子句中的空值。这很难维护,因为任何具有SQL基本工作知识的人都可以看到。如果我失去了你们中的任何一个,不要难过。我们花了很长时间才开始使用它,但仍然很难对它进行编辑或创建类似它的新东西。值得庆幸的是,它不需要经常进行更改,否则它将很快变得“不值得麻烦”。
然而它确实起作用了。
我的问题是: 还有更好的方法吗?
除了存储过程解决方案外,我还可以解决其他问题,因为我意识到这可能不是解决之道。最好是,我想知道是否有人可以在存储过程中做得更好,但是如果不能,那么大家如何处理如何让用户使用ASP.NET对数据表进行动态排序(也双向)?
感谢您阅读(或至少浏览)这么长的问题!
PS:很高兴我没有展示存储过程的示例,该示例支持动态排序,动态过滤/列文本搜索,通过ROWNUMBER()OVER进行分页,并尝试...捕获错误时的事务回滚... “庞然大物”甚至没有开始描述它们。
更新:
- 我想避免使用动态SQL。将字符串解析在一起并在其上运行EXEC会破坏许多首先具有存储过程的目的。有时候我想知道,这样做的弊端是否值得,至少在这些特殊的动态排序情况下是不值得的。尽管如此,每当执行这样的动态SQL字符串时,我总是感到肮脏-就像我仍然生活在Classic ASP世界中一样。
- 我们首先要存储过程的许多原因是出于安全性考虑。我不会打电话给安全问题,只建议解决方案。使用SQL Server 2005,我们可以在架构级别上对单个存储过程设置权限(如果需要,可以基于每个用户),然后直接拒绝对表的任何查询。批评这种方法的优缺点也许是另一个问题,但这并不是我的决定。我只是主要代码猴子。:)