Перейти до вмісту

Внутрішні таблиці

Внутрішня таблиця (internal table) — динамічний обʼєкт даних, що тримає у памʼяті послідовність рядків одного типу. Це найчастіше вживана структура даних в ABAP — будь-яка робота з набором записів (з бази, від користувача, згенерованих програмно) зводиться до внутрішньої таблиці.

Три ключові властивості визначають, як таблиця поводиться:

  • Line type — тип одного рядка. Зазвичай це структура, але може бути й елементарний тип (наприклад, таблиця рядків string_table).
  • Table category — механізм внутрішнього зберігання: STANDARD, SORTED або HASHED. Від цього залежать доступні способи доступу і швидкість.
  • Table key — одне або кілька полів, за якими оптимізовано пошук. Primary key є завжди, secondary keys — опційні.

Від категорії залежить як продуктивність, так і доступні операції. Обирай її відповідно до того, як таблиця буде використовуватись.

КатегоріяДоступPrimary keyКоли брати
STANDARDindex, keynon-unique або emptyпослідовна обробка, додавання в кінець, індексний доступ
SORTEDindex, keyunique або non-uniqueавтосортування при вставці, швидкий пошук за ключем (бінарний)
HASHEDтільки keyлише uniqueвеликі таблиці з частим доступом за ключем (O(1))

STANDARD — дефолтний вибір: дешева вставка в кінець, але лінійний пошук. SORTED підтримує впорядкування автоматично, тому кожна вставка коштує O(log n) — вигідно, коли даних багато і ми часто шукаємо. HASHED — для випадків, коли доступ завжди за ключем і ніколи за індексом.

Primary key існує завжди. Для STANDARD може бути EMPTY (тобто ключа нема — пошук тільки за індексом або через WHERE). Для SORTED/HASHED ключ впливає на внутрішнє впорядкування, тому обов’язковий.

Secondary keys дають альтернативні оптимізовані шляхи доступу без зміни primary — корисно, коли одна і та сама таблиця обробляється різними способами.

Загальний шаблон:

TYPES itab_type TYPE STANDARD TABLE OF data_type WITH KEY ...
DATA itab TYPE itab_type.

Якщо категорія не вказана — STANDARD TABLE за замовчуванням.

Варіанти задання ключа:

" Усі символьні поля структури автоматично стають ключем
DATA it1 TYPE TABLE OF zdemo_flsch WITH DEFAULT KEY.
" Явний primary key з одного або кількох полів
DATA it2 TYPE TABLE OF zdemo_flsch WITH KEY carrid.
" Без ключа взагалі — коли ключ не потрібен і ми хочемо
" уникнути випадкових повільних пошуків за DEFAULT KEY
DATA it3 TYPE TABLE OF zdemo_flsch WITH EMPTY KEY.
" Primary empty + додатковий sorted secondary для швидкого пошуку
DATA it4 TYPE TABLE OF zdemo_flsch WITH EMPTY KEY
WITH UNIQUE SORTED KEY cities COMPONENTS cityfrom cityto.

WITH DEFAULT KEY — історична спадщина, зручно виглядає, але може створювати дивні ключі автоматично. У сучасному коді краще WITH EMPTY KEY або явний WITH KEY ....

Не оголошуємо тип наперед — компілятор виводить його з правої частини:

DATA(itab) = VALUE string_table( ( `text1` ) ( `text2` ) ).
FINAL(ro) = itab. " імутабельна копія
SELECT * FROM tab INTO TABLE @DATA(itab3).

FINAL(...) створює read-only локальну змінну — ніхто далі не зможе випадково модифікувати таблицю.

itab = itab2. " існуючий вміст itab перезаписується
DATA(itab3) = itab. " inline-копія

Копіювання — по значенню. Зміни в itab3 не впливають на itab.

APPEND і INSERT виглядають схоже, але важлива різниця у тому, куди додається рядок і для яких категорій працює:

" APPEND — тільки для STANDARD, завжди в кінець
APPEND VALUE #( comp1 = a comp2 = b ) TO itab.
APPEND INITIAL LINE TO itab.
" INSERT — працює для всіх категорій
" Для SORTED сам знайде правильну позицію за ключем
INSERT VALUE #( comp1 = a comp2 = b ) INTO TABLE itab.
" З явним індексом — тільки STANDARD
INSERT VALUE #( comp1 = a comp2 = b ) INTO itab INDEX 5.
" Масове додавання з іншої таблиці
APPEND LINES OF itab2 TO itab.
APPEND LINES OF itab2 FROM 3 TO 5 TO itab.

Правило памʼяті: APPEND = “додати в кінець списку”, INSERT INTO TABLE = “додати з урахуванням категорії/ключа”.

Конструкторські вирази

Section titled “Конструкторські вирази”

VALUE будує таблицю з літералів або комбінує кілька джерел у одному виразі:

itab = VALUE #( ( comp1 = a comp2 = b )
( comp1 = c comp2 = d ) ).
" BASE — не очищати, дописати до існуючого вмісту
itab = VALUE #( BASE itab ( comp1 = e comp2 = f ) ).
" Злити кілька джерел в одну таблицю
itab = VALUE #( ( LINES OF itab2 )
( LINES OF itab3 FROM 2 TO 5 ) ).

Без BASE таблиця повністю перезаписується, навіть якщо ти додаєш один рядок. Часта помилка на стрес-інтервʼю.

CORRESPONDING копіює з автоматичним мапінгом за іменами полів — незамінне для обміну даних між несумісними структурами:

itab = CORRESPONDING #( itab2 ). " за збігом імен полів
itab = CORRESPONDING #( BASE ( itab ) itab2 ). " зберегти наявний вміст
itab = CORRESPONDING #( itab2 MAPPING a = c b = d ). " явний мапінг імен
itab = CORRESPONDING #( itab2 EXCEPT e ). " виключити одне поле
itab = CORRESPONDING #( itab2 DISCARDING DUPLICATES ). " пропустити дублі за ключем

FILTER — читає тільки частину рядків за умовою:

DATA(f1) = FILTER #( itab WHERE comp1 >= 3 ).
DATA(f2) = FILTER #( itab EXCEPT WHERE comp1 >= 3 ).
DATA(f3) = FILTER #( itab USING KEY sec_key WHERE comp1 = 3 ).

FILTER — швидкий (особливо з sorted-ключем), бо не копіює всю таблицю у память перед фільтрацією.

Три способи залежно від того, що треба: читати дані, модифікувати через field symbol або працювати з data reference.

" Читання у work area — повна копія рядка
READ TABLE itab INTO wa INDEX 1.
" Через field symbol — працюємо напряму з рядком таблиці, без копіювання
READ TABLE itab ASSIGNING FIELD-SYMBOL(<fs>) INDEX 2.
" Через data reference — коли потрібен вказівник
READ TABLE itab REFERENCE INTO DATA(dref) INDEX 3.
" Табличний вираз table expression (7.40+) — коротше
DATA(line) = itab[ 1 ].
DATA(comp) = itab[ 2 ]-field_name.
" Безпечне читання без exception
DATA(safe) = VALUE #( itab[ 5 ] OPTIONAL ).
DATA(with_default) = VALUE #( itab[ 6 ] DEFAULT itab[ 1 ] ).

Якщо таблиця має ключ — найшвидший спосіб:

READ TABLE itab INTO wa WITH TABLE KEY primary_key COMPONENTS a = 1 b = 2.
READ TABLE itab INTO wa WITH TABLE KEY sec_key COMPONENTS c = 3.
" Те саме через table expression
DATA(line) = itab[ TABLE KEY primary_key a = 1 b = 2 ].
DATA(line2) = itab[ TABLE KEY sec_key c = 3 ].

За вільним ключем (WITH KEY)

Section titled “За вільним ключем (WITH KEY)”

Якщо ключа на цих полях немає — ABAP зробить лінійний пошук по STANDARD або бінарний по SORTED:

READ TABLE itab INTO wa WITH KEY comp1 = value.
DATA(line) = itab[ comp1 = value ].

Системні поля після READ TABLE

Section titled “Системні поля після READ TABLE”

Після READ TABLELOOP AT) заповнюються:

ПолеЗначення
sy-subrc0 — знайдено, 4 — не знайдено (STANDARD/HASHED), 8 — не знайдено (SORTED)
sy-tabixіндекс рядка (для primary/sorted-ключів; 0 для HASHED)

Звичка завжди перевіряти sy-subrc після READ TABLE — рятує від дампів.

READ TABLE itab INTO wa WHERE comp1 > 3.
READ TABLE itab INTO wa WHERE comp2 CS 'підрядок'.
READ TABLE itab INTO wa WHERE comp1 BETWEEN 5 AND 10.
" Придушити performance-warning свідомо, якщо так і треба
READ TABLE itab INTO wa WHERE comp1 = value ##read_where_ok.
" За індексом
MODIFY itab FROM wa INDEX 1.
MODIFY itab FROM wa INDEX 2 TRANSPORTING comp1 comp2. " змінити тільки ці поля
" За ключем
MODIFY itab FROM wa USING KEY primary_key.
" Через field symbol у циклі — найшвидше
LOOP AT itab ASSIGNING FIELD-SYMBOL(<fs>).
<fs>-comp1 = new_value. " зміна напряму в таблиці
ENDLOOP.
DELETE itab INDEX 1.
DELETE itab WHERE comp1 = value.
DELETE itab FROM 2 TO 5.
" Видалення дублікатів
SORT itab BY comp1. " <-- обовʼязково
DELETE ADJACENT DUPLICATES FROM itab COMPARING comp1.
DELETE ADJACENT DUPLICATES FROM itab COMPARING ALL FIELDS.
" Очистити повністю — обидва варіанти еквівалентні
CLEAR itab.
DELETE itab.

Три типи доступу всередині циклу — той самий вибір, що й у READ TABLE:

" Копія у work area — безпечно, але повільніше для великих таблиць
LOOP AT itab INTO wa.
" Робота з копією; зміни wa не впливають на таблицю
ENDLOOP.
" Через field symbol — без копіювання, зміни впливають напряму
LOOP AT itab ASSIGNING FIELD-SYMBOL(<fs>).
<fs>-comp = value. " модифікація рядка таблиці
ENDLOOP.
" Через data reference — коли потрібен вказівник (напр., передати в метод)
LOOP AT itab REFERENCE INTO DATA(dref).
" Доступ через dref->comp
ENDLOOP.
LOOP AT itab INTO wa FROM 3 TO 7. ENDLOOP. " тільки рядки 3..7
LOOP AT itab BACKWARD INTO wa. ENDLOOP. " у зворотному порядку
LOOP AT itab INTO wa USING KEY sec_key. ENDLOOP. " обхід за secondary key
LOOP AT itab INTO wa FROM 1 TO 10 STEP 2. ENDLOOP. " через один

Керування потоком у циклі

Section titled “Керування потоком у циклі”
LOOP AT itab INTO wa.
IF wa-flag = 'X'.
EXIT. " повний вихід з циклу
ENDIF.
IF wa-skip = 'X'.
CONTINUE. " пропустити цей рядок, перейти до наступного
ENDIF.
" ... обробка ...
ENDLOOP.
SORT itab. " за primary key
SORT itab BY comp1 comp2 DESCENDING.
SORT itab BY comp1 ASCENDING comp2 DESCENDING.

Для SORTED таблиць SORT не потрібен і не має сенсу — вона вже відсортована за ключем. Для STANDARD — ручне сортування, коли треба.

Інформація про таблицю

Section titled “Інформація про таблицю”
" Чи існує рядок за умовою — без виключень
IF line_exists( itab[ comp1 = value ] ).
...
ENDIF.
" Кількість рядків
DATA(n) = lines( itab ).
" Рефлексія — отримати метадані про тип таблиці
DATA(td) = cl_abap_typedescr=>describe_by_data( itab ).

line_exists( ) — безпечний спосіб перевірити наявність рядка без TRY/CATCH або READ TABLE з перевіркою sy-subrc.

ABAP SQL і внутрішні таблиці

Section titled “ABAP SQL і внутрішні таблиці”
" Як ціль SELECT
SELECT * FROM dbtab INTO TABLE @itab.
SELECT col1, col2 FROM dbtab INTO TABLE @itab WHERE cond.
" Як джерело для WHERE ... IN
SELECT * FROM other WHERE id IN @itab.
" Як джерело запиту (7.56+) — SELECT безпосередньо з internal table
SELECT * FROM @itab AS t WHERE t~comp1 = value INTO TABLE @DATA(res).

Зверни увагу на @ перед іменем — це escape для ABAP-змінних у ABAP SQL.

Основна ідея: одна й та сама таблиця обробляється по-різному у різних місцях коду. Замість того, щоб робити кілька копій або сортувати, заводимо secondary key:

DATA it TYPE TABLE OF struc WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY sec_sorted COMPONENTS a b
WITH UNIQUE HASHED KEY sec_hashed COMPONENTS c.
READ TABLE it WITH TABLE KEY sec_sorted COMPONENTS a = 1 b = 2.
LOOP AT it USING KEY sec_hashed.
ENDLOOP.

Виграш у продуктивності:

  • SORTED secondary — бінарний пошук, O(log n)
  • HASHED secondary — доступ за константний час, O(1)
  • Додають індексний доступ навіть до HASHED-таблиць (primary у HASHED не підтримує індексний)

Мінус — додаткова память і час на підтримку secondary-ключа при вставці/видаленні. Має сенс, коли таблиця читається значно частіше, ніж змінюється.

Зміна рядків у циклі (без MODIFY):

LOOP AT itab ASSIGNING FIELD-SYMBOL(<fs>).
<fs>-component = new_value.
ENDLOOP.

Трансформація у нову таблицю через FOR:

DATA(transformed) = VALUE ty_target( FOR line IN source_table
( target_comp = line-source_comp ) ).

Фільтр за умовою:

DATA(filtered) = FILTER #( itab WHERE comp1 >= 100 AND comp2 = 'X' ).

Групування і агрегація: див. окрему сторінку Групування внутрішніх таблиць.

Адаптовано з 01_Internal_Tables.md (Apache 2.0). Повний перелік нюансів — в оригіналі.