Оптимизация запроса T- SQL с использованием пока l oop и перекрестного применения - PullRequest
0 голосов
/ 22 января 2020

В поисках оптимизации запроса T- SQL, написанного для расчета скользящей суммы дохода 12M. Основная проблема заключается в том, что мне нужно рассчитать его по 6 измерениям, в результате чего получается более 45 000 комбинаций, каждая из которых требует собственного скользящего дохода. Каждая комбинация занимает около 5 минут для расчета. Из-за специфики бизнеса комбинации будут расти со временем.

Все действия необходимо выполнить как подзапрос.

DECLARE @NUMBER INT
DECLARE @ROWCOUNT INT = 0
DECLARE @REFERENCE VARCHAR(50)

SET @NUMBER =
    (SELECT MAX(r.Row)
        FROM (
            SELECT 
                a.GroupKey
                ,a.LevelKey
                ,a.StateKey
                ,a.ProductKey
                ,a.OptionKey
                ,a.LocKey
                ,ROW_NUMBER() OVER(ORDER BY a.GroupKey ASC) AS Row
            FROM Members AS a
            GROUP BY 
                a.GroupKey
                ,a.LevelKey
                ,a.StateKey
                ,a.ProductKey
                ,a.OptionKey
                ,a.LocKey ) r)
BEGIN
DECLARE @OUTPUT TABLE (
        RefKey VARCHAR(50)
        ,DateID INT
        ,GroupKey INT
        ,LevelKey INT
        ,StateKey INT
        ,ProductKey INT
        ,OptionKey INT
        ,LocKey INT
        ,Revenue DECIMAL(28,9)
        ,Rolling12Months DECIMAL(28,9)
         )

WHILE @ROWCOUNT < @NUMBER

BEGIN
SET @REFERENCE =
    (SELECT r.RefKey
        FROM (
            SELECT
                r.Refkey
                ,r.Row
                FROM (          
                    SELECT 
                        CONCAT( a.GroupKey, '-',a.LevelKey, '-',a.StateKey, '-',a.ProductKey, '-',a.OptionKey, '-',a.LocKey) AS RefKey
                        ,ROW_NUMBER() OVER(ORDER BY a.GroupKey ASC) AS Row
                    FROM Members AS a
                    GROUP BY 
                        a.GroupKey
                        ,a.LevelKey
                        ,a.StateKey
                        ,a.ProductKey
                        ,a.OptionKey
                        ,a.LocKey ) r) r WHERE r.Row = @ROWCOUNT + 1 )

INSERT INTO @OUTPUT         
SELECT
    a.RefKey
    ,a.DateID
    ,a.GroupKey
    ,a.LevelKey
    ,a.StateKey
    ,a.ProductKey
    ,a.OptionKey
    ,a.LocKey
    ,a.Revenue
    ,Rolling12Months=a.Revenue + b.RM 

FROM (

        SELECT
            CONVERT(datetime,STR(CI.DateID)) AS Date
            ,CI.DateID
            ,CI.GroupKey
            ,CI.LevelKey
            ,CI.StateKey
            ,CI.ProductKey
            ,CI.OptionKey
            ,CI.LocKey
            ,CONCAT( CI.GroupKey, '-',CI.LevelKey, '-',CI.StateKey, '-',CI.ProductKey, '-',CI.OptionKey, '-',CI.LocKey) AS RefKey
            ,SUM(CI.Revenue) AS Revenue
        FROM Members AS CI
        WHERE CONCAT( CI.GroupKey, '-',CI.LevelKey, '-',CI.StateKey, '-',CI.ProductKey, '-',CI.OptionKey, '-',CI.LocKey) = @REFERENCE
        GROUP BY CI.DateID,CI.StateKey,CI.GroupKey  ,CI.LevelKey,CI.StateKey,CI.ProductKey,CI.OptionKey ,CI.LocKey

        ) a
CROSS APPLY
( 
    SELECT RM=SUM(b.Revenue)
    FROM
    (
        SELECT TOP 11 b.Date, b.Revenue 
        FROM (

                SELECT
                    CONVERT(datetime,STR(CI.DateID)) AS Date
                    ,CI.DateID
                    ,CI.GroupKey
                    ,CI.LevelKey
                    ,CI.StateKey
                    ,CI.ProductKey
                    ,CI.OptionKey
                    ,CI.LocKey
                    ,CONCAT( CI.GroupKey, '-',CI.LevelKey, '-',CI.StateKey, '-',CI.ProductKey, '-',CI.OptionKey, '-',CI.LocKey) AS RefKey
                    ,SUM(CI.Revenue) AS Revenue
                FROM Members AS CI
                WHERE CONCAT( CI.GroupKey, '-',CI.LevelKey, '-',CI.StateKey, '-',CI.ProductKey, '-',CI.OptionKey, '-',CI.LocKey) = @REFERENCE
                GROUP BY CI.DateID,CI.StateKey,CI.GroupKey  ,CI.LevelKey,CI.StateKey,CI.ProductKey,CI.OptionKey ,CI.LocKey

        ) b
        WHERE b.Date < a.Date AND b.RefKey = a.RefKey
        ORDER BY b.Date DESC
    ) b
) b

    SET @ROWCOUNT = @ROWCOUNT + 1
    END
    SELECT * FROM @OUTPUT
END

1 Ответ

0 голосов
/ 23 января 2020

У меня есть пара моментов с вашим кодом

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

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

Вы преобразовываете свой столбец даты в дату и время, а затем присоединяетесь к нему - опять же, вам просто нужно использовать неизмененный столбец для сравнения. .

Вы не объяснили свой столбец дат - вы рассчитываете свои скользящие 12 месяцев просто как верхние 11 последних дат меньше текущей, плюс текущая дата

,Rolling12Months=a.Revenue + b.RM 

и

 SELECT RM=SUM(b.Revenue)
    FROM
    (
        SELECT TOP 11 b.Date, b.Revenue 

Рассматривали ли вы, что может быть более одной даты в месяц? В этом случае вы получите менее 12 месяцев данных в сумме. Или ii, для некоторой комбинации вашей группы по столбцам существуют месяцы без данных, в этом случае ваша сумма будет рассчитана на большее количество месяцев!

Однако, если нам просто нужно сложить последние 12 дат для каждой комбинации группировать по столбцам, тогда я бы просто сделал это так:

/* When using many rows a temp table often performs better than a table variable */
DROP TABLE IF EXISTS #OUTPUT

CREATE TABLE #OUTPUT  (
        RefKey VARCHAR(50)
        ,DateID INT
        ,GroupKey INT
        ,LevelKey INT
        ,StateKey INT
        ,ProductKey INT
        ,OptionKey INT
        ,LocKey INT
        ,Revenue DECIMAL(28,9)
        ,Rolling12Months DECIMAL(28,9)
         )

/* I am doing a simple select and using the analytical sum, which can be set up with a window */
SELECT
    CONCAT(CI.GroupKey, '-', CI.LevelKey, '-', CI.StateKey, '-', CI.ProductKey, '-', CI.OptionKey, '-', CI.LocKey) RefKey
   ,CI.DateID
   ,CI.GroupKey
   ,CI.LevelKey
   ,CI.StateKey
   ,CI.ProductKey
   ,CI.OptionKey
   ,CI.LocKey
   ,CI.Revenue
   ,SUM(CI.Revenue) OVER (PARTITION BY CI.GroupKey, CI.LevelKey, CI.StateKey
    , CI.ProductKey, CI.OptionKey, CI.LocKey ORDER BY CI.DateID 
    ROWS BETWEEN 11 PRECEDING AND CURRENT ROW) Rolling12Months
FROM Members AS CI
GROUP BY
    CI.DateID
   ,CI.GroupKey
   ,CI.LevelKey
   ,CI.StateKey
   ,CI.ProductKey
   ,CI.OptionKey
   ,CI.LocKey

SELECT
    *
FROM #OUTPUT

Это должно дать вам тот же результат, однако я бы не стал использовать его в том виде, в каком он есть, поскольку он просто возвращает сумму последние 11 дат плюс текущая дата для каждой комбинации столбцов! Но если это работает в вашем исходном коде, это должно повторить ваши результаты

...