[Из песочницы] Подход к реализации больших форматированных отчетов в SAP BW

На проектах внедрения отчетности с использованием хранилища данных SAP BW многим архитекторам и консультантам приходится решать задачи подготовки больших форматированных отчетов: разнообразных ведомостей, выписок и т.п. Такие отчеты обычно характеризуются:

  • Нестандартными относительно инструментов SAP требованиями к форматированию;
  • Фиксированным числом столбцов;
  • Значительным количеством столбцов и строк (соответственно, десятки и десятки тысяч и более);
  • Требованием наличия Excel-представления;
  • Требованием к времени выполнения не более нескольких минут


К сожалению, нередко приходится наблюдать ситуацию, когда архитекторы BW-проектов выбирают стандартный для BW подход реализации таких отчетов. Кратко суть этого подхода изложена ниже.

Консультантом создается рабочая книга BW-BEx, которая содержит один или несколько BW-BEx-отчетов. Отчеты выгружаются на отдельные листы этой книги, которые обычно скрывают от пользователей. Видимым оставляют лишь один лист книги, содержащий целевую форму отчета с необходимым форматированием.

Работа пользователя с таким отчетом выглядит следующим образом:

  • в зависимости от используемого Excel-инструмента SAP BW, пользователь запускает BW-BEx Analyzer или SBOP Analysis for Office, подключается к серверу SAP BW, выбирает из роли рабочую книгу и запускает ее на выполнение.
    Через несколько секунд (иногда – десятка секунд) появляется селекционный экран.
    На экране пользователь выбирает значения параметров. Например, год-месяц, балансовую единицу, группу материала и т.п. Затем нажимает кнопку «выполнить».
  • Теперь настала очередь «поработать» для SAP BW: все BW-BEx-отчеты рабочей книги выполняются последовательно, отчет за отчетом, передавая на рабочие листы Excel свои данные.
  • После получения в Excel данных каждого отчета запускается VBA-макрос. Логика работы макроса такова, что он ничего не делает, пока данные всех отчетов не будут получены на Excel-листы.
  • Когда данные последнего отчета поступили на Excel-лист, VBA-макрос выполняет основную работу по подготовке форматирования отчета.
  • Когда VBA-макрос завершил работу, пользователь может увидеть результат отчета в своем Excel.


У стандартного подхода есть ряд преимуществ: он прост в реализации и им хорошо владеют большинство специалистов на рынке. Но определенные ограничения не позволяют эффективно реализовывать большие отчеты. А неэффективная реализация получается (если вообще получается) очень неудобной в работе, что негативно сказывается на отношении пользователей к проекту внедрения вообще и к SAP BW в частности. Основное ограничение – максимальное количество ячеек (число строк, умноженное на число столбцов) в отчете. Если их число приближается к эмпирическим 750000, то вероятность сбоя из-за нехватки памяти практически 100%. Т.е. отчет из всего 18 колонок и чуть более 40000 строк уже попадает под это ограничение. А ведь лимиты у Excel намного больше.

Чего только не придумывают консультанты, чтобы, оставаясь в рамках стандартного подхода, качественно сделать-таки большой отчет. Но почти всегда ничего не получается. «Почти» означает компромиссы, послабления в требованиях. Бизнес-пользователи либо соглашаются применять более ограничивающие фильтры и отчет возвращает меньше данных, либо ждать выполнения подольше, либо вручную сводить несколько фрагментов отчета в один.

Чтобы все-таки не говорить клиенту «нет, мы не можем этого реализовать при таких требованиях», необходимо для начала сделать правильные выводы из очевидного: каждый инструмент предназначен для своей задачи.
Инструменты BW BEx Analyzer и SBOP Analysis for Office в общем случае не предназначены для реализации эффективных отчетов с большим количеством ячеек, с числом около 750000 и более (см SAP-ноту 1040454). Поэтому, используя модель данных SAP BW, надо выбрать другой инструмент, другой подход в реализации. Тогда решение не только обязательно получится, но и будет при этом эффективным.

Последние версии SAP Netweaver, SAP BW и HANA внесли большее разнообразие подходов публикации BW-данных в Excel, без использования BW BEx. Можно упомянуть такие:

  • Подключение Excel через OData-сервисы напрямую к SAP Netweaver или даже к SAP HANA
  • Подключение Excel к SAP HANA, как к базе данных, напрямую, через MDX


Однако, эти подходы требуют либо BW on HANA, либо новейших версий Excel, либо отклонений от привычных концепций полномочий, при которой пользователи не работают с приложениями, обращающимися напрямую к БД.

Я хочу рассказать о подходе, гораздо менее требовательном к новизне версий используемых продуктов, и в чем-то менее сложным. Речь идет о публикации данных отчетов в шаблон Excel-документа через OLE-интерфейс. Excel-шаблон при этом хранится в репозитории BDS на стороне SAP BW.

Преимущества подхода с OLE очевидны:

  • Работает на любых современных версиях продуктов SAP и Microsoft Excel
  • Никаких ограничений на объемы данных в отчете, кроме собственных в Excel
  • Обеспечивает максимальную производительность передачи данных от сервера BW в Excel через OLE. Пример: выборка 525000 ячеек (70 колонок на 7500 строк) передается за 7 сек.
  • Подготовка данных на «сервере BW» выполняется в ABAP-отчете, который, собрав выборку во внутреннюю таблицу, передает ее через OLE в Excel-шаблон, полученный из BDS.
  • Централизованное (в одной BW-системе) ведение всех объектов, релевантных для отчета: шаблон Excel, модель данных BW, программа ABAP для заполнения шаблона.
  • Соответствие обычным SAP-стандартам по разграничению доступа, разработке, транспорту настроек и пр.


«Обратная сторона» медали – этот подход требует программирования на ABAP. Но, по мнению автора, этот аспект не должен вызывать существенных трудностей. «Обертка» из вызова Excel-файла из BDS, его заполнения данными и сохранения, например, в файл на диске или обратно в BDS — более менее стандартный код, который с минимальными вариациями используется от отчета к отчету.

Сложности в ABAP могут возникнуть при получении данных из модели BW. Возможные варианты: вызов BEx-отчета в ABAP, вызов FM RSDRI_INFOPROV_READ, SQL-SELECT по таблицам модели данных. Но это обычно есть в арсенале навыков опытного BW-консультанта. Глубокие знания программировании ABAP понадобятся, если возникнет потребность еще более ускорить работу кода по подготовке данных за счет тюнинга ABAP-программы или даже распараллеливания вычислений. Последнее, кстати, невозможно архитектурно в подходе с рабочими книгами BW BEx.

Вкратце, порядок создания отчета с использованием подхода с OLE следующий.

  • Разработка и отладка кода ABAP, который возвращает во внутреннюю таблицу данных отчета в соответствии с входными параметрами. ABAP-код может быть в виде FM, а лучше – в виде статического метода ABAP-класса;
  • Подготовка Excel-шаблона отчета с базовым форматированием и vBA-макросом, который выполняется после заполнения данными. Такой макрос обычно принимает параметр «число строк», хотя и это не обязательно. Задачи макроса – применить форматирование ячеек отчета при условии неизвестного наперед количества строк;
  • Помещение Excel-шаблона в репозиторий BDS;
  • Разработка и отладка ABAP-кода, который заполняет внутреннюю таблицу результатов отчета, считывает из BDS Excel-шаблон, помещает в него данные из внутренней таблицы в соответствии с мэппингом «поле в таблице – поле в шаблоне», запускает на выполнение VBA-макрос, сохраняет заполненный файл на диске во временном каталоге и открывает его на просмотр пользователю;
  • Подготовка пользовательской транзакции, которая готовится на основе разработки из предыдущего пункта.


Что может понадобится, чтобы сделать первый пример на основе подхода с ABAP-OLE и успешно применять его в дальнейшем?

  • Транзакция работы с репозиторием BDS: OAOR
  • Фрагменты ABAP-кода по работе с документами из BDS через OLE (см ниже)
  • Сертификат для макроса VBA или разрешающая опция Excel по запуску макросов (см. support.microsoft.com/en-us/kb/206637)
data: l_iref_template    type ref to cl_bds_document_set,
        l_oref_container   type ref to cl_gui_custom_container,
        l_iref_control     type ref to i_oi_container_control,
        l_iref_error       type ref to i_oi_error,
        l_iref_document    type ref to i_oi_document_proxy,
        l_iref_spreadsheet type ref to i_oi_spreadsheet,
        l_retcode          type soi_ret_string.

  data: lt_signature type sbdst_signature,
        lw_signature type bapisignat,
        lt_uri       type sbdst_uri,
        lw_uri       type bapiuri,
        lt_sheet     type soi_sheets_table,
        lw_sheet     type soi_sheets.

  data: lt_fields   type standard table of rfc_fields,
        lv_last_row type i,
        lv_last_col type i.
call method c_oi_container_control_creator=>get_container_control
    importing
      control = l_iref_control
      retcode = l_retcode.

  check l_retcode = c_oi_errors=>ret_ok.

  call method l_iref_control->init_control // инициализация открытия шаблона pv_template из BDS
    exporting
      r3_application_name      = pv_template
      inplace_enabled          = 'X'
      inplace_scroll_documents = 'X'
      parent                   = l_oref_container
    importing
      retcode                  = l_retcode.

  check l_retcode = c_oi_errors=>ret_ok.

  create object l_iref_template.
  lw_signature-prop_name  = 'DESCRIPTION'.
  lw_signature-prop_value = pv_template.
  append lw_signature to lt_signature.

  refresh lt_uri.
  call method l_iref_template->get_with_url
    exporting
      classname       = 'SOFFICEINTEGRATION'
      classtype       = 'OT'
      object_key      = 'SOFFICEINTEGRATION'
    changing
      uris            = lt_uri
      signature       = lt_signature
    exceptions
      nothing_found   = 1
      error_kpro      = 2
      internal_error  = 3
      parameter_error = 4
      not_authorized  = 5
      not_allowed     = 6.

  clear lw_uri.
  read table lt_uri into lw_uri index 1.
  check sy-subrc = 0.

  call method l_iref_control->get_document_proxy
    exporting
      document_type  = 'Excel.Sheet'
    importing
      document_proxy = l_iref_document
      retcode        = l_retcode.

  check l_retcode = c_oi_errors=>ret_ok.

  call method l_iref_document->open_document
    exporting
      document_url = lw_uri-uri
      open_inplace = 'X'
    importing
      retcode      = l_retcode.

  check l_retcode = c_oi_errors=>ret_ok.

  free l_iref_error.
  call method l_iref_document->get_spreadsheet_interface
    importing
      error           = l_iref_error
      sheet_interface = l_iref_spreadsheet.

  call method l_iref_spreadsheet->get_sheets
    importing
      sheets = lt_sheet
      error  = l_iref_error.

  check l_iref_error->error_code = c_oi_errors=>ret_ok.

  clear lw_sheet.
  read table lt_sheet into lw_sheet index 1.
  check sy-subrc = 0.

  call method l_iref_spreadsheet->select_sheet
    exporting
      name  = lw_sheet-sheet_name
    importing
      error = l_iref_error.

  check l_iref_error->error_code = c_oi_errors=>ret_ok.

  refresh lt_fields.
  call function 'DP_GET_FIELDS_FROM_TABLE' // получение состава полей lt_fields передаваемой таблицы pt_excel
    tables
      data   = pt_excel
      fields = lt_fields.

  lv_last_row = lines( pt_excel ).
  lv_last_col = lines( lt_fields ).

  call method l_iref_spreadsheet->set_selection // выделение левого верхнего угла
    exporting
      left    = 1
      top     = 1
      rows    = lv_last_row
      columns = lv_last_col.

  call method l_iref_spreadsheet->insert_range // выделение диапазона
    exporting
      columns = lv_last_col
      rows    = lv_last_row
      name    = pv_template.

  call method l_iref_spreadsheet->insert_one_table // собственно, вставка данных в Excel
    exporting
      data_table   = pt_excel[]
      fields_table = lt_fields
      rangename    = pv_template.
…

  call method l_iref_document->execute_macro // запуск макроса MakeFormat из модуля Module1
    exporting
      macro_string = 'Module1.MakeFormat'
      param1       = lv_last_row
      param_count  = 1
    importing
      error        = l_iref_error
      retcode      = l_retcode
…
  concatenate  pv_file sy-uzeit '.xls' into pv_file.
  call method l_iref_document->save_as
    exporting
      file_name = pv_file.

  call method l_iref_document->release_document
    importing
      retcode = l_retcode.

  free: l_iref_spreadsheet,
        l_iref_document.

  call method l_iref_control->release_all_documents.
  call method l_iref_control->destroy_control.


© Habrahabr.ru