Оптимизация сложного представления с использованием нескольких CTE - PullRequest
0 голосов
/ 17 сентября 2018

Я ищу некоторые рекомендации по поводу любых явных ошибок, которые я допускаю в отношении производительности для этого представления, поскольку в настоящее время требуется более 1 м 46 с, чтобы запросить представление, и более 3 м, если я добавлю предложение WHERE к периоду и году.

Цель представления состоит в том, чтобы собрать финансовую информацию за несколько недель и сгруппировать эту информацию по периоду и году.Затем это представление используется во всей финансовой системе для создания отчетов и создания заказов в других финансовых системах.

Запрос был слишком длинным, чтобы я мог опубликовать его здесь, поэтому я предоставил ссылку на запрос и могу предоставить любойинформация о таблицах по запросу: 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-м годам.

Ответы [ 2 ]

0 голосов
/ 20 сентября 2018

Если все ваши запросы CTE выполняются на приемлемом уровне, но весь оператор не (общий), то вы должны преобразовать CTE в временные значения, например:

--WITH
--ctePeriodWeekCount AS (...

--becomes

SELECT Count(Week.WeekNo) AS WeekCount, PrdNo AS Period, PrdYear AS Year
INTO #ctePeriodWeekCount FROM Week
INNER JOIN Period on Week.WeekEndDate BETWEEN Period.StartDate AND Period.EndDate
GROUP BY PrdNo, PrdYear

Затем следуйте порядку CTE, создав и используясоответствующая временная таблица (просто добавьте # перед именем).Не забудьте сбросить temp при последнем использовании, чтобы освободить ресурсы как можно скорее.Наконец, если фильтр используется в конце, убедитесь, что вы используете его в инструкции SELECT для временного создания.

Даже если конечный CTE работает близко к отдельным, хорошо для производительности использовать временные параметры - когда произойдут будущие измененияс меньшей вероятностью он столкнется с проблемами производительности.

0 голосов
/ 17 сентября 2018

Согласно комментарию Тима, представление (не материализованное) работает только так же, как и базовые запросы.Поэтому сначала проанализируйте запросы для оптимизации, например, добавьте некластеризованные индексы для часто используемых столбцов (в соединениях или в качестве внешних ключей).

Кроме этого, вы также можете посмотреть на Индексированные представления для дальнейшего увеличения производительности.Но есть и некоторые уловки для индексных представлений, так как они не являются надежным способом повысить производительность представления, например:

  1. При выполнении DML для таблицы, на которую ссылается большое количество проиндексированных представлений, или меньше, ноочень сложные индексированные представления, эти ссылочные индексированные представления также должны быть обновлены.В результате производительность запросов DML может значительно снизиться, а в некоторых случаях план запроса даже не может быть создан.В таких сценариях тестируйте свои запросы DML перед производственным использованием, анализируйте план запроса и настраивайте / упрощайте инструкцию DML, например операции UPDATE, DELETE или INSERT.
  2. Стоимость индексированного представления заключается в обслуживаниикластеризованный индекс (и любые некластеризованные индексы, которые вы можете добавить).Нужно взвесить затраты на поддержание индекса и преимущества оптимизации запросов, предоставляемой индексом.

Для получения дополнительной информации см .:

https://docs.microsoft.com/en-us/sql/relational-databases/views/create-indexed-views?view=sql-server-2017

https://www.red -gate.com / simple-talk / sql / learn-sql-server / sql-server-indexed-views-the-basics /

https://www.codeproject.com/Articles/199058/SQL-Server-Indexed-Views-Speed-Up-Your-Select-Quer

...