sql-server – 嵌套循环(内部联接)成本为83%.有什么方法可以用某种方式重写它吗?

SP运行速度很慢.当我查看执行计划时 – 我可以看到其成本的83%用于嵌套循环(内部联接)

《sql-server – 嵌套循环(内部联接)成本为83%.有什么方法可以用某种方式重写它吗?》

有什么机会以某种方式替代它吗?

这是我的SP

ALTER PROCEDURE [dbo].[EarningPlazaCommercial] 
    @State      varchar(50),
    @StartDate  datetime,
    @EndDate    datetime,
    @AsOfDate   datetime,
    @ClassCode  nvarchar(max),
    @Coverage   varchar(100)
AS
BEGIN
SET NOCOUNT ON;  
CREATE TABLE #PolicyNumbers  (PolicyNumber varchar(50))
INSERT INTO #PolicyNumbers SELECT  PolicyNumber FROM tblClassCodesPlazaCommercial T1 
                                WHERE NOT EXISTS    (
                                                    SELECT 1 FROM tblClassCodesPlazaCommercial T2  
                                                    WHERE  T1.PolicyNumber = T2.PolicyNumber
                                                    AND ClassCode  IN 
                                                    (SELECT * FROM [dbo].[StringOfStringsToTable](@ClassCode,','))
                                                    )   
CREATE CLUSTERED INDEX IDX_C_PolicyNumbers_PolicyNumber ON #PolicyNumbers(PolicyNumber)

; WITH Earned_to_date AS (
   SELECT Cast(@AsOfDate AS DATE) AS Earned_to_date
), policy_data AS (
    SELECT
            PolicyNumber
,           Cast(PolicyEffectiveDate AS DATE) AS PolicyEffectiveDate
,           Cast(PolicyExpirationDate AS DATE) AS PolicyExpirationDate
,           WrittenPremium
     FROM   PlazaInsuranceWPDataSet pid
     WHERE  NOT EXISTS (SELECT PolicyNumber FROM #PolicyNumbers pn WHERE pn.PolicyNumber = pid.PolicyNumber)
            AND State IN (SELECT * FROM [dbo].[StringOfStringsToTable](@State,',')) 
            AND Coverage    IN (SELECT * FROM [dbo].[StringOfStringsToTable](@Coverage,','))        
) 

– 执行计划的一部分
《sql-server – 嵌套循环(内部联接)成本为83%.有什么方法可以用某种方式重写它吗?》

《sql-server – 嵌套循环(内部联接)成本为83%.有什么方法可以用某种方式重写它吗?》

这里我要添加我对存储过程的完整查询:

ALTER PROCEDURE [dbo].[EarningPlazaCommercial] 
    @State      varchar(50),
    @StartDate  datetime,
    @EndDate    datetime,
    @AsOfDate   datetime,
    @ClassCode  nvarchar(max),
    @Coverage   varchar(100)
AS
BEGIN
SET NOCOUNT ON;  
CREATE TABLE #PolicyNumbers  (PolicyNumber varchar(50))
INSERT INTO #PolicyNumbers SELECT  PolicyNumber FROM tblClassCodesPlazaCommercial T1 
                                WHERE NOT EXISTS    (
                                                    SELECT 1 FROM tblClassCodesPlazaCommercial T2  
                                                    WHERE  T1.PolicyNumber = T2.PolicyNumber
                                                    AND ClassCode  IN 
                                                    (SELECT * FROM [dbo].[StringOfStringsToTable](@ClassCode,','))
                                                    )   
CREATE CLUSTERED INDEX IDX_C_PolicyNumbers_PolicyNumber ON #PolicyNumbers(PolicyNumber)

; WITH Earned_to_date AS (
   SELECT Cast(@AsOfDate AS DATE) AS Earned_to_date
   --SELECT @AsOfDate 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 pid
     WHERE  NOT EXISTS (SELECT PolicyNumber FROM #PolicyNumbers pn WHERE pn.PolicyNumber = pid.PolicyNumber)
            AND State IN (SELECT * FROM [dbo].[StringOfStringsToTable](@State,',')) 
            AND Coverage    IN (SELECT * FROM [dbo].[StringOfStringsToTable](@Coverage,','))        
) 

, 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      --PolicyEffectiveDate,
            --PolicyExpirationDate,
            --PolicyNumber,
            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 @StartDate AND  @EndDate
WHERE c.YearNum Not IN (2017) --and PolicyNumber = 'PACA1000191-00'
GROUP BY    convert(varchar(7), StartRiskMonth, 120),
            Year(StartRiskMonth) , Month(StartRiskMonth),
            c.YearNum,c.MonthNum--,PolicyNumber--,PolicyEffectiveDate,PolicyExpirationDate
ORDER BY     c.YearNum,c.MonthNum
            --convert(varchar(7), StartRiskMonth, 120)
DROP TABLE #PolicyNumbers
END 
GO

来自生产链接的完整实际执行计划:

https://aligngeneral-my.sharepoint.com/personal/oserdyuk_aligngeneral_com/_layouts/15/guestaccess.aspx?guestaccesstoken=VuiFBK6zMim%2fyIh%2bNrQaOcgrg%2fpIJNKDTStt765cBfQ%3d&docid=1abc31e385da14574a930e99e22f00c7b&rev=1&expiration=2017-01-06T22%3a20%3a34.000Z

这就是我的TempDB配置方式:
《sql-server – 嵌套循环(内部联接)成本为83%.有什么方法可以用某种方式重写它吗?》

最佳答案 我认为问题出在你的“日历”子查询中.它返回10000行而没有任何索引.也许你的实际日期范围在1950年到2033年之间:

试试这个

ALTER PROCEDURE [dbo].[EarningPlazaCommercial] 
    @State      varchar(50),
    @StartDate  datetime,
    @EndDate    datetime,
    @AsOfDate   datetime,
    @ClassCode  nvarchar(max),
    @Coverage   varchar(100)
AS
BEGIN
    SET NOCOUNT ON;  

    CREATE TABLE #PolicyNumbers (PolicyNumber varchar(50))

    INSERT INTO #PolicyNumbers 
        SELECT PolicyNumber 
        FROM tblClassCodesPlazaCommercial T1 
        WHERE NOT EXISTS (SELECT 1 
                          FROM tblClassCodesPlazaCommercial T2  
                          WHERE T1.PolicyNumber = T2.PolicyNumber
                            AND ClassCode IN  (SELECT * 
                                               FROM [dbo].[StringOfStringsToTable](@ClassCode,','))
                         )   

CREATE CLUSTERED INDEX IDX_C_PolicyNumbers_PolicyNumber 
ON #PolicyNumbers(PolicyNumber)

DECLARE @Calendar TABLE (
    month_of     DATE, 
    month_after  DATE, 
    PRIMARY KEY (month_of, month_after)
);

WITH digits AS 
(
    SELECT digit
    FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) AS z2 (digit)
), numbers AS (
    SELECT 100 * d3.digit + 10 * d2.digit + d1.digit AS number
    FROM digits AS d1
    CROSS JOIN digits AS d2
    CROSS JOIN digits AS d3
), calendar AS 
(
    SELECT
        DateAdd(month, number, '1950-01-01') AS month_of,
        DateAdd(month, number, '1950-02-01') AS month_after
    FROM numbers
)
insert into @Calendar
    select * 
    from calendar

; WITH policy_data AS  
(
    SELECT
        PolicyNumber,
        Cast(PolicyEffectiveDate AS DATE) AS PolicyEffectiveDate,
        Cast(PolicyExpirationDate AS DATE) AS PolicyExpirationDate,
        WrittenPremium
        --,         State
    FROM   
        PlazaInsuranceWPDataSet pid
    WHERE 
        NOT EXISTS (SELECT PolicyNumber FROM #PolicyNumbers pn 
                    WHERE pn.PolicyNumber = pid.PolicyNumber)
        AND State IN (SELECT * FROM [dbo].[StringOfStringsToTable](@State,',')) 
        AND Coverage IN (SELECT * FROM [dbo].[StringOfStringsToTable](@Coverage,','))        
),  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 calendar ON (policy_data.PolicyEffectiveDate < calendar.month_after
                           AND calendar.month_of < policy_data.PolicyExpirationDate)
    WHERE  
        month_of < Cast(@AsOfDate AS DATE)
)
SELECT      --PolicyEffectiveDate,
            --PolicyExpirationDate,
            --PolicyNumber,
    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 @StartDate AND @EndDate
WHERE 
    c.YearNum Not IN (2017) --and PolicyNumber = 'PACA1000191-00'
GROUP BY    
    convert(varchar(7), StartRiskMonth, 120),
    Year(StartRiskMonth), Month(StartRiskMonth),
    c.YearNum, 
    c.MonthNum    --,PolicyNumber
    --,PolicyEffectiveDate,PolicyExpirationDate
ORDER BY     
    c.YearNum,c.MonthNum
    --convert(varchar(7), StartRiskMonth, 120)

DROP TABLE #PolicyNumbers
END 
GO

如果它工作,问题确实在“日历”子查询中.

修复它的想法:

>返回表的TVP仅包含策略活动月份(我已更改了最后一行).我想这将是几行

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
OUTER APPLY 
    TableFunction_ListOfMonth (PolicyEffectiveDate, PolicyExpirationDate)
WHERE  
    month_of < CAST(@AsOfDate AS DATE)

>将子查询的结果放在具有聚簇索引的表变量中

DECLARE @Calendar TABLE (
    month_of     DATE, 
    month_after  DATE, 
    PRIMARY KEY (month_of, month_after)
);

WITH digits AS (
   SELECT digit
   FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) AS z2 (digit)
), numbers AS (

SELECT 100 * d3.digit 10 * d2.digit d1.digit AS编号
    FROM数字AS d1
    CROSS JOIN数字AS d2
    CROSS JOIN数字AS d3
),日历AS(
选择
    DateAdd(月,号码,’1950-01-01′)AS month_of
,DateAdd(月,号,’1950-02-01′)AS month_after
    从数字
)
插入@Calendar
从日历中选择*

点赞