Оптимизация запроса, который использует много агрегатов - PullRequest
1 голос
/ 20 сентября 2019

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

Это был вопрос (и он содержит пример базовой таблицы) -> Есть ли способ повысить эффективность этого SQL-запроса и ускорить его выполнение?

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

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

Вот что я закончил после того, как все очистил:

BEGIN

DECLARE @LocalCompanyCode VARCHAR(5)
SET @LocalCompanyCode = '09'

DECLARE @LocalDivisionCode VARCHAR(5)
SET @LocalDivisionCode = '001'

DECLARE @LocalCustomerBaseFromDate DATETIME 
SET @LocalCustomerBaseFromDate = '1/1/2018' 

DECLARE @LocalCustomerBaseToDate DATETIME
SET @LocalCustomerBaseToDate = '9/1/2019' 

DECLARE @LocalRecurringBaseFromDate DATETIME
SET @LocalRecurringBaseFromDate = '1/1/2017'

DECLARE @LocalLifetimeBaseFromDate DATETIME 
SET @LocalLifetimeBaseFromDate = '1/1/2016' 

    SELECT 
    *
    FROM (

        SELECT 

        Email
        ,Date_Created
        ,BrandNewCustomer
        ,RecurringCustomer
        ,ReactivatedCustomer
        ,TotalOrders
        ,TotalCustomerValue
        ,TotalQuantity
        ,TotalOrdersNewBase
        ,TotalCustomerValueNewBase
        ,TotalQuantityNewBase
        ,TotalOrdersRecurringBase
        ,TotalCustomerValueRecurringBase
        ,TotalQuantityRecurringBase
        ,TotalOrdersLifetimeBase
        ,TotalCustomerValueLifetimeBase
        ,TotalQuantityLifetimeBase

        ,SUM(TotalCustomerValueNewBase)  Over () BaseCustomersTotal
        ,SUM(TotalCustomerValueRecurringBase) Over ()  RecurringCustomersTotal
        ,SUM(TotalCustomerValueLifetimeBase) Over () LifetimeCustomersTotal
        ,SUM(TotalCustomerValue) Over ()  AllCustomersTotal

        ,(dense_rank() over (order by (case when Date_Created BETWEEN @LocalCustomerBaseFromDate and DATEADD(dayofyear, 1, @LocalCustomerBaseToDate) then Email end) asc) +
         dense_rank() over (order by  (case when Date_Created BETWEEN @LocalCustomerBaseFromDate and DATEADD(dayofyear, 1, @LocalCustomerBaseToDate) then Email end) desc) - 1
        ) as TotalCustomersOverCustomerBase

        ,(dense_rank() over (order by (case when Date_Created BETWEEN @LocalRecurringBaseFromDate and @LocalCustomerBaseFromDate then Email end) asc) +
         dense_rank() over (order by  (case when Date_Created BETWEEN @LocalRecurringBaseFromDate and @LocalCustomerBaseFromDate then Email end) desc) - 1
        ) as TotalCustomersOverRecurringBase

        ,(dense_rank() over (order by (case when Date_Created BETWEEN @LocalLifetimeBaseFromDate and @LocalRecurringBaseFromDate then Email end) asc) +
         dense_rank() over (order by  (case when Date_Created BETWEEN @LocalLifetimeBaseFromDate and @LocalRecurringBaseFromDate then Email end) desc) - 1
        ) as TotalCustomersOverLifetimeBase

        ,(DENSE_RANK() over (order by Email asc) 
        +DENSE_RANK() over ( order by Email desc) 
        - 1) as TotalCustomersOverBase

    ,SUM( CASE WHEN (BrandNewCustomer + RecurringCustomer + ReactivatedCustomer) = 1 THEN 1 ELSE 0 END) over () KeptCustomers
    ,SUM( CASE WHEN (BrandNewCustomer + RecurringCustomer + ReactivatedCustomer) = 0 THEN 1 ELSE 0 END) over () LostCustomers

        FROM (
            SELECT 

            T.Email

            ,MAX(T.Date_Created) Date_Created

            ,COUNT(*) TotalOrders
            ,SUM(T.Order_Sell_price) TotalCustomerValue
            ,SUM(T.Quantity_Ordered) TotalQuantity

            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalCustomerBaseFromDate and DATEADD(dayofyear, 1, @LocalCustomerBaseToDate) THEN 1 ELSE 0 END)  TotalOrdersNewBase
            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalCustomerBaseFromDate and DATEADD(dayofyear, 1, @LocalCustomerBaseToDate) THEN Order_Sell_price ELSE 0 END)   TotalCustomerValueNewBase
            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalCustomerBaseFromDate and DATEADD(dayofyear, 1, @LocalCustomerBaseToDate) THEN Quantity_Ordered ELSE 0 END)   TotalQuantityNewBase

            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalRecurringBaseFromDate and @LocalCustomerBaseFromDate THEN 1 ELSE 0 END)  TotalOrdersRecurringBase
            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalRecurringBaseFromDate and @LocalCustomerBaseFromDate THEN Order_Sell_price ELSE 0 END)   TotalCustomerValueRecurringBase
            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalRecurringBaseFromDate and @LocalCustomerBaseFromDate THEN Quantity_Ordered ELSE 0 END)   TotalQuantityRecurringBase

            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalLifetimeBaseFromDate and @LocalRecurringBaseFromDate THEN 1 ELSE 0 END)  TotalOrdersLifetimeBase
            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalLifetimeBaseFromDate and @LocalRecurringBaseFromDate THEN Order_Sell_price ELSE 0 END)   TotalCustomerValueLifetimeBase
            ,SUM(CASE WHEN T.Date_Created BETWEEN @LocalLifetimeBaseFromDate and @LocalRecurringBaseFromDate THEN Quantity_Ordered ELSE 0 END)   TotalQuantityLifetimeBase

            ,CASE WHEN  
                (   ISNULL(SUM(CASE WHEN T.Date_Created BETWEEN @LocalCustomerBaseFromDate and DATEADD(dayofyear, 1, @LocalCustomerBaseToDate) THEN 1 ELSE 0 END),0) >= 1
                AND ISNULL(SUM(CASE WHEN T.Date_Created BETWEEN @LocalRecurringBaseFromDate and @LocalCustomerBaseFromDate THEN 1 ELSE 0 END),0) = 0
                AND ISNULL(SUM(CASE WHEN T.Date_Created BETWEEN @LocalLifetimeBaseFromDate and @LocalRecurringBaseFromDate THEN Quantity_Ordered ELSE 0 END),0) = 0) 
            THEN 1 ELSE 0 END BrandNewCustomer

            ,CASE WHEN  
                (   ISNULL(SUM(CASE WHEN T.Date_Created BETWEEN @LocalCustomerBaseFromDate and DATEADD(dayofyear, 1, @LocalCustomerBaseToDate) THEN 1 ELSE 0 END),0)  >= 1 
                AND ISNULL(SUM(CASE WHEN T.Date_Created BETWEEN @LocalRecurringBaseFromDate and @LocalCustomerBaseFromDate THEN 1 ELSE 0 END),0) >= 1)
            THEN 1 ELSE 0 END RecurringCustomer

            ,CASE WHEN  
                (   ISNULL(SUM(CASE WHEN T.Date_Created BETWEEN @LocalCustomerBaseFromDate and DATEADD(dayofyear, 1, @LocalCustomerBaseToDate) THEN 1 ELSE 0 END),0) >= 1 
                AND ISNULL(SUM(CASE WHEN T.Date_Created BETWEEN @LocalRecurringBaseFromDate and @LocalCustomerBaseFromDate THEN 1 ELSE 0 END),0) = 0
                AND ISNULL(SUM(CASE WHEN T.Date_Created BETWEEN @LocalLifetimeBaseFromDate and @LocalRecurringBaseFromDate THEN Quantity_Ordered ELSE 0 END),0) >= 1) 
            THEN 1 ELSE 0 END ReactivatedCustomer

            FROM (
                SELECT 

                F.Email
                ,F.Coal_Date Date_Created
                ,Month(F.Coal_Date) Month
                ,Year(F.Coal_Date) Year
                ,F.Customer_Purchase_Order_Number
                ,F.Order_Status
                ,Row_Number() over (Partition by Email order by Coal_Date asc) OrderCount
                ,F.Order_Sell_price
                ,F.Order_Quantity_Ordered Quantity_Ordered

                FROM
                    FinalEcomTable F

                WHERE
                    1=1
                    AND (F.Company_Code = @LocalCompanyCode OR @LocalCompanyCode IS NULL)  
                    AND (F.Division_Code = @LocalDivisionCode OR @LocalDivisionCode IS NULL)
                    AND F.Coal_Date BETWEEN @LocalLifetimeBaseFromDate AND DATEADD(dayofyear, 1, @LocalCustomerBaseToDate)
                    AND F.Order_Status <> 'CANCELLED'
                    AND F.Odet_Line_Number = 1
            ) T

            GROUP BY T.Email

        ) TT

    ) TTT

    WHERE (BrandNewCustomer + RecurringCustomer + ReactivatedCustomer) = 1
    ORDER BY email DESC

END

И вот полный план выполнения:

https://www.brentozar.com/pastetheplan/?id=SkDyXPfDH

Эти 4 строки dens_rank сами по себе почти удваивают время выполнения.Теперь я понимаю, что обычно это делают только такие типы строк.

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

Таким образом, самый внутренний запрос принимает каждое электронное письмо клиента, а затем по каждому электронному письму получает каждый заказ и какой это номер заказа (первый, второй и т. Д.).Это проблема, которую я делаю Over () для моего агрегата Row_Number?Я хотел бы сгруппировать по электронной почте, но тогда как мне получить каждый фактический номер заказа?Потому что, если я группирую электронную почту, я должен сделать что-то вроде Max () для каждого номера заказа, но мне нужен фактический номер заказа.Есть какой-либо способ сделать это?

Затем следующий внешний запрос получает эти данные и фактически группирует их по электронной почте.По этой группе я вычисляю итоги за определенные диапазоны дат.Идея этого отчета заключается в том, что существует три диапазона дат.Новый, на котором основан фактический отчет, и этот диапазон примерно такой же, как за последние 3 месяца.Я смотрю на всех клиентов за последние три месяца, а затем вижу, есть ли у них какие-либо покупки в повторяющемся диапазоне дат (за год до трех месяцев) или в диапазоне дат жизни.Затем я определяю, являются ли они новым клиентом или постоянным клиентом (или повторно активированным), просто поставив цифру 1, на какой тип клиента он идет.

Затем мой последний внешний запрос берет эти данные и вычисляет общие итоги, такие как Общая стоимость всех моих постоянных клиентов или общее количество сохраненных и потерянных клиентов и так далее.И, как я уже упоминал ранее, заластные шутки действительно меня здесь сбивают.Мне кажется, что если бы я мог добавить Group By к этому последнему запросу, я мог бы решить многие проблемы с производительностью, но я не могу понять, как его интегрировать.

В настоящее время этот запрос выполняется около 10-15 секунд, и я чувствую, что он может быть мгновенным.Я попытался создать индекс, предложенный планом выполнения, но он ничего не сделал.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...