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