[Из песочницы] Запуск SQL запросов в SAP

358fc06ea30840b3a73aded2a3602cc2.jpg При внедрении информационных решений на базе SAP ERP, как правило, разворачиваются три системы:1. Система разработки.2. Система тестирования.3. Система продуктивной эксплуатации.

В процессе разработки программ очень часто возникает необходимость оперативно протестировать SQL-запросы в продуктивной или тестовой системе, так как система разработки обычно содержит минимум данных и их не всегда достаточно. Давайте рассмотрим существующие для этого варианты, оценим их недостатки и в итоге разработаем свой инструмент.Мне удалось насчитать 5 доступных вариантов:

1. Транзакция SE16/SE16NС помощью этой транзакции можно делать выборку только с одной таблицы. Не подходит для запросов с несколькими таблицами.2. Транзакция ST04 (Additional functions → SQL Command Editor) Этот инструмент позволяет выполнять SQL-запросы любой сложности, но имеет 2 недостатка: во-первых, воспринимает только Native SQL-запросы (синтаксис СУБД), что накладывает некоторые неудобства, так как при разработке программ на ABAP для универсальности используются Open SQL-запросы, несколько отличающиеся синтаксисом, но это можно было бы пережить, если бы не «во-вторых»; во-вторых, работает только в том случае, если в качестве СУБД используется Oracle. 3. Транзакция SQVI В транзакции нельзя писать напрямую SQL-запросы, но можно с помощью конструктора строить достаточно сложные выборки из нескольких таблиц с JOIN`ами. Не умеет работать с подзапросами и к тому же в конструкторе приходится выполнять слишком много манипуляций мышкой, поэтому для тестирования запросов не подходит.4. Написать простенькую программу с тестируемым запросом и перенести ее в тестовую систему Процесс переноса измененного кода в тестовую (продуктивную) систему требует выполнения некоторых рутинных манипуляций и занимает в среднем 5–7 минут, поэтому данный вариант тоже не подходит, так как никакого терпения не хватит проделывать всё это после каждой правки запроса.5. Прямой доступ к СУБД В большинстве случаев получить разработчикам такой доступ на проектах не представляется возможным, поэтому данный вариант не подходит.Вывод Получается, что удобного универсального инструмента, который бы позволял оперативно тестировать SQL-запросы любой сложности в SAP, не существует. Придя к такому выводу, я решил разработать такой инструмент.Приступаем к разработке Для начала в транзакции SE80 создаем программу ZSQL, GUI-статус MAIN100 с кнопкой «Выполнить» и Экран 0100.Укрупнённо алгоритм программы выглядит так:

ade9d18cac5f4839b33bd02a2960e16a.png Получение SQL-запроса SELECT Для получения SQL-запроса будем использовать текстовый редактор, который создадим на экране с помощью класса CL_GUI_TEXTEDIT. Для этого добавим на Экран 0100 пустой контейнер с именем MYEDIT, в который будем выводить редактор.Фрагмент кода, создающий текстовый редактор на экране data: g_editor type ref to cl_gui_textedit, g_editor_container type ref to cl_gui_custom_container.

if g_editor is initial. create object g_editor_container exporting container_name = `MYEDIT` exceptions cntl_error = 1 cntl_system_error = 2 create_error = 3 lifetime_error = 4 lifetime_dynpro_dynpro_link = 5.

create object g_editor exporting parent = g_editor_container wordwrap_mode = cl_gui_textedit=>wordwrap_at_fixed_position wordwrap_to_linebreak_mode = cl_gui_textedit=>true exceptions others = 1.

if sy-subrc <> 0. leave program. endif. endif. Парсинг SQL-запроса Из введенного SQL-запроса нам необходимо получить список выбираемых полей и таблиц для того, чтобы в дальнейшем на основании этого списка динамически сгенерировать структуру ALV-Grid для вывода результата.Фрагмент кода, анализирующий запрос types: ty_simple_tab type standard table of ty_simple_struc. types: ty_t_code type standard table of rssource-line.

data lt_sql_query type ty_simple_tab. lt_fields type ty_simple_tab, lt_tables type ty_simple_tab, l_use_cnt (1) type c.

» Анализируем запрос построчно» loop at lt_sql_query assigning . » Удаляем нечитаемые спец-символы replace all occurrences of con_tab in -line with space. concatenate ` ` -line ` ` into -line. » Разбиваем строку на отдельные слова» refresh lt_parsed_sql_line.

split -line at space into table lt_parsed_sql_line. delete lt_parsed_sql_line where line = ''.

loop at lt_parsed_sql_line assigning . translate -line to upper case.

if -line = 'SELECT'. continue. endif.

» Если дошли до * — считаем, что все выбираемые поля получены»

if -line = '*'. l_field_names_obtained = 'X'. continue. endif.

» Если дошли до FROM или JOIN — считаем, что все выбираемые поля получены. » Следующее слово будет названием таблицы»

if -line = 'FROM' or -line = 'JOIN'. l_field_names_obtained = 'X'. l_is_tabname = 'X'. continue. endif.

» Получаем названия полей»

if l_field_names_obtained is initial. » Ищем конструкцию COUNT ()»

find 'COUNT (' in -line ignoring case.

if sy-subrc = 0. l_use_cnt = 'X'. continue. endif.

» Название поля указано с названием таблицы через ~»

search -line for '~'.

if sy-subrc = 0. add 1 to sy-fdpos. endif.

append -line+sy-fdpos to lt_fields. endif.

» Получаем названия таблиц»

if l_is_tabname = 'X'. append -line to lt_tables. clear l_is_tabname. endif. endloop. endloop. Выполнение SQL-запроса Чтобы выполнить наш запрос, воспользуемся оператором generate subroutine pool, который позволяет динамически генерировать временные ABAP-программы на основании переданного в качестве параметра исходного кода, которым мы подготовим из введенного SQL-запроса.Фрагмент кода, генерирующий ABAP-программу types: ty_t_code type standard table of rssource-line.

data: code type ty_t_code, prog (8) type c, msg (120) type c, lt_parsed_sql_line type ty_simple_tab, l_sub_order (1) type c.

field-symbols: type ty_simple_struc, type ty_simple_struc.

append `program z_sql.` to code. append `form get_data using fs_data type standard table.` to code. append `try.` to code.

loop at lt_sql_query assigning . clear: lt_parsed_sql_line.

split -line at space into table lt_parsed_sql_line. delete lt_parsed_sql_line where line = ''.

loop at lt_parsed_sql_line assigning . concatenate ` ` -line ` ` into -line. translate -line to upper case.

» добавляем into… только 1 раз, иначе будет добавляться во все подзапросы»

if -line = ' FROM ' and l_sub_order is initial. append `into corresponding fields of table fs_data` to code.

l_sub_order = 'X'. endif.

append -line to code. endloop. endloop.

append `.` to code. append `rollback work.` to code. append `catch cx_root.` to code. append `rollback work.` to code. append `message ``Что-то пошло не так, проверьте запрос`` type ``i``.` to code. append `endtry.` to code. append `endform.` to code.

generate subroutine pool code name prog message msg. Вывод результата на экран Так как состав полей и их тип нам заранее неизвестны, то для получения результата и вывода его на экран нам необходимо динамически сгенерировать внутреннюю таблицу и структуру ALV-Grid на основании выбираемых в запросе полей. Для этого будем использовать метод create_dynamic_table класса cl_alv_table_create.Фрагмент кода, генерирующий структуру ALV-Grid data: ref_table_descr type ref to cl_abap_structdescr, lt_tab_struct type abap_compdescr_tab, ls_fieldcatalog type slis_fieldcat_alv.

field-symbols: type abap_compdescr, type ty_simple_struc, type ty_simple_struc.

loop at lt_tables assigning . refresh lt_tab_struct.

» Получаем все поля для выбираемой таблицы»

ref_table_descr?= cl_abap_typedescr=>describe_by_name (-line). lt_tab_struct[] = ref_table_descr→components[].

loop at lt_tab_struct assigning . » если поля нет среди выбираемых в SQL-запросе — не выводим его не экран»

if lines (lt_fields) > 0. read table lt_fields transporting no fields with key line = -name.

if sy-subrc <> 0. continue. endif. endif.

» если поле с таким именем уже есть, то не добавляем повторно»

read table lt_fieldcatalog transporting no fields with key fieldname = -name.

if sy-subrc = 0. continue. endif.

clear ls_fieldcatalog. ls_fieldcatalog-fieldname = -name. ls_fieldcatalog-ref_tabname = -line.

append ls_fieldcatalog to lt_fieldcatalog. endloop. endloop.

» В запросе есть конструкция COUNT () — добавляем колонку с именем CNT и типом INT»

if l_use_cnt = 'X'. clear ls_fieldcatalog.

ls_fieldcatalog-fieldname = 'CNT'. ls_fieldcatalog-seltext_l = 'Кол-во'. ls_fieldcatalog-seltext_m = 'Кол-во'. ls_fieldcatalog-seltext_s = 'Кол-во'. ls_fieldcatalog-datatype = 'INT4'.

if p_tech_names = 'X'. ls_fieldcatalog-seltext_l = 'CNT'. ls_fieldcatalog-seltext_m = 'CNT'. ls_fieldcatalog-seltext_s = 'CNT'. ls_fieldcatalog-reptext_ddic = 'CNT'. endif.

append ls_fieldcatalog to lt_fieldcatalog. endif. Фрагмент кода, создающий динамическую таблицу data: dyn_table type ref to data, dyn_line type ref to data, lt_lvc_fieldcatalog type lvc_t_fcat, ls_lvc_fieldcatalog type lvc_s_fcat.

field-symbols: type slis_fieldcat_alv.

» Преобразуем данные в другой тип»

loop at lt_fieldcatalog assigning . clear ls_lvc_fieldcatalog.

move-corresponding to ls_lvc_fieldcatalog. ls_lvc_fieldcatalog-ref_table = -ref_tabname.

append ls_lvc_fieldcatalog to lt_lvc_fieldcatalog. endloop.

» Создаем динамически таблицу»

call method cl_alv_table_create=>create_dynamic_table exporting it_fieldcatalog = lt_lvc_fieldcatalog importing ep_table = dyn_table.

assign dyn_table→* to . create data dyn_line like line of . assign dyn_line→* to . Полный листинг исходного кода программы ZSQL: Раскрыть type-pools: slis.

types: begin of ty_simple_struc, line (255) type c, end of ty_simple_struc.

types: ty_simple_tab type standard table of ty_simple_struc.

types: ty_t_code type standard table of rssource-line.

data: g_editor type ref to cl_gui_textedit, g_editor_container type ref to cl_gui_custom_container, g_ok_code like sy-ucomm, p_tech_names (1) type c.

field-symbols: type standard table, type any.

call screen 100.

module pbo output. set pf-status `MAIN100`.

» Выводим на форму текстовый редактор для SQL-запроса»

if g_editor is initial. create object g_editor_container exporting container_name = `MYEDIT` exceptions cntl_error = 1 cntl_system_error = 2 create_error = 3 lifetime_error = 4 lifetime_dynpro_dynpro_link = 5.

create object g_editor exporting parent = g_editor_container wordwrap_mode = cl_gui_textedit=>wordwrap_at_fixed_position wordwrap_to_linebreak_mode = cl_gui_textedit=>true exceptions others = 1.

if sy-subrc <> 0. leave program. endif. endif. endmodule.

module pai input. case sy-ucomm. when `EXIT`. leave program. when `EXEC`.» Нажатие кнопки «Выполнить» perform exec. endcase. endmodule. form exec. » Получаем введенный запрос с формы»

data lt_sql_query type ty_simple_tab. clear lt_sql_query.

call method g_editor→get_text_as_r3table importing table = lt_sql_query exceptions others = 1.

delete lt_sql_query where line = ''.

» Парсим запрос и получаем названия выбираемых полей и таблиц»

data: lt_fields type ty_simple_tab, lt_tables type ty_simple_tab, l_use_cnt (1) type c.

clear: lt_fields, lt_tables, l_use_cnt.

perform parse_sql_query using lt_sql_query changing lt_fields lt_tables l_use_cnt.

» Генерируем ABAP-программу из полученного SQL-запроса»

data: code type ty_t_code, prog (8) type c, msg (120) type c.

clear: code, prog, msg.

perform create_get_function using lt_sql_query changing code.

generate subroutine pool code name prog message msg.

if sy-subrc <> 0. message msg type 'I'. return. endif.

» Формируем структуру ALV-Grid на основе выбираемых полей и таблиц»

data: lt_fieldcatalog type slis_t_fieldcat_alv. refresh: lt_fieldcatalog.

perform get_fieldcat using lt_tables lt_fields p_tech_names l_use_cnt changing lt_fieldcatalog.

» Динамически, на основе выбираемых полей и таблиц, создаем таблицу , » в которую будем помещать результат выполнения запроса»

perform create_itab_dynamically using lt_fieldcatalog.

» Выполняем SQL-запрос, вызывая функцию из сгенерированной программы»

perform get_data in program (prog) using .

» Выводим результат на экран»

perform show_alv using lt_fieldcatalog. endform.

» Функция разбора запроса»

form parse_sql_query using lt_sql_query type ty_simple_tab changing lt_fields type ty_simple_tab lt_tables type ty_simple_tab l_use_cnt.

data: l_field_names_obtained (1) type c, l_is_tabname (1) type c, lt_parsed_sql_line type ty_simple_tab.

clear: l_field_names_obtained, l_is_tabname.

field-symbols: type ty_simple_struc, type ty_simple_struc.

constants: con_tab type c value cl_abap_char_utilities=>horizontal_tab.

» Анализируем запрос построчно»

loop at lt_sql_query assigning . » Удаляем нечитаемые спец-символы replace all occurrences of con_tab in -line with space. concatenate ` ` -line ` ` into -line. » Разбиваем строку на отдельные слова» refresh lt_parsed_sql_line.

split -line at space into table lt_parsed_sql_line. delete lt_parsed_sql_line where line = ''.

loop at lt_parsed_sql_line assigning . translate -line to upper case.

if -line = 'SELECT'. continue. endif.

» Если дошли до * — считаем, что все выбираемые поля получены»

if -line = '*'. l_field_names_obtained = 'X'. continue. endif.

» Если дошли до FROM или JOIN — считаем, что все выбираемые поля получены. » Следующее слово будет названием таблицы»

if -line = 'FROM' or -line = 'JOIN'. l_field_names_obtained = 'X'. l_is_tabname = 'X'. continue. endif.

» Получаем названия полей»

if l_field_names_obtained is initial. » Ищем конструкцию COUNT ()»

find 'COUNT (' in -line ignoring case.

if sy-subrc = 0. l_use_cnt = 'X'. continue. endif.

» Название поля указано с названием таблицы через ~»

search -line for '~'.

if sy-subrc = 0. add 1 to sy-fdpos. endif.

append -line+sy-fdpos to lt_fields. endif.

» Получаем названия таблиц»

if l_is_tabname = 'X'. append -line to lt_tables. clear l_is_tabname. endif. endloop. endloop. endform.

» Функция создания исходного кода ABAP-программы для последующей генерации»

form create_get_function using lt_sql_query type ty_simple_tab changing code type ty_t_code.

data: lt_parsed_sql_line type ty_simple_tab, l_sub_order (1) type c.

clear l_sub_order.

field-symbols: type ty_simple_struc, type ty_simple_struc.

append `program z_sql.` to code. append `form get_data using fs_data type standard table.` to code. append `try.` to code.

loop at lt_sql_query assigning . clear: lt_parsed_sql_line.

split -line at space into table lt_parsed_sql_line. delete lt_parsed_sql_line where line = ''.

loop at lt_parsed_sql_line assigning . concatenate ` ` -line ` ` into -line. translate -line to upper case.

» добавляем into… только 1 раз, иначе будет добавляться во все подзапросы»

if -line = ' FROM ' and l_sub_order is initial. append `into corresponding fields of table fs_data` to code.

l_sub_order = 'X'. endif.

append -line to code. endloop. endloop.

append `.` to code. append `rollback work.` to code. append `catch cx_root.` to code. append `rollback work.` to code. append `message ``Что-то пошло не так, проверьте запрос`` type ``i``.` to code. append `endtry.` to code. append `endform.` to code. endform.

» Функция генерации структуры ALV-грида»

form get_fieldcat using lt_tables type ty_simple_tab lt_fields type ty_simple_tab p_tech_names l_use_cnt changing lt_fieldcatalog type slis_t_fieldcat_alv.

data: ref_table_descr type ref to cl_abap_structdescr, lt_tab_struct type abap_compdescr_tab, ls_fieldcatalog type slis_fieldcat_alv.

field-symbols: type abap_compdescr, type ty_simple_struc, type ty_simple_struc.

loop at lt_tables assigning . refresh lt_tab_struct.

» Получаем все поля для выбираемой таблицы»

ref_table_descr?= cl_abap_typedescr=>describe_by_name (-line). lt_tab_struct[] = ref_table_descr→components[].

loop at lt_tab_struct assigning . » если поля нет среди выбираемых в SQL-запросе — не выводим его не экран»

if lines (lt_fields) > 0. read table lt_fields transporting no fields with key line = -name.

if sy-subrc <> 0. continue. endif. endif.

» если поле с таким именем уже есть, то не добавляем повторно»

read table lt_fieldcatalog transporting no fields with key fieldname = -name.

if sy-subrc = 0. continue. endif.

clear ls_fieldcatalog. ls_fieldcatalog-fieldname = -name. ls_fieldcatalog-ref_tabname = -line.

append ls_fieldcatalog to lt_fieldcatalog. endloop. endloop.

» В запросе есть конструкция COUNT () — добавляем колонку с именем CNT и типом INT»

if l_use_cnt = 'X'. clear ls_fieldcatalog.

ls_fieldcatalog-fieldname = 'CNT'. ls_fieldcatalog-seltext_l = 'Кол-во'. ls_fieldcatalog-seltext_m = 'Кол-во'. ls_fieldcatalog-seltext_s = 'Кол-во'. ls_fieldcatalog-datatype = 'INT4'.

if p_tech_names = 'X'. ls_fieldcatalog-seltext_l = 'CNT'. ls_fieldcatalog-seltext_m = 'CNT'. ls_fieldcatalog-seltext_s = 'CNT'. ls_fieldcatalog-reptext_ddic = 'CNT'. endif.

append ls_fieldcatalog to lt_fieldcatalog. endif. endform.

» Функция создания динамической внутренней таблицы»

form create_itab_dynamically using lt_fieldcatalog type slis_t_fieldcat_alv.

data: dyn_table type ref to data, dyn_line type ref to data, lt_lvc_fieldcatalog type lvc_t_fcat, ls_lvc_fieldcatalog type lvc_s_fcat.

field-symbols: type slis_fieldcat_alv.

» Преобразуем данные в другой тип»

loop at lt_fieldcatalog assigning . clear ls_lvc_fieldcatalog.

move-corresponding to ls_lvc_fieldcatalog. ls_lvc_fieldcatalog-ref_table = -ref_tabname.

append ls_lvc_fieldcatalog to lt_lvc_fieldcatalog. endloop.

» Создаем динамически таблицу»

call method cl_alv_table_create=>create_dynamic_table exporting it_fieldcatalog = lt_lvc_fieldcatalog importing ep_table = dyn_table.

assign dyn_table→* to . create data dyn_line like line of . assign dyn_line→* to . endform.

» Функция отображения ALV-Grid на экране»

form show_alv using lt_fieldcatalog type slis_t_fieldcat_alv.

data: ls_event type slis_alv_event, lt_event type slis_t_event, ls_layout type slis_layout_alv, l_repid like sy-repid.

ls_layout-colwidth_optimize = 'X'. l_repid = sy-repid.

call function 'REUSE_ALV_GRID_DISPLAY' exporting i_callback_program = l_repid is_layout = ls_layout it_fieldcat = lt_fieldcatalog i_save = 'X' tables t_outtab = exceptions program_error = 1 others = 2.

if sy-subrc <> 0. leave program. endif. endform. Разработанная программа позволяет выполнять Open SQL-запросы SELECT любой сложности. Только нужно соблюдать одно правило при написании запроса: если используется конструкция COUNT (), то после нее нужно дописывать «AS cnt», чтобы корректно сгенерировался ALV-Grid.Программу, по идее, можно немного доработать и использовать не только для тестирования запросов, но и для формирования пользовательских отчетов.

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

Пользуйтесь!

© Habrahabr.ru