Tính tổng số cán trong khoảng thời gian 7 ngày liên tiếp trên PostgreSQL


9

Tôi cần nhận được số tiền trong vòng 7 ngày cho mỗi hàng (1 hàng mỗi ngày).

Ví dụ:

| Date       | Count | 7-Day Rolling Sum |
------------------------------------------
| 2016-02-01 | 1     | 1
| 2016-02-02 | 1     | 2
| 2016-02-03 | 2     | 4
| 2016-02-04 | 2     | 6
| 2016-02-05 | 2     | 8
| 2016-02-06 | 2     | 10
| 2016-02-07 | 2     | 12
| 2016-02-08 | 2     | 13 --> here we start summing from 02-02
| 2016-02-09 | 2     | 14 --> here we start summing from 02-03
| 2016-02-10 | 5     | 17 --> here we start summing from 02-04

Tôi cần điều này trong một truy vấn trả về các hàng với tổng số 7 ngày và ngày của ngày cuối cùng của phạm vi tổng. Ví dụ: ngày = 2016-02-10, tổng 17.

Cho đến nay tôi có cái này nhưng nó không hoạt động đầy đủ:

DO
$do$
DECLARE 
    curr_date date;
    num bigint;
BEGIN
FOR curr_date IN (SELECT date_trunc('day', d)::date FROM generate_series(CURRENT_DATE-31, CURRENT_DATE-1, '1 day'::interval) d)
LOOP 
    SELECT curr_date, SUM(count)
    FROM generate_series (curr_date-8, curr_date-1, '1 day'::interval) d
    LEFT JOIN m.ping AS p ON p.date = d
    LEFT JOIN m.ping_type AS pt ON pt.id = p.ping_type_id
    LEFT JOIN m.ping_frequency AS pf ON pf.id = p.ping_frequency_id
    WHERE
        pt.url_slug = 'active' AND
        pf.url_slug = 'weekly';
END LOOP;
END
$do$;

Tôi đang sử dụng PostgreSQL 9.4.5. Có thể có nhiều hàng với cùng một ngày. Nếu có một khoảng cách (thiếu một ngày), phạm vi 7 ngày liên tục sẽ vẫn được theo dõi.


Bạn có thể xem tổng cứ 3 hàng của một bảng trên Stack Overflow.
Luân Huỳnh

Câu trả lời:


11

Cho đến nay, giải pháp sạch nhất là sử dụng chức năng cửa sổ sumvới rows between:

with days as (
        SELECT date_trunc('day', d)::date as day
        FROM generate_series(CURRENT_DATE-31, CURRENT_DATE-1, '1 day'::interval) d ),
    counts as (
        select 
            days.day,
            sum((random()*5)::integer) num
        FROM days
        -- left join other tables here to get counts, I'm using random
        group by days.day
    )
select
    day,
    num,
    sum(num) over (order by day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
from counts
order by day;

Phần quan trọng là tạo khung thời gian trong daysCTE và tham gia vào nó để không bỏ lỡ bất kỳ ngày nào không có dữ liệu.

Thí dụ

Ví dụ: nếu tôi tạo một số dữ liệu thử nghiệm với 20 bản ghi trong 14 ngày qua:

SELECT (current_date - ((random()*14)::integer::text || 'days')::interval)::date as day, (random()*7)::integer as num
into test_data from generate_series(1, 20);;

Và cũng thêm một giá trị trước đó:

insert into test_data values ((current_date - '25 days'::interval), 5);

Sau đó sử dụng truy vấn trên:

with days as (
        SELECT date_trunc('day', d)::date as day
        FROM generate_series(CURRENT_DATE-31, CURRENT_DATE-1, '1 day'::interval) d ),
    counts as (
        select 
            days.day,
            sum(t.num) num
        FROM days
        left join test_data t on t.day = days.day
        group by days.day
    )
select
    day,
    num,
    sum(num) over (order by day rows between 6 preceding and current row)
from counts
order by day;

Và nhận được kết quả cho cả tháng:

    day     | num | sum 
------------+-----+-----
 2016-01-31 |     |    
 2016-02-01 |     |    
 2016-02-02 |     |    
 2016-02-03 |     |    
 2016-02-04 |     |    
 2016-02-05 |     |    
 2016-02-06 |   5 |   5
 2016-02-07 |     |   5
 2016-02-08 |     |   5
 2016-02-09 |     |   5
 2016-02-10 |     |   5
 2016-02-11 |     |   5
 2016-02-12 |     |   5
 2016-02-13 |     |    
 2016-02-14 |     |    
 2016-02-15 |     |    
 2016-02-16 |     |    
 2016-02-17 |     |    
 2016-02-18 |   2 |   2
 2016-02-19 |   5 |   7
 2016-02-20 |     |   7
 2016-02-21 |   4 |  11
 2016-02-22 |  15 |  26
 2016-02-23 |   1 |  27
 2016-02-24 |   1 |  28
 2016-02-25 |   2 |  28
 2016-02-26 |   4 |  27
 2016-02-27 |   9 |  36
 2016-02-28 |   5 |  37
 2016-02-29 |  11 |  33
 2016-03-01 |   5 |  37
(31 rows)

Điều này là hoàn toàn tốt nếu không có khoảng trống và chỉ có 1 hàng mỗi ngày. Nếu có khoảng trống hoặc nhiều hàng mỗi ngày (như OP mô tả) và chúng tôi muốn tổng số cho các hàng rơi trong 7 ngày qua (từ hôm nay đến 6 ngày trước), thì điều này sẽ không hoạt động. Mặc dù vậy, tôi không chắc chắn OP là gì sau đó, ngay cả sau khi họ làm rõ, vì vậy hãy lấy +1.
ypercubeᵀᴹ

Không, điều này là ổn. Đó là lý do tại sao bạn phải tham gia daysvà đó là lý do tại sao có nhóm counts.
hruske

Ah, tôi nghĩ rằng bạn có điều đó chỉ để tạo ra dữ liệu ngẫu nhiên. Có lẽ bạn có thể làm rõ hơn phần nào là dữ liệu và tính toán là gì. Có vẻ đúng, đúng vậy.
ypercubeᵀᴹ

0

Đã kết thúc bằng FOR LOOP, bảng TEMP và CHỌN trên bảng tạm thời sau khi vòng lặp for được thực hiện:

DO
$do$
DECLARE 
    curr_date DATE;
BEGIN

-- Create temp table to hold results
DROP TABLE IF EXISTS rolling_7day_sum;
CREATE TEMP TABLE rolling_7day_sum (
    date DATE,
    count BIGINT
);

-- Iterate dates and get 7 day rolling sum for each
FOR curr_date IN (SELECT date_trunc('day', d)::date FROM generate_series(
    -- Get earliest date from table
    (
        SELECT date FROM m.ping AS p
            LEFT JOIN m.ping_type AS pt ON pt.id = p.ping_type_id
            LEFT JOIN m.ping_frequency AS pf ON pf.id = p.ping_frequency_id
        WHERE
            pt.url_slug = 'active' AND
            pf.url_slug = 'weekly'
        ORDER BY date ASC
        LIMIT 1
    ), CURRENT_DATE-1, '1 day'::interval) d)
LOOP
    INSERT INTO rolling_7day_sum 
        SELECT curr_date, SUM(count)
            FROM generate_series (curr_date-8, curr_date-1, '1 day'::interval) d
                LEFT JOIN m.ping AS p ON p.date = d
                LEFT JOIN m.ping_type AS pt ON pt.id = p.ping_type_id
                LEFT JOIN m.ping_frequency AS pf ON pf.id = p.ping_frequency_id
            WHERE
                pt.url_slug = 'active' AND
                pf.url_slug = 'weekly';
END LOOP;
END
$do$;

SELECT date, count FROM rolling_7day_sum ORDER BY date ASC;

Nhưng tôi tưởng tượng có một cách làm sạch 7 lần liên tiếp hơn thế này.


Bạn có thể có thể thực hiện một truy vấn đệ quy đi sâu 7 cấp.
Joishi Bodio

0

Một truy vấn SQL đệ quy đi sâu 7 có thể hoạt động, nhưng tôi không biết nó hiệu quả đến mức nào.

WITH RECURSIVE totals(start_day, end_day, total, depth) AS (
    SELECT date, date, count, 1 FROM table
  UNION ALL
    SELECT
      t.start_day,
      t.start_day + INTERVAL '1 day',
      total + COALESCE((SELECT count FROM table WHERE date = t.start_day + INTERVAL '1 day'), 0),
      t.depth + 1
    FROM totals t
) SELECT
  *
FROM totals
WHERE end_day = '2016-03-01' AND depth = 7;

Không được kiểm tra cú pháp hay bất cứ điều gì.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.