2009-05-26 1 views
7

J'essaie d'avoir une colonne de moyenne mobile dans l'instruction SELECT basée sur une colonne des n lignes précédentes dans la même instruction SELECT. La moyenne dont j'ai besoin est basée sur les n lignes précédentes dans le jeu de résultats.Instruction SQL Select pour le calcul d'une colonne en cours d'exécution

Laissez-moi vous expliquer

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 
.    .    . 
.    .    . 

Les 3 premières lignes de la colonne moyenne sont nuls, car il n'y a pas de lignes précédentes. La ligne 4 dans la colonne Moyenne affiche la moyenne de la colonne Nombre des 3 lignes précédentes.

J'ai besoin d'aide pour essayer de construire une instruction SQL Select qui va le faire.

+0

Quel type de base de données SQL que vous utilisez? –

+0

J'utilise SQL Server 2008. – HYP

+0

Im pensant que c'est l'un de ces très rares cas où les curseurs vont être le plus rapide ... juste garder les 3 dernières lignes vars ... –

Répondre

11

Cela devrait le faire:

--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, il fonctionne correctement sur mon système –

+0

+ 1- Solution très élégante. MVP, MVP, MVP! –

+0

Solution très élégante. Avec 9 000 lignes, cela prend environ 45 secondes sur mon serveur de dev. Est-il possible d'utiliser cette technique plus efficacement. –

0

Découvrez quelques solutions here. Je suis sûr que vous pourriez facilement en adapter un d'entre eux.

+1

Bien que cela puisse théoriquement répondre à la question, [il serait préférable] (http://meta.stackexchange.com/q/8259) pour inclure la parties essentielles de la réponse ici, et fournir le lien pour référence. – Sklivvz

1

Edit: Je Maladroit qu'il devrait atteindre en moyenne les trois précédents records ...

Pour une moyenne de fonctionnement général, je pense que quelque chose comme ça fonctionnerait :

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

lors de l'utilisation de la table de RowsToAverage de Alton @ Aaron (je changé de MyTable à partir RowsToAverage), je reçois une erreur: Msg 102, niveau 15, état 1, ligne 3 syntaxe incorrecte près de « l'ordre ». –

+0

Quels SGBDR utilisez-vous? Les fonctions de fenêtrage ne sont disponibles que dans SQL 2005 et supérieur. –

+0

Je devrais ajouter que l'OP a mentionné qu'ils utilisent SQL 2008. –

0

Si vous voulez que ce soit vraiment performant, et arn't peur de creuser dans une zone rarement utilisée de SQL Server, vous devez envisager d'écrire une fonction personnalisée globale. SQL Server 2005 et 2008 ont apporté l'intégration CLR à la table, y compris la possibilité d'écrire des fonctions d'agrégat utilisateur. Un agrégat total personnalisé serait le moyen le plus efficace de calculer une moyenne mobile comme celle-ci, de loin.

8

Si l'on suppose que la colonne Id est séquentielle, voici une requête simplifiée pour une table nommée « 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. En supposant des Ids séquentiels, cela fonctionne aussi. +1 –

+0

Cela peut également fonctionner si aucune ligne n'a été supprimée dans la table. J'ai accepté la solution d'Aaron Alton à cause du numéro de rangée() OVER (ORDER BY rta.ID ASC) fonctionne pour tous les cas. – HYP

2

Un auto simple, rejoindre sembleraient bien meilleurs résultats qu'une ligne sous-requête faisant référence

Generate 10k lignes de données de test:

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 

Je tirerais le cas particulier des 3 premières lignes de la requête principale, vous pouvez UNION tous ceux de retour si vous le voulez vraiment dans le jeu de lignes. Auto de jointure:

;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 

Sur ma machine, cela prend environ 10 secondes, l'approche sous-requête Aaron Alton démontré prend environ 45 secondes (après avoir changé pour refléter ma table de source de test):

;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 vous faites un SET STATISTICS PROFILE ON, vous pouvez voir que la jointure automatique a 10k s'exécute sur le spool de la table. La sous-requête a 10k s'exécute sur le filtre, l'agrégat et d'autres étapes.

Questions connexes