ABAP: Выборка справочных значений по их ключам из таблиц БД

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

К примеру есть у нас внутренняя таблица:

DATA:
  begin of it_TABLE occurs 0, 
      BE type T001-BUKRS,
      BENAME type T001-BUTXT,
  end of it_TABLE
.

В таблице заполнено поле BE и нам нужно выбрать из T001 значение BUTXT по соответствующему BE и заполнить поле BENAME. Как это нужно сделать «правильно» (т.е. с минимальными затратами памяти и максимально быстро).

  1. Выберем уникальные значения поля BE;
  2. Сделаем FOR ALL ENTRIES из T001;
  3. Пройдем по внутренней таблице и для каждого BE найдем соответствующее значение в выбранных из T001 данных. (Само собой используем HASH-таблицу для ускорения поиска)
И эта последовательность всегда одна и та же.
Вот код этого процесса
FIELD-SYMBOLS:
  <wa_Table> like line of it_Table
.  
*-- Создаем список уникальных значений поля BE
TYPES:
	BEGIN OF s_k1,
		BUKRS type T001-BUKRS,
	END OF s_k1
.
DATA:
	it_k1 TYPE SORTED TABLE OF s_k1 WITH UNIQUE KEY
	 BUKRS
	, wa_k1 like line of it_k1.
LOOP AT it_Table ASSIGNING <wa_Table>.
	wa_k1-BUKRS = <wa_Table>-BE.
	INSERT wa_k1 INTO TABLE it_k1.
ENDLOOP.
*-- Выбираем список значений из T001 по сформированным ключам
TYPES:
	BEGIN OF s_v1,
		BUKRS TYPE T001-BUKRS,
		BUTXT TYPE T001-BUTXT,
	END OF s_v1
.
DATA:
	it_v1 TYPE SORTED TABLE OF s_v1 WITH NON-UNIQUE KEY
	BUKRS
	, wa_v1 like line of it_v1.

IF it_k1[] IS INITIAL.
	REFRESH it_v1.
ELSE.
	SELECT
	 BUKRS
	 BUTXT
	 FROM T001
	 INTO CORRESPONDING FIELDS OF TABLE it_v1
	  FOR ALL ENTRIES in it_k1
	WHERE BUKRS = it_k1-BUKRS
	.
ENDIF.
*-- Присваиваем значения по ключам
LOOP AT it_Table ASSIGNING <wa_Table>.
	READ TABLE it_v1 INTO wa_v1
	 with table key
		BUKRS = <wa_Table>-BE
	.
	IF sy-subrc = 0.
		<wa_Table>-BENAME = wa_v1-BUTXT.
	ELSE.
		CLEAR <wa_Table>-BENAME.
	ENDIF.
ENDLOOP.
А если у нас всегда выполняются одни и те же действия, то значит стоит сократить их описание до минимума. В частности в данном случае нам нужно указать таблицу из которой нужно выбирать, ключевые поля и поля из которых в которые нужно записать данные.

Т.е. имеем следующую небольшую строку, в которой записана вся суть приведенного выше кода:
  'T001{BUKRS=BE}{BUTXT>BENAME}'   

Передаем в метод строку, указанную выше + нашу внутреннюю таблицу. И после вызова метода поле BENAME будет заполнено соответствующими значениями из T001-BUTXT. Мы сократили наш код, к тому же другому программисту достаточно иметь под рукой справку по методу и он быстро поймет что делает данная строка. При этом весь алгоритм укладывается в небольшую строку.
Вот код , сгенерированнй моим методом
 TYPES:
   BEGIN OF s_k1,
     BUKRS type T001-BUKRS,
   END OF s_k1
 .
 DATA:
    it_k1 TYPE SORTED TABLE OF s_k1 WITH UNIQUE KEY
     BUKRS
   , wa_k1 like line of it_k1.
 FIELD-SYMBOLS:
    <fs_1_BUKRS> type ANY,
    <fs_1_BUTXT> type ANY
 .
"-- Создаем список уникальных значений поля BE
 LOOP AT <it_Table> ASSIGNING <wa_Table>.
   "--
   ASSIGN COMPONENT 'BE'
       OF STRUCTURE <wa_Table>
       TO <fs_1_BUKRS>.
   wa_k1-BUKRS = <fs_1_BUKRS>.
   "-- Добавить в список ключей
   INSERT wa_k1 INTO TABLE it_k1.
 ENDLOOP.
 TYPES:
   BEGIN OF s_v1,
     BUKRS TYPE T001-BUKRS,
     BUTXT TYPE T001-BUTXT,
   END OF s_v1
 .
 DATA:
   it_v1 TYPE SORTED TABLE OF s_v1 WITH NON-UNIQUE KEY
    BUKRS
   , wa_v1 like line of it_v1.
*-- Выбираем значения из T001
 IF it_k1[] IS INITIAL.
   REFRESH it_v1.
 ELSE.
   SELECT
     BUKRS
     BUTXT
     FROM T001
     INTO CORRESPONDING FIELDS OF TABLE it_v1
      FOR ALL ENTRIES in it_k1
    WHERE BUKRS = it_k1-BUKRS
   .
 ENDIF.
 FREE it_k1.
*-- Присваиваем значения по ключам
 LOOP AT <it_Table> ASSIGNING <wa_Table>.
   ASSIGN COMPONENT 'BE'
       OF STRUCTURE <wa_Table> TO <fs_1_BUKRS>.
   ASSIGN COMPONENT 'BENAME'
       OF STRUCTURE <wa_Table> TO <fs_1_BUTXT>.
   "-- Читаем значение
   READ TABLE it_v1 INTO wa_v1
     with table key
        BUKRS = <fs_1_BUKRS>
   .
   IF sy-subrc = 0.
     <fs_1_BUTXT> = wa_v1-BUTXT.
   ELSE.
     CLEAR:
       <fs_1_BUTXT>
     .
   ENDIF.
 ENDLOOP.
 FREE it_v1.
 
Для полноценной работы метода добавим в него возможность указывать константы и системные переменные (к примеру sy-langu часто требуется для выборки текстов). Также добавим возможность указания списка команд — тогда мы сможем последовательно выбирать данные.

Как это работает? — с помощью динамических программ. Т.е. метод анализирует переданные через строку параметры, генерирует динамическую программу и запускает её. Добавление программы происходит через «INSERT REPORT l_repid FROM CODE.» чтобы не ограничиваться количеством созданных программ.

P.S. Особо я рад этому методу когда приходится выбирать много значений и периодически уже в процессе работы от постановщика приходит указание на добавление новых поле для выборки.

update: код процесса написал в правильном виде — получилось 53 строки. Строка параметров + вызов метода = 5 строк. Т.е. код сократился в 10 раз и теперь он размещается в пределах одной страницы. Что, по моему мнению, сильно повышает читабельность.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 30

    0
    Т.е. вы придумали свой макроязык с целью сократить размер текста запросов с пяти строчек до трех? :)
    Навскидку вижу такие проблемы:
    1. Нечитабельно. Чтобы вы не говорили, но любому другому программисту нужно будет привыкать к синтаксису, не говоря уж о том чтобы писать не задумываясь
    2. _Не_ быстро — генерация программ занимает время, ваш сгенеренный код тоже далеко не оптимален — просто в силу своей универсальности
    3. Динамическая генерация — зло. Вот так, безапелляционно :) Конечно иногда без них никак (очень редко), но если есть возможность обойтись без динамической генерации — лучше обойтись

    Это интересное упражнение, такое наваять. Но именно как упражнение
      +2
      Скорее с 10 до одной, так будет вернее.
      1. Достаточно один раз разобраться в синтаксисе метода и будет гораздо проще читать код. Т.е. разобраться в одной строчке в разы быстрее, чем в 10-ти. Писать не задумываясь — я как-то больше склоняюсь чтобы писать задумываясь, а то такая фигня может выйти ;)
      2.Само собой если написать этот код вручную, то можно выжать больше скорости. Однако в случае небольших объемов выборки данных — скорость генерации программы и обработка (коллеги особо указывали на ASSIGN) не будет иметь значения. В случае же долгих выборов время генерации программы и обработка вообще можно не учитывать в силу ничтожности этого времени в процентном отношении относительно общей работы. По крайней мере я исходил именно из этого
      3. В чем заключается зло динамических программ? В данном случае, на мой взгляд, оно весьма к месту.

      p.s.Как вариант решение — генерировать код программы динамически и вставлять его в реальную программу. Такой генератор шаблонов по типу как вставлять вызов, к примеру, ФМ-а. Однако в этом случае модифицировать такой код сложнее, хотя в этом случае можно сгенерировать более оптимальный код без всяких ASSIGN-ов. В комментариях можно вставлять строку параметров, чтобы при изменении условий можно было изменить эту строку параметров и перегенерировать шаблон.
        0
        Спасибо за интересную идею, должно быть, это очень удобно. Я всегда «за» сокращение технического кода!
        Однако, есть пара моментов, которые хотелось бы уточнить:

        1. В коде из выбранных ключей не убираются дубликаты, хотя вы обещали ;-)
        2. Для ускорения решено использовать Hashed Table с константным временем поиска, но возможно для маленьких выборок её стоит заменить на Standard Table с Binary Search при чтении?

        А вообще, если позволяет ситуация...
        Лучше обходиться без «довыборок» — явное лучше неявного.
        Нужны БЕ с наименованиями? Делаем JOIN, чтобы наименования подтягивались в момент получения кодов, а не где-нибудь там, далеко в другом месте.

        SELECT a~bukrs
               x~butxt
          FROM a INNER JOIN t001 AS x ON x~bukrs = a~bukrs
          ...
        
          0
          1. Вы просмотрели «TYPE SORTED< TABLE OF s_k1 WITH UNIQUE KEY». Т.е. дублирующие записи все-таки убираются как и обещал. :)
          2. Посмотрев еще раз свой код обнаружил что Hashed Table таблиц там нет, а есть SORTED таблицы. Это чтобы в дамп не падало если вдруг ключ окажется задвоен.

          Если позволяется ситуация, то само собой JOIN будет гораздо проще и читабельнее.
            0
            Да, действительно, смотрю в книгу код — вижу…
            А ваша разработка не открытая, случайно? Было бы очень интересно посмотреть вживую :)
              +1
              Как сказало мое начальство — нет. :(
              Я то как раз и хотел выложить на суд общественности, чтобы люди покритиковали мой код, может что-то полезное бы предложили. Однако начальство сказало что «это собственность компании». И пока я тут работаю (а я надеюсь что еще долго) к сожалению выложить исходный код не получится.
          +1
          Я солидарен с Armann. Претензии ровно те же
          1) не читабельно
          2) динамические вызовы без особой на то нужды — зло (никакой проверки синтаксиса)
          3) скорость
            0
            Да, за синтаксисом в динамическом коде уследить довольно сложно, взять хотя бы ограничение длины строки в 72 символа, из-за которого программа крэшится во время выполнения, правда, в случае с Subroutine Pool. Я так попадал на списки полей, выбираемые пользователем галочками: несколько полей с именами подлинее — и всё, конец.
              0
              Имел место такой казус. Падение в дамп при превышении строки в 72 символа. Однако небольшая корректировка — и все работает прекрасно. И выявлен он был еще в системе разработки.

              В общем-то в этом и смысл делать отдельный метод в виде библиотеки — чем чаще используют, тем быстрее находятся и исправляются ошибки.
              0
              1) Я пока так и не понял почему 5 строк менее читабельны 50 строк. Непонятный мета-язык, так для этого есть справка по методу. Достаточно один раз прочитать — и вуаля- все станет понятно при одном взгляде.
              2) Зачем проверка синтаксиса? Вы написали в программе вызов метода с параметрами — запустили, если дампа нет, то его уже и не будет. Не будет до тех пор, пока вы входные параметры не исправите.
              3) про скорость я уже выше писал (пп.2). Существенного прироста в скорости вы уже не получите.
                0
                1) они менее читабельны в силу отличия от синтаксиса самого языка. А так же по той причине, по которой в языках программирования делают константы: подробный код документирует сам себя. А кроме того, такой подход уничтожает всю прелесть такого инструмента как «журнал использования». Становится очень тяжело понять где же находятся точки изменения переменных в программе.
                2) Проверка синтаксиса нужна затем, что бы юзер не любовался на дампы, а мы, как разработчики, обнаруживали ошибки еще на этапе проверки программы. Вот изменилась структура таблицы. Я по журналу использования нашел все места ее использования, и поправил, при необходимости код ее использования. С вашим же подходом — этого не будет.
                3) это вы думаете, что не получу. Как верно выше указал Yaruson, в ряде случае вместо бездумного впаивания такого кода, правильнее подумать и использовать что-то другое: начиная от join в его примере и заканчивая готовыми ФМ по чтению стандартных справочников
                  0
                  Отсутствие возможности использовать журнал использования — это да, минус.
                  По скорости — я не говорю что вы не сделаете быстрее, я говорю что это ускорение существенно не повлияет на работоспособность. Теоретически конечно можно найти какой-нибудь вариант, когда ручное написание кода позволит выжать скорости побольше чем несколько процентов — но ведь никто не заставляет использовать мой метод всегда.
          +1
          Нет, нет, только не хабру. ABAP… Скоро куски кода для 1С пойдут.
            0
            Статья о том «как не надо делать», причины вам уже написали. Проблема не в коде, а в самом подходе — динамическая генерация ABAP-кода в рантайме.

            Если бы вы его как-нибудь еще кэшировали, может было бы лучше. Т.е. встречается строка — она ищется по z-таблице из пар <hash(строки), название сгенерированного ФМ> и при нахождении он вызывается, иначе генерируется новый фм и z-таблица обновляется.

            Но лучший вариант — это предварительная генерация кода и потом его использование в тексте программе.

            Если все таки хочется светлого пушистого динамического кода, то можно посмотреть в сторону через динамических переменных
            "здесь много строчек парсинга входной строки в рабочие таблицы и переменные, потом работаем так:
            
            select (fields_string)
            	from  (table1_string) 
            	into corresponging fields of <it_Table>
            	where (сonditions_string).
            
            "-- Присваиваем значения по ключам
             loop at assigning <wa_Table>.
               assign component keyfield of structure <wa_Table> to ...
            "-- Читаем значение
            


            Справка для метода — буллшит, когда есть самодокументируемый код:
            	zcl=>fill_table_with_help_texts(
            		importing 
            			search_table = 't001'
            			keyfields = 'bukrs'
            			fields = 'butxt'
            			mapping = 'bukrs = be, butxt = bename'
            		table
            			in_out = it_table ).
            
              0
              Динамические переменные: можно динамически задать какие поля и откуда выбирать. Но ведь это нужно куда то выбрать. А как вы зададите через динамические переменные эту таблицу для данных?

              Про генерацию кода и вставку в код я уже писал. В общем то это достаточно хорошая альтернатива моему методу.
                0
                А как вы зададите через динамические переменные эту таблицу для данных?


                Вы знакомы с reference и с generic types? С их помощью вполне можно задавать динамические переменные.
                  0
                  Я правильно понимаю что создавать таблицы нужно через «CREATE DATA dref TYPE (name).»?
                    0
                    Не обязательно. Можно так, а можно через CREATE DATA dref TYPE HANDLE
                      0
                      Жалко, что версия SAP у меня не той системы. :(
                      А то можно было бы переписать метод без динамических программ.
                0
                Вот… еще один велосипедик с шестиугольными колесиками… Почему бы не воспользоваться готовым инструментом от SAP? Query service к вашим услугам
                  0
                  Нашел описание на русском, может кому пригодиться. Сам полез читать.
                  abap-blog.ru/database-work/abap-object-services-query-service/
                    +1
                    Можно еще и вот это посмотреть ADBC
                    Правда, много интересного есть в стандартной документации?
                      0
                      Правда.
                      Хотя использование всех этих методов тоже, насколько я понимаю, не позволит использовать «журнал использования». А это пока основной минус использования динамической программы.
                    0
                    Ну что я могу вам ответить… «Вот еще один замечательный инструмент, на который с апломбом указывают, но не дают примеров по применению к данной задаче.»
                    Напишите, как вы его будете использовать. Куски кода интересуют.
                      0
                      1) мне не нравится не один из указанных подходов. Динамические реализации\вызовы должны быть строго обоснованы. Никакого приличного основания применять описанный в статье подход я не знаю (кстати. забыл про замечательную особенность автогенеренных программ — падение в дамп после 36 генераций в пределах одной сессии. хотя, может быть, в новых версиях пофиксали?)
                      2) пример использования Query service вам указали выше
                      3) пример использования ADBC.
                        0
                        1) При добавлении программы через «INSERT REPORT l_repid FROM CODE.» ограничение в 36 генераций не действует
                        2,3 ) Спрашивали про то для чего это можно использовать. По крайней мере у меня именно такой вопрос возник. К примеру не очень ясно с ADBC. Из примера, на мой взгляд, не очень понятна зачем оно нужно? Т.е. что мешает написать такой запрос без таких заморочек.
                          0
                          Т.е. что мешает написать такой запрос без таких заморочек.


                          Да-да! Именно эта мысль меня посетила при чтении вашей статьи!
                            0
                            Мой метод предполагает сокращение времени на написание кода, как следствие — минимизация ошибок при разработке.

                            Указанные вами инструменты предполагают… не очень понятна что. Т.е. они наверняка полезные и нужные, но простое указание на них не дает понимание для чего они вообще нужны. На это вам и указали.
                              0
                              1) К сожалению, мне трудно помочь вам, если вы не способны понять зачем нужны ORM ( Query service это часть реализации ORM в SAP). Попробуйте погуглить, что ли.
                              3) ADBC — инструмент построения в том числе и динамических запросов
                              2) Указанные инструменты были перечислены мной в ответ на сообщение AtomKrieg, а не ваше
                              3) по вашему инструменту я уже все сказал. С моей скромной точки зрения, невозможность юзать журнал использования съедает все мнимые преимущества ваше разработки. Грамотное построение системы программных модулей повторного использования (подпрограммы, ФМ, классы) удобнее, нагляднее и понятнее.
                  0
                  Было бы неплохо чтобы редактор ABAP кода доработали до возможности писать свои генераторы шаблонов.
                  Тогда мой метод можно было сразу в топку.

                  Only users with full accounts can post comments. Log in, please.