2012-10-25 11 views
5

Ho una tabella con il nome dell'entità, l'anno e il numero dell'attività come qui sotto. Durante alcuni anni non c'è alcuna attività.Calcolo della somma parziale che inizia x anni prima

name | year | act_num 
-----+------+--------- 
aa | 2000 |  2 
aa | 2001 |  6 
aa | 2002 |  9 
aa | 2003 |  15 
aa | 2005 |  17 
b | 2000 |  3 
b | 2002 |  4 
b | 2003 |  9 
b | 2005 |  12 
b | 2006 |  2 

Per crearlo su postgresql;

CREATE TABLE entity_year_activity (
name character varying(10), 
year integer, 
act_num integer 
); 

INSERT INTO entity_year_activity 
VALUES 
    ('aa', 2000, 2), 
    ('aa', 2001, 6), 
    ('aa', 2002, 9), 
    ('aa', 2003, 15), 
    ('aa', 2005, 17), 
    ('b', 2000, 3), 
    ('b', 2002, 4), 
    ('b', 2003, 9), 
    ('b', 2005, 12), 
    ('b', 2006, 2); 

mi piacerebbe avere il numero totale degli x anni passati con il numero di queste attività l'anno per ogni entità per ogni anno come muggito.

Come esempio per x = tre anni.

name | year | act_num | total_3_years 
-----+------+---------+--------------- 
aa | 2000 |  2 |  2 
aa | 2001 |  6 |  8 
aa | 2002 |  9 |  17 
aa | 2003 |  15 |  30 
aa | 2004 |  0 |  24 
aa | 2005 |  17 |  32 
b | 2000 |  3 |  3 
b | 2001 |  0 |  3 
b | 2002 |  4 |  7 
b | 2003 |  9 |  13 
b | 2005 |  12 |  21 
b | 2006 |  2 |  14 
+2

Grande domanda. Dati campione, uscita prevista, DDL. Vale la pena notare che Stack Overflow non conserva le tabulazioni, quindi, l'output di 'COPY' è stato alterato. Meglio usare 'COPY ... CSV'. –

risposta

3

Ecco un approccio che utilizza la capacità di utilizzare la sum aggregato come funzione finestra con un serramento gamma-based - vedere SUM(...) OVER (PARTITION BY name ORDER BY year ROWS 2 PRECEDING) e window framing.

WITH name_years(gen_name, gen_year) AS (
    SELECT gen_name, s 
    FROM generate_series(
    (SELECT min(year) FROM entity_year_activity), 
    (SELECT max(year) FROM entity_year_activity) 
) s CROSS JOIN (SELECT DISTINCT name FROM entity_year_activity) n(gen_name) 
), 
windowed_history(name, year,act_num,last3_actnum) AS (
    SELECT 
    gen_name, gen_year, coalesce(act_num, 0), 
    SUM(coalesce(act_num,0)) OVER (PARTITION BY gen_name ORDER BY gen_year ROWS 2 PRECEDING) 
    FROM name_years 
    LEFT OUTER JOIN entity_year_activity ON (gen_name = name AND gen_year = year) 
) 
SELECT name, year, act_num, sum(last3_actnum) as total_3_years 
FROM windowed_history 
GROUP BY name, year, act_num 
HAVING sum(last3_actnum) <> 0 
ORDER BY name, year; 

Vedere SQLFiddle.

La necessità di generare voci per anni senza voce di accesso complica questa query. Genero una tabella di tutte le coppie (nome, anno), quindi left outer join entity_year_activity su di essa prima di fare la somma della finestra, quindi tutti gli anni per tutti i set di nomi sono rappresentati. Ecco perché è così complicato. Quindi filtro il risultato aggregato per escludere voci con zero nella somma.

+0

Anche questo non salta l'anno mancante, quindi non è corretto. –

+0

@GordonLinoff ... quindi "ancora". –

+0

@GordonLinoff Aggiornato –

2
SELECT en_key.name, en_key.year, en_key.act_num, SUM(en_sum.act_num) as total_3_years 
FROM entity_year_activity en_key 
    INNER JOIN entity_year_activity en_sum 
    ON en_key.name = en_sum.name 
WHERE en_sum.year BETWEEN en_key.year - 2 AND en_key.year 
GROUP BY en_key.name, en_key.year 
+0

Se risolvi questo problema spostando la clausola 'where' nella clausola' on' e rimuovendo 'en_key.year = en_sum.year', allora funzionerà. –

+0

Modificato per rimuovere en_key.year = en_sum.year. Ciò sconfigge lo scopo. Non sono sicuro se la clausola where debba essere spostata nella clausola on. Penso che funzionerebbe in entrambi i modi. Stilisticamente, non l'ho visto come parte del join, ma piuttosto come una condizione del report. Mi aspetto che sia una questione di opinione e preferenza. –

+0

. . Come pratica generale, le condizioni di join dovrebbero andare nella clausola 'on', quindi, in termini etici, essa appartiene piuttosto che nella clausola' where'. Tuttavia, le prestazioni delle due versioni dovrebbero essere le stesse. –

3

SQL Fiddle

select 
    s.name, 
    d "year", 
    coalesce(act_num, 0) act_num, 
    coalesce(act_num, 0) 
    + lag(coalesce(act_num, 0), 1, 0) over(partition by s.name order by d) 
    + lag(coalesce(act_num, 0), 2, 0) over(partition by s.name order by d) 
    total_3_years 
from 
    entity_year_activity eya 
    right join (
     generate_series(
      (select min("year") from entity_year_activity), 
      (select max("year") from entity_year_activity) 
     ) d cross join (
     select distinct name 
     from entity_year_activity 
     ) f 
    ) s on s.name = eya.name and s.d = eya."year" 
order by s.name, d 
+0

Questo non funziona, perché non salta anni senza dati. –

+0

@GordonLinoff La nuova versione lo fa. –

1

Un'altra prova. Questo manca gli anni di fila 0, però:

select t1.name, t1.year, t1.act_num, 
     (select sum(t2.act_num) from entity_year_activity t2 
           where t2.year between t1.year - 2 and t1.year 
            and t2.name = t1.name) total 
from entity_year_activity t1; 
Problemi correlati