Oracle APEX: Итоги в Interactive report на каждой странице [RU]

Одним из самых мощных инструментов визуализации данных в Oracle APEX является Interactive Report. Он предоставляет богатые возможности поиска, фильтрации, сортировки и вычисления итогов. Но есть ряд задач, часто возникающих в разработке бизнес-приложений, которые вызывают сложности. В нескольких постах я хочу предложить простой выход из положения.

Первым рассмотрим тривиальный вопрос — вывод итогов по всем данным на каждой странице отчета. При использовании встроенных итогов в IR, итоги выводятся на самой последней странице, если страниц несколько. В реальных приложениях очень часто итоги как раз и будут целью фильтрации и отображения данных — пользователю нужно сначала увидеть общую сумму, а потом уже, если она устраивает, смотреть детализацию. Если после применения фильтра пользователю приходится «отмотать» сначала 10-20 страничек, либо грузить ооочень длинную таблицу — пользователь звереет.

Оптимальным выходом является отображение итогов на каждой странице, но IR не предоставляет такой возможности. Значит, сделаем это сами.

In english, please…

Подробно расскажу, что нам для этого потребуется (на примере для APEX версий <4.2).

1. Начнем с того, что для подсчета итогов нам потребуется запрос. Получить его можно с помощью замечательного пакета APEX_IR_PKG (для версий 4.2 и выше можно воспользоваться встроенным пакетом APEX_IR).

Но сразу стоит заметить, что в реализации этого пакета есть одна небольшая ошибка, которая может вылиться в очень большие неприятности. А именно — неправильно собираются условия фильтрации.
При поиске по всем колонкам выполняется цикл, который собирает условия отбора. Ошибка в том, что после каждого «критерия» переменная для формирования набора условий должна сбрасываться, а этого не происходит. Для того, чтобы исправить это, нужно в цикле сбора условий после строки 946:

lv_searches := lv_searches||'AND ('||lv_search || ') ';

добавить инструкцию для обнуления переменной lv_search

lv_search := '';

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

2. Теперь мы можем создать процесс, который будет подсчитывать итоги на основе текущего запроса, по которому отображаются данные в IR. Процесс, разумеется, создаем в AJAX Callbacks, on-Demand:

Oracle Apex IR summary
Oracle Apex IR summary
Oracle Apex IR summary

declare
-- "пустая таблица" для работы с пакетом
l_nullTable DBMS_SQL.VARCHAR2_TABLE;

-- переменная для хранения текста запроса
l_sql VARCHAR2(32767);

-- переменная для хранения посчитанной суммы
l_result varchar2(32767);
begin
-- получим запрос (подробное описание параметров есть в пакете)
l_sql := apex_ir_pkg.get_ir_sql (
p_app_id => :APP_ID,
p_session_id => :APP_SESSION,
p_page_id => :APP_PAGE_ID,
p_report_id => NULL,
p_app_user => :APP_USER,
p_use_session_state => TRUE,
p_binds => l_nullTable,
p_binds_val => l_nullTable,
p_incl_filters => TRUE,
p_incl_order_by => FALSE
);

-- дополним запрос, завернув его как подзапрос, и посчитав нужное значение
l_sql := 'select sum(SAL) from ('||l_sql||')';

-- выполним запрос, получим результат
execute immediate l_sql into l_result;

-- и отпечатаем его на вывод
sys.htp.p(l_result);
end;

3. Осталась мелочь — после каждого обновления IR асинхронно дергать процесс и выводить сумму в нужное место. Создаем Dynamic Action на Refresh отчета:

Oracle Apex IR summary
Oracle Apex IR summary
Oracle Apex IR summary
Oracle Apex IR summary

// формируем AJAX-запрос к процессу
var get = new htmldb_Get(null, $v('pFlowId'), 'APPLICATION_PROCESS=GET_SUMMARY', $v('pFlowStepId'));

// выполняем этот запрос
var gReturn = get.get();

// формируем строку, которую надо добавить в IR для вывода суммы
var pData = '<tr style="text-align:right;"><td colspan="5"><b>Summary:</b></td><td><b>'
+ gReturn + '</b></td><td colspan="3"></td></tr>';

// через jQuery находим таблицу IR (она одна на странице - это ограничение APEX),
// и выводим нашу строку
$(".apexir_WORKSHEET_DATA tbody").append($(pData));

Ну вот и все. Теперь в IR на каждой странице будут отображаться итоги.
Пример того, как описанный метод работает, можно посмотреть на демо-странице.

Разумеется, все это можно завернуть в настраиваемый Dynamic Action Plugin (как и было сделано мной в реальном проекте). Вопрос с адаптацией при изменении набора колонок я так же оставлю на «самостоятельное изучение», потому как он решается не сложно, за счет того же пакета APEX_IR_PKG/APEX_IR.
Расширенную версию, на основе плагина, можно увидеть на соседней странице.