В комментариях указывали на
"не читаемость кода" — свой микроязык, который нужно учить.
"динамические вызовы" — не приветствуется динамические вызовы, в том числе и потом что в этом случае журнал использования не находит места использования соответствующих таблиц/полей.
В качестве альтернативы я ещё тогда предлагал вариант с автоматической генерацией кода, но вот только сейчас довел это дело до готового инструмента. Кому интересно, прошу под кат.
Идея простая: есть набор параметров, по которым по заданному шаблону генерируется ABAP-код.
Возьмем простой пример: во внутренней таблице проставить поле наименование БЕ по значению поля БЕ. Говоря техническим языком разработчика: во внутренней таблице по значению поля BUKRS проставить значение поля BUTXT по таблице БД T001.
Простая постановка всего в одно предложение. Но чтобы это реализовать нужно либо делать LOOP по внутренней таблице и SELECT SINGLE в цикле (что быстро, но с точки зрения быстродейтсвия не очень приветствуется, особенно если строк во внутренней таблице много), либо выбрать уникальные значения BUKRS, запрос в таблицу БД T001, а затем проставляем соответствуещие значения поля BUTXT через READ TABLE. В этом случае код будет более оптимальный, но его написание уже не такое быстрое, потому что этого кода уже много. При этом в нем нет ничего сложного — практически простая «обезьянья» работа по набору текста.
И вот тут на помощь и приходит автоматический генератор, ведь такой код имеет шаблонный вид, а значит его написание можно автоматизировать.
Указываем следующие параметры:
таблица БД: T001
поля для связи: BUKRS
инициализируемые поля: BUTXT
*-- Выбрать данные из таблицы БД T001
FORM values_from_tab_T001
USING
"-- Входные(ключевые) поля
uv_in_BUKRS TYPE any
"-- Выходные поля
uv_out_BUTXT TYPE any
CHANGING
"-- Таблица для обработки
ct_table TYPE ANY TABLE
.
"-- А может таблица пустая?
CHECK NOT ( ct_table[] IS INITIAL ).
"-- Определить структуру и данные
TYPES:
BEGIN OF lty_item,
BUKRS TYPE T001-BUKRS,
BUTXT TYPE T001-BUTXT,
END OF lty_item.
DATA ls_item TYPE lty_item.
DATA lt_item LIKE TABLE OF ls_item.
FIELD-SYMBOLS <ls_item> LIKE LINE OF lt_item.
FIELD-SYMBOLS <ls_table> TYPE any.
FIELD-SYMBOLS <lv_BUKRS> TYPE any.
FIELD-SYMBOLS <lv_BUTXT> TYPE any.
"-- Выбрать список ключей
LOOP AT ct_table ASSIGNING <ls_table>.
CLEAR ls_item.
ASSIGN COMPONENT uv_in_BUKRS OF STRUCTURE <ls_table> TO <lv_BUKRS>.
ls_item-BUKRS = <lv_BUKRS>.
APPEND ls_item TO lt_item.
ENDLOOP.
"-- Оставить уникальные значения ключей
SORT lt_item BY BUKRS.
DELETE ADJACENT DUPLICATES FROM lt_item
COMPARING BUKRS.
"-- Выбор значений из таблицы
SELECT
BUKRS
BUTXT
FROM T001
INTO TABLE lt_item
FOR ALL ENTRIES IN lt_item
WHERE BUKRS = lt_item-BUKRS
.
"-- Сортировать для быстрого поиска
SORT lt_item BY BUKRS.
"-- Инициализируем поля в таблице
LOOP AT ct_table ASSIGNING <ls_table>.
"-- Инициализировать ключевые поля
ASSIGN COMPONENT uv_in_BUKRS OF STRUCTURE <ls_table> TO <lv_BUKRS>.
ls_item-BUKRS = <lv_BUKRS>.
"-- Привязать выходные поля
ASSIGN COMPONENT uv_out_BUTXT OF STRUCTURE <ls_table> TO <lv_BUTXT>.
"-- По ключевым полям выбрать данные
READ TABLE lt_item ASSIGNING <ls_item>
WITH KEY
BUKRS = ls_item-BUKRS
BINARY SEARCH.
IF sy-subrc = 0.
IF <lv_BUTXT> IS ASSIGNED. <lv_BUTXT> = <ls_item>-BUTXT. ENDIF.
ELSE.
IF <lv_BUTXT> IS ASSIGNED. CLEAR <lv_BUTXT>. ENDIF.
ENDIF.
ENDLOOP.
ENDFORM.
Теперь достаточно в своей программе вызвать сгенерированную подпрограмму
PERFORM values_from_tab_T001 USING 'BUKRS' 'BUTXT' CHANGING lt_TABLE.
Посмотреть код на сайте ABAP генератора
В качестве параметров в подпрограмму передаются имена полей во внутренней таблице и сама внутренняя таблица. После вызова поле BUTXT будет иметь нужное значение.
Имена полей во внутренней таблице сделаны в виде параметров чтобы одну и ту же подпрограмму можно было вызывать для разных полей. Т.е. если у вас во внутренней таблице два поля БЕ (к примеру BUKRS1 и BUKRS2), то вам достаточно сгенерировать одну подпрограмму и вызывать её два раза
PERFORM values_from_tab_T001 USING 'BUKRS1' 'BUTXT1' CHANGING lt_TABLE.
PERFORM values_from_tab_T001 USING 'BUKRS2' 'BUTXT1' CHANGING lt_TABLE.
В этом случае появляется «неоптимальность», так как будет два SELECT из таблицы БД T001, хотя в идеальном случае можно сделать все через один. Но это уже издержки автоматизации. Вряд ли это сильно замедлить выполнение программы, но при необходимости всегда можно «допилить» подпрограмму под свои частные нужды.
Можно инициализировать сразу несколько полей, а в условиях указывать константы (к примеру sy-langu, которая часто используется при выборе текстов). В стандартном примере выбираются названия ЕИ по таблице T006A.
При генерации кода также создается ссылка, по которой созданный пример можно открыть в браузере.
На текущий момент в генараторе имеется несколько шаблонов:
- Определить структуру [+ табличный тип] — определяем структуру + табличный тип + таблицу этого типа + рабочую область таблицы + field-symbol таблицы. Не всегда это всё нужно, но проще удалить сгенерированное чем набирать это вручную
- Удалить дублирующие записи из внутренней таблице по полям — совсем простой генератор и в общем то такой код можно написать вручную, но если полей много, то, на мой взгляд, лучше воспользоваться этим шаблоном
- Выбрать справочные значения из таблицы БД — описанный выше шаблон
- Выбрать справочные значения — шаблон позволяет устанавливаеть значения полей через вызов ФМ-а, подпрограмм. При этом вызовы кешируются.
- Выбрать справочные значения из домена — данный шаблон не имеет параметров и был добавлен чтобы просто иметь возможность скопировать этот код в свою программу
Думаю подробное описывать работу каждого шаблона нет смысла, так как проще посмотреть результат его работы на сайте. Каждый шаблон имеет значения по умолчанию для своих параметров и если вы разработчик abap, то вы по коду быстро разберетесь.
Комментарии (15)
DragonB
19.10.2018 12:00Редко когда динамический код является читаемым и понятным.
При этом данный пример не является полностью динамическим, так как нельзя выполнить поиск по любым полям таблицы.
Кроме того я бы выполнял динамический вызов SELECT, так как это не изменение данных БД. Через журнал применения в таблице его не найти, но можно найти по наименованию подпрограммы в репозитарии.shasoft Автор
19.10.2018 12:03Суть в том чтобы не набирать самому этот код. Ну а вариантов то решения в данном случае много, хоть динамически, можно статически.
нельзя выполнить поиск по любым полям таблицы.
В данном случае поля справочные, по личному опыту, поиск по их использованию редко требуется, в основном поиск использования таблицы.
UnknownUser
19.10.2018 14:28В принципе, можно чуть обобщить код, вместо значения uv_in_BUKRS принимать внутреннюю таблицу со значениями. Если система 7.4, с помощью нового синтаксиса такую входную табличку создавать довольно просто.
Вообще, жаль, что в ABAP довольно громоздкий синтаксис объявления классов. По идее, можно было б изящно решить проблему классом, который принимает на вход экземпляр интерфейса, который выбирает нужные данные из таблицы (теперь таблица будет искаться через «использование») — а внутри уже лепить динамику от души )).DragonB
19.10.2018 16:38не знаю как в 7.4, но в версии, на которой я работаю, полиморфизм у классов реализуется через определение аргументов необязательными и в результате этот код станет не читаем вообще. А вот к громоздкости можно привыкнуть))
UnknownUser
19.10.2018 18:31Я имел в виду, что вместо:
PERFORM values_from_tab_T001 USING 'BUKRS1' 'BUTXT1' CHANGING lt_TABLE.
PERFORM values_from_tab_T001 USING 'BUKRS2' 'BUTXT1' CHANGING lt_TABLE.
Сделать такую функцию, сигнатура которой позволит принимать вот такие параметры:
DATA lt_table TYPE TABLE OF string.
INSERT 'BUKRS1' INTO TABLE lt_table.
INSERT 'BUKRS2' INTO TABLE lt_table.
PERFORM values_from_tab_T001 USING lt_table 'BUTXT1' CHANGING lt_TABLE.
Что в 7.4 можно записать как:
data(lt_table) = VALUE #( ( 'BUKRS1' ) ( 'BUKRS2 ) ).
PERFORM values_from_tab_T001 USING lt_table 'BUTXT1' CHANGING lt_TABLE.
Что позволит не бегать кучу раз по таблице.
Что до полиморфизма, насколько знаю, везде где есть классы, полиморфизм реализуется нормально — как у всех. Вы какую версию системы имеете в виду?DragonB
19.10.2018 19:59В 7.3 такие конструкции не работают и ситуация с ними выглядит в коллективе следующим образом: находят такие конструкции, удивляются, потом вспоминают текущую версию и забывают до следующего раза))
А насчет полиморфизма я имел виду, что следующее объявление не поддерживается, как в Delphi или C#:
CLASS lcl_calculator DEFINITION. PUBLIC SECTION. METHODS: sum IMPORTING iv_value1 TYPE c iv_value2 TYPE c, sum IMPORTING iv_value1 TYPE n iv_value2 TYPE n. ENDCLASS.
UnknownUser
19.10.2018 20:33В 7.3 да, inline объявления не работают. Печально, когда попадается такая система, в то время, когда космические корабли, понимаете ли, бороздят просторы Тихого Океана )).
Насчёт полиморфизма, боюсь, вы путаете его с перегрузкой функций. Перегрузка не работает ни в одной из версий SAP.
А вот полиморфизм есть везде, где доступны классы. Собственно говоря, это одно из основ ООП, без которой вводить классы не имеет смысла.shasoft Автор
20.10.2018 09:21Часто встречаются программы, написанные в ABAP на классах. При этом все приложение сделано одним классом со статическими методами, а данные определены как члены класса. Вот такое вот использование ООП. :(
Т.е. фактически подпрограммы и глобальные данные, но обернутые в класс.UnknownUser
20.10.2018 19:11Да, есть такое. Не все ещё прониклись духом ООП )). На Java, к примеру, тоже некоторые так пишут — там тупо нельзя писать вне классов.
Хотя по мне, это все равно удобнее perform'ов.shasoft Автор
21.10.2018 12:21Хотя по мне, это все равно удобнее perform'ов.
Фишка с необязательными параметрами методов класса — это, на мой взгляд, весьма полезная функция.UnknownUser
21.10.2018 21:10Да, а ещё простой вызов методов с importing параметрами, функциональные методы, которые можно (наконец то !) использовать в выражениях, ну и возможность использования ООП фишек, конечно.
AndreySu
Какой ужас!
shasoft Автор
А по существу?
AndreySu
ABAP
UnknownUser
Трудно только первые лет пять, а потом привыкаешь! )))