使用子查询时如何提高查询性能

Posted

技术标签:

【中文标题】使用子查询时如何提高查询性能【英文标题】:How to improve query performance when using sub-query 【发布时间】:2016-11-18 23:01:59 【问题描述】:

查询的第一部分获取PolicyPremiumsEffectiveExpiration 日期。 第二部分创建Calendar,第三部分(最终SELECT 声明)返回按MonthYear 细分的收益 一切正常,只需 3 秒即可显示结果。 但是然后我需要过滤PolicyNumbers 可以使用的内容,基本上我需要摆脱没有@ClassCodePolicyNumber。因此,在查询的第一部分中,我放置了WHERE 子句:

WHERE  State IN ('CA','NV','AZ')        
        AND PolicyNumber IN (
                            SELECT  PolicyNumber
                            FROM    tblClassCodesPlazaCommercial 
                            GROUP BY PolicyNumber
                            HAVING COUNT (CASE WHEN ClassCode NOT IN (@ClassCode)                                                                       
 THEN 1 END)=0
                            )

感谢@Prdp 用户,我有这样的声明:Case 语句将为列表中存在的 ClassCode 生成 1,否则将生成 NULL。现在计数聚合将为每个 PolicyNumber 计数 1。通过设置 = 0,我们可以确保 PolicyNumber 在给定列表中没有任何 ClassCode。

在该查询永远旋转之后,因为@ClassCode 在 s-s-rS 报告中可以有超过 200 个ClassCodes

有趣的是,这两个语句分别可以正常工作。但是当我一起使用它们时(将WHERE 子句放在cte policy_data 中,那么执行将永远存在。 有什么方法可以告诉引擎执行查询的第一部分,即

; WITH Earned_to_date AS (
   SELECT Cast(EOMONTH (GETDATE(), -1) AS DATE) AS Earned_to_date
), policy_data AS (
    SELECT
        PolicyNumber
,       Cast(PolicyEffectiveDate AS DATE) AS PolicyEffectiveDate
,       Cast(PolicyExpirationDate AS DATE) AS PolicyExpirationDate
,       WrittenPremium
,       State
        FROM PlazaInsuranceWPDataSet
        WHERE  State IN ('CA','NV','AZ') 
    /* -------This statement gives me trouble ----------------------*/
        AND PolicyNumber IN (
                            SELECT  PolicyNumber
                            FROM    tblClassCodesPlazaCommercial 
                            GROUP BY PolicyNumber
                            HAVING COUNT (CASE WHEN ClassCode NOT IN (5151)                                                                     
 THEN 1 END)=0
                            )
)

然后仅计算和分解那些已过滤的保单的收益。

我的整个代码如下:

; WITH Earned_to_date AS (
   SELECT Cast(EOMONTH (GETDATE(), -1) AS DATE) AS Earned_to_date
), policy_data AS (
    SELECT
        PolicyNumber
,       Cast(PolicyEffectiveDate AS DATE) AS PolicyEffectiveDate
,       Cast(PolicyExpirationDate AS DATE) AS PolicyExpirationDate
,       WrittenPremium
,       State
        FROM PlazaInsuranceWPDataSet
        WHERE  State IN ('CA','NV','AZ') 
    /* -------This statement gives me trouble ----------------------*/
        AND PolicyNumber IN (
                            SELECT  PolicyNumber
                            FROM    tblClassCodesPlazaCommercial 
                            GROUP BY PolicyNumber
                            HAVING COUNT (CASE WHEN ClassCode NOT IN (@ClassCode)                                                                       
 THEN 1 END)=0
                            )
)
, digits AS (
SELECT digit
   FROM (VALUES (0), (1), (2), (3), (4)
,      (5), (6), (7), (8), (9)) AS z2 (digit)
), numbers AS (
SELECT 1000 * d4.digit + 100 * d3.digit + 10 * d2.digit + d1.digit AS number
    FROM digits AS d1
    CROSS JOIN digits AS d2
    CROSS JOIN digits AS d3
    CROSS JOIN digits AS d4
), calendar AS (
SELECT
    DateAdd(month, number, '1753-01-01') AS month_of
,   DateAdd(month, number, '1753-02-01') AS month_after
    FROM numbers
), policy_dates AS (
SELECT
   PolicyNumber
,   CASE
        WHEN month_of < PolicyEffectiveDate THEN PolicyEffectiveDate
        ELSE month_of
    END AS StartRiskMonth
,   CASE
       WHEN PolicyExpirationDate < month_after THEN PolicyExpirationDate
       WHEN Earned_to_date.Earned_to_date < month_after THEN Earned_to_date
       ELSE month_after
    END AS EndRiskMonth
,   DateDiff(day, PolicyEffectiveDate, PolicyExpirationDate) AS policy_days
,   WrittenPremium
    FROM policy_data
    JOIN calendar
        ON (policy_data.PolicyEffectiveDate < calendar.month_after
        AND calendar.month_of < policy_data.PolicyExpirationDate)
    CROSS JOIN Earned_to_date
    WHERE  month_of < Earned_to_date
)
SELECT      
            Year(StartRiskMonth) as YearStartRisk, 
            Month(StartRiskMonth) as MonthStartRisk,
            c.YearNum,c.MonthNum,
            convert(varchar(7), StartRiskMonth, 120) as RiskMonth,
            sum(WrittenPremium * DateDiff(day, StartRiskMonth, EndRiskMonth) / policy_days) as EarnedPremium
FROM        tblCalendar  c
            LEFT  JOIN policy_dates l ON c.YearNum=Year(l.StartRiskMonth) AND c.MonthNum = Month(l.StartRiskMonth) 
            AND l.StartRiskMonth BETWEEN '01-01-2015' AND  '12-31-2016'
WHERE       c.YearNum Not IN (2017) 
GROUP BY    convert(varchar(7), StartRiskMonth, 120),
            Year(StartRiskMonth) , Month(StartRiskMonth),
            c.YearNum,c.MonthNum
ORDER BY    c.YearNum,c.MonthNum

提高性能的最佳方法是什么? 我在两个表的PolicyNumber 上创建了non-clustered 索引。但还是一无所获。 就像我说的,在我看来,如果 SQL 引擎将处理需要 3 秒的第一部分(PolicyNumber 过滤),然后再执行需要 3 秒的第二部分(计算那些 PolicyNumber) - 那将是非常棒的。 但我是 DBA 的新手,所以我不确定它是否可能。 有什么建议吗? 谢谢

执行计划::

最终结果:

【问题讨论】:

Codereview.stackexchange.com 是一个更好的地方 【参考方案1】:

您必须使您的代码更具可读性。通过临时表而不是 cte 将其拆分为更小的块。

你的麻烦:

HAVING COUNT (CASE WHEN ClassCode NOT IN (5151) THEN 1 END)=0

如果您的 HAVING COUNT (TRUE -> 1) 中的 ClassCode 为 5000。

如果您的 HAVING COUNT (FALSE -> NULL) 中的 ClassCode 为 5151。

应该在分组前过滤:

WHERE ClassCode IN (5151) -- and check index

【讨论】:

tblClassCodesPlazaCommercial 没有 NULL ClassCode。那么,如果我按 Temp 表分解它,性能会变得更好吗?谢谢 当您拆分代码时,它会更具可读性,并且您可以快速更改执行计划 - 您将更容易获得性能问题或 qry 没有返回正确数据的信息。跨度>

以上是关于使用子查询时如何提高查询性能的主要内容,如果未能解决你的问题,请参考以下文章

优化 SQL:如何重写此查询以提高性能? (使用子查询,摆脱 GROUP BY?)

如何提高包含部分公共子查询的 SQL 查询性能

如何提高 SQL Server 查询的性能以选择具有值的行不在子查询中的一次计数

需要帮助来提高 MYSQL 子查询性能

删除相关子查询

如何证明在 SQL 中使用子选择查询会降低服务器的性能