在SQL中重新使用聚合级公式-有什么好的策略吗?
想象一下这种情况,但是有更多的组件存储桶和更多的中间产品和输出。许多中间产品是在细节层面进行计算的,但也有一些是在聚合层面进行计算的:在SQL中重新使用聚合级公式-有什么好的策略吗?,sql,sql-server,database-design,dry,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 ,Liabilit
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(标量UDF或带有外部应用程序的表值UDF,因为许多最终结果可能共享必须在聚合级别计算的中间结果),但根据我的经验,标量UDF和多语句表值UDF的性能非常差
还考虑了使用更动态的SQL并按名称应用公式
有没有其他技巧、技巧或策略来保持这些公式的同步和/或组织在不同层次上的应用
请注意,在不同的聚合级别上必须使用相同的公式。这会导致代码重复
如果函数更复杂,则可以从创建自定义CLR
聚合中获益
但是,对于这样一个简单的函数,内置的SUM
是最好的
与
PostgreSQL
不同,SQL Server
不允许使用内置语言创建自定义聚合。对于您的简化示例,我将通过返回原始数据来重构计算(SUM(Income)
和SUM(Expense)
)在每个结果集中分别计算利润
和利润
如果在实际情况下这是不可能的,你能把你的简单例子稍微复杂一点,这样我就能明白你的意思了吗
我最近参与了一个项目,该项目需要在查询中进行复杂的业务分析计算。事实证明,不可能在数据查询之外执行这些操作,因此我们最终求助于将所有内容转换为动态SQL。这允许我们构造宏函数来构建每个查询的各个部分。通过这样做,我们牺牲了可读性,但获得了可维护性。我们没有牺牲可测试性,因为我们编写了单元测试,通过宏函数执行每个可能的代码路径,并在生成查询时记录每个查询。您可以在视图中分离部分复杂性:
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
示例查询很难复杂到足以保证视图的简化。但是,视图对于非常复杂的查询非常有帮助。如果我理解正确,您需要类似于
CREATE VIEW myview AS SELECT SUM(Income-Expense)FROM mytable GROUP BY x
?@Quassnoi-我基本上希望能够将相同的公式一致地应用于任何具有所有适当输入列的集合,不管聚合如何。无论如何,这都需要对每个查询进行重新分类和重新优化,因此如果您的公式真的那么难,您也可以使用动态的SQL
。对不起,我应该说明SUM(margin)没有意义。如果您运行代码,您将看到边距不能简单地求和。因此,需要在聚合后应用它(而SUM(利润)实际上很好)。@Cade Roux:对!答案已编辑,尽管视图现在解决的复杂性更低:)现在我有一个视图,由十几个细节级别的CTE组成,生成几十个中间列结果,但是,由于聚合公式中的分层条件逻辑,每个必须聚合的不同查询都有3个CTE。@Cade Roux:我们试图避免CTE,因为它增加了复杂性,但是对于第二次主计算的第七次子计算,我们有名为vw_0207_Profit
的视图层。我发现CTE相当容易维护-更少的数据库对象,您可以在此过程中轻松添加/删除列。谢谢,很高兴知道我想做的事情已经被用作有效的方法。