【发布时间】:2018-09-17 11:09:22
【问题描述】:
我正在寻找有关我在此视图的性能方面所犯的任何明显错误的一些指导,因为它目前需要超过 1m 46s 来查询视图,如果我添加 WHERE 子句来反对期间和年份,则需要超过 3m .
视图的目的是整理几周的财务信息,并按期间和年份对这些信息进行分组。然后在整个财务系统中引用此视图,以便向其他财务系统报告和生成订单。
查询太长,我无法在此处发布,因此我提供了查询链接,并可应要求提供有关表格的任何信息: https://pastebin.com/x8dV8KLJ
以下只是查询的公用表表达式,因为 StackOverflow 坚持我发布一些代码来附上链接:
ALTER VIEW [dbo].[UnitFinancialFigures] AS
WITH
ctePeriodWeekCount AS (
SELECT Count(Week.WeekNo) AS WeekCount, PrdNo AS Period, PrdYear AS Year
FROM Week
INNER JOIN Period on Week.WeekEndDate BETWEEN Period.StartDate AND Period.EndDate
GROUP BY PrdNo, PrdYear),
cteLabourFigures AS (
SELECT Labour.LabUnitId, Labour.LabPeriod, Labour.LabYear,
SUM(CASE WHEN LabourTypes.LTypeIsPayrollLabour = 1 THEN LabourBreakdown.LBrkAmount ELSE 0 END) AS PayRollLabour,
SUM(CASE WHEN LabourTypes.LTypeIsPayrollLabour = 0 THEN LabourBreakdown.LBrkAmount ELSE 0 END) AS OtherLabour
FROM Labour
INNER JOIN LabourBreakdown on LabourBreakDown.LBrkLabId = Labour.LabId
INNER JOIN LabourTypes on LabourTypes.LTypeId = LabourBreakdown.LBrkLTypeId
GROUP BY Labour.LabUnitId, Labour.LabPeriod, Labour.LabYear),
cteMobileRelief AS (
SELECT McDtUnitId, McHdPeriod, McHdYear, SUM(McDtAmount) AS MobileReliefTotal
FROM MobileCostHeader
INNER JOIN MobileCostDetail on MobileCostDetail.McDtHdId = MobileCostHeader.MCHdId
GROUP BY McDtUnitId, McHdPeriod, McHdYear),
cteInvoices AS (
SELECT Period.PrdNo, Period.PrdYear, Invoice.AccountID, SUM(Invoice.InvTotalInclVat) AS TotalInvoices,
SUM(Invoice.InvVatAmount) AS TotalInvoicesVat
FROM [SOP].Invoice
INNER JOIN Period on Invoice.InvoiceDate BETWEEN Period.StartDate AND Period.EndDate
GROUP By Period.PrdNo, Period.PrdYear, Invoice.AccountID),
cteSundryCosts AS (
SELECT ScDtAccountId, ScDtPeriodNo, ScDtYearNo, SUM(ScdtTotal) AS SundryTotal,
SUM(ScdtAmount) AS SundryNetTotal,
SUM(ScdtVatAmount) AS SundryVatAmount
FROM SundryCostDetail
WHERE ScdtRepeat = 0
GROUP BY ScDtAccountId, ScDtPeriodNo, ScDtYearNo),
cteAdditionalProfit AS (
SELECT ScDtAccountId, ScDtPeriodNo, ScDtYearNo, SUM(ScdtTotal) AS AdditionalProfitTotal,
SUM(ScdtAmount) AS AdditionalProfitNetTotal,
SUM(ScdtVatAmount) AS AdditionalProfitVatAmount
FROM SundryCostDetail
WHERE ScdtRepeat = 0 AND SCdtExcludeFromGP = 0
GROUP BY ScDtAccountId, ScDtPeriodNo, ScDtYearNo),
cteCashReceived AS (
SELECT CtUnitId, CtPeriod, CtYear,
SUM(CtAmount - CtAgentVatAmount) AS GoodsAmount, SUM(CtVatAmount + CtAgentVatAmount) AS VatTotal, SUM(CtTotal) AS Total,
SUM(VATFree.CanaAmount - VATFree.CanaAgentVatAmount) AS VATFreeGoodsAmount,
SUM(Catering.CanaAmount - Catering.CanaAgentVatAmount) AS CateringGoodsAmount,
SUM(Vending.CanaAmount - Vending.CanaAgentVatAmount) AS VendingGoodsAmount,
SUM(CansConf.CanaAmount - CansConf.CanaAgentVatAmount) AS CansConfGoodsAmount
FROM CashTran
LEFT JOIN CashAnalysis AS VATFree ON CashTran.CtId = VATFree.CanaTranId AND VATFree.CanaCode = 'G201'
LEFT JOIN CashAnalysis AS Catering ON CashTran.CtId = Catering.CanaTranId AND Catering.CanaCode = 'G205'
LEFT JOIN CashAnalysis AS Vending ON CashTran.CtId = Vending.CanaTranId AND Vending.CanaCode = 'G202'
LEFT JOIN CashAnalysis AS CansConf ON CashTran.CtId = CansConf.CanaTranId AND CansConf.CanaCode = 'G203'
WHERE CtCategoryId = 3 AND CtApproved = 1 ANd CtTranType = 'R'
GROUP BY CtUnitId, CtPeriod, CtYear),
cteFreeIssues AS (
SELECT CtUnitId, CtPeriod, CtYear,
SUM(CtAmount - CtAgentVatAmount) AS GoodsAmount, SUM(CtVatAmount + CtAgentVatAmount) AS VatTotal, SUM(CtTotal) AS Total,
SUM(Catering.CanaAmount - Catering.CanaAgentVatAmount) AS CateringGoodsAmount,
SUM(Vending.CanaAmount - Vending.CanaAgentVatAmount) AS VendingGoodsAmount,
SUM(CansConf.CanaAmount - CansConf.CanaAgentVatAmount) AS CansConfGoodsAmount,
SUM(Labour.CanaAmount - Labour.CanaAgentVatAmount) AS LabourGoodsAmount
FROM CashTran
INNER JOIN [SOP].Accounts ON Accounts.AcUniqueId = CtUnitId
LEFT JOIN CashAnalysis AS Catering ON CashTran.CtId = Catering.CanaTranId AND Catering.CanaCode = 'G204'
LEFT JOIN CashAnalysis AS Vending ON CashTran.CtId = Vending.CanaTranId AND Vending.CanaCode = 'G202'
LEFT JOIN CashAnalysis AS CansConf ON CashTran.CtId = CansConf.CanaTranId AND CansConf.CanaCode = 'G203'
LEFT JOIN CashAnalysis AS Labour ON CashTran.CtId = Labour.CanaTranId AND Labour.CanaCode = 'H995'
WHERE CtCategoryId = 2
GROUP BY CtUnitId, CtPeriod, CtYear),
cteExternalFreeIssues AS (
SELECT CtUnitId, CtPeriod, CtYear, SUM(CtAmount - CtAgentVatAmount) AS GoodsAmount, SUM(CtVatAmount + CtAgentVatAmount) AS VatTotal, SUM(CtTotal) AS Total
FROM CashTran
INNER JOIN [SOP].Accounts on Accounts.AcUniqueId = CtUnitId
WHERE CtCategoryId = 4
GROUP BY CtUnitId, CtPeriod, CtYear),
cteAgencyLabour AS (
SELECT PtUnitId, PtYear, PtPeriod, SUM(CASE WHEN PtTranType = 'I' THEN PanaGoodsAmount - PanaAgentVatAmount ELSE -(PanaGoodsAmount - PanaAgentVatAmount) END) Total
FROM PurchaseTran
INNER Join PurchaseAnalysis on PurchaseAnalysis.PanaTranId = PurchaseTran.PtId
INNER JOIN [SOP].Accounts on Accounts.AcUniqueID = PtUnitId
INNER JOIN UnitAnalysisConfig on UnitAnalysisConfig.UACfgAnalysisCode = PurchaseAnalysis.PanaPCode AND UnitAnalysisConfig.UACfgType = 1
WHERE PurchaseAnalysis.PanaPCode = 'H995' AND (PtTranType = 'I' Or PtTranType = 'C') AND PtApproved =1
GROUP BY PtUnitId, PtPeriod, PtYear),
ctePurchases AS (
SELECT PurchaseTran.PtUnitId, PurchaseTran.PtPeriod, PurchaseTran.PtYear,
SUM(CASE WHEN PtTranType = 'I' THEN PtTotal WHEN PtTranType = 'C' THEN - PtTotal ELSE 0 END) AS PurchaseTotal,
SUM(CASE WHEN PtTranType = 'I' THEN PtAmount - PtAgentVatAmount WHEN PtTranType = 'C' THEN -(PtAmount - PtAgentVatAmount) ELSE 0 END) AS PurchaseNetTotal,
SUM(CASE WHEN PtTranType = 'I' THEN PtVat + PtAgentVatAmount WHEN PtTranType = 'C' THEN -(PtVat + PtAgentVatAmount) ELSE 0 END) AS VatTotal,
SUM(CASE WHEN PtSupplierId = CmpCashAccount THEN PtTotal ELSE 0 END) AS TotalCashPaid,
SUM(CASE WHEN PtTranType = 'I' THEN Catering.PanaGoodsAmount - Catering.PanaAgentVatAmount WHEN PtTranType = 'C' THEN -(Catering.PanaGoodsAmount - Catering.PanaAgentVatAmount) ELSE 0 END) CateringTotal,
SUM(CASE WHEN PtTranType = 'I' THEN Catering.PanaAgentVatAmount + Catering.PanaVatAmount WHEN PtTranType = 'C' THEN -(Catering.PanaAgentVatAmount + Catering.PanaVatAmount) ELSE 0 END) AS CateringVatAmount,
SUM(CASE WHEN PtTranType = 'I' THEN Vending.PanaGoodsAmount - Vending.PanaAgentVatAmount WHEN PtTranType = 'C' THEN -(Vending.PanaGoodsAmount - Vending.PanaAgentVatAmount) ELSE 0 END) VendingTotal,
SUM(CASE WHEN PtTranType = 'I' THEN Vending.PanaAgentVatAmount + Vending.PanaVatAmount WHEN PtTranType = 'C' THEN -(Vending.PanaAgentVatAmount + Vending.PanaVatAmount) ELSE 0 END) AS VendingVatAmount,
SUM(CASE WHEN PtTranType = 'I' THEN CansConf.PanaGoodsAmount - CansConf.PanaAgentVatAmount WHEN PtTranType = 'C' THEN -(CansConf.PanaGoodsAmount - CansConf.PanaAgentVatAmount) ELSE 0 END) CansConfTotal,
SUM(CASE WHEN PtTranType = 'I' THEN CansConf.PanaAgentVatAmount + CansConf.PanaVatAmount WHEN PtTranType = 'C' THEN -(CansConf.PanaAgentVatAmount + CansConf.PanaVatAmount) ELSE 0 END) AS CansConfVatAmount
FROM PurchaseTran
INNER JOIN CompParam on Compparam.CmpDefault = 1
INNER JOIN [SOP].Accounts on Accounts.AcUniqueID = PurchaseTran.PtUnitId
LEFT JOIN PurchaseAnalysis AS Catering on Catering.PanaTranId = PurchaseTran.PtId AND (Catering.PanaPCode = 'H201' OR Catering.PanaPCode = 'H401')
LEFT JOIN PurchaseAnalysis AS Vending on Vending.PanaTranId = PurchaseTran.PtId AND Vending.PanaPCode = 'H202'
LEFT JOIN PurchaseAnalysis AS CansConf on CansConf.PanaTranId = PurchaseTran.PtId AND CansConf.PanaPCode = 'H211'
WHERE (PtTranType = 'I' OR PtTranType = 'C') AND PtApproved = 1
GROUP BY PtUnitId, PtPeriod, PtYear),
cteSundryPurchases AS (
SELECT PtUnitId, PtYear, PtPeriod,
SUM(CASE WHEN PtTranType = 'I' THEN PanaGoodsAmount - PanaAgentVatAmount ELSE -(PanaGoodsAmount - PanaAgentVatAmount) END) Total,
SUM(CASE WHEN PtTranType = 'I' THEN PanaAgentVatAmount + PanaVatAmount WHEN PtTranType = 'C' THEN -(PanaAgentVatAmount + PanaVatAmount) ELSE 0 END) AS VatAmount
FROM PurchaseTran
INNER Join PurchaseAnalysis on PurchaseAnalysis.PanaTranId = PurchaseTran.PtId
INNER JOIN [SOP].Accounts on Accounts.AcUniqueID = PurchaseTran.PtUnitId
INNER JOIN UnitAnalysisConfig on UnitAnalysisConfig.UACfgAnalysisCode = PurchaseAnalysis.PanaPCode AND (UnitAnalysisConfig.UACfgType = 1 OR UnitAnalysisConfig.UACfgType = 3)
WHere UnitAnalysisConfig.UACfgTandOLabel = 'Sundry' AND (PtTranType = 'I' Or PtTranType = 'C') AND PtApproved = 1
GROUP BY PtUnitId, PtPeriod, PtYear),
cteDiscount AS (
SELECT PurchaseTran.PtUnitId, SUM(CASE WHEN PtTRanType = 'I' THEN PtDiscountAmount ELSE -PtDiscountAmount END) AS DiscountTotal, PtPeriod, PtYear
FROM PurchaseTran
WHERE PurchaseTran.PtApproved = 1 And (PurchaseTran.PtTranType = 'I' OR PurchaseTran.PtTranType = 'C')
GROUP BY PurchaseTran.PtUnitId, PtPeriod, PtYear),
cteUnitPeriodOpeningStock AS (
SELECT UStkId, UStkOpening, UnitStock.UStkUnitId, UnitStock.UStkPeriod, UnitStock.UStkYearNo, SUM(OpeningCatering.USAnaAmount) AS OpeningCateringStock, SUM(OpeningBeverage.USAnaAmount) AS OpeningBeverageStock, SUM(OpeningSundry.USAnaAmount) AS OpeningSundryStock, SUM(OpeningCansConf.USAnaAmount) AS OpeningCansConfStock
FROM UnitStock
LEFT JOIN UnitStockAnalysis AS OpeningCatering ON UnitStock.UStkId = OpeningCatering.USAnaTranId AND OpeningCatering.USanaType = 'O' AND OpeningCatering.USAnaCode = 'CAT001'
LEFT JOIN UnitStockAnalysis AS OpeningBeverage ON UnitStock.UStkId = OpeningBeverage.USAnaTranId AND OpeningBeverage.USanaType = 'O' AND OpeningBeverage.USAnaCode = 'BEV001'
LEFT JOIN UnitStockAnalysis AS OpeningSundry ON UnitStock.UStkId = OpeningSundry.USAnaTranId AND OpeningSundry.USanaType = 'O' AND OpeningSundry.USAnaCode = 'SUN001'
LEFT JOIN UnitStockAnalysis AS OpeningCansConf ON UnitStock.UStkId = OpeningCansConf.USAnaTranId AND OpeningCansConf.USanaType = 'O' AND OpeningCansConf.USAnaCode = 'CAN001'
INNER JOIN Week on Week.WeekNo = UnitStock.UStkWeeKNo AND Week.WeekYear = UnitStock.UStkYearNo
INNER JOIN (
SELECT UStkUnitId, UStkPeriod, MIN(Week.WeekEndDate) OpeningDate
FROM UnitStock
INNER JOIN Week on Week.WeekNo = UnitStock.UStkWeeKNo AND Week.WeekYear = UnitStock.UStkYearNo
GROUP BY UStkUnitId, UStkPeriod) OpeningData on OpeningData.OpeningDate = Week.WeekEndDate AND OpeningData.UStkUnitId = UnitStock.UStkUnitId
GROUP BY UStkId, UStkOpening, UnitStock.UStkUnitId, UnitStock.UStkPeriod, UnitStock.UStkYearNo),
cteUnitPeriodClosingStock AS (
SELECT UStkId, UStkClosing, UnitStock.UStkUnitId, UnitStock.UStkPeriod, UnitStock.UStkYearNo, SUM(ClosingCatering.USAnaAmount) AS ClosingCateringStock, SUM(ClosingBeverage.USAnaAmount) AS ClosingBeverageStock, SUM(ClosingSundry.USAnaAmount) AS ClosingSundryStock, SUM(ClosingCansConf.USAnaAmount) AS ClosingCansConfStock
FROM UnitStock
LEFT JOIN UnitStockAnalysis AS ClosingCatering ON UnitStock.UStkId = ClosingCatering.USAnaTranId AND ClosingCatering.USanaType = 'C' AND ClosingCatering.USAnaCode = 'CAT001'
LEFT JOIN UnitStockAnalysis AS ClosingBeverage ON UnitStock.UStkId = ClosingBeverage.USAnaTranId AND ClosingBeverage.USanaType = 'C' AND ClosingBeverage.USAnaCode = 'BEV001'
LEFT JOIN UnitStockAnalysis AS ClosingSundry ON UnitStock.UStkId = ClosingSundry.USAnaTranId AND ClosingSundry.USanaType = 'C' AND ClosingSundry.USAnaCode = 'SUN001'
LEFT JOIN UnitStockAnalysis AS ClosingCansConf ON UnitStock.UStkId = ClosingCansConf.USAnaTranId AND ClosingCansConf.USanaType = 'C' AND ClosingCansConf.USAnaCode = 'CAN001'
INNER JOIN Week on Week.WeekNo = UnitStock.UStkWeeKNo AND Week.WeekYear = UnitStock.UStkYearNo
INNER JOIN (
SELECT UStkUnitId, UStkPeriod, MAX(Week.WeekEndDate) ClosingDate
FROM UnitStock
INNER JOIN Week on Week.WeekNo = UnitStock.UStkWeeKNo AND Week.WeekYear = UnitStock.UStkYearNo
GROUP BY UStkUnitId, UStkPeriod) ClosingData on ClosingData.ClosingDate = Week.WeekEndDate AND ClosingData.UStkUnitId = UnitStock.UStkUnitId
GROUP BY UStkId, UStkClosing, UnitStock.UStkUnitId, UnitStock.UStkPeriod, UnitStock.UStkYearNo)
回复后编辑 感谢您的反馈。我遵循了这些建议,并分别优化了每个 CTE 查询。我的错误是期望执行计划告诉我在运行视图时是否缺少建议的索引;直到我分解了查询,我才真正得到了任何建议。知道我没有做任何明显损害性能的明显事情也很有帮助。添加各种索引后,我得到了大约 30 秒的视图。
【问题讨论】:
-
您的查询看起来太大,不可能在这里得到答案,但作为一般性评论,我会说(非物化)视图的性能与基础查询一样好。因此,如果您想加快查看速度,则需要调整底层查询。
-
很多 CTE 看起来都是多余的,因为它们查询的是相同的基表,但条件略有不同。优化器将尝试尽可能智能地查询它,但它不会主动合并具有不同条件的查询。考虑是否可以将其中一些条件粘贴在列中 (
CASE),然后稍后按它们查询或分组。 -
另外,您在这里获得的表、连接和条件的绝对数量表明实际上还有另一个数据库正在努力摆脱这个数据库,或者至少是另一种形式的索引。考虑具有列存储索引的(新的或现有的)表或 Analysis Services 数据库,甚至可能是单独维护的仓库数据库,以提前计算和存储这些派生表。虽然这是多余的,但它是一种有用的冗余(结果永远不会被独立修改),而不是邪恶的那种。
-
另外一个参与者也不喜欢long reads——sql server 的查询优化器。当一个语句那么大(并且整个
view是一个语句)时,它可能无法找到执行它的最佳方式。这个看起来像一个大报告,作为一个有几个@或#的大 SP 可能会更好。一个带有小语句的SP,可以很容易地被sql server的优化器分析,被开发者理解、调试和重构。这再次引出了蒂姆关于“潜在查询”的第一条评论。
标签: sql-server tsql optimization common-table-expression