WHERE 子句中的动态可选参数

Posted

技术标签:

【中文标题】WHERE 子句中的动态可选参数【英文标题】:Dynamic optional parameters in WHERE clause 【发布时间】:2020-05-27 22:57:27 【问题描述】:

考虑所有参数都是可选的 SQL 查询

SELECT ...
FROM Table
WHERE
 (@Col1 IS NULL OR Col1 = @Col1)
AND
 (@Col2 IS NULL OR Col2 = @Col2)
...
 (@ColN IS NULL OR ColN = @ColN)

大约有 8 个参数,但将来可能会更多。

代码审查员要求永远不要编写这样的 WHERE 构造(检查 NULL 或相等性),而是使用动态 SQL(不给出原因)。

对我来说,它看起来可读且明确。 我还没有运行执行计划,但理论上 DBMS 会发现任何为空的参数并且不考虑括号内的第二项。因此,我希望只比较指定的参数。

    有人可以支持或反驳为什么这是不好的吗? 索引。如果搜索参数可以任何组合到达,我们甚至可以索引这样的查询(以及如何)?

【问题讨论】:

“从不”在涉及 SQL 时是一个不常见的词。就个人而言,除非它开始表现不佳,否则我会使用您编写的 SQL。恕我直言,动态 SQL 永远不应成为首选。为此类查询编制索引非常困难。根据我的经验,即使使用如此笼统的搜索条件,某些搜索条件也比其他搜索条件具有更高的相关性或更可能被使用,因此有时您可以使用预期的参数进行预过滤。 为什么不使用 WHERE ( ISNULL(@Col1,Col1 )= Col1) AND 之类的东西...只是为了让审阅者高兴:) 绝对喜欢“从不”使用动态 sql。我发现它几乎总是被那些不了解如何编写 SQL 的人使用。答案中建议的一些极端情况,但也可以解决。 在Erland's dynamic search conditions进行了详细讨论 Erland 的页面还讨论了参数嗅探,其中查询优化器使用第一组参数来构建优化后的查询,然后缓存该查询以供重复使用。使用option ( recompile ) 的另一个原因。 【参考方案1】:

这很复杂。使用非动态查询有一些优势:

很清楚。 更易于调试和维护。 在创建存储过程时进行验证。 如果代码在存储过程中,SQL Server 将为您维护依赖关系。

从编码的角度来看,我认为编译时验证是一个巨大的胜利。它可以防止意外的运行时故障。

在这种情况下,动态查询有一个优点:每次执行时可能都会重新编译。

什么时候会有所作为?如果您对正在使用的每一列都有一个索引,那么重新编译总是有帮助的。

但是,您可以使用option (recompile) 获得两全其美的效果。这将根据当前参数值重新编译代码。这可能是您想做的最佳选择。

【讨论】:

【参考方案2】:

我倾向于同意您的评论者的观点。原因如下:

您所拥有的风险是 SQL Server 将在第一次执行时编译此查询一次。它将根据当时提供的参数值进行基数估计,并以此为基础制定执行计划。此执行计划将在缓存中保留一段时间,无论后续运行实际发生什么。这可能会导致某些参数值的执行计划非常糟糕。

正如 Linoff 先生建议的那样(他真的知道他的东西),您可以通过 option(recompile) 来解释这一点。但是,这意味着 SQL Server 将需要在 每次 执行时重新编译此查询。如果这些查询经常运行,可能会导致 SQL Server 花费大量额外精力重新编译。

此外,我对此类查询的经验是,用户提供某些列的频率往往比其他列高得多。如果动态构建查询,SQL Server 可以缓存常见排列的计划,并节省额外的重新编译工作。从 DBA 的角度来看,这些现在也是计划缓存中的单独条目,您可以检查这些条目以了解随着时间的推移哪些索引可能更有价值以支持此搜索功能。

当然,知道这是否会成功意味着了解您的数据、系统和用户。您必须返回并检查用户是否真的在相同的列集上进行搜索,或者它是否更随机分布。

我也可能做不同的事情,我倾向于在客户端代码中构建动态 SQL,这听起来有点像您希望将其作为存储过程的一部分。

例如,使用类似 C# 的伪代码:

sql = "SELECT ... FROM... WHERE 1=1";

if (txtCol1.Text != "")

   sql += " AND Col1 = @Col1"
   cmd.Parameters.AddWithValue("@Col1", txtCol1.Text);

if (txtCol1.Text != "")

    sql += " AND Col2 = @Col2"
    cmd.Parameters.AddWithValue("@Col2", txtCol2.Text);

//...
// Note this is just pseudocode. I'm not a fan of AddWithValue() in actual practice.

cmd.ExecuteReader();

我知道1=1 的事情看起来很奇怪,但它不会伤害 SQL Server,而且它是一种确保语法仍然有效的简单方法,无论哪个(如果有)进一步的条件是第一个持有一个值。

【讨论】:

感谢您的回复。然而,每个列搜索参数都对应于 UI 搜索面板控件 - 这使得它无法预测用户将搜索的内容。 @Nickolodeon 您无需预测哪些字段很重要。 Sql Server 计划缓存将为您完成将重要计划保存在缓存中的工作。您需要做的就是编写代码以在查询中包含用户选择的字段(并且仅包含这些字段)。 @Nickolodeon 我添加了一些示例代码来解释这可能是如何工作的。

以上是关于WHERE 子句中的动态可选参数的主要内容,如果未能解决你的问题,请参考以下文章

sql Where子句中的可选参数

使用可选参数对 Where 子句进行续集

可选的SSRS参数

SQLAlchemy中的可选参数

在 WHERE 子句中使用可选条件的正确方法

用于返回结果集的 where 子句中的 case 语句包含空值