COLLECT и MODIFY оптимизация для больших itab

У меня есть 2 части кода. Оба они обрабатывают 1,5 миллиона записей, но первая часть занимает 20 минут, а вторая часть 13,5 часов!!!!!!
Вот первая часть:

  loop at it_bkpf.
    select * from bseg into corresponding fields of itab
      where bukrs = it_bkpf-bukrs and
            belnr = it_bkpf-belnr and
            gjahr = it_bkpf-gjahr and
            hkont in s_hkont.

      if sy-subrc = 0 .
        itab-budat = it_bkpf-budat.

        clear *bseg .
        select single * from *bseg
          where bukrs = itab-bukrs and
            belnr = itab-belnr and
            gjahr = itab-gjahr and
            hkont = wtax .
        if sy-subrc <> 0 .
          itab-budat = '99991231'.
        endif.
      endif.
      append itab.
    endselect.
  endloop.

2-я часть, которая длится 13,5 часов, выглядит следующим образом:

sort itab by belnr.

  loop at itab where hkont(2) = '73'.
    move-corresponding itab to itab2.
    collect itab2.
  endloop.

  loop at itab2.
    lv_5per_total = con_5per_tax * itab2-dmbtr.
    lv_5per_upper = lv_5per_total + '0.02'.
    lv_5per_lower = lv_5per_total - '0.02'.

    read table itab with key belnr = itab2-belnr
                             hkont = wtax.

    if sy-subrc = 0.
      if itab-dmbtr between lv_5per_lower and lv_5per_upper.
        itab-budat = '99991231'.
        modify itab transporting budat where belnr = itab2-belnr.
      endif.
    endif.
  endloop.

Кто-нибудь знает, как исправить 2-ю часть?
Некоторые дополнительные вещи:
it_bkpf имеет 1,5 миллиона записей.
После 1-го процесса ITAB имеет 1,5 миллиона записей.
В 2-я часть в 1-м цикле суммирует суммы за белнр для счетов, которые начинаются с 73.
Во 2-м цикле я сравниваю сумму за белнр с суммой белнр/счет и делаю то, что говорит код.< бр> Спасибо

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:
Во-первых, исходный код существовал, и я добавил новый. ITAB существовал, и ITAB2 принадлежит мне. Итак, объявление таблиц было:

DATA : BEGIN OF itab OCCURS 0,
        bukrs LIKE bseg-bukrs,
        hkont LIKE bseg-hkont,
        belnr LIKE bkpf-belnr,
        gjahr LIKE bkpf-gjahr,
        dmbtr LIKE bseg-dmbtr,
        shkzg LIKE bseg-shkzg ,
        budat LIKE bkpf-budat,
        zzcode LIKE bseg-zzcode.
DATA END OF itab.
DATA : BEGIN OF itab2 OCCURS 0 ,
        belnr LIKE bkpf-belnr,
        dmbtr LIKE bseg-dmbtr,
       END OF itab2.

После вашего предложения я сделал следующие изменения:

types: begin of ty_belnr_sums,
        belnr like bkpf-belnr,
        dmbtr like bseg-dmbtr,
       end of ty_belnr_sums.
data: git_belnr_sums type sorted table of ty_belnr_sums
                                    with unique key belnr.
data: gwa_belnr_sums type ty_belnr_sums.

  data: lv_5per_upper type p decimals 2,
        lv_5per_lower type p decimals 2,
        lv_5per_total type p decimals 2.

  sort itab by belnr hkont.

  loop at itab where hkont(2) = '73'.
    move-corresponding itab to gwa_belnr_sums.
    collect gwa_belnr_sums into git_belnr_sums .
  endloop.

  loop at git_belnr_sums into gwa_belnr_sums.
    lv_5per_total = con_5per_tax * gwa_belnr_sums-dmbtr.
    lv_5per_upper = lv_5per_total + '0.02'.
    lv_5per_lower = lv_5per_total - '0.02'.

    read table itab with key belnr = gwa_belnr_sums-belnr
                             hkont = wtax
                    binary search.

    if sy-subrc = 0.
      if itab-dmbtr between lv_5per_lower and lv_5per_upper.
        itab-budat = '99991231'.
        modify itab transporting budat
                        where belnr = gwa_belnr_sums-belnr.
      endif.
    endif.
  endloop.

Теперь я бегу в фоновом режиме на 1,5 миллиона записей, и это продолжается через 1 час.


person ekekakos    schedule 02.03.2018    source источник
comment
сложно сказать, почему вторая часть занимает больше времени, есть несколько возможностей в зависимости от данных в itab. Сначала вы сортируете его по номеру, а затем фильтруете по подстроке. Если фильтр эффективен и удаляет большую часть записей, я бы сортировал после фильтрации, а не до. И если вы можете фильтровать по полю hkont, почему бы вам не фильтровать при построении itab в первую очередь (1-я часть вашего кода), вместо того, чтобы сначала создавать itab со всеми записями, а затем передавать только те, которые вам нужны, в itab2.   -  person Dirk Trilsbeek    schedule 02.03.2018
comment
и вы сможете сократить время выполнения вашего первого фрагмента кода, избегая select...endselect. Это действительно убивает производительность, когда вы обрабатываете 1,5 миллиона строк.   -  person Dirk Trilsbeek    schedule 02.03.2018
comment
Любопытно, что проблема с производительностью во второй половине, где у него нет SELECT...ENDSELECT (хотя в этом вы правы)   -  person VXLozano    schedule 02.03.2018
comment
Пожалуйста, вы должны были четко указать с самого начала, что ваш вопрос был только о части 2, а часть 1 здесь только для нашей информации. Я вижу, как люди отвечают о первой части и тратят на нее время.   -  person Sandra Rossi    schedule 02.03.2018


Ответы (5)


Я думаю, все люди дали вам все подсказки по оптимизации вашего кода. По сути, поскольку вы хотите оптимизировать только вторую часть, единственные проблемы, по-видимому, связаны с операциями itab (цикл, чтение, изменение), которые вы можете улучшить, используя индекс или хеш-таблицу во внутренней таблице itab. Если эти концепции неясны, я рекомендую документацию ABAP: Затраты на администрирование внутренних таблиц на основе строк и Внутренние таблицы — Примечания по производительности

Еще две вещи:

  1. вам следует измерить производительность вашего кода с помощью SAT транзакций, и вы увидите, на какую операцию тратится время.
  2. Не по производительности, а по модели данных: ключ финансового документа состоит из 3-х полей, компания (БУКРС), номер документа (БЕЛНР) и год (ГЕАХР). Таким образом, вы не должны группировать («собирать») только по номеру документа, иначе вы рискуете смешать документы разных компаний или лет. Вместо этого оставьте 3 поля.

Теперь, если вы согласитесь адаптировать несколько мелочей в 1-й части, сразу после прочтения строк BSEG каждого документа, тогда вы можете просто зацикливаться на этих строках, не нуждаясь в индексе, двоичном поиске или хэш-таблице, и это Готово. Единственное, что нужно сделать, это сначала сохранить строки BSEG во временном itab_temp, чтобы содержать только строки одного документа за раз, чтобы вы зацикливались только на этих строках, а затем добавляли их в itab.

Если вы хотите обновить компонент BUDAT позже, а не в первой части, сохраните ключ документа + BUDAT в отдельной внутренней таблице (скажем, itab_budat) и определите неуникальный вторичный индекс для itab. с ключевыми полями документа (скажем, by_doc_key), и вам понадобится только этот прямой код:

LOOP AT itab_budat ASSIGNING <line_budat>.
  MODIFY itab FROM itab USING KEY by_doc_key TRANSPORTING budat
       WHERE bukrs = <line_budat>-bukrs
         AND belnr = <line_budat>-belnr
         AND gjahr = <line_budat>-gjahr.
ENDLOOP.

Обратите внимание, что я предлагаю (неуникальный) вторичный индекс, а не первичный, потому что, поскольку вы не хотите изменять (слишком сильно) 1-ю часть, это позволит избежать потенциальных проблем, которые могут возникнуть. имеют первичный индекс.

Если вам нужна дополнительная информация о терминах, синтаксисе и т. д., вы найдете ее в документации по ABAP (встроенной или в Интернете).

person Sandra Rossi    schedule 02.03.2018

Здесь есть несколько проблем с производительностью:

1-я часть:

Никогда не делайте SELECT в LOOP. Я сказал никогда. Вместо этого вы должны сделать SELECT ... FOR ALL ENTRIES или SELECT ... JOIN (BSEG - это кластерная таблица в ECC, поэтому JOIN невозможен, но прозрачная таблица в S4/HANA, поэтому вы можете ПРИСОЕДИНИТЬСЯ к ней с помощью BKPF). Насколько я вижу из кода, вы выбираете элементы ГК, поэтому вы можете проверить, будет ли лучше использовать BSIS/BSAS (вместо BSEG). Вы также можете проверить таблицу BSET (похоже, вас интересуют «налоговые» строки)

2-я часть:

Если вы выполняете LOOP с условием WHERE, вы получаете лучшую производительность, если внутренняя таблица TYPE SORTED (с правильным ключом). Однако ваше условие WHERE использует смещение, поэтому я не уверен, поможет ли таблица SORTED, но попробовать стоит.

Однако настоящая проблема здесь: COLLECT itab2. COLLECT не поддерживается для TYPE STANDARD таблиц (это четко указано в SAPHelp), внутренняя таблица должна быть TYPE HASHED (на то есть технические причины, в подробности не вдаюсь).

После этого вы LOOP одну внутреннюю таблицу (itab2) и READ TABLE другую (itab). Чтобы иметь наилучшую производительность при чтении одной строки, itab должен быть TYPE HASHED (или по крайней мере TYPE SORTED с правильным ключом).

Вместо того, чтобы использовать внутренние таблицы с HEADER LINES, вы должны LOOP ... ASSIGNING FIELD-SYMBOL(<...>), так что вам больше не нужен MODIFY, это также немного улучшит производительность.

person József Szikszai    schedule 02.03.2018
comment
Насчет последнего абзаца не уверен, потому что он ПРОЧИТАЕТ таблицу с ДВУМЯ условиями, а МОДИФИЦИРУЕТ только для ОДНОГО. Поэтому я предположил, что он хочет обновить все строки в этой таблице для этого единственного условия. - person VXLozano; 02.03.2018
comment
Вы можете быть правы, я мог пропустить это. Он зацикливает itab2, затем ЧИТАЕТ одну строку в itab и (если применимы условия) изменяет несколько строк для itab. В этом случае LOOP над таблицей TYPE SORTED даст гораздо лучшую производительность (с правильным ключом). Еще одна причина использовать разумные внутренние имена таблиц, а не такие, как itab, itab2 и т. д. (Чистый код!) - person József Szikszai; 02.03.2018
comment
С другой стороны, мой совет использовать ПОЛЕВЫЕ СИМВОЛЫ вместо рабочих областей дает лучшую производительность (вероятно, около 10% улучшения в соответствии с моим опытом). - person József Szikszai; 02.03.2018
comment
Ответ начинается довольно хорошо, но позже возникает несколько проблем: COLLECT абсолютно поддерживается для стандартных таблиц, генерируется только временный хэш. Во-вторых, тип itab2 не имеет значения в LOOP, медленность вызвана itab, и это должно быть хешировано. - person András; 02.03.2018
comment
@ András полностью согласен, за исключением того, что сделал itab хешированный, потому что (после последнего редактирования ekekakos) itab имеет теоретический уникальный ключ от bukrs, belnr, gjahr и hkont, вероятно, но доступ осуществляется через частичный ключевые букры, бельнр и гьяр (без хконта). - person Sandra Rossi; 03.03.2018
comment
@ Андрас, извините, я не являюсь носителем английского языка, поэтому мой выбор слов может быть неправильным. Когда я написал COLLECT не поддерживается для СТАНДАРТНЫХ таблиц, я не имел в виду, что он вообще не будет работать, только имел в виду, что будут огромные проблемы с производительностью (если таблица большая). В то время я проверил в SAPHelp, и вот как там сформулировано: оператор COLLECT не подходит для стандартных таблиц и не должен больше использоваться для них. (help.sap.com/doc/abapdocu_751_index_htm/7.51/en -США/) - person József Szikszai; 03.03.2018
comment
@ András генерируется только временный хэш: это правильно, но только половина правды. Если вы СОБИРАЕТЕ СТАНДАРТНУЮ таблицу и не находите подходящей строки, ABAP выполняет ПРИСОЕДИНЕНИЕ (внутренне), временный хэш уничтожается и должен быть создан снова при следующем СБОРЕ - вот почему могут быть серьезные проблемы с производительностью при СБОРЕ. огромные СТАНДАРТНЫЕ столы - person József Szikszai; 03.03.2018
comment
@András Во-вторых, тип itab2 не имеет значения в LOOP, медлительность вызвана itab, и это нужно хэшировать - спасибо за указание, я это исправил (в конце я запутался с кодом и внутренней таблицей имена :) ) - person József Szikszai; 03.03.2018
comment
@JozsefSzikszai, ангол сем vagyok :) - person András; 03.03.2018
comment
@JozsefSzikszai, хэш в COLLECT уничтожается, только если вы измените таблицу. Добавление - это суть сбора, конечно, это работает. Через 3 абзаца после того, который вы цитируете, поясняется, что только правок надо избегать. - person András; 03.03.2018

Как сказал JozsefSzikszai, вероятно, было бы лучше использовать BSAS/BSIS, поскольку у них есть индексы. Если вы действительно хотите использовать BSEG: BSEG — это кластерная таблица. Насколько мне известно, для этих таблиц предпочтительнее выполнять SELECT с использованием ключевых полей для получения внутренней таблицы, а затем работать с внутренней таблицей для дополнительных полей.

 select * from bseg into corresponding fields of itab
      for all entries in it_bkpf
      where bukrs = it_bkpf-bukrs and
            belnr = it_bkpf-belnr and
            gjahr = it_bkpf-gjahr.
 delete itab where hkont not in s_hkont.

Во второй части вы выполняете READ TABLE для стандартной таблицы (itab). Использование ДВОИЧНОГО поиска значительно сократит ваше время... Чтение таблицы в стандартной таблице — это полное сканирование таблицы (строки считываются до тех пор, пока мы не найдем значение). Сортировка по belnr и hkont (вместо только belnr) и добавление BINARY SEARCH сделает поиск дихотомическим.

person PATRY Guillaume    schedule 02.03.2018
comment
Удаление какого-либо ограничивающего компонента из условия WHERE только для того, чтобы потом удалить эти строки, является очень плохой идеей. - person András; 03.03.2018
comment
Обычно я бы согласился, но в случае кластерной таблицы мы получили производительность, делая именно это (для поля, которого не было в ключе). - person PATRY Guillaume; 04.03.2018

Первый быстрее, потому что он просто линейно плохой

Второй — квадратичный.
Первый тоже выглядит квадратичным, но bukrs, belnr, gjahr — это почти полный первичный ключ bseg, поэтому для каждой строки it_bkpf не так уж много строк найдено с помощью SELECT.

Первая часть

Как писал JozsefSzikszai, FOR ALL ENTRIES намного быстрее, чем вложенные SELECT. Более того, огромные объемы ненужных данных перемещаются из БД на сервер приложений, так как SELECT SINGLE — это просто проверка существования, но он читает все 362 столбца BSEG.

Поэтому создайте СОРТИРОВАННУЮ таблицу перед ЦИКЛОМ: ТИПЫ: НАЧАЛО tt_bseg, bukrs TYPE bukrs, belnr TYPE belnr, gjahr TYPE gjahr, END OF tt_bseg.

DATA: lt_bseg2 TYPE SORTED TABLE OF tt_bseg WITH NON-UNIQUE KEY bukrs belnr gjahr.

SELECT bukrs belnr gjahr FROM bseg
    INTO TABLE lt_bseg
    FOR ALL ENTRIES IN it_bkpf
    WHERE bukrs = it_bkpf-bukrs
      AND belnr = it_bkpf-belnr
      AND gjahr = it_bkpf-gjahr
      AND hkont = wtax.

Вместо SELECT SINGLE используйте READ TABLE:

READ TABLE lt_bseg TRANSPORTING NO FIELDS
    WITH KEY bukrs = itab-bukrs
             belnr = itab-belnr
             gjahr = itab-gjahr.

Вторая часть

Это квадратично, потому что для каждой итерации itab2 все строки itab проверяются на MODIFY.

Самое простое решение: вместо SORT itab BY belnr загрузите содержимое itab в таблицу SORTED и используйте ее позже:

DATA: lt_itab TYPE SORTED TABLE OF itab 
            WITH NON-UNIQUE KEY belnr hkont.
lt_itab = itab.
FREE itab.
person András    schedule 03.03.2018

Было много разумных рекомендаций от ребят, особенно от Сандры Росси, но я просто добавлю свои пять копеек к вашему последнему варианту, который длится 1 час:

  • используйте как можно больше полевых символов. Они действительно могут влиять на производительность больших наборов данных (а 1,5 МБ — это много, да!)
  • используйте вторичные ключи, как предложила Сандра. Это действительно имеет большое значение (см. стандартную программу DEMO_SECONDARY_KEYS)
  • не используйте INTO CORRESPONDING FIELDS без необходимости, это немного замедляет запросы
  • какая-то двойная работа в вашем фрагменте, так как вы собираете ненужные строки в первом цикле по itab (BSEG), но BUDAT пишется только тогда, когда сумма позиции BSEG соответствует нижнему верхнему диапазону. Так что некоторые собранные суммы теряют время.

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

Тем не менее, если попытаться просто повторить вашу логику без каких-либо изменений и применить новый GROUP BY синтаксис, то вот что может получиться.

* To omit `INTO CORRESPONDING` you should properly declare your structure
* and fetch only those fields which are needed. Also avoid obsolete `OCCURS` syntax.
TYPES: BEGIN OF ty_itab,
        bukrs TYPE bseg-bukrs,
        belnr TYPE bkpf-belnr,
        gjahr TYPE bkpf-gjahr,
        hkont TYPE bseg-hkont,
        dmbtr TYPE bseg-dmbtr,
        shkzg TYPE bseg-shkzg ,
        budat TYPE bkpf-budat,
      END OF ty_itab.

TYPES: begin of ty_belnr_sums,
        belnr type bkpf-belnr,
        dmbtr type bseg-dmbtr,
       end of ty_belnr_sums.
DATA: itab TYPE TABLE OF ty_itab INITIAL SIZE 0,
      con_5per_tax type p decimals 2 value '0.03'.

SELECT g~bukrs g~belnr g~gjahr g~hkont g~dmbtr g~shkzg f~budat UP TO 1500000 rows
INTO table itab
FROM bseg AS g
JOIN bkpf AS f
 ON g~bukrs = f~bukrs
AND g~belnr = f~belnr
AND g~gjahr = f~gjahr.

DATA members LIKE itab.
LOOP AT itab ASSIGNING FIELD-SYMBOL(<fs_itab>)
                       GROUP BY ( belnr = <fs_itab>-belnr )
                                  hkont = <fs_itab>-hkont )
                       ASCENDING
                       ASSIGNING FIELD-SYMBOL(<group>).
  CLEAR members.
  CHECK <group>-hkont(2) = '73'.

  LOOP AT GROUP <group> ASSIGNING FIELD-SYMBOL(<group_line>).
    members = VALUE #( BASE members ( <group_line> ) ).
  ENDLOOP.

  DATA(sum) = REDUCE dmbtr( INIT val TYPE dmbtr
                            FOR wa IN members
                            NEXT val = val + wa-dmbtr ).

  IF members[1]-dmbtr BETWEEN con_5per_tax * sum - '0.02' AND con_5per_tax * sum + '0.02'.
    <first_line>-budat = '99991231'.
  ENDIF.
ENDLOOP.

Во время моего теста на наборе данных 1,5 млн я измерил время выполнения с помощью GET RUN TIME FIELD и получил следующие результаты.

Старый фрагмент:

введите здесь описание изображения

Мой фрагмент:

введите здесь описание изображения

person Suncatcher    schedule 26.05.2018