web-dev-qa-db-ja.com

T-SQLの加重平均(ExcelのSUMPRODUCTなど)

同じ列数の2行のデータから加重平均を導出する方法を探しています。平均は次のとおりです(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
17
ProfK

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'
_

より複雑な部分は、どのフィールドが[データ]で、どのフィールドが[重み]であるかを「動的に指定」する場所です。簡単に言うと、現実的には動的SQLを使用する必要があります。次のようなもの:
-文字列テンプレートを作成する
-[tbl] .dataのすべてのインスタンスを適切なデータフィールドに置き換えます
-[tbl] .weightのすべてのインスタンスを適切な重みフィールドに置き換えます
-文字列を実行します

ただし、動的SQLには独自のオーバーヘッドがあります。クエリの頻度が比較的低い場合、またはクエリ自体の実行時間が比較的長い場合、これは問題ではない可能性があります。ただし、それらが一般的で短い場合は、動的SQLを使用すると顕著なオーバーヘッドが発生することに気付くかもしれません。 (SQLインジェクション攻撃などに注意することは言うまでもありません)

編集:

最新の例では、次の3つのフィールドを強調表示しています。

  • RecordDate
  • KPI
  • 実際

[KPI]が「WeightY」の場合、[Actual]使用する重み係数。
[KPI]が「TonsMilled」の場合、[Actual]は集計するデータです。


私が持っているいくつかの質問は次のとおりです。

  • 他にフィールドはありますか?
  • KPIごとに日付ごとに実際に1つしかありませんか?

私がお願いする理由は、あなたが行う参加を確実にしたいということは、これまで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の関係を確保するために追加の述語が必要な場合にのみ必要です。


日付ごとに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が回避され、テーブルが1回だけスキャンされます。

これは、AVG()を計算するときにNULL値が無視されるという事実に依存しています。

19
MatBailie
SELECT  SUM(A * B) / SUM(A)
FROM    mytable
12
Quassnoi

問題を理解している場合は、これを試してください

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
1
priyanka.sarkar