Групування внутрішніх таблиць
Групування через LOOP AT ... GROUP BY — сучасний спосіб обробити таблицю по групах однакових значень. Це аналог SQL-ного GROUP BY, але над внутрішньою таблицею. Замінює стару конструкцію AT NEW ... AT END OF, яка вимагала попереднього сортування і покладалась на порядок рядків — крихке і незручне рішення.
Синтаксис GROUP BY працює у LOOP AT itab і у формі FOR ... IN GROUP всередині FOR-ітерації (constructor expressions VALUE, REDUCE).
Групування за однією колонкою
Section titled “Групування за однією колонкою”Найпростіша форма — groupувати за одним полем без додаткових опцій:
LOOP AT spfli_tab INTO DATA(wa) GROUP BY wa-carrid. " wa містить ПЕРШИЙ рядок кожної групи " wa-carrid — значення, за яким групуємо out->write( |Авіакомпанія: { wa-carrid }| ).ENDLOOP.Це називається representative binding — work area wa всередині group-циклу представляє всю групу (містить перший її рядок). Під час зовнішнього циклу на кожну унікальну carrid ми заходимо рівно раз.
Доступ до членів групи
Section titled “Доступ до членів групи”Щоб пройти по всіх рядках поточної групи — вкладений LOOP AT GROUP:
LOOP AT spfli_tab INTO DATA(wa) GROUP BY wa-carrid. out->write( |Авіакомпанія { wa-carrid }:| ).
LOOP AT GROUP wa INTO DATA(member). out->write( | рейс { member-connid } { member-cityfrom } -> { member-cityto }| ). ENDLOOP.ENDLOOP.LOOP AT GROUP wa — ключове слово GROUP плюс представник (у цьому випадку wa). Внутрішній цикл проходить тільки по рядках, що належать до поточної групи.
Групування за кількома колонками
Section titled “Групування за кількома колонками”Для ключа з кількох полів ключ робиться структурований — імена компонентів задаються в дужках:
LOOP AT spfli_tab INTO DATA(wa) GROUP BY ( carr = wa-carrid from = wa-airpfrom ). " унікальна комбінація (carrid, airpfrom) на кожну ітерацію out->write( |{ wa-carrid } з { wa-airpfrom }| ).
LOOP AT GROUP wa INTO DATA(member). " всі рейси цієї авіакомпанії з цього аеропорту ENDLOOP.ENDLOOP.Тут теж representative binding — wa представляє групу. Різниця лише у тому, що тепер за кожним полем ключа звертаємось як до компонента структури.
Group key binding — явне імʼя для ключа
Section titled “Group key binding — явне імʼя для ключа”Якщо не хочеться змішувати “представник групи” і “ключ групи” в одному wa — можна оголосити окрему змінну через INTO:
LOOP AT spfli_tab INTO DATA(wa) GROUP BY wa-carrid INTO DATA(key). " key — окремий обʼєкт, що містить тільки значення ключа out->write( key ).ENDLOOP.При groupуванні за одним полем key — елементарний обʼєкт (тип відповідного поля). При групуванні за кількома — структура з компонентами:
LOOP AT spfli_tab INTO DATA(wa) GROUP BY ( carr = wa-carrid from = wa-airpfrom ) INTO DATA(key). " key-carr, key-from LOOP AT GROUP key INTO DATA(member). ENDLOOP.ENDLOOP.GROUP SIZE, GROUP INDEX, WITHOUT MEMBERS
Section titled “GROUP SIZE, GROUP INDEX, WITHOUT MEMBERS”GROUP SIZE і GROUP INDEX — спеціальні “псевдо-колонки” у ключі, що дають метадані про групу:
GROUP INDEX— номер групи по порядку (1, 2, 3, …).GROUP SIZE— кількість членів у групі.
LOOP AT spfli_tab INTO DATA(wa) GROUP BY ( carr = wa-carrid from = wa-airpfrom idx = GROUP INDEX size = GROUP SIZE ) INTO DATA(key). out->write( |Група #{ key-idx }: { key-carr }/{ key-from }, { key-size } рейсів| ).ENDLOOP.WITHOUT MEMBERS — оптимізація. Якщо потрібно тільки перерахувати групи з метаданими і не планується LOOP AT GROUP — додай цей модифікатор, і runtime не буде тримати рядки-члени у внутрішній структурі групи:
LOOP AT spfli_tab INTO DATA(wa) GROUP BY ( carr = wa-carrid size = GROUP SIZE ) WITHOUT MEMBERS INTO DATA(key). " доступу до членів вже нема — тільки key-carr, key-sizeENDLOOP.FOR … IN GROUP у constructor expressions
Section titled “FOR … IN GROUP у constructor expressions”Те саме групування можна зробити всередині VALUE або REDUCE — без явного LOOP:
" Агрегація: сума по групах у нову таблицюTYPES: BEGIN OF ty_sum, carrid TYPE s_carr_id, count TYPE i, END OF ty_sum.
DATA(summary) = VALUE TABLE OF ty_sum( FOR GROUPS <grp> OF flight IN spfli_tab GROUP BY ( carrid = flight-carrid count = GROUP SIZE ) ( carrid = <grp>-carrid count = <grp>-count ) ).Це той самий функціонал у декларативному стилі — особливо корисно, коли результат потрібен одним виразом, без проміжних змінних.
Порівняння з AT NEW / AT END OF
Section titled “Порівняння з AT NEW / AT END OF”AT NEW ... AT END OF — legacy-конструкція у LOOP AT, що спрацьовує при зміні значення поля між ітераціями:
LOOP AT spfli_tab INTO DATA(wa). AT NEW carrid. " виконується на першій ітерації для кожного carrid " АЛЕ: wa-connid, wa-cityfrom заповнені '*' — бо перевірка номінальна ENDAT. " ...ENDLOOP.Проблеми старого підходу:
- таблиця має бути відсортована за groupуючим полем — інакше результат неправильний;
- поля справа від groupуючого заповнюються зірочками
*у блоціAT NEW— несподівана поведінка; - порядок колонок у структурі впливає на семантику.
GROUP BY цих вад не має: не потребує сортування, не спотворює work area, явно виражає намір. У новому коді — тільки він.
Адаптовано з 11_Internal_Tables_Grouping.md (Apache 2.0). Повний перелік нюансів — в оригіналі.