2009-11-08 36 views
11

Sto cercando un modo per ricavare una media ponderata da due righe di dati con lo stesso numero di colonne, dove la media è la seguente (prendendo in prestito la notazione Excel):Media ponderata in T-SQL (come Excel SUMPRODUCT)

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

La prima parte riflette la stessa funzionalità della funzione SUMPRODUCT() di Excel.

Il mio problema è che ho bisogno di specificare in modo dinamico quale riga viene calcolata in media con i pesi, e da quale riga provengono i pesi e un intervallo di date.

MODIFICA: Questo è più facile di quanto pensassi, perché Excel mi stava facendo pensare che avevo bisogno di una sorta di pivot. La mia soluzione finora è così: risposta

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 
+0

modo l'intervallo di date c'entra? Quante colonne: poche o molte? Il numero di colonne è fisso? –

+0

@martin, solo una colonna. Era uno per KPI, ma non era divertente. L'intervallo di date è per un periodo di riferimento. – ProfK

+0

L'istruzione sopra è considerata un CTE? Se no, come potresti trasformarlo in un CTE? Chiunque? – PositiveGuy

risposta

13

di Quassnoi mostra come fare la SUMPRODUCT, e utilizzando una clausola WHERE permetterebbe di limitare da un campo Data ...

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

La parte più complessa è dove si desidera "specificare dinamicamente" il campo che è [dati] e quale campo è [peso]. La risposta breve è che realisticamente dovresti usare Dynamic SQL. Qualcosa sulla falsariga di:
- Creare una stringa di modello
- Sostituire tutte le istanze di [TBL] .data con il campo i dati appropriati
- Sostituire tutte le istanze di [TBL] .weight con il campo di peso appropriato
- Esegui la stringa

SQL dinamico, tuttavia, svolge il proprio overhead. Le query sono relativamente poco frequenti o il tempo di esecuzione della query è relativamente lungo, ciò potrebbe non avere importanza. Se sono comuni e brevi, tuttavia, si può notare che l'utilizzo di SQL dinamico introduce un notevole overhead. (Per non parlare facendo attenzione degli attacchi di SQL injection, ecc)

EDIT:

Nel tuo esempio lastest di evidenziare tre campi:

  • RecordDate
  • KPI
  • Actual

Quando il [KPI] è "Peso Y ", quindi [Attuale] il fattore di ponderazione da utilizzare.
Quando [KPI] è "Tonnellate", quindi [Effettivo] è i ​​dati che si desidera aggregare.


Alcune domande che ho sono:

  • Esistono altri campi?
  • C'è solo UNO effettivo per data per KPI?

La ragione per cui chiedo di voler garantire che il JOIN che fai sia sempre 1: 1.(Se non si desidera 5 effettivi unirsi con 5 pesi, dando record 25 resultsing)

Indipendentemente da ciò, un leggero semplificazione delle query è certamente possibile ...

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' 

La linea commentata necessari solo se hai bisogno di predicati aggiuntivi per assicurare una relazione 1: 1 tra i tuoi dati e i pesi.


Se non è possibile guarnatee un solo valore per la data, e non hanno tutti gli altri campi di aderire, è possibile modificare la versione basata sub_query leggermente ...

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 

Ciò presuppone che AVG del peso sia valido se sono presenti più pesi per lo stesso giorno.


EDIT: Qualcuno ha appena votato per questo quindi ho pensato di migliorare la risposta finale :)

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 

questo modo si evita il join e anche analizza solo la tabella una volta.

Si basa sul fatto che i valori NULL vengono ignorati durante il calcolo dello AVG().

+0

@Dems, sembra che stia vedendo le cose come troppo complicate, perché i valori forniti dinamicamente sono valori di campo, non nomi, come ho modificato sopra. – ProfK

10
SELECT SUM(A * B)/SUM(A) 
FROM mytable 
+0

Si presume che i valori provengano da due colonne diverse. In realtà provengono dalla stessa colonna in diversi set di record. – ProfK

+0

Potresti quindi postare alcuni dati di esempio? – Quassnoi

1

Se devo capire il problema, allora provate questo

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