2009-05-26 7 views
7

Sto provando ad avere una colonna media in esecuzione nell'istruzione SELECT in base a una colonna dalle n righe precedenti nella stessa istruzione SELECT. La media di cui ho bisogno è basata sulle n righe precedenti nel set di risultati.Istruzione SQL Select per il calcolo di una colonna media corrente

Mi spiego

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

Le prime 3 righe della colonna media sono nulli perché non ci sono righe precedenti. La riga 4 nella colonna Media mostra la media della colonna Numero delle precedenti 3 righe.

Ho bisogno di aiuto per provare a costruire un'istruzione SQL Select che faccia questo.

+0

Che tipo di database SQL stai usando? –

+0

Sto usando SQL Server 2008. – HYP

+0

Sto pensando che questo è uno di quei casi davvero rari in cui i cursori saranno più veloci ... basta tenere le ultime 3 righe in vars ... –

risposta

11

Questo dovrebbe farlo:

--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, funziona correttamente sul mio sistema –

+0

+ 1- Soluzione molto elegante. MVP, MVP, MVP! –

+0

Soluzione molto elegante. Con 9.000 file, ci vogliono circa 45 secondi sul mio server di sviluppo. C'è un modo per utilizzare questa tecnica in modo più efficiente. –

0

Scopri alcune soluzioni here. Sono sicuro che potresti adattare uno di loro abbastanza facilmente.

+1

Mentre questo può teoricamente rispondere alla domanda, [sarebbe preferibile] (http://meta.stackexchange.com/q/8259) per includere parti essenziali della risposta qui e fornire il collegamento per riferimento. – Sklivvz

1

Edit: ho perso il punto che dovrebbe calcolare la media dei tre dischi precedenti ...

Per una media generale in esecuzione, penso che qualcosa di simile potrebbe funzionare :

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

quando si utilizza la tabella RowsToAverage di @Aaron Alton (ho modificato FROM MyTable in FROM RowsToAverage), viene visualizzato un messaggio di errore: messaggio 102, livello 15, stato 1, riga 3 sintassi errata vicino a "ordine". –

+0

Quali RDBMS stai utilizzando? Le funzioni di visualizzazione sono disponibili solo in SQL 2005 e versioni successive. –

+0

Devo aggiungere che l'OP ha menzionato che stanno usando SQL 2008. –

0

Se si desidera che questo sia veramente performante, e arn't paura di scavare in una zona raramente utilizzato di SQL Server, si dovrebbe guardare in scrittura di una funzione di aggregazione personalizzata. SQL Server 2005 e 2008 hanno portato l'integrazione CLR alla tabella, inclusa la possibilità di scrivere funzioni aggregate dell'utente. Un aggregato totale in esecuzione personalizzato sarebbe il modo più efficiente per calcolare una media corrente come questa, di gran lunga.

8

Partendo dal presupposto che la colonna id è sequenziale, ecco una query semplificato per una tabella denominata "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. Supponendo gli ID sequenziali, anche questo funziona. +1 –

+0

Questo può funzionare anche se nessuna riga è stata cancellata nella tabella. Ho accettato la soluzione di Aaron Alton perché row_number() OVER (ORDER BY rta.ID ASC) funziona per tutti i casi. – HYP

2

Un semplice auto unirsi sembrerebbe svolgere molto meglio di una fila riferimento sottoquery

generare 10k righe di dati di 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 

vorrei tirare il caso particolare delle prime 3 righe fuori la query principale, puoi UNIONE TUTTI quelli che rientrano se lo vuoi veramente nel set di righe. Auto unirsi query:

;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 

Sulla mia macchina questo richiede circa 10 secondi, l'approccio subquery che Aaron Alton dimostrato richiede circa 45 secondi (dopo l'ho cambiato per riflettere il mio test tabella di origine):

;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 

Se si esegue un SET STATISTICS PROFILE ON, è possibile vedere che self join ha eseguito 10k sullo spool della tabella. La subquery esegue 10k sul filtro, sull'aggregazione e su altri passaggi.

Problemi correlati