[Из песочницы] Запуск SQL запросов в SAP
При внедрении информационных решений на базе 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.Укрупнённо алгоритм программы выглядит так:
Получение 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
split
loop at lt_parsed_sql_line assigning
if
» Если дошли до * — считаем, что все выбираемые поля получены»
if
» Если дошли до FROM или JOIN — считаем, что все выбираемые поля получены. » Следующее слово будет названием таблицы»
if
» Получаем названия полей»
if l_field_names_obtained is initial. » Ищем конструкцию COUNT ()»
find 'COUNT (' in
if sy-subrc = 0. l_use_cnt = 'X'. continue. endif.
» Название поля указано с названием таблицы через ~»
search
if sy-subrc = 0. add 1 to sy-fdpos. endif.
append
» Получаем названия таблиц»
if l_is_tabname = 'X'.
append
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:
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
split
loop at lt_parsed_sql_line assigning
» добавляем into… только 1 раз, иначе будет добавляться во все подзапросы»
if
l_sub_order = 'X'. endif.
append
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:
loop at lt_tables assigning
» Получаем все поля для выбираемой таблицы»
ref_table_descr?= cl_abap_typedescr=>describe_by_name (
loop at lt_tab_struct assigning
if lines (lt_fields) > 0.
read table lt_fields transporting no fields with key line =
if sy-subrc <> 0. continue. endif. endif.
» если поле с таким именем уже есть, то не добавляем повторно»
read table lt_fieldcatalog transporting no fields with key fieldname =
if sy-subrc = 0. continue. endif.
clear ls_fieldcatalog.
ls_fieldcatalog-fieldname =
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:
» Преобразуем данные в другой тип»
loop at lt_fieldcatalog assigning
move-corresponding
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
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:
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:
constants: con_tab type c value cl_abap_char_utilities=>horizontal_tab.
» Анализируем запрос построчно»
loop at lt_sql_query assigning
split
loop at lt_parsed_sql_line assigning
if
» Если дошли до * — считаем, что все выбираемые поля получены»
if
» Если дошли до FROM или JOIN — считаем, что все выбираемые поля получены. » Следующее слово будет названием таблицы»
if
» Получаем названия полей»
if l_field_names_obtained is initial. » Ищем конструкцию COUNT ()»
find 'COUNT (' in
if sy-subrc = 0. l_use_cnt = 'X'. continue. endif.
» Название поля указано с названием таблицы через ~»
search
if sy-subrc = 0. add 1 to sy-fdpos. endif.
append
» Получаем названия таблиц»
if l_is_tabname = 'X'.
append
» Функция создания исходного кода 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:
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
split
loop at lt_parsed_sql_line assigning
» добавляем into… только 1 раз, иначе будет добавляться во все подзапросы»
if
l_sub_order = 'X'. endif.
append
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:
loop at lt_tables assigning
» Получаем все поля для выбираемой таблицы»
ref_table_descr?= cl_abap_typedescr=>describe_by_name (
loop at lt_tab_struct assigning
if lines (lt_fields) > 0.
read table lt_fields transporting no fields with key line =
if sy-subrc <> 0. continue. endif. endif.
» если поле с таким именем уже есть, то не добавляем повторно»
read table lt_fieldcatalog transporting no fields with key fieldname =
if sy-subrc = 0. continue. endif.
clear ls_fieldcatalog.
ls_fieldcatalog-fieldname =
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:
» Преобразуем данные в другой тип»
loop at lt_fieldcatalog assigning
move-corresponding
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
» Функция отображения 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 =
if sy-subrc <> 0. leave program. endif. endform. Разработанная программа позволяет выполнять Open SQL-запросы SELECT любой сложности. Только нужно соблюдать одно правило при написании запроса: если используется конструкция COUNT (), то после нее нужно дописывать «AS cnt», чтобы корректно сгенерировался ALV-Grid.Программу, по идее, можно немного доработать и использовать не только для тестирования запросов, но и для формирования пользовательских отчетов.
В статье я не затрагивал вопросы безопасности. Входящий запрос никак не проверяется на корректность, после него можно написать любой ABAP-код и он будет выполняться. Для исключения такой возможности достаточно дописать несложные проверки.
Пользуйтесь!