T-SQL 中的加权平均值(如 Excel 的 SUMPRODUCT)

Posted

技术标签:

【中文标题】T-SQL 中的加权平均值(如 Excel 的 SUMPRODUCT)【英文标题】:Weighted average in T-SQL (like Excel's SUMPRODUCT) 【发布时间】:2010-12-14 11:31:29 【问题描述】:

我正在寻找一种方法来从具有相同列数的两行数据中得出加权平均值,其中平均值如下(借用 Excel 表示法):

(A1*B1)+(A2*B2)+...+(An*Bn)/SUM(A1:An)

第一部分反映了与 Excel 的 SUMPRODUCT() 函数相同的功能。

我的问题是,我需要动态指定使用权重对哪一行进行平均、权重来自哪一行以及日期范围。

编辑:这比我想象的要容易,因为 Excel 让我觉得我需要某种支点。到目前为止,我的解决方案是:

select sum(baseSeries.Actual * weightSeries.Actual) / sum(weightSeries.Actual)
from (
    select RecordDate , Actual 
    from CalcProductionRecords 
    where KPI = 'Weighty'
) baseSeries inner join (       
    select RecordDate , Actual 
    from CalcProductionRecords 
    where KPI = 'Tons Milled'   
) weightSeries on baseSeries.RecordDate = weightSeries.RecordDate

【问题讨论】:

日期范围是怎么算进去的?多少列 - 几个或很多?列数是固定的吗? @martin,只有一栏。它曾经是每个 KPI 一个,但这并不有趣。日期范围是一个报告期。 上述语句是否被视为 CTE?如果不是,你怎么能把它变成 CTE?有人吗? @CoffeeAddict 我从不认为它是 CTE,但对于新手来说,它看起来可以用作一个。我只是不能告诉你怎么做。 【参考方案1】:

Quassnoi 的回答显示了如何进行 SumProduct,并且使用 WHERE 子句将允许您通过日期字段进行限制...

SELECT
   SUM([tbl].data * [tbl].weight) / SUM([tbl].weight)
FROM
   [tbl]
WHERE
   [tbl].date >= '2009 Jan 01'
   AND [tbl].date < '2010 Jan 01'

更复杂的部分是您要“动态指定”什么字段是 [data] 以及什么字段是 [weight]。简短的回答是,实际上您必须使用动态 SQL。类似于: - 创建字符串模板 - 将 [tbl].data 的所有实例替换为适当的数据字段 - 将 [tbl].weight 的所有实例替换为适当的权重字段 - 执行字符串

然而,动态 SQL 也有它自己的开销。是查询相对不频繁,还是查询本身的执行时间比较长,这可能无关紧要。但是,如果它们很常见且很短,您可能会注意到使用动态 sql 会带来显着的开销。 (更别提小心SQL注入攻击等了)

编辑:

在您的最新示例中,您突出显示了三个字段:

记录日期 关键绩效指标 实际

当 [KPI] 为“权重 Y”时,则 [实际] 要使用的权重因子。 当 [KPI] 为“Tons Milled”时,[Actual] 是您要聚合的数据。

我的一些问题是:

还有其他字段吗? 每个 KPI 每个日期是否只有一个实际值?

我问的原因是你想确保你所做的 JOIN 永远是 1:1。 (您不希望 5 个实际值与 5 个权重相结合,给出 25 个结果记录)

无论如何,对您的查询稍作简化当然是可能的...

SELECT
   SUM([baseSeries].Actual * [weightSeries].Actual) / SUM([weightSeries].Actual)
FROM
   CalcProductionRecords AS [baseSeries]
INNER JOIN
   CalcProductionRecords AS [weightSeries]
      ON [weightSeries].RecordDate = [baseSeries].RecordDate
--    AND [weightSeries].someOtherID = [baseSeries].someOtherID
WHERE
   [baseSeries].KPI = 'Tons Milled'
   AND [weightSeries].KPI = 'Weighty'

仅当您需要额外的谓词来确保数据和权重之间的 1:1 关系时才需要注释掉的行。

如果您不能保证每个日期只有一个值,并且没有任何其他字段可以加入,您可以稍微修改基于 sub_query 的版本...

SELECT
   SUM([baseSeries].Actual * [weightSeries].Actual) / SUM([weightSeries].Actual)
FROM
(
    SELECT
        RecordDate,
        SUM(Actual)
    FROM
        CalcProductionRecords
    WHERE
        KPI = 'Tons Milled'
    GROUP BY
        RecordDate
)
   AS [baseSeries]
INNER JOIN
(
    SELECT
        RecordDate,
        AVG(Actual)
    FROM
        CalcProductionRecords
    WHERE
        KPI = 'Weighty'
    GROUP BY
        RecordDate
)
   AS [weightSeries]
      ON [weightSeries].RecordDate = [baseSeries].RecordDate

这假设如果同一天有多个重量,则重量的 AVG 是有效的。

编辑:有人刚刚投了赞成票,所以我想我会改进最终答案:)

SELECT
   SUM(Actual * Weight) / SUM(Weight)
FROM
(
    SELECT
        RecordDate,
        SUM(CASE WHEN KPI = 'Tons Milled' THEN Actual ELSE NULL END)   AS Actual,
        AVG(CASE WHEN KPI = 'Weighty'     THEN Actual ELSE NULL END)   AS Weight
    FROM
        CalcProductionRecords
    WHERE
        KPI IN ('Tons Milled', 'Weighty')
    GROUP BY
        RecordDate
)
   AS pivotAggregate

这样就避免了 JOIN,也只扫描表一次。

它依赖于在计算AVG() 时忽略NULL 值这一事实。

【讨论】:

@Dems,似乎我认为事情太复杂了,因为动态提供的值是字段值,而不是名称,正如我在上面修改的那样。 在权重总和 = 0 的情况下,此代码将失败。 @TKBruin 1. 这是一个十年前的帖子。 2.如果没有Weighty值,聚合将返回NULL而不是0。3.如果所有Weighty值都是0,聚合将返回0,但是数据有问题,应该更正。【参考方案2】:
SELECT  SUM(A * B) / SUM(A)
FROM    mytable

【讨论】:

您假设这些值来自两个不同的列。它们实际上来自不同记录集中的同一列。 那你能发一些示例数据吗?【参考方案3】:

如果我理解了问题,那么试试这个

SET DATEFORMAT dmy
    declare @tbl table(A int, B int,recorddate datetime,KPI varchar(50))
    insert into @tbl 
        select 1,10 ,'21/01/2009', 'Weighty'union all 
        select 2,20,'10/01/2009', 'Tons Milled' union all
        select 3,30 ,'03/02/2009', 'xyz'union all 
        select 4,40 ,'10/01/2009', 'Weighty'union all
        select 5,50 ,'05/01/2009', 'Tons Milled'union all 
        select 6,60,'04/01/2009', 'abc' union all
        select 7,70 ,'05/01/2009', 'Weighty'union all 
        select 8,80,'09/01/2009', 'xyz' union all
        select 9,90 ,'05/01/2009', 'kws'    union all 
        select 10,100,'05/01/2009', 'Tons Milled'

    select SUM(t1.A*t2.A)/SUM(t2.A)Result  from  
                   (select RecordDate,A,B,KPI from @tbl)t1 
        inner join(select RecordDate,A,B,KPI from @tbl t)t2
        on t1.RecordDate = t2.RecordDate
        and t1.KPI = t2.KPI

【讨论】:

以上是关于T-SQL 中的加权平均值(如 Excel 的 SUMPRODUCT)的主要内容,如果未能解决你的问题,请参考以下文章

计算每个产品的加权平均值 [关闭]

在 s-s-rS 2008 R2 中计算 Tablix 中的加权平均值

MySql中的加权平均值

如何在 s-s-rS 2008 的矩阵中做加权平均(sumproduct/total)

是否可以使用 excel 单元格值 - 作为 T-SQL 语句中的参考?

group的加权平均值不等于pandas groupby中的总平均值