Modern ABAP Syntax Notes — Patterns and Pitfalls from Real Development

ABAP syntax actually used while developing our custom Z reports, organized by topic. Examples come from working sources, together with the pitfalls we actually hit.

Structure: syntax item → explanation → example code. Last updated: 2026-08-13

1. Data Declarations and Types

▷ Inline declarations DATA(...) / @DATA(...) — Declare and assign in one statement. SELECT result tables land directly in @DATA(lt_...) — the structure is derived from the SELECT list

SELECT matnr, maktx FROM makt
  INTO TABLE @DATA(lt_makt)
  WHERE spras = @sy-langu.

DATA(lv_qty) = ls_stpo-menge * iv_losgr / lv_bmeng.
LOOP AT lt_mat INTO DATA(ls_mat). " loop variable inline too
ENDLOOP.

▷ FIELD-SYMBOLS / ASSIGNING — Modify internal table rows in place without copying. ASSIGN COMPONENT lets you pick the column name at runtime (used for dynamic logic such as auto-hiding empty columns)

LOOP AT gt_list ASSIGNING FIELD-SYMBOL(<ls_list>) WHERE typps = 'M'.
  <ls_list>-comp_txt = ls_makt-maktx.  " the original row changes
ENDLOOP.

" access a column by name at runtime (empty-column check in ZCO0041)
ASSIGN COMPONENT ls_comp-name OF STRUCTURE ls_row TO FIELD-SYMBOL(<lv_val>).
IF <lv_val> IS ASSIGNED AND <lv_val> IS NOT INITIAL. ...

▷ TYPES / structure and table types — Declaring ty_list (one ALV row) and tt_... (table type) at the top of the report is the common skeleton of all our reports

TYPES: BEGIN OF ty_list,
         matnr TYPE mara-matnr,
         maktx TYPE makt-maktx,
         dmbtr TYPE mseg-dmbtr,
         waers TYPE t001-waers,
       END OF ty_list,
       tt_list TYPE STANDARD TABLE OF ty_list WITH DEFAULT KEY.
DATA gt_list TYPE tt_list.

2. Constructor Expressions (VALUE / COND / CONV)

▷ VALUE #( ... ) — Build a structure or table row in one statement and APPEND it — replaces multiple field-by-field assignments

APPEND VALUE ty_list(
  bukrs = ls_ce1-bukrs
  matnr = ls_ce1-artnr
  dmbtr = ls_ce1-vv010 ) TO gt_list.

APPEND VALUE #( racct = ls_rpt-racct rfarea = ls_rpt-rfarea
                rpt_amt = ls_rpt-amt ) TO gt_amt.

▷ COND — value from a condition — An IF/ELSE assignment as a single expression. Used for things like the movement type sign (+1/-1)

DATA(lv_sign) = COND i( WHEN ls_mseg-bwart = '262'
                          OR ls_mseg-bwart = '544'
                        THEN -1 ELSE 1 ).

DATA(lv_val) = COND ckis-wertn(
  WHEN sy-subrc <> 0 OR ls_mbew-peinh = 0 THEN 0
  WHEN ls_mbew-vprsv = 'S' THEN lv_qty * ls_mbew-stprs / ls_mbew-peinh
  ELSE lv_qty * ls_mbew-verpr / ls_mbew-peinh ).

▷ CONV — explicit type conversion — Strictly-typed parameters (class methods etc.) need CONV up front — we actually hit this with i_node_text (lvc_value) of CL_GUI_ALV_TREE and with level arithmetic

" tree node text: type mismatch without CONV
CALL METHOD go_tree->add_node
  EXPORTING i_node_text = CONV lvc_value( ls_item-descript ).

" an arithmetic expression can't go straight into a comparison —
" pre-compute it (hit in ZFI0031)
DATA(lv_child_lvl) = CONV stufe_f02e( lv_rs + 1 ).

3. Internal Table Handling

▷ READ TABLE ... BINARY SEARCH — The basic pattern for mass matching such as text enrichment — always SORT first. Read everything with FOR ALL ENTRIES, then attach via BINARY SEARCH

SORT lt_makt BY matnr.
LOOP AT gt_list ASSIGNING FIELD-SYMBOL(<ls>).
  READ TABLE lt_makt INTO DATA(ls_makt)
       WITH KEY matnr = <ls>-matnr BINARY SEARCH.
  IF sy-subrc = 0.
    <ls>-maktx = ls_makt-maktx.
  ENDIF.
ENDLOOP.

▷ SORT + DELETE ADJACENT DUPLICATES — When only existence matters in a FOR ALL ENTRIES result (a GROUP BY substitute) — used for the invoice-receipt check in ZMM0030

SORT lt_inv BY ebeln ebelp.
DELETE ADJACENT DUPLICATES FROM lt_inv COMPARING ebeln ebelp.

▷ COLLECT — Automatically sums numeric fields of rows with the same key — used for per-component quantity/amount totals

LOOP AT lt_mseg INTO DATA(ls_mseg).
  CLEAR ls_amt.
  ls_amt-comp  = ls_mseg-matnr.
  ls_amt-menge = ls_mseg-menge * lv_sign.
  ls_amt-dmbtr = ls_mseg-dmbtr * lv_sign.
  COLLECT ls_amt INTO ct_act.
ENDLOOP.

4. String Processing

▷ String templates |...{ }...| — Variable interpolation and formatting options in one place. ALPHA = OUT strips leading zeros (account 0000004010 → 4010)

DATA(lv_comp) = CONV mara-matnr( |{ ls_frl-racct ALPHA = OUT }| ).
DATA(lv_pair) = |{ ls_key-field }:{ lv_txt }|.

" caution: passing |...| directly as a PERFORM argument is a syntax
" error ("Field \"|\" is unknown") — put it in a variable first
PERFORM add_line USING lv_pair.   " OK
" PERFORM add_line USING |{ ... }|.  " syntax error

▷ CONCATENATE vs && — Prefer && or string templates in new code — CONCATENATE only really earns its place when you need SEPARATED BY

lv_text = lv_a && ' / ' && lv_b.
CONCATENATE lv_a lv_b INTO lv_text SEPARATED BY space.

5. Selection Screens

▷ PARAMETERS / SELECT-OPTIONS — SELECT-OPTIONS must be anchored on a structure that can appear in TABLES — CDS views and dynamic tables can't anchor, so use a similar table instead (ZFI0040 anchors on SKB1/BKPF, ZCO0030 on T001W)

TABLES: t001, mara.
PARAMETERS:
  p_bukrs TYPE bukrs OBLIGATORY DEFAULT 'CC10',
  p_expand AS CHECKBOX DEFAULT 'X'.
SELECT-OPTIONS:
  s_matnr FOR mara-matnr,
  s_werks FOR t001w-werks.

▷ MODIF ID + screen control — Hide fields and toggle them with a specific OK-code (=A) — the hidden option in ZCO0041. Inputs starting with '/' are intercepted by the kernel, so use a plain character (=A)

PARAMETERS p_exec AS CHECKBOX MODIF ID hid.

AT SELECTION-SCREEN OUTPUT.
  LOOP AT SCREEN.
    IF screen-group1 = 'HID' AND gv_exec_vis = abap_false.
      screen-active = '0'.
      MODIFY SCREEN.
    ENDIF.
  ENDLOOP.

AT SELECTION-SCREEN.
  IF sy-ucomm = 'A'.   " when =A is typed in the command field
    gv_exec_vis = boolc( gv_exec_vis = abap_false ).
  ENDIF.

▷ Custom F4 (possible entries) — At F4 time PAI has not run yet, so other fields' values must be read straight off the screen with DYNP_VALUES_READ — the cycle F4 in ZCO0041

AT SELECTION-SCREEN ON VALUE-REQUEST FOR s_cycle-low.
  PERFORM f4_cycle CHANGING s_cycle-low.

" inside the FORM: show the list with F4IF_INT_TABLE_VALUE_REQUEST,
" read the controlling-area input with DYNP_VALUES_READ and filter

6. Report Event Sequence

▷ Event blocks — LOAD-OF-PROGRAM (once at load) → INITIALIZATION (selection screen defaults) → AT SELECTION-SCREEN OUTPUT (before display, every time) → AT SELECTION-SCREEN (input validation / OK-codes) → START-OF-SELECTION (execution). Report globals reset on every run, so state across runs is kept with EXPORT/IMPORT MEMORY ID

LOAD-OF-PROGRAM.
  IMPORT vis = gv_exec_vis FROM MEMORY ID gc_vis_memid.

INITIALIZATION.
  p_spmon = sy-datum(6).

AT SELECTION-SCREEN.
  IF sy-ucomm = 'A'.
    gv_exec_vis = boolc( gv_exec_vis = abap_false ).
    EXPORT vis = gv_exec_vis TO MEMORY ID gc_vis_memid.
  ENDIF.

START-OF-SELECTION.
  PERFORM build_list.

▷ Double-click navigation — SET PARAMETER + CALL TRANSACTION AND SKIP FIRST SCREEN — the user_command pattern of nearly every report

FORM user_command USING rv_ucomm TYPE sy-ucomm
                        rs_selfield TYPE slis_selfield.
  READ TABLE gt_list INTO DATA(ls_row) INDEX rs_selfield-tabindex.
  CASE rv_ucomm.
    WHEN '&IC1'.   " double-click
      SET PARAMETER ID 'AUN' FIELD ls_row-vbeln.
      CALL TRANSACTION 'VA03' AND SKIP FIRST SCREEN.
  ENDCASE.
ENDFORM.

7. Syntax Traps We Actually Hit

▷ SELECT SINGLE + ORDER BY — Syntax error ("ORDER is invalid here") — for the latest single record use UP TO 1 ROWS + ORDER BY + ENDSELECT

SELECT kalnr, kadky FROM keko
  WHERE matnr = @iv_matnr AND freig = 'X'
  ORDER BY kadky DESCENDING
  INTO @DATA(ls_keko)
  UP TO 1 ROWS.
ENDSELECT.

▷ String template as a PERFORM argument — PERFORM ... USING |...| is a syntax error — assign to a variable first (actually hit while deploying ZCO0040)

DATA(lv_pair) = |{ ls_key-field }:{ lv_val }|.
PERFORM add_etc USING lv_pair.   " OK
" PERFORM add_etc USING |{ ls_key-field }:{ lv_val }|.  " error

▷ Event parameter type mismatch — Receiving the OO ALV event's e_row-index in a FORM parameter typed TYPE i fails activation — receive as TYPE ANY or convert first (hit in ZJNC)

FORM on_double_click USING iv_row TYPE any
                           io_grid TYPE REF TO cl_gui_alv_grid.
  DATA lv_index TYPE i.
  lv_index = iv_row.   " convert here
ENDFORM.

▷ FOR ALL ENTRIES implicit duplicate removal — Without a unique key in the SELECT list, identical rows collapse into one — always include the document key (MBLNR/ZEILE, ...). This is why subcontracting actuals once showed zero in ZCO0020

SELECT mblnr, mjahr, zeile,   " unique key columns required
       bwart, matnr, menge, dmbtr
  FROM mseg
  FOR ALL ENTRIES IN @lt_aufnr
  WHERE aufnr = @lt_aufnr-aufnr
  INTO TABLE @DATA(lt_mseg).
Where these documents come from
Everything on this blog comes from one small dumpling factory built end-to-end in an S/4HANA sandbox — one plant, one product, one month, every document number real. The whole build is written up in two books.
· Free 16-page sample — the opening chapters, no email required
· How Sarah Joined a K-Dumpling Company and Became an SAP Genius — the business novel, 53 pages, $25
· The Dumpling Factory: Building a Complete SAP S/4HANA Company from Scratch — the build manual, 85 pages, $50

Comments

Popular posts from this blog

SAP S/4HANA Table Reference — Tables I Actually Query, by Topic

A 36% margin became 6% at month-end, and every posting was correct