同じ列数の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
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つのフィールドを強調表示しています。
[KPI]が「WeightY」の場合、[Actual]使用する重み係数。
[KPI]が「TonsMilled」の場合、[Actual]は集計するデータです。
私が持っているいくつかの質問は次のとおりです。
私がお願いする理由は、あなたが行う参加を確実にしたいということは、これまで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
値が無視されるという事実に依存しています。
SELECT SUM(A * B) / SUM(A)
FROM mytable
問題を理解している場合は、これを試してください
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