2012-10-25 8 views
5

Mam tabelę z nazwą podmiotu, rokiem i numerem aktywności pod numerem poniżej. Przez kilka lat nie ma żadnej aktywności.Obliczanie sumy wyjściowej począwszy od x lat przed

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 

Aby utworzyć go na 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); 

chciałbym mieć całkowitą liczbę ostatnie x lat z numerem tegorocznych działań dla każdego podmiotu za każdy rok jak mieszka.

Jako przykład dla x = trzech lat.

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

Świetne pytanie. Przykładowe dane, oczekiwany wynik, DDL. Warto zauważyć, że Stack Overflow nie zachowuje kart, więc twoje wyjście "COPY" zostało zmanipulowane. Lepiej używać 'COPY ... CSV'. –

Odpowiedz

3

Oto podejście, które wykorzystuje możliwość korzystania z kruszywa sum jako funkcja okna z ramy okna zakres własnych - patrz SUM(...) OVER (PARTITION BY name ORDER BY year ROWS 2 PRECEDING) i 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; 

Zobacz SQLFiddle.

Potrzeba generowania pozycji przez lata bez własnego wpisu komplikuje to zapytanie. Generuję tabelę wszystkich par (nazwa, rok), a następnie left outer join entity_year_activity przed wykonaniem sumy okna, więc reprezentowane są wszystkie lata dla wszystkich zestawów nazw. Dlatego jest to tak skomplikowane. Następnie filtruje zagregowany wynik, aby wykluczyć z sumy pozycje o wartości zero.

+0

To również nie pomija brakującego roku, więc nie jest poprawne. –

+0

@GordonLinoff ... stąd "jeszcze". –

+0

@GordonLinoff Zaktualizowano –

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

Jeśli to naprawisz, przenosząc klauzulę 'where' do klauzuli' on' i usuwając 'en_key.year = en_sum.year', to będzie działać. –

+0

Edytowane w celu usunięcia en_key.year = en_sum.year. To pokonuje cel. Nie jestem pewien, czy klauzula where musi zostać przeniesiona do klauzuli on. Myślę, że to działałoby tak czy inaczej. Stylistycznie nie widziałem tego jako elementu połączenia, ale raczej warunek w raporcie. Oczekuję, że jest to kwestia opinii i preferencji. –

+0

. . Jako ogólną praktykę, warunki łączenia powinny iść w klauzuli "on", więc w sensie estetycznym należy do nich raczej niż w klauzuli "where". Jednak wydajność obu wersji powinna być taka sama. –

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

To nie działa, ponieważ nie pomija lat bez danych. –

+0

@GordonLinoff Nowa wersja ma. –

1

Kolejna próba. Ten brakuje jednak rzędu 0 wierszy:

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; 
Powiązane problemy