2009-05-26 26 views
7

Estoy tratando de tener una columna de promedio en ejecución en la instrucción SELECT basada en una columna de las n filas anteriores en la misma instrucción SELECT. El promedio que necesito se basa en las n filas anteriores en el conjunto de resultados.SQL Seleccionar instrucción para calcular una columna promedio en ejecución

Me explico

Id  Number  Average 
1    1   NULL 
2    3   NULL 
3    2   NULL 
4    4    2 <----- Average of (1, 3, 2),Numbers from previous 3 rows 
5    6    3 <----- Average of (3, 2, 4),Numbers from previous 3 rows 
.    .    . 
.    .    . 

Las 3 primeras filas de la columna Media son nulas porque no hay filas anteriores. La fila 4 en la columna Promedio muestra el promedio de la columna Número de las 3 filas anteriores.

Necesito ayuda para tratar de construir una instrucción SQL Select que lo haga.

+0

¿Qué tipo de base de datos SQL está utilizando? –

+0

Estoy usando SQL Server 2008. – HYP

+0

Estoy pensando que este es uno de esos casos realmente raros donde los cursores van a ser más rápidos ... solo mantenga las últimas 3 filas en vars ... –

Respuesta

11

Esto debe hacerlo:

--Test Data 
CREATE TABLE RowsToAverage 
    (
    ID int NOT NULL, 
    Number int NOT NULL 
    ) 

INSERT RowsToAverage(ID, Number) 
SELECT 1, 1 
UNION ALL 
SELECT 2, 3 
UNION ALL 
SELECT 3, 2 
UNION ALL 
SELECT 4, 4 
UNION ALL 
SELECT 5, 6 
UNION ALL 
SELECT 6, 8 
UNION ALL 
SELECT 7, 10 

--The query 
;WITH NumberedRows 
AS 
(
SELECT rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber 
FROM RowsToAverage rta 
) 

SELECT nr.ID, nr.Number, 
     CASE 
      WHEN nr.RowNumber <=3 THEN NULL 
      ELSE ( SELECT avg(Number) 
        FROM NumberedRows 
        WHERE RowNumber < nr.RowNumber 
        AND  RowNumber >= nr.RowNumber - 3 
       ) 
     END AS MovingAverage 
FROM NumberedRows nr 
+0

+1, funciona correctamente en mi sistema –

+0

+ 1- Solución muy elegante. MVP, MVP, MVP! –

+0

Solución muy elegante. Con 9,000 filas, toma aproximadamente 45 segundos en mi servidor de desarrollo. ¿Hay alguna forma de utilizar esta técnica de manera más eficiente? –

0

Vea algunas soluciones here. Estoy seguro de que podrías adaptar uno de ellos con la suficiente facilidad.

+1

Si bien esto puede responder teóricamente a la pregunta, [sería preferible] (http://meta.stackexchange.com/q/8259) incluir el partes esenciales de la respuesta aquí, y proporcionar el enlace de referencia. – Sklivvz

1

Editar: echaba de menos el punto que se debe promediar los tres discos anteriores ...

Para un promedio de ejecución general, creo que algo como esto funcionaría :

SELECT 
    id, number, 
    SUM(number) OVER (ORDER BY ID)/
     ROW_NUMBER() OVER (ORDER BY ID) AS [RunningAverage] 
FROM myTable 
ORDER BY ID 
+0

al usar @Aaron Alton's RowsToAverage tabla (cambié de MyTable a FROM RowsToAverage), me aparece un error: Msg 102, nivel 15, estado 1, línea 3 Sintaxis incorrecta cerca de 'orden'. –

+0

¿Qué RDBMS estás usando? Las funciones de ventana solo están disponibles en SQL 2005 y posteriores. –

+0

Debo añadir que los OP mencionaron que están usando SQL 2008. –

0

Si quieres que esto sea realmente performant y ar no tiene miedo de excavar en una zona poco usada de SQL Server, usted debe buscar en escribir una función personalizada agregado. SQL Server 2005 y 2008 trajeron la integración de CLR a la tabla, incluida la capacidad de escribir funciones agregadas de usuario. Un agregado total acumulado personalizado sería la manera más eficiente de calcular un promedio continuo como este, con diferencia.

8

Suponiendo que la columna de Id es secuencial, aquí está una consulta simplificada para una tabla denominada "MyTable":

SELECT 
    b.Id, 
    b.Number, 
    (
     SELECT 
     AVG(a.Number) 
     FROM 
     MyTable a 
    WHERE 
     a.id >= (b.Id - 3) 
     AND a.id < b.Id 
     AND b.Id > 3 
    ) as Average 
FROM 
    MyTable b; 
+0

Uhmm. Suponiendo sequentials Ids, esto también funciona. +1 –

+0

Esto también puede funcionar si no se borraron filas en la tabla. Acepté la solución de Aaron Alton porque row_number() OVER (ORDER BY rta.ID ASC) funciona para todos los casos. – HYP

2

Un simple auto unirse parecería realizar mucho mejor que una fila referencia subconsulta

Generar 10k filas de datos de prueba:

drop table test10k 
create table test10k (Id int, Number int, constraint test10k_cpk primary key clustered (id)) 

;WITH digits AS (
    SELECT 0 as Number 
    UNION SELECT 1 
    UNION SELECT 2 
    UNION SELECT 3 
    UNION SELECT 4 
    UNION SELECT 5 
    UNION SELECT 6 
    UNION SELECT 7 
    UNION SELECT 8 
    UNION SELECT 9 
) 
,numbers as (
    SELECT 
     (thousands.Number * 1000) 
     + (hundreds.Number * 100) 
     + (tens.Number * 10) 
     + ones.Number AS Number 
    FROM digits AS ones 
    CROSS JOIN digits AS tens 
    CROSS JOIN digits AS hundreds 
    CROSS JOIN digits AS thousands 
) 
insert test10k (Id, Number) 
select Number, Number 
from numbers 

Me tire el caso especial de las 3 primeras filas de la consulta principal, puede UNION TODOS los que volverán si realmente lo quiere en la fila establecida. Auto consulta de combinación:

;WITH NumberedRows 
AS 
(
    SELECT rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber 
    FROM test10k rta 
) 

SELECT nr.ID, nr.Number, 
    avg(trailing.Number) as MovingAverage 
FROM NumberedRows nr 
    join NumberedRows as trailing on trailing.RowNumber between nr.RowNumber-3 and nr.RowNumber-1 
where nr.Number > 3 
group by nr.id, nr.Number 

En mi máquina esto tarda unos 10 segundos, el enfoque subconsulta que Aaron Alton demostró tarda unos 45 segundos (después de cambiar para reflejar mi mesa fuente de prueba):

;WITH NumberedRows 
AS 
(
    SELECT rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber 
    FROM test10k rta 
) 
SELECT nr.ID, nr.Number, 
    CASE 
      WHEN nr.RowNumber <=3 THEN NULL 
      ELSE ( SELECT avg(Number) 
          FROM NumberedRows 
          WHERE RowNumber < nr.RowNumber 
          AND    RowNumber >= nr.RowNumber - 3 
        ) 
    END AS MovingAverage 
FROM NumberedRows nr 

Si realiza un PERFIL DE ESTADÍSTICAS CONECTADO ENCENDIDO, puede ver que la autocomunicación tiene ejecuciones de 10k en el spool de la tabla. La subconsulta tiene ejecuciones de 10k en el filtro, el agregado y otros pasos.

Cuestiones relacionadas