2017-01-16 3 views
1

У меня есть данные в этом форматескользящего среднего в случае недостающего месяца в Teradata

student_id,month1,fees 
A1,201612,22 
A1,201611,33 
A1,201610,44 
A1,201609,55 
A1,201608,66 
A1,201607,77 
A1,201606,88 
A2,201612,12 
A2,201610,24 
A2,201609,36 
A2,201607,48 

Я хочу гонорары каждого студента с учетом средних трех последних месяца сборов означает для студентов А1, за месяц 201612, плата будет сумма (22,33,44)/3, так что я использовал этот запрос

(select student_id,month1,fees,(sum(fees) over(partition by 
student_id 
order by 
student_id 
, 
month1 
asc rows between 2 preceding and current row))/3 as avg1 from table where 
month1 
>(select trim(Add_Months(cast(trim(maxrepmonth) as DATE Format 'YYYYMM'),-5) (format 'YYYYMM')) from (select max(
month1 
) as maxrepmonth from table) z2) group by 1,2,3) 

и это прекрасно работает для студентов A1, поскольку это оказывает все месяцы данные, но в случае студент А2, за месяц 201612, что оно принимает сборы за эти месяцы 201612,201610,201609, что неверно, вместо этого он должен взять только с 201612,201610, так как 201611 пропущен ING. Пожалуйста, помогите.

Благодаря

+0

Ну, Teradata, не поддерживающая оговорку окна RANGE, была для меня неожиданностью. Должен сначала прочитать документ ... Но я только что опубликовал возможное обходное решение за минуту назад – marcothesane

ответ

0

функции OLAP от стандарта ANSI-99 ваш друг здесь - особенно пункт окно RANGE.

Попробуйте это - я делаю это с Vertica, но Teradata должен быть так же, как ANSI уступчивым и сделать это для вас тоже:

WITH 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 
    ) 
    SELECT 
     * 
    , AVG(fees) OVER (
      PARTITION BY student_id ORDER BY month1 
      RANGE BETWEEN INTERVAL '3 MONTHS' PRECEDING AND CURRENT ROW 
     ) AS rolling_avg_3_months 
    FROM foo; 

student_id|month1 |fees|rolling_avg_3_months 
A1  |2016-06-01| 88|     88 
A1  |2016-07-01| 77|    82.5 
A1  |2016-08-01| 66|     77 
A1  |2016-09-01| 55|     66 
A1  |2016-10-01| 44|     55 
A1  |2016-11-01| 33|     44 
A1  |2016-12-01| 22|     33 
A2  |2016-07-01| 48|     48 
A2  |2016-09-01| 36|     42 
A2  |2016-10-01| 24|     30 
A2  |2016-12-01| 12|     18 
+0

Teradata не поддерживает RANGE –

+0

Привет, Он дает ошибку «ожидается сброс слова» или «) после предложения по порядку« Не поддерживается диапазон интервал в терадате. –

0

Благодаря Дуду, благодаря Amit

Так Teradata делает не поддерживают ДИАПАЗОН ...

Теперь становится сложнее.

Нашел рабочее решение, но ему нужно некоторое объяснение.

Надеюсь, что комментариев достаточно.

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 
) 
-- add two columns fees1before and fees2before, that can be null, 
-- containing the fees of the two previous rows if the 'month1' 
-- value of those rows is less than 3 months back 
, foo_3_months AS (
SELECT 
    student_id 
, month1 
, fees AS fees_now 
, CASE 
    WHEN 
     MONTHS_BETWEEN(month1,LAG(month1) OVER (PARTITION BY student_id ORDER BY month1)) 
    < 3 
    THEN LAG(fees) OVER (PARTITION BY student_id ORDER BY month1) 
    END AS fees_1before 
, CASE 
    WHEN MONTHS_BETWEEN(month1,LAG(month1,2) OVER (PARTITION BY student_id ORDER BY month1)) 
    < 3 
    THEN LAG(fees,2) OVER (PARTITION BY student_id ORDER BY month1) 
    END AS fees_2before 
    FROM foo 
) 
-- finally, build a hard-wired average formula that takes care of 
-- the fact that two of the three values can be NULL 
-- I'm keeping the two additional columns for debugging purposes. 
-- They can be removed in the end. 
SELECT 
    * 
, (fees_now+NVL(fees_1before,0)+NVL(fees_2before,0)) 
/(
    1 
    + (CASE WHEN fees_1before IS NOT NULL THEN 1 ELSE 0 END) 
    + (CASE WHEN fees_2before IS NOT NULL THEN 1 ELSE 0 END) 
) 
AS rolling_avg_3months 
FROM foo_3_months 
; 

Вот результат:

student_id|month1 |fees_now|fees_1before|fees_2before|rolling_avg_3months 
A1  |2016-06-01|  88|-   |-   |88.000000000000000000 
A1  |2016-07-01|  77|   88|-   |82.500000000000000000 
A1  |2016-08-01|  66|   77|   88|77.000000000000000000 
A1  |2016-09-01|  55|   66|   77|66.000000000000000000 
A1  |2016-10-01|  44|   55|   66|55.000000000000000000 
A1  |2016-11-01|  33|   44|   55|44.000000000000000000 
A1  |2016-12-01|  22|   33|   44|33.000000000000000000 
A2  |2016-07-01|  48|-   |-   |48.000000000000000000 
A2  |2016-09-01|  36|   48|-   |42.000000000000000000 
A2  |2016-10-01|  24|   36|-   |30.000000000000000000 
A2  |2016-12-01|  12|   24|-   |18.000000000000000000 

Не простая задача - и, возможно, запрос на доработку в Teradata?

Счастливые игры - Marco здравомыслящего

0

Я только что закончила второй способ борьбы с вашими потребностями. Мне потребовалось два часа, и мне пришлось найти время, чтобы сделать это. Но мне было любопытно, так что это не пропало даром.

Преимущество этого подхода в том, что оно более гибкое. Это легче изменить, если вам нужно 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 
; 
Смежные вопросы