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

时间:2009-11-08 23:53:21

标签: sql sql-server tsql statistics

我正在寻找一种方法,从具有相同列数的两行数据中导出加权平均值,其中平均值如下(借用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

3 个答案:

答案 0 :(得分:18)

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

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

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

然而,

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

编辑:

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

  • RecordDate
  • KPI
  • 实际

当[KPI]为“Weight Y”时,则[Actual]使用加权因子 当[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关系,则只需要注释掉的行。


如果您不能仅为每个日期保留一个值,并且没有任何其他字段可以加入,则可以稍微修改基于子查询的版本...

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,也只扫描一次表。

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

答案 1 :(得分:12)

SELECT  SUM(A * B) / SUM(A)
FROM    mytable

答案 2 :(得分:1)

如果我已经理解了这个问题,请尝试这个

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