Internal Tables Part 9: Nested LOOP Optimization and Parallel Cursor
Fix the most common ABAP performance problem — nested loops over two large tables — with sorted tables and LOOP WHERE, hashed lookups, and the classic parallel cursor technique.
Explanation
📌 KEY POINTS
✅ LOOP inside LOOP over two standard tables = rows1 × rows2 comparisons — minutes become hours.
✅ Fix 1: inner table SORTED by the join key → LOOP … WHERE is optimized.
✅ Fix 2: one matching row per key → HASHED inner table + READ TABLE.
✅ Fix 3 (classic): parallel cursor — find the start with BINARY SEARCH, loop FROM that index, EXIT when the key changes.
━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE
Part 8 covered loop patterns. When two tables are combined in memory (headers and items, flights and bookings), the nested loop is where performance is won or lost.
━━━━━━━━━━━━━━━━━━━━
▶ 1. THE PROBLEM
LOOP AT gt_flight INTO gs_flight. " 10,000 rows
LOOP AT gt_book INTO gs_book WHERE carrid = gs_flight-carrid AND connid = gs_flight-connid AND fldate = gs_flight-fldate. " 500,000 rows
ENDLOOP.
ENDLOOP.
With standard tables, the inner WHERE scans all 500,000 rows for each of the 10,000 flights: 5 billion comparisons.
━━━━━━━━━━━━━━━━━━━━
▶ 2. FIX 1 — SORTED INNER TABLE
Declare gt_book as SORTED TABLE … WITH NON-UNIQUE KEY carrid connid fldate. The inner LOOP WHERE on the leading key fields now uses binary search to find the group — typically the simplest, cleanest fix.
━━━━━━━━━━━━━━━━━━━━
▶ 3. FIX 2 — HASHED LOOKUP
If the inner table has at most one match per key (e.g. flight → airline name), use a HASHED table with UNIQUE KEY and READ TABLE … WITH TABLE KEY instead of an inner loop.
━━━━━━━━━━━━━━━━━━━━
▶ 4. FIX 3 — PARALLEL CURSOR (CLASSIC STANDARD TABLES)
When you must keep standard tables (existing code):
- SORT both tables by the join key.
- For each outer row: READ TABLE inner WITH KEY … BINARY SEARCH TRANSPORTING NO FIELDS → sy-tabix = first matching row.
- LOOP AT inner FROM lv_index; EXIT when the key differs.
Each inner row is visited only for its own group.
━━━━━━━━━━━━━━━━━━━━
▶ 5. BETTER STILL: THE DATABASE
If both tables come from the database, a JOIN or CDS view often avoids the in-memory join entirely (Open SQL topic).
━━━━━━━━━━━━━━━━━━━━
▶ 6. MEASURE
Use SAT (runtime analysis) or GET RUN TIME with realistic volumes. Small test data hides nested-loop problems completely.
━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP
Run the code example with larger UP TO values: compare the nested standard loop, the sorted-table loop and the parallel cursor. The runtime difference grows quadratically.
✅ CHECKPOINT: you never leave a nested loop over two large standard tables in your code.
Code example
REPORT zitab_part9. TYPES ty_book_sorted TYPE SORTED TABLE OF sbook WITH NON-UNIQUE KEY carrid connid fldate. DATA: gt_flight TYPE STANDARD TABLE OF sflight, gs_flight TYPE sflight, gt_book TYPE STANDARD TABLE OF sbook, gt_book_s TYPE ty_book_sorted, gs_book TYPE sbook, gv_index TYPE sy-tabix, gv_count TYPE i, gv_t0 TYPE i, gv_t1 TYPE i. START-OF-SELECTION. SELECT * FROM sflight INTO TABLE gt_flight UP TO 500 ROWS. SELECT * FROM sbook INTO TABLE gt_book UP TO 50000 ROWS. * ===== BAD: nested loop over two standard tables ===== GET RUN TIME FIELD gv_t0. LOOP AT gt_flight INTO gs_flight. LOOP AT gt_book INTO gs_book WHERE carrid = gs_flight-carrid AND connid = gs_flight-connid AND fldate = gs_flight-fldate. gv_count = gv_count + 1. ENDLOOP. ENDLOOP. GET RUN TIME FIELD gv_t1. gv_t1 = gv_t1 - gv_t0. " elapsed microseconds WRITE: / 'nested standard :', gv_t1, 'microsec,', gv_count, 'bookings'. * ===== FIX 1: sorted inner table, LOOP WHERE on key ===== gt_book_s = gt_book. CLEAR gv_count. GET RUN TIME FIELD gv_t0. LOOP AT gt_flight INTO gs_flight. LOOP AT gt_book_s INTO gs_book WHERE carrid = gs_flight-carrid AND connid = gs_flight-connid AND fldate = gs_flight-fldate. gv_count = gv_count + 1. ENDLOOP. ENDLOOP. GET RUN TIME FIELD gv_t1. gv_t1 = gv_t1 - gv_t0. " elapsed microseconds WRITE: / 'sorted inner :', gv_t1, 'microsec,', gv_count, 'bookings'. * ===== FIX 3: parallel cursor on standard tables ===== SORT gt_flight BY carrid connid fldate. SORT gt_book BY carrid connid fldate. CLEAR gv_count. GET RUN TIME FIELD gv_t0. LOOP AT gt_flight INTO gs_flight. READ TABLE gt_book TRANSPORTING NO FIELDS WITH KEY carrid = gs_flight-carrid connid = gs_flight-connid fldate = gs_flight-fldate BINARY SEARCH. CHECK sy-subrc = 0. gv_index = sy-tabix. " first booking of this flight LOOP AT gt_book INTO gs_book FROM gv_index. IF gs_book-carrid <> gs_flight-carrid OR gs_book-connid <> gs_flight-connid OR gs_book-fldate <> gs_flight-fldate. EXIT. " group finished ENDIF. gv_count = gv_count + 1. ENDLOOP. ENDLOOP. GET RUN TIME FIELD gv_t1. gv_t1 = gv_t1 - gv_t0. " elapsed microseconds WRITE: / 'parallel cursor :', gv_t1, 'microsec,', gv_count, 'bookings'.Real project scenario
A freight settlement job matched 40,000 shipments against 1.2 million cost lines with a nested loop over standard tables. It ran for nine hours and was often cancelled by the batch window.
Changing the cost table to a sorted table keyed by shipment number (inner LOOP WHERE) brought runtime down to under five minutes. In another older program that could not be restructured, the parallel cursor technique achieved a similar improvement.
Common mistakes
- Nested loops over two large standard tables.
- Parallel cursor without sorting both tables by the same key.
- Forgetting EXIT when the key changes (loops to the end).
- Testing only with small data.
- Doing in-memory joins that the database could do.
Best practices
- Sorted inner tables with LOOP WHERE on leading key fields.
- Hashed tables for one-to-one lookups.
- Parallel cursor for existing standard-table code.
- Push joins to the database where possible.
- Measure with SAT and realistic volumes.
Interview angle
Questions: "How do you optimize nested loops?", "Explain the parallel cursor technique.", "Why does a sorted inner table help?"
Strong answer: nested loops over standard tables cost rows × rows; use a sorted inner table so LOOP WHERE on leading key fields uses binary search, hashed tables for 1:1 lookups, or the parallel cursor: sort both tables, find the first match with READ … BINARY SEARCH, loop FROM sy-tabix and EXIT when the key changes; better, join in the database.
Practice this lesson
Quick check
1. A nested LOOP over 10,000 flights and 500,000 bookings (both standard tables) is slow. What is the best simple fix?
2. In the parallel cursor technique, how is the first matching inner row found?
3. Why does a sorted inner table speed up LOOP ... WHERE on its key fields?
Interview questions
1. A report scans 200,000 items for every sales order header. How do you fix the lookup design?
Answer it in your own words first, then compare.
2. A pricing report uses several nested loops over large standard tables. How do you optimize it?
Answer it in your own words first, then compare.
Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.
More lessons in this topic
- Internal Tables Part 1: Standard, Sorted and Hashed Tables
- Internal Tables Part 10: Memory Management and Large Internal Tables
- Internal Tables Part 2: Choosing the Right Table Type
- Internal Tables Part 3: Work Area vs Field Symbol vs Data Reference
- Internal Tables Part 4: READ TABLE, sy-subrc and Table Expressions
- Internal Tables Part 5: SORT, BINARY SEARCH and Secondary Keys
- Internal Tables Part 6: APPEND, INSERT, MODIFY, DELETE and COLLECT
- Internal Tables Part 7: Removing Duplicates and Building Unique Datasets