2012-10-22 31 views
8

Giả sử bạn có (trong Postgres 9.1) một bảng như thế này:GROUP BY ngày liên tiếp giới hạn bởi khoảng trống

date | value 

đó có một số lỗ hổng trong nó (tôi muốn nói: không phải mọi ngày có thể có giữa min (date) và tối đa (ngày) có hàng của nó).

Vấn đề của tôi là làm thế nào để tổng hợp dữ liệu này để từng nhóm phù hợp (không có khoảng trống) được xử lý riêng biệt, như thế này:

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

Bất kỳ ý tưởng làm thế nào để làm điều đó? Tôi tin rằng nó có thể với chức năng cửa sổ nhưng sau một thời gian cố gắng với lag()lead() Tôi là một chút khó khăn.

Ví dụ nếu dữ liệu là như thế này:

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 

đầu ra (cho sum như là tổng hợp) sẽ là:

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

Dữ liệu bài viết và kết quả mong muốn –

+0

Clodoaldo, cảm ơn bạn đã quan tâm. ví dụ: nếu dữ liệu như sau: ngày \t | giá trị --------------- + ------- 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 đầu ra (cho "tổng" là tổng hợp) sẽ là: phút | tối đa | tổng số ----------- + ------------ + ------- 2011-10-31 | 2011-11-02 | 20 2012-09-13 | 2012-09-16 | 30 2012-10-30 | 2012-10-30 | 10 –

+0

Từ bạn đang tìm kiếm là * liên tiếp *. Xem [câu trả lời này] (http://stackoverflow.com/a/8015107/398670). –

Trả lời

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

đơn giản hơn và phiên bản rẻ hơn:

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 

thử đầu tiên của tôi là phức tạp hơn và tốn kém:

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; 

Đầu ra:

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 trên phiên bản dense_rank(). –

0

Đây là một cách giải quyết nó.

Trước tiên, để có được sự khởi đầu của loạt bài liên tiếp, truy vấn này sẽ cung cấp cho bạn những ngày đầu tiên:

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 

tương tự như vậy cho đến cuối của loạt bài liên tiếp,

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 

Bạn có thể xem xét đưa ra những lượt xem, để đơn giản hóa các truy vấn sử dụng chúng.

Chúng tôi chỉ cần là người đầu tiên để tạo thành nhóm khoảng

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