Question

Je suis en train d'avoir une colonne moyenne en cours d'exécution dans l'instruction SELECT basée sur une colonne des n lignes précédentes dans la même instruction SELECT. La moyenne j'ai besoin est basé sur les n lignes précédentes dans le resultset.

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 représente la moyenne de la colonne de numéro à partir des 3 dernières lignes.

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

Était-ce utile?

La solution

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

Autres conseils

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;

Un auto simple, joindre semble faire beaucoup mieux qu'un sous-requête de référencement de ligne

Générer 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 en arrière si vous le voulez vraiment dans l'ensemble de la ligne. 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 SET STATISTIQUES SUR LE PROFIL, vous pouvez voir l'auto se joindre a 10k sur la bobine exécute de table. La sous-requête a 10k exécute sur le filtre, des agrégats et d'autres étapes.

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

Découvrez quelques-unes des solutions . Je suis sûr que vous pouvez adapter l'un d'entre eux assez facilement.

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 a l'intégration du CLR à la table, y compris la possibilité d'écrire des fonctions d'agrégation de l'utilisateur. Un agrégat total serait le moyen le plus efficace en cours d'exécution sur mesure pour calculer une moyenne en cours d'exécution comme celui-ci, de loin.

Sinon, vous pouvez dénormaliser et stocker les valeurs précalculées en cours d'exécution. Décrite ici:

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/01/23/denormalizing-to-enforce-business-rules-running-totals.aspx

Performance de selects est aussi rapide que ça se passe. Bien sûr, les modifications sont plus lentes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top