Повторное использование формул агрегированного уровня в SQL - хорошая тактика? - PullRequest
1 голос
/ 02 апреля 2010

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

DECLARE @Profitability AS TABLE
    (
     Cust INT NOT NULL
    ,Category VARCHAR(10) NOT NULL
    ,Income DECIMAL(10, 2) NOT NULL
    ,Expense DECIMAL(10, 2) NOT NULL
    ,Liability DECIMAL(10, 2) NOT NULL
    ,AllocatedCapital DECIMAL(10, 2) NOT NULL
    ) ;

INSERT  INTO @Profitability
VALUES  ( 1, 'Software', 100, 50, 0, 0 ) ; 
INSERT  INTO @Profitability
VALUES  ( 2, 'Software', 100, 20, 0, 0 ) ; 
INSERT  INTO @Profitability
VALUES  ( 3, 'Software', 100, 60, 0, 0 ) ; 
INSERT  INTO @Profitability
VALUES  ( 4, 'Software', 500, 400, 0, 0 ) ; 
INSERT  INTO @Profitability
VALUES  (
         5
        ,'Hardware'
        ,1000
        ,550
        ,0
        ,0 
        ) ; 
INSERT  INTO @Profitability
VALUES  (
         6
        ,'Hardware'
        ,1000
        ,250
        ,500
        ,200 
        ) ; 
INSERT  INTO @Profitability
VALUES  (
         7
        ,'Hardware'
        ,1000
        ,700
        ,500
        ,600 
        ) ; 
INSERT  INTO @Profitability
VALUES  (
         8
        ,'Hardware'
        ,5000
        ,4500
        ,2500
        ,800 
        ) ; 

WITH    ProfitView
          AS ( SELECT   Cust
                       ,Category
                       ,Income
                       ,Expense
                       ,Profit = Income - Expense
                       ,NetProfit = Income - Expense
                        - CASE WHEN Liability - AllocatedCapital > 0
                               THEN Liability - AllocatedCapital
                               ELSE 0
                          END
               FROM     @Profitability
             )
    SELECT  Cust
           ,Category
           ,Income
           ,Expense
           ,Profit
           ,NetProfit
           ,Margin = Profit / Income
           ,NetMargin = NetProfit / Income
    FROM    ProfitView ; -- NOTE I've left off the AFTER grouping formulas on this one.

WITH    ProfitView
          AS ( SELECT   Cust
                       ,Category
                       ,Income
                       ,Expense
                       ,Profit = Income - Expense
                       ,NetProfit = Income - Expense
                        - CASE WHEN Liability - AllocatedCapital > 0
                               THEN Liability - AllocatedCapital
                               ELSE 0
                          END
               FROM     @Profitability
             ),
        GROUP1
          AS ( SELECT   Category
                       ,SUM(Profit) AS Profit
                       ,SUM(NetProfit) AS NetProfit
                       ,SUM(Income) AS Income
                       ,SUM(Profit) / SUM(Income) AS Margin
                       ,SUM(NetProfit) / SUM(Income) AS NetMargin
               FROM     ProfitView
               GROUP BY Category
             ),
        GROUP2
          AS ( SELECT   GROUP1.*
                       ,NetProfit - Profit AS Exposure
               FROM     GROUP1
             )
    SELECT  *
           ,Exposure / Income AS ExposureRatio
    FROM    GROUP2 ;

WITH    ProfitView
          AS ( SELECT   Cust
                       ,Category
                       ,Income
                       ,Expense
                       ,Profit = Income - Expense
                       ,NetProfit = Income - Expense
                        - CASE WHEN Liability - AllocatedCapital > 0
                               THEN Liability - AllocatedCapital
                               ELSE 0
                          END
               FROM     @Profitability
             ),
        GROUP1
          AS ( SELECT   SUM(Profit) AS Profit
                       ,SUM(NetProfit) AS NetProfit
                       ,SUM(Income) AS Income
                       ,SUM(Profit) / SUM(Income) AS Margin
                       ,SUM(NetProfit) / SUM(Income) AS NetMargin
               FROM     ProfitView
             ),
        GROUP2
          AS ( SELECT   GROUP1.*
                       ,NetProfit - Profit AS Exposure
               FROM     GROUP1
             )
    SELECT  *
           ,Exposure / Income AS ExposureRatio
    FROM    GROUP2 ;

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

Я думал об использовании UDF (скалярных или табличных значений с OUTER APPLY, поскольку многие из конечных результатов могут иметь общие промежуточные значения, которые должны рассчитываться на агрегированном уровне), нопо моему опыту, скалярные и табличные UDF с несколькими операторами работают очень плохо.

Также задумывался об использовании более динамического SQL и применении формул по имени.

Любые другие приемы, приемыили тактика сохранения формул такого рода, которые должны применяться на разных уровнях синхронно и / или организованно?

Ответы [ 3 ]

1 голос
/ 02 апреля 2010

Для вашего упрощенного примера я бы провел рефакторинг расчетов, возвращая необработанные данные (SUM(Income) и SUM(Expense)) отдельно в каждом наборе результатов и вычисляя Profit и Margin на бизнес-уровне.

Если это невозможно в реальном случае, не могли бы вы сделать свой простой пример немного более сложным, чтобы я мог видеть, к чему вы клоните?

Недавно я работал над проектом, который требовал сложных расчетов бизнес-анализа, выполняемых внутри запросов. Оказалось, что это невозможно сделать за пределами запросов данных, поэтому мы в конечном итоге прибегли к преобразованию всего в динамический SQL. Это позволило нам построить макрофункции для построения различных частей каждого запроса. Делая это, мы жертвовали удобочитаемостью, но получили удобство обслуживания. Мы не жертвовали тестируемостью, потому что мы написали модульные тесты, которые проверяли каждый возможный путь кода через макро-функции, и регистрировали каждый запрос по мере его создания.

1 голос
/ 02 апреля 2010

Вы можете выделить часть сложности в виде:

create view dbo.vw_Profit
as
SELECT  
    Cust
,   Income,
,   Expense
,   Income - Expense as Profit
FROM dbo.Profitability

Это позволяет выполнять несколько более простые запросы:

SELECT cust, SUM(profit), SUM(Income) / SUM(Expense)
FROM dbo.vw_Profit
GROUP BY cust

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

1 голос
/ 02 апреля 2010

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

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

Однако для такой простой функции лучше всего подойдет SUM.

В отличие от PostgreSQL, SQL Server не позволяет создавать пользовательские агрегаты на встроенном языке.

...