FOR ALL ENTRIES with an empty table: the trap and the fix
TL;DR
- If the internal table after FOR ALL ENTRIES is empty, the entire WHERE condition is ignored, including conditions that don't use the table. Every row is read.
- Always wrap the SELECT in IF itab IS NOT INITIAL. ... ENDIF.
- FOR ALL ENTRIES removes duplicate result rows. Select all key fields, or rows that happen to be identical are lost.
- An empty ranges table has the same effect: WHERE col IN @range is always true when the range is initial.
- AI assistants often forget the guard, so make it a rule in your Cursor, Copilot or AGENTS.md instructions.
FOR ALL ENTRIES is one of the most common patterns in classic ABAP: read some keys into an internal table, then fetch the matching rows from the database in one statement. It is also behind a whole class of production incidents, because of one documented rule that is easy to forget.
The trap
Here is code an AI assistant (or a tired developer) writes every day:
SELECT vbeln, posnr, matnr, netwr
FROM vbap
FOR ALL ENTRIES IN @orders
WHERE vbeln = @orders-vbeln
AND erdat >= @date_from
INTO TABLE @DATA(items).
It activates, the syntax check is happy, and in a test with a few orders it returns exactly what you expect. Now imagine orders is empty because an earlier selection found nothing.
SAP's ABAP Keyword Documentation is explicit: if the internal table is empty, the entire WHERE condition is ignored. No row of the database table is skipped. That includes erdat >= @date_from, which has nothing to do with the internal table. The statement reads every item in VBAP for the current client.
On a development system with a few hundred rows, nobody notices. In production it can mean a very long runtime, a memory dump, or worse, a program that silently processes every document in the system.
The fix: guard the driver table
The fix is one IF:
IF orders IS NOT INITIAL.
SELECT vbeln, posnr, matnr, netwr
FROM vbap
FOR ALL ENTRIES IN @orders
WHERE vbeln = @orders-vbeln
AND erdat >= @date_from
INTO TABLE @DATA(items).
ENDIF.
A few practical points:
- Put the guard directly around the SELECT. A check twenty lines earlier stops protecting you as soon as someone adds a
DELETEin between. - Decide what "empty" means for the caller. If the SELECT is skipped, the target table stays as it was. That's usually what you want, but if the code runs in a loop, clear the target first so old rows don't leak into the next pass.
- Remove duplicate keys from the driver. Sorting the driver and deleting adjacent duplicates keeps the generated database statements smaller:
SORT orders BY vbeln.
DELETE ADJACENT DUPLICATES FROM orders COMPARING vbeln.
The second trap: duplicate rows disappear
The same documentation page says that FOR ALL ENTRIES removes rows that occur more than once in the result set, considering the entire content of the row. It behaves like SELECT DISTINCT.
That's harmless when you select the full primary key. It's a silent data bug when you don't:
" Wrong: no POSNR, so two items with the same net value collapse into one
SELECT vbeln, netwr
FROM vbap
FOR ALL ENTRIES IN @orders
WHERE vbeln = @orders-vbeln
INTO TABLE @DATA(values).
If an order has two items worth 100.00 each, values contains one row, and any total you compute from it is wrong. Add the key field and the problem goes away:
SELECT vbeln, posnr, netwr
FROM vbap
FOR ALL ENTRIES IN @orders
WHERE vbeln = @orders-vbeln
INTO TABLE @DATA(values).
Rule of thumb: with FOR ALL ENTRIES, always select all key fields of the table you read.
Other restrictions worth knowing
FOR ALL ENTRIES doesn't combine freely with the rest of ABAP SQL. According to the keyword documentation:
- It can't be used with
SELECT SINGLE. - Aggregate expressions other than
COUNT( * )aren't allowed in the SELECT list, andGROUP BYhas no effect. ORDER BYis only possible asORDER BY PRIMARY KEY, for a single table or view.- It can't be combined with
UNION,INTERSECTorEXCEPT.
These are exactly the details an AI assistant tends to get wrong, because they only show up in the documentation, not in typical example code.
A related trap: empty ranges tables
Ranges tables have a similar rule. The documentation for IN range_tab says that if the ranges table is initial, the expression is always true. With no other conditions, every row is selected:
" If material_range is initial, this reads all of MARA
SELECT matnr, mtart
FROM mara
WHERE matnr IN @material_range
INTO TABLE @DATA(materials).
That is often intended for selection-screen input (an empty select-option means "no restriction"). It's a bug when the range is built in code from a list that turned out to be empty. Guard it the same way, and keep in mind that very large ranges tables can exceed the database's limit on input values, which raises CX_SY_OPEN_SQL_DB.
Alternatives to FOR ALL ENTRIES
FOR ALL ENTRIES isn't wrong: the documentation notes that it can use table buffering and can be more efficient than a join in some cases. But it's not your only option:
- A join or subquery, when the keys come from another database table anyway. One statement, no driver table, no empty-table trap.
- An internal table as a data source (
SELECT ... FROM @itab), available from 7.52. Check the keyword documentation for your release and database before relying on it in a join. - A CDS view, on S/4HANA, when the same combination of tables is read in several places.
Whichever you choose, avoid the worst option: a SELECT SINGLE inside a LOOP.
Why AI assistants get this wrong
Language models learn from public code, and a lot of public ABAP uses FOR ALL ENTRIES without a guard because the guard sits somewhere else, or because the example was never run on an empty table. The model reproduces the pattern without the context.
The reliable fix is to state the rule where the assistant reads it on every request: "Always guard FOR ALL ENTRIES with IF itab IS NOT INITIAL, and select all key fields." Our free lite rules include it in the ABAP SQL rule, together with strict-mode @ escaping and the SELECT SINGLE full-key rule. For more of these patterns, see ABAP mistakes AI coding assistants make.
As always, rules reduce repeat mistakes but don't replace review. An ATC run and a unit test with an empty driver table will catch this bug before it reaches production.
Get the rules
Start with the free rules: ABAP for Agents Lite (MIT) has the core rules from this guide, ready for Cursor, Claude Code, GitHub Copilot, Windsurf and AGENTS.md, in English and Portuguese.
Need more? The full Rules Pack (v1.1) has 300+ rules across 20 topics (adding performance, security, RAP, CDS, OData and Fiori, ABAP Cloud and clean core, LUW and locks, BAPIs, ABAP Unit and S/4HANA migration) plus 8 agent skills, with free updates. It is 19 € at launch (29 € after).
More guides
ABAP mistakes AI coding assistants make, and how to prevent them
The pillar guide. Nine repeat mistakes, why models make them, and the rule or code pattern that prevents each one.
Cursor rules for SAP ABAP: a setup guide
Project rules in .cursor/rules for ABAP: rule types, glob patterns for abapGit and ADT files, an always-on core rule and a quick test.
Using Claude Code and AGENTS.md with SAP ABAP
One AGENTS.md for every agent, a CLAUDE.md that imports it, an on-demand review skill, and how to check that Claude Code loaded them.
GitHub Copilot custom instructions for ABAP
copilot-instructions.md for the core rules, applyTo-scoped instruction files for ABAP sources, a review prompt file, and where each one works.
Column totals in a Fiori Elements List Report with RAP and CDS
How to get a totals row in an OData V4 List Report from RAP: the two CDS annotations, currencies and units, the table type, and what to check when the sum doesn't show.