想象一下,您有一个状态表的订单:
Id (PK)
Status (INT)
...
有一个用户界面(UI),业务用户可以在其中选择感兴趣的订单的状态。神奇的数字“ -1”表示-用户对每种状态都感兴趣
实现查询的最佳方法是什么?
1. select * from orders where status = @user-selected-status-id or @user-selected-status-id = -1
2. select * from orders where status = ISNULL(NULLIF(@user-selected-status-id, -1), status)
3. Dynamic sql approach with string concatenation "where" operator is joined only if required (EXEC sp_executesql at the very end)
我对所有三种方法都使用了“执行计划”,它告诉我们,对于动态sql来说,估计成本是最好的选择,然后isnull / nullif和“ or”是局外人。
我曾经认为“或”方法的优胜之处在于它的简洁性isull / nullif方法
让我感到更开心的是-结果(估计)取决于订单表的大小。当它增长十倍时(从10K-> 100K行),局外人就是“动态SQL”。
我可以相信吗?
答案 0 :(得分:3)
盖尔·肖(Gail Shaw)在这方面做了几篇很棒的文章:Catch-all Queries和Revisting Catch-all Queries。
这些内容的摘要是,您是在2008年,然后使用参数化动态语句。如果您使用的是SQL Server 2012+,则使用WHERE Column = @Variable OR @Variable IS NULL
并在末尾添加OPTION (RECOMPILE)
。
但是,如果在生成查询计划方面确实有昂贵的查询,那么您可能仍要使用动态SQL。然后,只要经常调用它们,每个语句都会有自己的缓存计划。
尽管如此,但不要使用status = ISNULL(NULLIF(@user-selected-status-id, -1), status)
。该子句是非SARGable的,并且性能最差。
因此,对于采用非高复杂度查询计划的生成方式,2012 +方法应为:
SELECT {Columns}
FROM YourTable YT
WHERE (YT.Column = @Variable
OR @Variable IS NULL)
OPTION (RECOMPILE);
OPTION (RECOMPILE)
很重要,因为它会使数据引擎在每次运行查询时重新创建查询计划(和估计值)。如果您有一个查询,其中@Variable
有一个值,那么将要返回的行数将分别估计为“低”。另一方面,对于@Variable
值为NULL
的查询,这意味着将返回每行(在此简单查询中);或“高”行数。 SQL Server缓存计划,这意味着数量少时的计划就不合适。错误的估计可能(并且确实)意味着性能不佳,因为事情可能会从tempdb中的RAM中溢出,并且资源时间不是预期的。强制数据引擎重新创建计划意味着将为每次运行重新创建这些估计(这确实是有代价的),但对于正在运行的查询会更好。只要您的统计信息也是最新的。
但是,如果您使用的是SQL Server 2008,则您无权访问OPTION (RECOMPILE)
,因此您需要沿着动态路线走下去:
DECLARE @SQL nvarchar(MAX);
SET @SQL = N'SELECT {Columns}' + NCHAR(13) + NCHAR(10) +
N'FROM YourTable' + NCHAR(13) + NCHAR(10) +
CASE WHEN @Variable IS NOT NULL THEN N'WHERE YT.Column = @Variable' ELSE '' END + N';';
EXEC sp_executesql @SQL, N'@Variable {DataType}', @Variable = @Variable;