各行の7日間のローリング合計を取得する必要があります(1日あたり1行)。
例えば:
| 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
7日ローリング合計と合計範囲の最終日の日付を含む行を返す1つのクエリでこれが必要です。たとえば、day = 2016-02-10、合計17。
これまでのところ私はこれを持っていますが、完全には機能していません:
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$;
PostgreSQL 9.4.5を使用しています。同じ日付の複数の行が存在する可能性があります。ギャップがある場合(1日が欠落している場合)は、7日連続の範囲が引き続き使用されます。
はるかにクリーンな解決策は、ウィンドウ関数sum
を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;
重要な部分は、days
CTEでタイムフレームを生成し、それに参加して、データがない日を逃さないようにすることです。
例
たとえば、過去14日間に20レコードのテストデータを作成した場合:
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);;
また、その前に値を追加します。
insert into test_data values ((current_date - '25 days'::interval), 5);
次に、上記のクエリを使用します。
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;
そして、1か月分の結果を取得します。
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)
Forループが完了すると、FOR LOOP、TEMPテーブル、および一時テーブルのSELECTを使用することになります。
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;
しかし、私はこれよりも7連続のローリングサムを行うよりクリーンな方法があると思います。
深さ7の再帰的SQLクエリは機能する可能性がありますが、それがどれほど効率的かはわかりません。
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;
構文などについてはテストされていません。