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