Я только что закончила второй способ борьбы с вашими потребностями. Мне потребовалось два часа, и мне пришлось найти время, чтобы сделать это. Но мне было любопытно, так что это не пропало даром.
Преимущество этого подхода в том, что оно более гибкое. Это легче изменить, если вам нужно 4, 5 или 6 месяцев вместо 3, и вам не нужно учитывать возможность того, что компоненты будут NULL, потому что вы можете использовать обычный AVG() OVER().
Недостатком является гораздо более сложной фазы подготовки данных: Вы должны заполнить пробелы, содержащий NULLs для измерения, и создать список всех возможных новинок в месяц между наименьшим month1
и величайших month1
значений в базовой таблице , С этой целью я копирую предложение TIMESERIES от Vertica.
Решение содержит много вещей, которые, я думаю, будут полезны в наборе для выживания любого, кто углубляется в SQL, например, создавая встроенные таблицы последовательных целых чисел и временные ряды. Вот почему я создаю серию из 100 целых чисел, когда 7 будет достаточно. Это также показывает, что CROSS JOIN не всегда является катастрофой.
Я попытался достаточно прокомментировать, что я здесь делаю, надеюсь, этого достаточно.
-- WITHOUT RANGE BETWEEN - vertical version
WITH
-- the input data
foo(student_id,month1,fees) AS (
SELECT 'A1',DATE '2016-12-01',22
UNION ALL SELECT 'A1',DATE '2016-11-01',33
UNION ALL SELECT 'A1',DATE '2016-10-01',44
UNION ALL SELECT 'A1',DATE '2016-09-01',55
UNION ALL SELECT 'A1',DATE '2016-08-01',66
UNION ALL SELECT 'A1',DATE '2016-07-01',77
UNION ALL SELECT 'A1',DATE '2016-06-01',88
UNION ALL SELECT 'A2',DATE '2016-12-01',12
UNION ALL SELECT 'A2',DATE '2016-10-01',24
UNION ALL SELECT 'A2',DATE '2016-09-01',36
UNION ALL SELECT 'A2',DATE '2016-07-01',48
)
-- 1. Mimick Vertica's TIMESERIES clause:
-- Prepare a series of month-start dates
-- from the first month to the last month
-- of the time series. Assuming it's more than
-- 10 months:
-- 1.a A series of 100 ints starting from 0
-- 1.a.1 start with 10 ints
, ten_ints(idx) AS (
SELECT 0
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9
)
-- 1.a.2 make 100 out of 10
, idx_series AS (
SELECT
tens.idx * 10 + units.idx AS idx
FROM ten_ints units
CROSS JOIN ten_ints tens
)
-- 1.b get limit dates and total month count
, month_limits AS (
SELECT
MIN(month1) AS start_month
, MAX(month1) AS end_month
, MONTHS_BETWEEN(MAX(month1), MIN(month1)) AS monthcount
FROM foo
)
-- 1.c create an artificial list of all student_id
-- and all possible months to fill gaps
-- This is the end of the TIMESERIES mimick.
, student_month_list AS (
SELECT
student_id
, ADD_MONTHS(start_month,idx) AS month1
FROM month_limits
JOIN idx_series
ON idx <= monthcount
CROSS
JOIN (
SELECT DISTINCT student_id FROM foo
) bar
)
-- This returns:
-- student_id|month1
-- A1 |2016-06-01
-- A1 |2016-07-01
-- A1 |2016-08-01
-- A1 |2016-09-01
-- A1 |2016-10-01
-- A1 |2016-11-01
-- A1 |2016-12-01
-- A2 |2016-06-01
-- A2 |2016-07-01
-- A2 |2016-08-01
-- A2 |2016-09-01
-- A2 |2016-10-01
-- A2 |2016-11-01
-- A2 |2016-12-01
-- Main query:
-- left join student_month_list to the base table
-- and filter out the rows whose measure is NULL
SELECT
mth.student_id
, mth.month1
, AVG(foo.fees) OVER (
PARTITION BY mth.student_id ORDER BY mth.month1
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS running_avg_3months
FROM student_month_list mth
LEFT JOIN foo USING(student_id, month1)
WHERE foo.fees IS NOT NULL
ORDER BY 1,2
;
Ну, Teradata, не поддерживающая оговорку окна RANGE, была для меня неожиданностью. Должен сначала прочитать документ ... Но я только что опубликовал возможное обходное решение за минуту назад – marcothesane