Часто при ABAP кодировании возникает типовая задача — по значениям одних полей внутренней таблицы проинициализировать другие поля (выбрать из таблицы БД, через вызов ФМ-а, подпрограммы). И код в этом случае очень простой в плане алгоритма, но его достаточно много. Всегда хотел сократить время, убиваемое на такие рутинные операции. И даже писал метод, основанный на динамическом создании программ, для Выборки справочных значений по их ключам из таблиц БД.

В комментариях указывали на

"не читаемость кода" — свой микроязык, который нужно учить.
"динамические вызовы" — не приветствуется динамические вызовы, в том числе и потом что в этом случае журнал использования не находит места использования соответствующих таблиц/полей.

В качестве альтернативы я ещё тогда предлагал вариант с автоматической генерацией кода, но вот только сейчас довел это дело до готового инструмента. Кому интересно, прошу под кат.

Идея простая: есть набор параметров, по которым по заданному шаблону генерируется 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.

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

На текущий момент в генараторе имеется несколько шаблонов:

  1. Определить структуру [+ табличный тип] — определяем структуру + табличный тип + таблицу этого типа + рабочую область таблицы + field-symbol таблицы. Не всегда это всё нужно, но проще удалить сгенерированное чем набирать это вручную
  2. Удалить дублирующие записи из внутренней таблице по полям — совсем простой генератор и в общем то такой код можно написать вручную, но если полей много, то, на мой взгляд, лучше воспользоваться этим шаблоном
  3. Выбрать справочные значения из таблицы БД — описанный выше шаблон
  4. Выбрать справочные значения — шаблон позволяет устанавливаеть значения полей через вызов ФМ-а, подпрограмм. При этом вызовы кешируются.
  5. Выбрать справочные значения из домена — данный шаблон не имеет параметров и был добавлен чтобы просто иметь возможность скопировать этот код в свою программу

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

Комментарии (15)


  1. AndreySu
    19.10.2018 09:14

    Какой ужас!


    1. shasoft Автор
      19.10.2018 12:03

      А по существу?


      1. AndreySu
        19.10.2018 21:25

        ABAP


        1. UnknownUser
          19.10.2018 21:48

          Трудно только первые лет пять, а потом привыкаешь! )))


  1. DragonB
    19.10.2018 12:00

    Редко когда динамический код является читаемым и понятным.
    При этом данный пример не является полностью динамическим, так как нельзя выполнить поиск по любым полям таблицы.
    Кроме того я бы выполнял динамический вызов SELECT, так как это не изменение данных БД. Через журнал применения в таблице его не найти, но можно найти по наименованию подпрограммы в репозитарии.


    1. shasoft Автор
      19.10.2018 12:03

      Суть в том чтобы не набирать самому этот код. Ну а вариантов то решения в данном случае много, хоть динамически, можно статически.

      нельзя выполнить поиск по любым полям таблицы.

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


  1. UnknownUser
    19.10.2018 14:28

    В принципе, можно чуть обобщить код, вместо значения uv_in_BUKRS принимать внутреннюю таблицу со значениями. Если система 7.4, с помощью нового синтаксиса такую входную табличку создавать довольно просто.
    Вообще, жаль, что в ABAP довольно громоздкий синтаксис объявления классов. По идее, можно было б изящно решить проблему классом, который принимает на вход экземпляр интерфейса, который выбирает нужные данные из таблицы (теперь таблица будет искаться через «использование») — а внутри уже лепить динамику от души )).


    1. DragonB
      19.10.2018 16:38

      не знаю как в 7.4, но в версии, на которой я работаю, полиморфизм у классов реализуется через определение аргументов необязательными и в результате этот код станет не читаем вообще. А вот к громоздкости можно привыкнуть))


      1. 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.

        Что позволит не бегать кучу раз по таблице.

        Что до полиморфизма, насколько знаю, везде где есть классы, полиморфизм реализуется нормально — как у всех. Вы какую версию системы имеете в виду?


        1. 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.  


          1. UnknownUser
            19.10.2018 20:33

            В 7.3 да, inline объявления не работают. Печально, когда попадается такая система, в то время, когда космические корабли, понимаете ли, бороздят просторы Тихого Океана )).
            Насчёт полиморфизма, боюсь, вы путаете его с перегрузкой функций. Перегрузка не работает ни в одной из версий SAP.
            А вот полиморфизм есть везде, где доступны классы. Собственно говоря, это одно из основ ООП, без которой вводить классы не имеет смысла.


            1. shasoft Автор
              20.10.2018 09:21

              Часто встречаются программы, написанные в ABAP на классах. При этом все приложение сделано одним классом со статическими методами, а данные определены как члены класса. Вот такое вот использование ООП. :(
              Т.е. фактически подпрограммы и глобальные данные, но обернутые в класс.


              1. UnknownUser
                20.10.2018 19:11

                Да, есть такое. Не все ещё прониклись духом ООП )). На Java, к примеру, тоже некоторые так пишут — там тупо нельзя писать вне классов.
                Хотя по мне, это все равно удобнее perform'ов.


                1. shasoft Автор
                  21.10.2018 12:21

                  Хотя по мне, это все равно удобнее perform'ов.
                  Фишка с необязательными параметрами методов класса — это, на мой взгляд, весьма полезная функция.


                  1. UnknownUser
                    21.10.2018 21:10

                    Да, а ещё простой вызов методов с importing параметрами, функциональные методы, которые можно (наконец то !) использовать в выражениях, ну и возможность использования ООП фишек, конечно.