ABAP FOR ALL ENTRIES: Why an Empty Table Reads Every Row, and How to Guard It

A SELECT meant to return a handful of rows can return a whole table. In ABAP, one missing IF statement is enough to cause it.

A few terms first. ABAP is the programming language of SAP systems. An internal table is a table that lives in your program's memory, not in the database. FOR ALL ENTRIES is an addition to the SELECT statement. It lets the rows of an internal table act as filter values, a bit like a long IN list. People often call that internal table the "driver table."

This write-up is from Aimpex Resources Group LLC. Our Lincoln, NE team works on ERP Architecture across SAP S/4HANA, Oracle Cloud ERP and Microsoft Dynamics, and we place SAP specialists with enterprise teams. An unguarded FOR ALL ENTRIES is one of the first things worth looking for in any ABAP code review.

What does the problem look like?

Here is a common version of the problem. A program reads BSID, a finance table of open customer items. At run time, its driver table lt_customer has no rows:

" No guard: if lt_customer is empty, this reads every BSID row of the client
SELECT bukrs, kunnr, gjahr, belnr, buzei
  FROM bsid
  FOR ALL ENTRIES IN @lt_customer
  WHERE bukrs = @p_bukrs
    AND belnr = @lt_customer-belnr
    AND gjahr = @lt_customer-gjahr
  INTO TABLE @lt_open_items.

You might expect a short dump (an ABAP runtime error). Instead, the query copies every BSID record of the current client into the result table.

This is a real risk. On a big table, one unguarded FOR ALL ENTRIES can slow down a whole production system.

Why does an empty table drop the whole WHERE?

SAP's ABAP Keyword Documentation says it in one line: "If the internal table itab is empty, the entire WHERE condition is ignored."

Note the word "entire." The conditions that use the driver table go away, and so does every other condition in the same WHERE. In the query above, bukrs = @p_bukrs filters by company code. It has nothing to do with lt_customer, but it was dropped too.

One filter does stay. In SAP, a client is a separate block of data inside one system. When implicit client handling is on, which is the normal setup, the documentation says "all data is only read from the current client." So you get every row of the current client. On a large table, that is still far too much.

SAP has a runtime check for this exact case. Its documentation says the check helps find "issues that lead to poor performance or high memory consumption."

How do you guard the SELECT?

Check the driver table right before the SELECT. SAP's documentation says "It is strongly recommended that the internal table itab is not initial before using it after FOR ALL ENTRIES." In ABAP, "initial" means empty for a table.

Here is the same query with a guard. (The @ sign marks a variable that comes from the ABAP program.)

" lt_open_items is declared earlier with the five columns below
CLEAR lt_open_items.

IF lt_customer IS NOT INITIAL.
  SELECT bukrs, kunnr, gjahr, belnr, buzei
    FROM bsid
    FOR ALL ENTRIES IN @lt_customer
    WHERE bukrs = @p_bukrs
      AND belnr = @lt_customer-belnr
      AND gjahr = @lt_customer-gjahr
    INTO TABLE @lt_open_items.
ENDIF.

Three details matter here.

  1. Place the guard last. If code between the guard and the SELECT can remove rows, for example a DELETE lt_customer WHERE ..., the table can become empty after the check passed. Keep the IF directly above the SELECT.
  2. Decide what an empty result means. The CLEAR makes sure the result table is empty when the SELECT does not run. Without it, a table reused in a loop can still hold rows from the last pass.
  3. Name the columns you need. SELECT * reads every column. Listing columns keeps the read small, and it also matters for the next trap.

Can FOR ALL ENTRIES lose rows, too?

Yes. This second trap is quieter than the first.

The documentation says: "Rows that occur more than once are removed from the result set automatically. The entire content of a row is considered here." In other words, FOR ALL ENTRIES acts as if you wrote DISTINCT.

So if your column list does not include the table key, two different database rows can look the same and be merged into one. Take sales order items in table VBAP. Two items in one order can have the same material and the same quantity:

" Risky: two items with the same material and quantity become one row
SELECT matnr, kwmeng
  FROM vbap
  FOR ALL ENTRIES IN @lt_orders
  WHERE vbeln = @lt_orders-vbeln
  INTO TABLE @DATA(lt_qty_risky).

" Safer: the key columns VBELN and POSNR keep every item row
SELECT vbeln, posnr, matnr, kwmeng
  FROM vbap
  FOR ALL ENTRIES IN @lt_orders
  WHERE vbeln = @lt_orders-vbeln
  INTO TABLE @DATA(lt_qty).

If you add up kwmeng from the first result, the total can come out too low. SAP's own example shows the same effect: a SELECT of one column from SPFLI usually returns fewer rows than a SELECT of the full key.

You also cannot fix this with SUM( kwmeng ) in the same statement. With FOR ALL ENTRIES, the only aggregate function allowed in the SELECT list is COUNT( * ), and GROUP BY "has no effect."

When is a JOIN the better choice?

Often the driver table comes straight from an earlier SELECT, with no changes in between. In that case, one SELECT with a JOIN can do both reads. There is no driver table, so there is no empty-table case. SAP's own FOR ALL ENTRIES example notes that its selection "could also be carried out in a single SELECT statement by using a join."

SELECT i~vbeln, i~posnr, i~matnr, i~kwmeng
  FROM vbak AS h
  INNER JOIN vbap AS i ON i~vbeln = h~vbeln
  WHERE h~vkorg = @p_vkorg
    AND h~erdat IN @s_erdat
  INTO TABLE @DATA(lt_items).

Here VBAK holds the sales order headers and VBAP holds the items. p_vkorg is a sales organization parameter, and s_erdat is a date range from the selection screen.

A JOIN is not always the answer, though. The documentation adds that "For buffered tables, the addition FOR ALL ENTRIES can be a more efficient alternative to join expressions." And when the program changes the driver table between the two reads, FOR ALL ENTRIES is still the natural fit. Then the guard is a must.

What should a code review check?

  1. Search the program for FOR ALL ENTRIES.
  2. For each hit, find the IS NOT INITIAL check. Confirm that nothing between the check and the SELECT can empty the table.
  3. Confirm the result table is cleared, or is meant to keep its old rows, when the SELECT is skipped.
  4. Compare the column list with the table key. If the key is missing, decide whether merged rows are really fine.
  5. Ask whether a JOIN could replace the two-step read.
  6. In test systems, SAP's Runtime Check Monitor has a check called "Empty table in FOR_ALL_ENTRIES clause." It records each time the driver table was empty at run time.

Sources

Aimpex Resources Group LLC is a Lincoln, NE firm founded in 2025 that works in ERP Architecture, Supply Chain Analytics and IT Staffing Solutions. Its ERP architects work across SAP S/4HANA, Oracle Cloud ERP and Microsoft Dynamics, and its staffing practice aligns SAP specialists, Java and .NET engineers, cloud practitioners, data professionals and quality engineers with clearly defined roles. Teams that want cleaner ABAP on their S/4HANA programs can learn more at aimpexresources.com.

参与讨论