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

1
Lewis Hamill 17 Сен 2018 в 14:09

2 ответа

Лучший ответ

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

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

  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

1
Pranav Singh 17 Сен 2018 в 11:59

Если все ваши запросы 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, создав и используйте соответствующую временную таблицу (просто добавьте # перед именем). Не забудьте сбросить временные файлы при их последнем использовании, чтобы высвободить ресурсы как можно скорее. Наконец, если фильтр используется в конце, убедитесь, что вы используете его при создании временного оператора SELECT.

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

0
Stefanos Zilellis 20 Сен 2018 в 12:28