【问题标题】:Is there a faster way to run an SQL Where Case是否有更快的方法来运行 SQL Where Case
【发布时间】:2020-10-22 17:03:13
【问题描述】:

我有以下存储过程(在 MS SQL 中):

ALTER PROCEDURE [dbo].[proc_GetWorksWithEngineerVisits3]
    @sTextSearch nvarchar(255) = NULL,
    @bCompleteFlag bit = NULL,
    @dExpectedStartDateTime datetime = NULL,
    @dExpectedEndDateTime datetime = NULL,
    @sResponsible_UserIDs nvarchar(255) = NULL,
    @bEnableTextSearchFilter bit = false,
    @bEnableCompleteFlagFilter bit = false,
    @bEnableExpectedDateTimeRangeFilter bit = false,
    @bEnableResponsible_UserIDFilter bit = false
AS
    SELECT *
    FROM dbo.vwWorksWithEngineerVisits
    WHERE
        --TextSearch Filter Start
        (sCustomer LIKE CASE
                           WHEN @bEnableTextSearchFilter = 1
                              THEN '%' + @sTextSearch + '%'
                              ELSE sCustomer
                        END
         OR
         sSite LIKE CASE
                       WHEN @bEnableTextSearchFilter = 1
                          THEN '%' + @sTextSearch + '%'
                          ELSE sSite
                    END
         OR
         sCallID LIKE CASE
                         WHEN @bEnableTextSearchFilter = 1
                            THEN '%' + @sTextSearch + '%'
                            ELSE sCallID
                      END)
    --TextSearch Filter End
    AND

    --Complete Filter Start
    bIsComplete = CASE 
                     WHEN @bEnableCompleteFlagFilter = 1
                        THEN @bCompleteFlag
                        ELSE bIsComplete
                  END
    --Complete Filter End

    AND

    --Expected DateTime Range Filter Start
    dExpectedStartDateTime >= CASE 
                                 WHEN @bEnableExpectedDateTimeRangeFilter = 1
                                    THEN @dExpectedStartDateTime
                                    ELSE dExpectedStartDateTime
                              END

AND

dExpectedEndDateTime <= 
CASE 
    WHEN @bEnableExpectedDateTimeRangeFilter = 1
    THEN @dExpectedEndDateTime
    ELSE dExpectedEndDateTime
END
----Expected DateTime Range Filter End

AND

--Responsible_UserID Filter Start

lResponsible_UserID in (
CASE 
    WHEN @bEnableResponsible_UserIDFilter = 0
    THEN lResponsible_UserID
    ELSE (SELECT Value FROM dbo.CSVToList(@sResponsible_UserIDs) AS CSVToList_1) 
END
)
--Responsible_UserID Filter End

ORDER BY dExpectedEndDateTime

输出很好,但速度很慢(仅 5000 行需要 15 秒)直接执行 dbo.vwWorksWithEngineerVisits 需要 1 秒相同的数字。执行 SP 时,我将所有启用标志设置为 0。

DECLARE @return_value int

EXEC    @return_value = [dbo].[proc_GetWorksWithEngineerVisits3]
        @sTextSearch = NULL,
        @bCompleteFlag = False,
        @dExpectedStartDateTime = N'01/01/1969',
        @dExpectedEndDateTime = N'01/01/2021',
        @sResponsible_UserIDs = NULL,
        @bEnableTextSearchFilter = 0,
        @bEnableCompleteFlagFilter = 0,
        @bEnableExpectedDateTimeRangeFilter = 0,
        @bEnableResponsible_UserIDFilter = 0

SELECT  'Return Value' = @return_value

如果设置了相应的标志,我希望能够只过滤一列。我可能只检查主参数中的 NULL 并减少参数,但我认为它不会改变我遇到的问题。

前 4 个 Case 过滤器非常基本,当我将剩下的最后 3 个注释掉时,性能/结果是即时的。一旦我将最后 3 个中的一个添加回组合中,事情就会像上面一样变慢。使这些不同的是它们执行“>=”或“in”,而不仅仅是“=”或“like”。我注意到的另一件事是,当我更改以下内容时:

lResponsible_UserID in (
CASE 
    WHEN @bEnableResponsible_UserIDFilter = 0
    THEN lResponsible_UserID
    ELSE (SELECT Value FROM dbo.CSVToList(@sResponsible_UserIDs) AS CSVToList_1) 
END

lResponsible_UserID in (
CASE 
    WHEN @bEnableResponsible_UserIDFilter = 0
    THEN lResponsible_UserID
    ELSE lResponsible_UserID
END

这也将速度提高到 1 秒。当标志始终为 0 时,更改语句的 else 部分有何不同,所以永远不应该运行?

我需要这些过滤器,而且我需要它们是动态的。有多种运算符类型(包括以函数为目标的 IN)。有没有办法重构这个存储过程以获得相同的结果(它确实有效),但以一种更可选的方式?

如果我在帖子中遗漏了某些内容,我深表歉意,如果指出,我会进行编辑。

谢谢

【问题讨论】:

标签: sql sql-server stored-procedures case query-performance


【解决方案1】:

这是一个很大的问题!

当您定义它时,SQL Server 会针对您的 sp 中的查询运行一个编译器。然后它使用编译过的过程,轻率地忽略可能来自您的特定参数值的任何优化。 This page explains:

当 SQL Server 执行过程时,过程在编译时使用的任何参数值都包含在生成查询计划中。如果这些值代表随后调用该过程的典型值,那么该过程在每次编译和执行时都会从查询计划中受益。如果过程中的参数值经常不典型,则强制重新编译过程和基于不同参数值的新计划可以提高性能。

在您的情况下,您的参数设置极大地简化了您想要的搜索。但是编译出来的 sp 不知道,所以它使用了一个过于笼统的搜索计划。

尝试将this 附加到您的 SP 中的查询(在您的 ORDER BY 子句之后)以强制生成一个新的、希望更具体的执行计划。

OPTION (RECOMPILE)

另外,你可以整理你的过滤子句,让它们不那么粗糙。

在您的文本搜索案例中试试这个:更改

    sCustomer LIKE CASE
                   WHEN @bEnableTextSearchFilter = 1
                          THEN '%' + @sTextSearch + '%'
                          ELSE sCustomer
                    END

  (@bEnableTextSearchFilter <> 1 OR sCustomer LIKE '%' + @sTextSearch + '%')

当您的过滤器被禁用时,这将避免说column LIKE column,并且可能会节省一些时间。

您也可以将相同的原则应用于您的 CASE 语句的其余部分。

注意:过滤器模式column LIKE '%value%'固有很慢;它不能在column 上使用索引范围扫描,因为文本匹配没有锚定在模式的开头。相反,它必须扫描所有值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-25
    • 1970-01-01
    • 1970-01-01
    • 2011-06-26
    • 2023-03-23
    • 1970-01-01
    • 2016-06-09
    相关资源
    最近更新 更多