Несколько последних дней я работал совместно с разработчиками заказчика над созданием нестандартных дэшбордов. Основным требованием заказчика было создание prompt’ов в виде чекбоксов. В результате у нас получились такие вот симпатичные дэшборды, как на скриншоте внизу.

Расскажу в двух словах, как это реализовано.
То, что на скриншоте справа – это обычный prompt. Помимо трех видимых комбо-боксов, там есть еще один невидимый мультиселект для валюты, условно назовем его HIDDEN_CURRENCY ( о том, как его спрятать, будет ниже). Чекбоксы слева – это обычные чекбоксы, создаваемые html-тэгом <input type=”checkbox”>. Фрагмент макета дэшборда при этом выглядит следующим образом:

Элемент под названием «Javascript валюты» - это Static Text с включенным свойством Contains HTML-markup. В этом элементе создаются сами чекбоксы, а также опеределения следующих java-script функций:
- getCurrencyString - функция на базе текущего состояния чекбоксов строки вида формирует и возвращает строку вида ’RUR’,’USD’, содержащую коды отмеченных валют.
- setPromptValue – функция находит на странице элемент <input>, соответствующий prompt’у HIDDEN_CURRENCY и устанавливает ему value = getCurrencyString(). Затем надо вызвать у HIDDEN_CURRENCY обработчик onChange, т.к. в prompt’е Банки и Счета должны подрезаться при выборе валюты (для этого в prompt’e, напомню, используется галочка Constrain). На эту функцию устанавливается обработчик onClick для чекбоксов.
- initCurrencyChk – функция инициализирует значения чекбоксов на основании значения prompt’а HIDDEN_CURRENCY
Теперь немного о том, как сделать элемент HIDDEN_CURRENCY спрятанным. Для этого была написана javascript-функция hideElements, которая ищет на странице элементы <input>, и у которых в innerHTML элемента parentNode.previousSibling.previousSibling содержит подстроку HIDDEN. Т.е. ищутся input’ы, лежащие рядом с заголовками, которые мы специально помечаем префиксом HIDDEN в названии. У найденных элементов в стили дописывается display:none. Вызов этой функции нужно вставить в файл ajax.js в самый конец обработчика sawr.iFrameConnection._onLoad. Файл этот лежит на сервере, путь к нему зависит от того, используется ли OC4J или AS. На моем лэптопе этот файл лежит в папках c:\oracle\biee\oc4j_bi\j2ee\home\applications\analytics\analytics\res\b_mozilla\common\ и c:\oracle\biee\web\app\res\b_mozilla\common\.
Наконец, на дэшбород положен Static Text под названием Hide, в котором однократно вызываются функции initCurrencyChk и hideElements.
Вот собственно и все. Не самый простой путь, требующий неплохого знания html и javascript, но игра часто стоит свеч.
Я как-то писал о том, что в BI Server имеется механизм очистки кэша по добавлению записей в специальную таблицу, так называемую Event Polling table (EPT). Сегодня на релизе 10.1.3.3 было обнаружено что-то похожее на баг.
Дело в следующем. Если создать EPT с помощью приведенного в документации скрипта, то опрос этой таблицы BI Server не выполняет, а в NQServer.log пишется сообщение об ошибке [56001] The cache polling event table … has an incorrect schema. Похоже при проверке структуры таблицы используются не названия столбцов, а их номера по порядку. А порядок в скрипте и на физическом уровне метаслоя разный - в метаслое столбцы отсортированы по алфавиту.
Проблема решается, если создавать EPT с помощью немного модифицированного скрипта. Например, так:
create table EPT
(
f1_UpdateType Integer not null,
f2_UpdateTime date default sysdate not null,
f3_DBName char(40) null,
f4_CatalogName varchar(40) null,
f5_SchemaName varchar(40) null,
f6_TableName varchar(40) not null,
f7_Other varchar(80) default null
);
Давно уже собирался написать про некоторые моменты настройки в OBI различных правил агрегации по разным измерениям. Я приведу пару примеров из реальных задач, а также укажу на некоторые ошибки в OBI.
Итак, случай первый. Пару месяцев назад я делал демонстрационный пример для одной компании: мне нужно было настроить в OBI модель с замерами различных показателей нефтяных скважин (дебет жидкости, обводненность и т.д.) Я построил на логическом уровне таблицу фактов MEASUREMENTS c одним показателем Qp (дебет жидкости), образмерил её таблицами DM_TIME и DM_WELL (скважины). Затем я наполнил таблицы тестовыми данными:

Замеры по одной и той же скважине за разные дни должны усредняться. Замеры, проведенные в один и тот же день для разных скважин, должны суммироваться. Для настройки правил агрегации я открываю в BI Administration Tool свойства логической колонки Qp, на вкладке Aggregation устанавливаю галочку Based on dimensions, и устанавливаю правило агрегации AVG для измерения DM_WELLDim, и SUM для DM_TIMEDim.


Для вычисления общего итога по всем скважинам за месяц необходимо сперва вычислить средний дебет жидкости по каждой скважине, а затем просуммировать получившиеся значения. Здесь важен порядок, в котором указаны измерения. Но если попытаться вывести в отчет одновременно оба итога, то мы увидим ошибочный результат.

Здесь итоги по дням (значения в нижей строчке) считаются верно. Также правильно посчитан общий итог (правая нижняя ячейка): 374 = AVG(250+230+240) + AVG(130+138)=240+134=374. А вот итог по скважинам (правый столбец) посчитан неправильно - выводится сумма вместо среднего. В других подобных экспериментах я получал подобные некорректные результаты, вплоть до ошибки [nQSError: 46036] Internal Assertion.
Для решения этой проблемы можно использовать следующий обходной маневр. Дело в том, что BI Server пускает один SQL-запрос к базе данных, которым получаются как детальные значения отчета, так и вычисляются итоги, и именно этот запрос генерируется неправильно. Можно в Administration Tool открыть на физическом уровне свойства базы данных, и снять опцию SUBTOTALLING_SUPPORTED. И тогда для вычисления итогов BI Server будет пускать к базе данных отдельный запрос, что легко проверить посмотрев в NQQuery.log.

В итоге после отключения опции мы получаем верный отчет.

Теперь о другом примере. Представим себе таблицу транзакций по переводу сумм со счета на счет. Так же представим, что транзакции возникают не сами по себе, а порождаются документами. Один документ может породить несколько транзакций, и все транзакции одного документа имеют одинаковые суммы. Таблица фактов создается следующим скриптом:
create table TRANSACTIONS
(
DOCUMENT_IS INTEGER,
ACCOUNT_ID_FROM INTEGER,
ACCOUNT_ID_TO INTEGER,
AMOUNT INTEGER
);
Рассмотрим следующие детальные данные:

Предположим, что в отчетах требуется для определенного счета получать два показателя - “Сумма по транзакциям” и “Сумма по документам”. Сумму по транзакциям нужно вычислять как сумму всех транзакций, в которых участвовал счет. Сумму по документам нужно вычислять аналогично, но при этом брать сумму для каждого документа только один раз. Например в приведенных данных для счета 100 сумма по транзакциям должна составлять 190, а сумма по документам 120.
Для удовлетворения описанных требований поступим следующим образом. Для логической колонки AMOUNT установим правило агрегации SUM - это будет наша “Сумма по транзакциям”. Затем продублируем колонку AMOUNT (Right Click -> Duplicate), назовем новую колонку AMOUNT_BY_DOCUMENT, и для неё установим правило агрегации как MIN по всем измерениям, за исключением SUM по документам:

В результате мы получаем два показателя, которые агрегируются нужным способом.

PS Все описанное проверялось на OBI 10.1.3.3.1 и Oracle Database 10.2.0.1