在SQL中重用聚合级别公式 - 任何好的策略?

时间:2010-04-02 17:49:22

标签: sql sql-server database-design dry

想象一下这种情况,但是有更多的组件桶和更多的中间产品和输出。许多中间体是在详细程度上计算的,但是在总体水平上计算了一些东西:

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 个答案:

答案 0 :(得分:1)

  

请注意必须在不同的聚合级别使用相同的公式。这导致代码重复。

如果您的功能更复杂,您可以从创建自定义CLR聚合中受益。

但是,对于这样一个简单的函数,内置的SUM是最好的。

PostgreSQL不同,SQL Server不允许以内置语言创建自定义聚合。

答案 1 :(得分:1)

对于简化示例,我将通过在每个结果集中单独返回原始数据(SUM(Income)SUM(Expense))来重构计算,并计算ProfitMargin业务层。

如果在实际案例中这是不可能的,你能否让你的简单例子变得更复杂,以便我能看到你得到的是什么?

我最近参与了一个项目,需要在查询中完成复杂的业务分析计算。事实证明,在数据查询之外不可能这样做,所以我们最终将所有内容都转换为动态SQL。这允许我们构造宏函数来构建每个查询的各个部分。通过这样做,我们牺牲了可读性,但获得了可维护性。我们没有牺牲可测试性,因为我们编写了单元测试,通过宏函数运行了每个可能的代码路径,并在生成时记录每个查询。

答案 2 :(得分:1)

您可以在视图中分离部分复杂性:

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

示例查询几乎不够复杂,无法通过视图进行简化。但是对于非常复杂的查询,视图可以提供很大帮助。