2012-10-22 14 views
8

Si supponga di avere (in Postgres 9.1) una tabella come questa:GROUP BY date consecutive delimitati da spazi vuoti

date | value 

che hanno alcune lacune in esso (voglio dire: non ogni possibile data tra min (data) e max (data) ha la sua riga).

Il mio problema è come aggregare questi dati in modo che ogni gruppo consistente (senza lacune) viene trattato separatamente, in questo modo:

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

Delle idee come farlo? Credo che sia possibile con le funzioni della finestra, ma dopo un po 'provando con lag() e lead() sono un po' bloccato.

Per esempio, se i dati sono simili:

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 

l'uscita (per sum come aggregato) sarebbe:

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

Dati di post e output desiderato –

+0

Clodoaldo, grazie per il vostro interesse. ad esempio se i dati sono in questo modo: data \t | valore --------------- + ------- 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 l'uscita (per "somma" come aggregato) sarebbe: min | max | sum ----------- + ------------ + ------- 2011-10-31 | 2011-11-02 | 20 2012-09-13 | 2012-09-16 | 30 2012-10-30 | 2012-10-30 | 10 –

+0

La parola che stai cercando è * consecutiva *. Vedi [questa risposta] (http://stackoverflow.com/a/8015107/398670). –

risposta

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); 

semplice e versione più economica:

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 

Il mio primo tentativo era più complessa e costosa:

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; 

L'output:

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 sulla versione dense_rank(). –

0

Ecco modo di risolverlo.

In primo luogo, per ottenere l'inizio della serie consecutiva, questa query darebbe la prima data:

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 

allo stesso modo per la fine della serie consecutiva,

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 

Si potrebbe considerare la possibilità di questi viste, per semplificare le query che li utilizzano.

abbiamo solo bisogno il primo a formare gruppo spazia

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