READ TABLE and table expressions — one row, two ways to fail¶
Level: 201 · working knowledge
One line: READ TABLE … INTO wa WITH KEY … reports a miss through sy-subrc and leaves the work area holding whatever it held before, while itab[ … ] raises CX_SY_ITAB_LINE_NOT_FOUND — so the statement fails quietly and the expression fails loudly, and which one you want depends on whether a miss is normal.
The statement form¶
Two things go wrong here, both famous:
- Nobody checks
sy-subrc. The next statement usesls_hitas if it were found. - The work area is not cleared on a miss. It still holds the previous row, so the code carries on with real-looking data from an unrelated record. This is the difference between a wrong answer and a dump, and it is why the expression form exists.
READ TABLE … ASSIGNING <fs> avoids the copy, and an unassigned field symbol at least dumps rather than lying.
The expression form¶
DATA(ls_two) = lt_rows[ id = 2 ]. " raises on a miss
DATA(ls_opt) = VALUE #( lt_rows[ id = 9 ] OPTIONAL ). " initial row on a miss
DATA(ls_def) = VALUE #( lt_rows[ id = 9 ] DEFAULT lt_rows[ 1 ] ).
There is no sy-subrc to forget. A miss is an exception you either catch or let end the program — and for a row that must exist, ending the program at the point of the missing row beats continuing with the last one. OPTIONAL and DEFAULT are the deliberate ways to say a miss is fine.
lt_rows[ 1 ] reads by index; lt_rows[ KEY by_region region = `EU` ] reads by a named secondary key.
line_exists( ) and line_index( )¶
Both take a table expression and neither raises. The cost is that asking-then-reading searches twice; for a test that is fine, for a read prefer OPTIONAL and one search.
BINARY SEARCH is a promise you have to keep¶
READ TABLE … WITH KEY … BINARY SEARCH on a standard table is fast only because it assumes the table is sorted by exactly those fields, in that order. If it is not, the read does not fail — it returns the wrong row or reports a miss on a row that is present. A sorted or hashed table type makes the guarantee structural instead of something a maintainer has to remember; see Internal tables.
z_kw_read_table.prog.abap — pasted here by tools/check_examples.py. Syntax-checked, never run: abaplint parses and type-checks it against the release in abaplint.json, but no system has produced output for it, so this page shows none.
REPORT z_kw_read_table.
TYPES: BEGIN OF ty_row,
id TYPE i,
name TYPE string,
END OF ty_row.
TYPES ty_tab TYPE STANDARD TABLE OF ty_row WITH EMPTY KEY.
DATA(lt_rows) = VALUE ty_tab( ( id = 1 name = `Ada` )
( id = 2 name = `Grace` )
( id = 3 name = `Katherine` ) ).
" The statement form reports through sy-subrc, and says nothing if you forget
" to look. The work area keeps its previous contents when the read misses.
READ TABLE lt_rows INTO DATA(ls_hit) WITH KEY id = 2.
IF sy-subrc = 0.
WRITE: / 'found', ls_hit-name.
ENDIF.
" The expression form has no sy-subrc: a miss is an exception.
TRY.
DATA(ls_two) = lt_rows[ id = 2 ].
WRITE: / 'expr ', ls_two-name.
DATA(ls_gone) = lt_rows[ id = 99 ].
WRITE: / 'never reached', ls_gone-name.
CATCH cx_sy_itab_line_not_found.
WRITE: / 'no row with id 99'.
ENDTRY.
" OPTIONAL turns a miss into an initial row; DEFAULT into a row you choose.
DATA(ls_opt) = VALUE #( lt_rows[ id = 99 ] OPTIONAL ).
DATA(ls_def) = VALUE #( lt_rows[ id = 99 ] DEFAULT lt_rows[ 1 ] ).
" Asking first costs a second search, so prefer it for a test, not for a read.
IF line_exists( lt_rows[ id = 3 ] ).
WRITE: / 'index', line_index( lt_rows[ id = 3 ] ).
ENDIF.
" ASSIGNING reads without copying the row.
READ TABLE lt_rows ASSIGNING FIELD-SYMBOL(<ls_first>) INDEX 1.
IF <ls_first> IS ASSIGNED.
WRITE: / 'first', <ls_first>-name.
ENDIF.
WRITE: / 'opt id', ls_opt-id.
WRITE: / 'def ', ls_def-name.
If you are coming from another language¶
- Python.
d[k]raisesKeyErrorandd.get(k)returnsNone: exactly theitab[ ]versusOPTIONALpair. TheREAD TABLEform has no Python equivalent, because nothing in Python leaves the old value in place on a miss. - Rust.
map[&k]panics,map.get(&k)returns anOption.DEFAULTisunwrap_or. - Java.
Map.getreturningnullis the closest, and it has the same problemREAD TABLEhas — the caller must remember to check.
See also¶
LOOP AT— when you want every row, and the sameINTO/ASSIGNINGchoice- Internal tables — which table kind makes this read cheap
VALUE— whereOPTIONALandDEFAULTcome fromTRY,CATCH,RAISE— catching the miss when a miss is normal- System fields —
sy-subrc,sy-tabix,sy-indexand the rest - Sets in ABAP —
line_exists( )as the membership test