2009-11-08 55 views
11

Estoy buscando una forma de derivar un promedio ponderado de dos filas de datos con el mismo número de columnas, donde el promedio es el siguiente (notación de Excel prestada):Promedio ponderado en T-SQL (como SUMPRODUCT de Excel)

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

La primera parte refleja la misma funcionalidad que la función SUMPRODUCT() de Excel.

Mi problema es que necesito especificar dinámicamente qué fila se promedia con los pesos, y de qué fila provienen los pesos, y un rango de fechas.

EDITAR: Esto es más fácil de lo que pensaba, porque Excel me estaba haciendo pensar que necesitaba algún tipo de pivote. Mi solución hasta ahora es así: respuesta

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

¿De qué manera el intervalo de fechas entre en ella? ¿Cuántas columnas, algunas o muchas? ¿Se ha corregido el número de columnas? –

+0

@martin, solo una columna. Solía ​​ser uno por KPI, pero eso no fue divertido. El rango de fechas es para un período de informe. – ProfK

+0

¿La declaración anterior se considera un CTE? Si no, ¿cómo podrías convertir eso en un CTE? ¿Nadie? – PositiveGuy

Respuesta

13

de Quassnoi muestra cómo hacer el sumproduct, y el uso de una cláusula WHERE permitiría restringir por un campo Fecha ...

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 más compleja es donde desea "especificar dinámicamente" qué campo es [datos] y qué campo es [peso]. La respuesta corta es que, de manera realista, tendrías que hacer uso de Dynamic SQL. Algo a lo largo de las líneas de:
- Crear una cadena de plantilla
- Sustituir todas las instancias de [FALLO] .data con el campo de los datos apropiados
- Sustituir todas las instancias de [FALLO] .Weight con el campo peso adecuado
- Ejecute la cadena

SQL dinámico, sin embargo, tiene su propia sobrecarga. Si las consultas son relativamente infrecuentes, o el tiempo de ejecución de la consulta en sí es relativamente largo, esto puede no importar. Sin embargo, si son comunes y cortos, es posible que observe que el uso de sql dinámico presenta una sobrecarga notable. (Por no mencionar teniendo cuidado de los ataques de inyección SQL, etc.)

EDIT:

En su ejemplo lastest resalta tres campos:

  • RecordDate
  • KPI
  • real

Cuando el [KPI] es "Peso Y ", luego [Real] el factor de ponderación a usar.
Cuando [KPI] es "Toneladas", entonces [Real] es el Dato que desea agregar.


Algunas de las preguntas que tengo son:

  • ¿Hay otros campos?
  • ¿Solo hay UNO real por fecha por KPI?

La razón por la que pregunto es por que desea asegurarse de que la UNIÓN que usted hace sea solo 1: 1.(Usted no quiere unirse a 5 datos reales con 5 pesos, dando registros 25 resultsing)

En cualquier caso, una ligera simplificación de la consulta es ciertamente posible ...

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 línea comentada a cabo sólo es necesario si necesita predicados adicionales para asegurar una relación 1: 1 entre sus datos y los pesos.


Si no puede guarnatee solo un valor por fecha, y no tiene ningún otro campo para unirse en, puede modificar su versión basada sub_query poco ...

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 

Esto supone que la AVG del peso es válida si hay pesos múltiples para el mismo día.


EDIT: Alguien acaba de votar para esto, así que pensé en mejorar la respuesta final :)

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 

Esto evita la unión y también sólo analiza la mesa una vez.

Se basa en el hecho de que NULL valores se ignoran al calcular el AVG().

+0

@Dems, parece que estaba viendo las cosas como demasiado complicadas, porque los valores suministrados dinámicamente son valores de campo, no nombres, como he modificado anteriormente. – ProfK

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

Está asumiendo que los valores provienen de dos columnas diferentes. En realidad, provienen de la misma columna en diferentes conjuntos de registros. – ProfK

+0

¿Podría publicar algunos datos de muestra? – Quassnoi

1

Si tengo que entender el problema entonces prueba este

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