На дворе февраль. И я хочу рассказать о пути решения следующей проблемы:
Как видно из скриншота - при детализации YearAGO-показателя до разреза дней в феврале года, следующего за високосным, total-значение по дням не равно значению показателя в разрезе месяца.
Очевидно, проблема возникает из-за того, что в феврале 2013 года 28 дней, а в феврале 2012 - 29 дней.
В приведенном примере YAGO-показатель определяется стандартной time-series функцией AGO. Но этого недостаточно...
Итак, как обработать подобные ситуации?
Для начала продемонстрирую исходные метаданные используемого примера.
где таблицы D_CALEDAR_DAY и F_SALES определяются и заполняются следующим образом:
---------------------------- drop table D_CALENDAR_DAY; create table D_CALENDAR_DAY ( day_key DATE not null, year_key INTEGER not null, half_year_key INTEGER not null, half_year_name VARCHAR2(20), quarter_key INTEGER not null, quarter_name VARCHAR2(20), month_key INTEGER not null, month_year VARCHAR2(20), month_num INTEGER, month_name VARCHAR2(20), week_start_key DATE not null, week_key INTEGER, week_month VARCHAR2(20), week_year INTEGER, week_year_with_start_dt VARCHAR2(20), day_year INTEGER, day_month INTEGER, day_week INTEGER, day VARCHAR2(30), date_name VARCHAR2(20), holiday_flag CHAR(1) ) / ---------------------------- truncate table D_CALENDAR_DAY; DECLARE StartDate date := TO_DATE('01.01.2000', 'DD.MM.YYYY'); EndDate date := TO_DATE('31.12.2015', 'DD.MM.YYYY'); LastDate date := TO_DATE('31.12.2999', 'DD.MM.YYYY'); MiddleDate date; BEGIN execute immediate 'alter session set nls_territory = CIS'; execute immediate 'alter session set nls_language = RUSSIAN'; MiddleDate := StartDate; loop insert into D_CALENDAR_DAY (day_key, year_key, half_year_key, half_year_name, quarter_key, quarter_name, month_key, month_year, month_name, month_num, week_key, week_month, week_year, week_start_key, week_year_with_start_dt, day_year, day_month, day_week, day, date_name, holiday_flag) select MiddleDate day_key, to_char(MiddleDate, 'YYYY') year_key, to_char(MiddleDate, 'YYYY') || decode(to_char(MiddleDate, 'Q'), '1', '1', '2', '1', '2') half_year_key, to_char(MiddleDate, 'YYYY') || 'г. ' || decode(to_char(MiddleDate, 'Q'), '1', '1', '2', '1', '2') || 'п.г.' half_year_name, to_char(MiddleDate, 'YYYYQ') quarter_key, to_char(MiddleDate, 'YYYY') || 'г. ' || to_char(MiddleDate, 'Q') || 'кв.' quarter_name, to_char(MiddleDate, 'YYYYMM') month_key, to_char(MiddleDate, 'YYYY.MM') month_year, to_char(MiddleDate, 'fmMonth') month_name, to_char(MiddleDate, 'fmMM') month_num, to_char(MiddleDate, 'YYYY') * 100 + CEIL((TO_CHAR(MiddleDate, 'DDD') + TO_CHAR(TRUNC(MiddleDate, 'YEAR'), 'D') - 1) / 7) week_key, trunc((to_number(to_char(MiddleDate, 'dd')) + to_number(to_char(trunc(MiddleDate, 'month'), 'd')) + 5) / 7) week_month, CEIL((TO_CHAR(MiddleDate, 'DDD') + TO_CHAR(TRUNC(MiddleDate, 'YEAR'), 'D') - 1) / 7) week_year, (MiddleDate - to_char(MiddleDate, 'D') + 1) week_start_key, to_char(CEIL((TO_CHAR(MiddleDate, 'DDD') + TO_CHAR(TRUNC(MiddleDate, 'YEAR'), 'D') - 1) / 7),'fm09') || ' - ' || to_char((MiddleDate - to_char(MiddleDate, 'D') + 1), 'DD.MM.YYYY') week_year_with_start_dt, to_char(MiddleDate, 'DDD') day_year, to_char(MiddleDate, 'DD') day_month, to_char(MiddleDate, 'D') day_week, to_char(MiddleDate, 'fmDay') day, to_char(MiddleDate, 'DD.MM.YYYY') date_name, decode(to_char(MiddleDate, 'D'), '6', 'H', '7', 'H', 'W') holiday_flag from dual; MiddleDate := MiddleDate + 1; exit when MiddleDate > LastDate; if MiddleDate > EndDate then MiddleDate := LastDate; end if; end loop; commit; dbms_stats.gather_table_stats(ownname => USER, tabname => 'D_CALENDAR_DAY', cascade => true); END; / ---------------------------- drop table F_SALES; create table F_SALES ( day_key DATE not null, sales_amt NUMBER ) / ---------------------------- insert into F_SALES (DAY_KEY, SALES_AMT) select d.day_key, round(dbms_random.value * 100) as sales_amt from D_CALENDAR_DAY d where d.year_key in (2011, 2012, 2013) / ----------------------------
Как вы видите, ничего необычного.
Стандартный подход по реализации YAGO-показателей через функцию AGO.
Я же предлагаю отбросить этот подход (если вы смотрели на физические SQL-запросы, которые генерирует BIServer для AGO-показателей, то знаете - они достаточно "тяжеловесны").
Идея в том, чтобы для каждой таблицы фактов (по которой нужны AGO-показатели) был создан алиас на физическом слое RPD.
Также необходимо в таблицу-календарь добавить ключ со смещением на -12 месяцев (YAGO_DAY_KEY).
И связать таблицу-календарь и новый алиас на фактовую таблицу через джойн
Calendar.YAGO_DAY_KEY = Fact.DAY_KEY
Затем следует добавить новый алиас фактовой таблицы как Logical Table Source в логическую фактовую таблицу и переименовать показатель из этого LTS - например, дать префикс "YAGO".
В нашем случае все немного сложнее: нужно размножить строки в таблице календаря для обработки ситуации "29 февраля".
То есть нам необходимо, чтобы строке календаря с ключом 20130228 соответствовало две "YAGO-строки" - 20120228 и 20120229.
Да-да, мы относим YAGO-показатель 29 февраля к 28 февраля текущего года. Я думаю, это небольшая плата за неизменность отображения YAGO-показателя при детализации от месяца к дням...
Итак, я создам дополнительную таблицу D_CALENDAR_DAY_YAGO_LINK
---------------------------- create table D_CALENDAR_DAY_YAGO_LINK (YAGO_DAY_KEY date, DAY_KEY date) / ---------------------------- alter table D_CALENDAR_DAY_YAGO_LINK add primary key (YAGO_DAY_KEY, DAY_KEY) / ---------------------------- insert into D_CALENDAR_DAY_YAGO_LINK (YAGO_DAY_KEY, DAY_KEY) select t.DAY_KEY as YAGO_DAY_KEY, decode(to_char(add_months(t.DAY_KEY, 12), 'DDMM'), '2902', add_months(t.DAY_KEY, 12) - 1, add_months(t.DAY_KEY, 12)) as DAY_KEY from D_CALENDAR_DAY t / ----------------------------
В данной таблице определяются связи между днями текущего года с днями прошлого. Используется для этого отдельная таблица - потому как крайне нежелательно дублировать данные в основной таблице-календаре.
Прописываю эту новую таблицу в RPD. а также создаю алиасы на таблицу фактов и календаря.
Определяю связи между таблицами-алиасами
Перетаскиваю в раздел Logical Table Source новый алиас календаря. Добавляю в этот LTS джойн с link-таблицей (строго inner-связь)
Важно, чтобы имеющиеся столбцы логического таблицы были "отмапплены" на новый LTS
Перетаскиваю в раздел LTS новый алиас фактовой таблицы. Переименовываю столбец-показатель этого LTS в "YAGO Sales Amount (Correct)". Задаю агрегацию. Перекидываю на презентационный слой.
Вот что получается в итоге
P.S. Попробуйте сравнить производительность стандартного подхода к реализации AGO-показателей и подхода через алиасы и "сдвиг календаря"...
Genex aids support in all major databases including Oracle MySQL, Percona MySQL, PostgreSQL, MongoDB and associated platforms.We at G enex, effortlessly handle all complexities of database installation & customize your solutions
ОтветитьУдалитьVisit genexdbs.com/