Реализация SCD типа 2 в Oracle
Во-первых, я хотел бы сказать, что я новичок в сообществе stackru и относительно плохо знаком с самим SQL, поэтому прошу прощения, если я неправильно отформатировал свой вопрос или не сформулировал свои требования четко.
Я пытаюсь реализовать тип 2 SCD в Oracle. Структура исходной таблицы (customer_records
) приводится ниже.
CREATE TABLE customer_records(
day date,
snapshot_day_dw_id number,
vend_loy_acct_dw_id number,
hub_cust_id number,
cur_loy_tier_dw_id number
);
INSERT INTO customer_records
(day,snapshot_day_dw_id,vend_loy_acct_dw_id,hub_cust_id,cur_loy_tier_dw_id)
VALUES
(9/24/2014,6266,71047795,476095,3103),
(10/1/2014,6273,71047795,476095,3103),
(10/8/2014,6280,71047795,476095,3103),
(10/15/2014,6287,71047795,476095,3103),
(10/22/2014,6291,71047795,476095,3102),
(10/29/2014,6330,71047795,476095,3102),
(11/05/2015,6351,71047795,476095,3102),
(11/12/2015,6440,71047795,476095,3103);
Таблица выше обновляется еженедельно, и я вытащил записи для конкретного клиента, представленного vend_loy_acct_dw_id
а также hub_cust_id
, Так что каждый клиент будет иметь уникальный vend_loy_acct_dw_id
а также hub_cust_id
, Я пытаюсь отслеживать изменения в уровне (cur_loy_tier_dw_id
) клиента. Может так случиться, что уровень клиента может оставаться неизменным в течение нескольких недель, и мы готовы отслеживать только изменения уровня клиента.
Желаемый результат (таблица измерений) будет выглядеть примерно так:
SK Version Date_From Date_To Vend_Loy_Acct_Dw_Id Hub_Cust_Id Cur_Cust_Tier_Id
1 1 9/24/2014 10/22/2014 71047795 476095 3103
2 2 10/22/2014 11/05/2015 71047795 476095 3102
3 3 11/05/2015 12/31/2199 71047795 476095 3103
Так что, когда уровень клиента попадает в какое-либо изменение, мы отслеживаем это в новой таблице. Кроме того, желая включить current_flag
= 'Y' для самого текущего уровня.
Я хочу быть в состоянии сделать это с помощью слияния.
2 ответа
Я хочу быть в состоянии сделать это с помощью слияния.
Мердж не сделает это за вас. MERGE - это, в основном, оператор case: для каждой записи в подзапросе USING мы можем вставлять сопоставленные записи или обновлять сопоставленные записи. Подвох заключается в том, что при изменении уровня существующего клиента необходимо выполнить DML для двух записей измерений:
- обновить предыдущую текущую запись - установить
current_flag
= 'N', установленоday_to
вsystimestamp
(или что угодно). - вставить новую текущую запись.
Таким образом, у вас должен быть процесс - возможно, процедура PL/SQL - который выполняет инструкцию UPDATE, чтобы закрыть текущие записи с истекшим сроком действия, а затем INSERT для добавления новых текущих записей.
Подзапрос не может быть лучшим маршрутом, я верю.
Вы описываете себя как относительно нового для SQL, поэтому вы можете беспокоиться об этом, но не волнуйтесь. Избегайте преждевременной оптимизации. Сделайте самое простое, что может сработать, и настройте его по мере необходимости. Подзапрос должен быть наиболее эффективным способом определения текущих записей, которые необходимо обновить. Базы данных Oracle являются рабочими лошадками и могут справляться с существенными нагрузками, если мы пишем разумный SQL.
В вашем случае это означает:
- используйте операции над множествами (то есть не построчно) для UPDATE и INSERT.
- убедитесь, что вы работаете с наименьшим необходимым набором записей. Применяйте изменения только к записям в базовой таблице, которые изменились с момента последнего обновления измерения. В вашем случае вам нужно отслеживать
customer_records.snapshot_day_dw_id
и применять изменения только для записей, которые имеют более высокийsnapshot_day_dw_id
(а может и нет, я догадываюсь на вашем процессе). - правильно индексировать таблицу измерений, чтобы она эффективно применяла подзапрос.
Вот подход к группированию последовательных записей, имеющих один и тот же уровень, при обнаружении изменений.
Идея состоит в том, чтобы самостоятельно присоединиться к таблице и связать каждую запись со следующей записью, имеющей другой уровень. Это делается с помощью NOT EXISTS
условие с коррелированным подзапросом.
LEFT JOIN
необходимо, чтобы избежать фильтрации последней записи (которой принадлежит текущий уровень), у которой еще нет следующей записи: для этой записи мы используем COALESCE()
установить дату окончания по умолчанию.
SELECT
c1.day day_from,
COALESCE(c2.day, TO_DATE('2199-12-31', 'yyyy-mm-dd')) day_to,
c1.VEND_LOY_ACCT_DW_ID,
c1.HUB_CUST_ID,
c1.CUR_LOY_TIER_DW_ID
FROM customer_records c1
LEFT JOIN customer_records c2
ON c2.VEND_LOY_ACCT_DW_ID = c1.VEND_LOY_ACCT_DW_ID
AND c2.HUB_CUST_ID = c1.HUB_CUST_ID
AND c2.CUR_LOY_TIER_DW_ID <> c1.CUR_LOY_TIER_DW_ID
AND c2.DAY > c1.DAY
AND NOT EXISTS (
SELECT 1
FROM customer_records c3
WHERE
c3.VEND_LOY_ACCT_DW_ID = c1.VEND_LOY_ACCT_DW_ID
AND c3.HUB_CUST_ID = c1.HUB_CUST_ID
AND c3.CUR_LOY_TIER_DW_ID <> c1.CUR_LOY_TIER_DW_ID
AND c3.DAY > c1.DAY
AND c3.DAY < c2.DAY
)
Это возвращает:
DAY_FROM | DAY_TO | VEND_LOY_ACCT_DW_ID | HUB_CUST_ID | CUR_LOY_TIER_DW_ID
:-------- | :-------- | ------------------: | ----------: | -----------------:
24-SEP-14 | 22-OCT-14 | 71047795 | 476095 | 3103
01-OCT-14 | 22-OCT-14 | 71047795 | 476095 | 3103
08-OCT-14 | 22-OCT-14 | 71047795 | 476095 | 3103
15-OCT-14 | 22-OCT-14 | 71047795 | 476095 | 3103
22-OCT-14 | 12-NOV-15 | 71047795 | 476095 | 3102
29-OCT-14 | 12-NOV-15 | 71047795 | 476095 | 3102
05-NOV-15 | 12-NOV-15 | 71047795 | 476095 | 3102
12-NOV-15 | 31-DEC-99 | 71047795 | 476095 | 3103
Теперь мы можем сгруппировать набор записей по уровню и дате окончания, чтобы получить ожидаемые результаты. ROW_NUMBER()
могу дать вам номер версии. Также легко проверить, какая запись является текущей, как описано выше.
SELECT
ROW_NUMBER() OVER(ORDER BY c2.day) version,
DECODE(c2.day, NULL, 'Y') current_flag,
MIN(c1.day) day_from,
COALESCE(c2.day, TO_DATE('2199-12-31', 'yyyy-mm-dd')) day_to,
c1.VEND_LOY_ACCT_DW_ID,
c1.HUB_CUST_ID,
c1.CUR_LOY_TIER_DW_ID
FROM customer_records c1
LEFT JOIN customer_records c2
ON c2.VEND_LOY_ACCT_DW_ID = c1.VEND_LOY_ACCT_DW_ID
AND c2.HUB_CUST_ID = c1.HUB_CUST_ID
AND c2.CUR_LOY_TIER_DW_ID <> c1.CUR_LOY_TIER_DW_ID
AND c2.DAY > c1.DAY
AND NOT EXISTS (
SELECT 1
FROM customer_records c3
WHERE
c3.VEND_LOY_ACCT_DW_ID = c1.VEND_LOY_ACCT_DW_ID
AND c3.HUB_CUST_ID = c1.HUB_CUST_ID
AND c3.CUR_LOY_TIER_DW_ID <> c1.CUR_LOY_TIER_DW_ID
AND c3.DAY > c1.DAY
AND c3.DAY < c2.DAY
)
GROUP BY
c1.VEND_LOY_ACCT_DW_ID,
c1.HUB_CUST_ID,
c1.CUR_LOY_TIER_DW_ID,
c2.day
ORDER BY
day_from
Результаты:
ВЕРСИЯ | CURRENT_FLAG | DAY_FROM | DAY_TO | VEND_LOY_ACCT_DW_ID | HUB_CUST_ID | CUR_LOY_TIER_DW_ID ------: |:----------- |:-------- |:-------- | ------------------: | ----------: | -----------------: 1 | N | 24-СЕНТЯБРЯ-14 | 22-ОКТ-14 | 71047795 | 476095 | 3103 2 | N | 22-ОКТ-14 | 12-НОЯБРЯ-15 | 71047795 | 476095 | 3102 3 | Y | 12-НОЯБРЯ-15 | 31-DEC-99 | 71047795 | 476095 | 3103
В Oracle вы можете превратить любой выбор в запрос слияния, используя синтаксис MERGE. Вы можете сопоставить все ожидаемые столбцы current_flag
а также day_to
и обновите их, если запись уже существует; иначе просто вставьте новый.
MERGE INTO dimensions dim
USING (
-- above query goes here --
) cust
ON dim.DAY_FROM = cust.DAY_FROM
AND dim.VEND_LOY_ACCT_DW_ID = cust.VEND_LOY_ACCT_DW_ID
AND dim.HUB_CUST_ID = cust.HUB_CUST_ID
AND dim.CUR_LOY_TIER_DW_ID = cust.CUR_LOY_TIER_DW_ID
WHEN MATCHED THEN UPDATE SET
dim.DAY_TO = cust.DAY_TO,
dim.CURRENT_FLAG = cust.CURRENT_FLAG
WHEN NOT MATCHED THEN
INSERT (
dim.DAY_FROM,
dim.VERSION,
dim.CURRENT_FLAG,
dim.DAY_FROM,
dim.DAY_TO,
dim.VEND_LOY_ACCT_DW_ID,
dim.HUB_CUST_ID,
dim.CUR_LOY_TIER_DW_ID
) VALUES (
cust.DAY_FROM,
cust.VERSION,
cust.CURRENT_FLAG,
cust.DAY_FROM,
cust.DAY_TO,
cust.VEND_LOY_ACCT_DW_ID,
cust.HUB_CUST_ID,
cust.CUR_LOY_TIER_DW_ID
)