2012-10-22 9 views
8

Załóżmy masz (w PostgreSQL 9.1) tabelę tak:GROUP BY kolejnych terminach ustalonych przez luk

date | value 

które mają pewne braki w nim (to znaczy: nie każdy możliwy termin pomiędzy min (data) i max (data) ma swój rząd).

Moim problemem jest to, jak agregować te dane tak, że każda grupa spójne (bez przerw) traktuje się osobno, tak:

min_date | max_date | [some aggregate of "value" column] 

Jakieś pomysły jak to zrobić? Wierzę, że jest to możliwe z funkcjami okna, ale po chwili próbując z lag() i lead() trochę utknąłem.

Na przykład, jeśli dane są tak:

date   | value 
---------------+------- 
2011-10-31 | 2 
2011-11-01 | 8 
2011-11-02 | 10 
2012-09-13 | 1 
2012-09-14 | 4 
2012-09-15 | 5 
2012-09-16 | 20 
2012-10-30 | 10 

wyjście (dla sum jako kruszywo) byłoby:

min  | max  | sum 
-----------+------------+------- 
2011-10-31 | 2011-11-02 | 20 
2012-09-13 | 2012-09-16 | 30 
2012-10-30 | 2012-10-30 | 10 
+1

Dane pocztowe i pożądane wyniki –

+0

Clodoaldo, dziękuję za zainteresowanie. na przykład, jeśli dane są takie: data \t | wartość --------------- + ------- 2011-10-31 | 2 2011-11-01 | 8 2011-11-02 | 10 2012-09-13 | 1 2012-09-14 | 4 2012-09-15 | 5 2012-09-16 | 20 2012-10-30 | 10 Dane wyjściowe (dla "sumy" jako agregatu) byłyby następujące: min | max | suma ----------- + ------------ + ------- 2011-10-31 | 2011-11-02 | 20 2012-09-13 | 2012-09-16 | 30 2012-10-30 | 2012-10-30 | 10 –

+0

Słowo, którego szukasz, jest * następujące po sobie *. Zobacz [tę odpowiedź] (http://stackoverflow.com/a/8015107/398670). –

Odpowiedz

8
create table t ("date" date, "value" int); 
insert into t ("date", "value") values 
    ('2011-10-31', 2), 
    ('2011-11-01', 8), 
    ('2011-11-02', 10), 
    ('2012-09-13', 1), 
    ('2012-09-14', 4), 
    ('2012-09-15', 5), 
    ('2012-09-16', 20), 
    ('2012-10-30', 10); 

prostsze i tańsze wersję:

select min("date"), max("date"), sum(value) 
from (
    select 
     "date", value, 
     "date" - (dense_rank() over(order by "date"))::int g 
    from t 
) s 
group by s.g 
order by 1 

Moja pierwsza próba była bardziej skomplikowana i kosztowna:

create temporary sequence s; 
select min("date"), max("date"), sum(value) 
from (
    select 
     "date", value, d, 
     case 
      when lag("date", 1, null) over(order by s.d) is null and "date" is not null 
       then nextval('s') 
      when lag("date", 1, null) over(order by s.d) is not null and "date" is not null 
       then lastval() 
      else 0 
     end g 
    from 
     t 
     right join 
     generate_series(
      (select min("date") from t)::date, 
      (select max("date") from t)::date + 1, 
      '1 day' 
     ) s(d) on s.d::date = t."date" 
) q 
where g != 0 
group by g 
order by 1 
; 
drop sequence s; 

Moc wyjściowa:

min  | max  | sum 
------------+------------+----- 
2011-10-31 | 2011-11-02 | 20 
2012-09-13 | 2012-09-16 | 30 
2012-10-30 | 2012-10-30 | 10 
(3 rows) 
+0

+1 w wersji dense_rank(). –

0

Oto sposobem rozwiązania go.

Po pierwsze, aby uzyskać początek kolejnej serii, to zapytanie nie daje pierwszą datę:

SELECT first.date 
FROM raw_data first 
    LEFT OUTER JOIN raw_data prior_first ON first.date = prior_first + 1 
WHERE prior_first IS NULL 

również na koniec kolejnego cyklu,

SELECT last.date 
FROM raw_data last 
    LEFT OUTER JOIN raw_data after_last ON last.date = after_last - 1 
WHERE after_last IS NULL 

Można rozważyć te widoki, aby uprościć zapytania za ich pomocą.

Musimy tylko pierwszą tworzą grupę waha

CREATE VIEW beginings AS 
SELECT first.date 
FROM raw_data first 
    LEFT OUTER JOIN raw_data prior_first ON first.date = prior_first + 1 
WHERE prior_first IS NULL 

CREATE VIEW endings AS 
SELECT last.date 
FROM raw_data last 
    LEFT OUTER JOIN raw_data after_last ON last.date = after_last - 1 
WHERE after_last IS NULL 

SELECT MIN(raw.date), MAX(raw.date), SUM(raw.value) 
FROM raw_data raw 
    INNER JOIN (SELECT lo.date AS lo_date, MIN(hi.date) as hi_date 
       FROM beginnings lo, endings hi 
       WHERE lo.date < hi.date 
       GROUP BY lo.date) range 
    ON raw.date >= range.lo_date AND raw.date <= range.hi_date 
GROUP BY range.lo_date