Учимся правильно использовать FOR ALL ENTRIES IN

2336
3

Кратко о конструкции

Конструкция FOR ALL ENTRIES IN применяется в SELECT запросах до оператора WHERE. После неё указывается внутренняя таблица с данными, поля которой можно использовать в операторе WHERE в качестве условий выборки.

Что происходит на уровне БД

Очень часто программисты при написании кода на ABAP используют эту конструкцию. Она удобна, экономит время при разработке, но мало кто задумывается над тем, как она работает. И вот однажды, из-за возросшего объемы данных, наступает такой момент, когда написанная программа начинает «тормозить». Чаще всего проблема бывает в запросах, и разработчик начинает оптимизировать их: добавляет индексы, убирает запросы из циклов и т.д. Но практически он никогда не обращает внимания на конструкцию FOR ALL ENTRIES IN, так как считает, что оптимизировать в ней нечего.

Давайте на простом примере (Рис. 1) проанализируем работу этой конструкции. Из таблицы BKPF выберем 1000 строк во внутреннюю таблицу LT_BKPF, а потом из таблицы BSIS выберем данные, используя конструкцию FOR ALL ENTRIES IN LT_BKPF.

======================================

report z_test.

" объявляем переменные
"

data: begin of ls_bkpf,
                bukrs type bkpf-bukrs,
                belnr type bkpf-belnr,
          end of ls_bkpf.
data: lt_bkpf like table of ls_bkpf.

data: lt_bsis like table of bsis.

" выбираем данные из таблицы bkpf и помещаем их во внутреннюю таблицу lt_bkpf
"

select
  bukrs belnr
up to 1000 rows from
  bkpf
into corresponding fields of table
  lt_bkpf
where
  gjahr = '2013'.

check lines( lt_bkpf ) > 0.

" выбираем данные из таблицы BSIS
" в FOR ALL ENTRIES IN передаем внутреннюю таблицу LT_BKPF
"
 

select
  *
from
  bsis
into corresponding fields of table
  lt_bsis
for all entries in
  lt_bkpf
where
  bsis~bukrs = lt_bkpf-bukrs and
  bsis~belnr = lt_bkpf-belnr and
  bsis~gjahr = '2013'.

======================================

Рис. 1 Пример работы конструкции FOR ALL ENTRIES IN

Для анализа работы конструкции будем использовать транзакцию ST05 – трассировка SQL-запросов.

1. В одном режиме запускаем 05 и включаем трассировку (Рис. 2):

Рис. 2 Запуск трассировки

2. В другом режиме выполняем нашу программу. После чего в 05 выключаем трассировку и выводим результат (Рис. 3, Рис. 4, Рис. 5), установив фильтр на таблицы и , чтобы отсеять ненужный нам мусор:

Рис. 3 Отключение трассировки

Рис. 4 Вывод результата трассировки

Рис. 5 Результат трассировки

Мы видим, что к таблице BKPF у нас отработал один запрос и вернул 1000 строк – здесь все нормально. А вот к таблице BSIS у нас выполнилось 100 запросов, да еще и каждый из них содержит 10 запросов, объединенных через конструкцию UNION ALL SELECT. Это, по сути, означает, что один запрос с FOR ALL ENTRIES IN превратился во время выполнения в 1000 отдельных запросов. Если обобщенно – сколько записей во внутренней таблице FOR ALL ENTRIES IN, столько будет отдельных запросов к базе данных. Понятно, что при большом объеме данных это всё будет очень медленно работать.

Оптимизируем

«Как же это оптимизировать?» - спросите вы. Чтобы увеличить быстродействие, нам придется немного усложнить код нашей программы (Рис. 6):

======================================

report z_test.

" объявляем переменные
"

data: begin of ls_bkpf,
                bukrs type bkpf-bukrs,
                belnr type bkpf-belnr,
          end of ls_bkpf.
data: lt_bkpf like table of ls_bkpf.
data: lt_bkpf_tmp like lt_bkpf.
field-symbols: <wa_bkpf> like ls_bkpf.

data: lt_bsis like table of bsis.

data: begin of ls_bukrs,
                bukrs type bukrs,
          end of ls_bukrs.
data: lt_bukrs like table of ls_bukrs.

" выбираем данные из таблицы BKPF и помещаем их во внутреннюю таблицу LT_BKPF
"

select
  bukrs belnr
up to 1000 rows from
  bkpf
into corresponding fields of table
  lt_bkpf
where
  gjahr = '2013'.

check lines( lt_bkpf ) > 0.

" получаем список уникальных БЕ во внутреннюю таблицу LT_BUKRS
"

loop at lt_bkpf assigning <wa_bkpf>.
  ls_bukrs-bukrs = <wa_bkpf>-bukrs.
  collect ls_bukrs into lt_bukrs.
endloop.

" для каждой БЕ выполняем запрос
"

loop at lt_bukrs into ls_bukrs.
  " выбираем из LT_BKPF номера документов, относящиеся к определенной БЕ
  " помещаем во временную таблицу LT_BKPF_TMP
  "

  clear lt_bkpf_tmp.

  loop at lt_bkpf assigning <wa_bkpf>

Ограниченный доступ

Для прочтения полной версии статьи необходимо зайти как зарегистрированный пользователь.

Комментарии:

Олег Точенюк (Рейтинг: 10202) 22:16, 11 августа 2015

За database-hints нормальные базисники обычно отрывают руки а тушку после этого вывешивают в назидание так сказать, а ибо нефиг :-) А вообще-то как заметили в другом месте, то как работает FOR ALL ENTRIES очень сильно зависти от настроек профиля системы, поэтому использовать его конечно можно, но лично я на эту радость забил давно.
16:03, 20 августа 2015

Руслан Закарьяев (Рейтинг: 406)

Ничего плохого в использовании database-hints не вижу, если подходить с умом. Бездумно можно и без их использования систему нагрузить.
 
Конечно, вместо FOR ALL ENTRIES можно использовать конструкцию "WHERE field IN r_field", но и тут есть подводные камни: если в r_field большое число строк (2000+), то будет dump с ошибкой DBIF_RSQL_INVALID_RSQL из-за того, что размер SQL-запроса в килобайтах превысил некоторую границу. Чтобы его избежать, придется выполнять несколько запросов, передавая значения в IN партиями.
 
Но, в целом, вы правы. Лучше, по возможности, избегать использование конструкций, работа которых неочевидна.

Николай Кронский (Рейтинг: 345) 16:11, 12 августа 2015

Рекомендую ознакомиться с SAP Note 48230 (Parameters for the SELECT ... FOR ALL ENTRIES statement).

Любое воспроизведение запрещено.
Копирайт © «Издательство ООО «Эксперт РП»