ABAP Performance Mistakes to Avoid in New Code
ABAP

ABAP Performance Mistakes to Avoid in New Code

You have been writing ABAP for a while. You know the syntax, you understand the data dictionary, and your code works. But does it scale? In my experience reviewing hundreds of custom developments, the performance problems that hurt production systems the most are not mysterious edge cases — they are the same ten mistakes appearing again and again in freshly written code. Let me walk you through the most damaging ABAP performance mistakes and show you exactly what to do instead.

Why ABAP Performance Mistakes Still Happen in New Code

The irony is that many developers who make these mistakes are not beginners. They are experienced ABAP programmers who learned patterns that were acceptable on classic R/3 databases but are silently catastrophic on HANA. The shift to S/4HANA changed the rules, and the code editor does not warn you when you break them.

Let us go through the ten most common killers, one by one.

1. SELECT * When You Only Need Three Fields

This is the most widespread ABAP performance mistake I see. Selecting every column from a wide table when your logic only touches two or three fields wastes network bandwidth, application server memory, and HANA resources in ways that compound at scale.

" BAD — fetches 80+ columns when you need 3
SELECT * FROM vbak INTO TABLE @DATA(lt_orders)
  WHERE erdat = @lv_date.

" GOOD — fetch only what you use
SELECT vbeln, kunnr, netwr
  FROM vbak
  INTO TABLE @DATA(lt_orders)
  WHERE erdat = @lv_date.

The rule is simple: name your fields. Always. Even if you end up needing most of them later, being explicit keeps you honest about what the code actually requires.

2. SELECT Inside a LOOP

If there is one pattern that makes DBAs lose sleep, it is a database call inside a loop. Each iteration sends a round-trip to the database. With 10,000 records in your internal table, you get 10,000 queries. I have seen single reports generate over 200,000 database calls this way.

" BAD — N+1 problem in disguise
LOOP AT lt_orders INTO DATA(ls_order).
  SELECT SINGLE * FROM kna1
    INTO @DATA(ls_customer)
    WHERE kunnr = @ls_order-kunnr.
  " ... process
ENDLOOP.

" GOOD — collect keys, fetch once
SELECT kunnr, name1
  FROM kna1
  INTO TABLE @DATA(lt_customers)
  FOR ALL ENTRIES IN @lt_orders
  WHERE kunnr = @lt_orders-kunnr.

Use FOR ALL ENTRIES or a JOIN to fetch the related data in a single statement before the loop. For a deeper comparison of these two approaches, see FOR ALL ENTRIES vs JOIN: HANA Performance.

3. Forgetting to Check FOR ALL ENTRIES Prerequisites

Speaking of FOR ALL ENTRIES — it has a well-known trap. If the driver table is empty, the WHERE clause is ignored entirely and the query returns every row in the target table. This is not just a performance issue; it is a data correctness bug that can cause memory dumps in production.

" Always guard FOR ALL ENTRIES with an empty check
IF lt_orders IS NOT INITIAL.
  SELECT vbeln, posnr, matnr
    FROM vbap
    INTO TABLE @DATA(lt_items)
    FOR ALL ENTRIES IN @lt_orders
    WHERE vbeln = @lt_orders-vbeln.
ENDIF.

4. Nested Loops Without Hash or Sorted Tables

The classic nested LOOP with a READ TABLE inside is O(n²) by default if you use the standard linear search. For small tables it is invisible. For tables with thousands of rows, it degrades fast.

" BAD — linear search on every outer iteration
LOOP AT lt_orders INTO DATA(ls_order).
  READ TABLE lt_customers INTO DATA(ls_cust)
    WITH KEY kunnr = ls_order-kunnr.
ENDLOOP.

" GOOD — hash table for O(1) lookup
DATA: lt_customers TYPE HASHED TABLE OF ty_customer
      WITH UNIQUE KEY kunnr.

LOOP AT lt_orders INTO DATA(ls_order).
  READ TABLE lt_customers INTO DATA(ls_cust)
    WITH TABLE KEY kunnr = ls_order-kunnr.
ENDLOOP.

Use HASHED TABLE for point lookups and SORTED TABLE when you need range access or binary search. The standard table type is almost never the right choice for lookup operations.

5. Missing or Wrong WHERE Clause Conditions

A WHERE clause that does not match the leading columns of an index forces a full table scan. On VBAK with 50 million rows, that difference is the gap between 20 milliseconds and 45 seconds.

" BAD — MANDT is implicit, but ERDAT alone may not use the primary key efficiently
SELECT vbeln FROM vbak
  INTO TABLE @DATA(lt_orders)
  WHERE erdat BETWEEN @lv_from AND @lv_to.

" BETTER — include high-selectivity fields that match an existing index
SELECT vbeln FROM vbak
  INTO TABLE @DATA(lt_orders)
  WHERE vkorg = @lv_org
    AND erdat BETWEEN @lv_from AND @lv_to.

Use ST05 or SQLM to verify your statements are hitting indexes before you transport code to production. The article on using ST05, SAT, and SQLM to find bottlenecks covers this tool chain in detail.

6. Pushing Logic That Belongs in the Database Back to ABAP

On HANA, the database is almost always faster at filtering, aggregating, and joining data than your application server. Fetching raw data and then filtering it in ABAP with a LOOP and IF statements is a common and expensive anti-pattern.

" BAD — fetch everything, filter in ABAP
SELECT vbeln, netwr FROM vbak INTO TABLE @DATA(lt_all).
LOOP AT lt_all INTO DATA(ls_row).
  IF ls_row-netwr > 10000.
    APPEND ls_row TO lt_big_orders.
  ENDIF.
ENDLOOP.

" GOOD — filter in the database
SELECT vbeln, netwr FROM vbak
  INTO TABLE @DATA(lt_big_orders)
  WHERE netwr > 10000.

The same principle applies to aggregations. Use SUM, COUNT, GROUP BY in ABAP SQL instead of summing values in a loop. For a comprehensive look at this approach, HANA code pushdown patterns in ABAP is worth reading end to end.

7. Not Using ABAP SQL Aggregations and Expressions

Modern ABAP SQL supports a wide range of expressions — CASE, CAST, string functions, arithmetic — that can replace post-processing loops entirely. Developers who learned ABAP before these features existed often reach for loops out of habit.

" Use SQL expressions instead of post-processing
SELECT
    vbeln,
    netwr,
    CASE
      WHEN netwr > 100000 THEN 'A'
      WHEN netwr > 10000  THEN 'B'
      ELSE 'C'
    END AS segment
  FROM vbak
  INTO TABLE @DATA(lt_segmented)
  WHERE erdat = @lv_date.

This pattern reduces the data volume transferred and eliminates a loop. Check ABAP SQL optimization and SELECT patterns on HANA for a broader set of these techniques.

8. Updating Records One Row at a Time

The same logic that kills SELECT performance inside loops applies to write operations. Calling UPDATE or MODIFY for a single record inside a loop generates one database round-trip per row.

" BAD — one UPDATE per loop iteration
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<ls_order>).
  <ls_order>-zstatus = 'P'.
  UPDATE vbak FROM <ls_order>.
ENDLOOP.

" GOOD — bulk update using internal table
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<ls_order>).
  <ls_order>-zstatus = 'P'.
ENDLOOP.
UPDATE vbak FROM TABLE @lt_orders.

Collect your changes in an internal table and execute a single bulk UPDATE ... FROM TABLE statement. The database handles this as a set operation and it is dramatically faster.

9. Ignoring FIELD-SYMBOLS and Using Work Area Copies Unnecessarily

This one is subtler. When you loop with INTO DATA(ls_row), ABAP copies every field of the structure on each iteration. For wide structures with many fields, this memory overhead adds up. ASSIGNING FIELD-SYMBOL gives you a reference to the actual table row instead.

" Use FIELD-SYMBOL for loops where you read OR modify the row
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<ls_order>).
  <ls_order>-zflag = abap_true.
ENDLOOP.

" Use INTO only when you explicitly need a copy
LOOP AT lt_orders INTO DATA(ls_order).
  lv_total = lv_total + ls_order-netwr.
ENDLOOP.

The performance difference is modest for small tables but measurable for millions of rows. More importantly, using ASSIGNING when modifying rows avoids the need for a separate MODIFY statement, which is a cleaner pattern overall.

10. Using Dynamic SQL When Static SQL Would Work

Dynamic SQL using string concatenation to build WHERE clauses prevents HANA from caching execution plans effectively. It also bypasses the ABAP syntax check, making bugs harder to catch and security vulnerabilities (like SQL injection via user input) more likely.

" BAD — dynamic SQL loses plan caching and syntax checking
DATA(lv_where) = |vkorg = '{ lv_org }' AND erdat = '{ lv_date }'|.
SELECT * FROM vbak INTO TABLE @DATA(lt_result) WHERE (lv_where).

" GOOD — static SQL with optional conditions
SELECT vbeln, kunnr, netwr
  FROM vbak
  INTO TABLE @DATA(lt_result)
  WHERE vkorg = @lv_org
    AND erdat = @lv_date.

If your WHERE clause genuinely needs to be conditional based on runtime values, use separate SELECT statements per case, or structure your logic so the static compiler sees all possible column references. Reserve dynamic SQL for genuinely dynamic table or field names — not for varying filter conditions.

How to Catch These Before They Reach Production

The best time to fix an ABAP performance mistake is before you hit Transport to Production. Here is the practical workflow I recommend:

  • Code review checklist: Add these ten items to your team's review template. A five-minute review catches what hours of profiling later cannot undo.
  • ST05 during development: Run SQL trace on your own code before release. If you see the same statement executing hundreds of times, something is wrong.
  • SAT for ABAP runtime: Use the ABAP trace to identify where CPU time is actually being spent — often it is the nested loop, not the SELECT.
  • SQLM in production: Monitor statement frequencies after go-live. Outliers in call count almost always point to a SELECT inside a loop that slipped through.

The tooling is covered in depth in finding ABAP bottlenecks with ST05, SAT, and SQLM.

One More Thing: CDS Views Are Not Immune

If your architecture uses CDS views heavily — and it should in S/4HANA — know that these views can carry their own performance anti-patterns. Stacked associations that trigger separate queries, missing client handling annotations, and unused virtual elements all add overhead. CDS view performance anti-patterns goes into the specifics if you want to extend your review process to the data model layer.

Summary

These ten ABAP performance mistakes are not obscure. They are common precisely because they produce working code — code that passes functional tests, code that looks fine in a developer system with 200 rows, code that quietly degrades when it meets real data volumes. The discipline of writing performant ABAP from the start is not about being pessimistic. It is about respecting the system your code will run in and the users who depend on it.

Review your last custom development against this list. I would be surprised if you do not find at least three of these patterns in code that went live in the last six months.