使用子查询时如何提高查询性能
Posted
技术标签:
【中文标题】使用子查询时如何提高查询性能【英文标题】:How to improve query performance when using sub-query 【发布时间】:2016-11-18 23:01:59 【问题描述】:查询的第一部分获取Policy
的Premiums
、Effective
和Expiration
日期。
第二部分创建Calendar
,第三部分(最终SELECT
声明)返回按Month
和Year
细分的收益
一切正常,只需 3 秒即可显示结果。
但是然后我需要过滤PolicyNumber
s 可以使用的内容,基本上我需要摆脱没有@ClassCode
的PolicyNumber
。因此,在查询的第一部分中,我放置了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?)