* ABAP = Advanced Business Application Programming
* Proprietary language developed by SAP
* Runs exclusively on SAP Application Server (NetWeaver)
* Key characteristics:
* - Strongly typed, statically checked
* - Built-in database integration (Open SQL)
* - Event-driven programming model
* - Backwards compatible (code from 1990s still runs)
* Two syntax forms exist:
* 1. Classic ABAP (statement-based)
* 2. ABAP 7.40+ (expression-based, more modern)
| Type | Code | Purpose | Execution |
|---|---|---|---|
| Executable Program | 1 |
Reports, batch jobs | Direct (SE38, SA38) |
| Module Pool | M |
Dialog programs with screens | Via transaction code |
| Function Group | F |
Container for function modules | Called by other programs |
| Class Pool | K |
Global ABAP class | Instantiated/called |
| Interface Pool | J |
Global interface definition | Implemented by classes |
| Include | I |
Reusable code fragment | Included in other programs |
Transaction Codes for Development:
SE80 - Object Navigator (main development hub)
SE38 - ABAP Editor (programs only)
SE37 - Function Builder
SE24 - Class Builder
SE11 - Data Dictionary (tables, structures, data elements)
SE16 - Data Browser (view table contents)
ST22 - ABAP Dump Analysis (runtime errors)
SE91 - Message Maintenance
Navigation in SE80:
1. Enter program/class/function group name
2. Double-click to navigate to definitions
3. F3 = Back, Ctrl+F3 = Check syntax
4. Ctrl+F2 = Activate, F8 = Execute
Modern IDE features:
- Code completion (Ctrl+Space)
- Inline error checking
- Refactoring tools
- Git integration
- Multiple system connections
Installation:
1. Download Eclipse (latest supported version)
2. Help → Install New Software
3. Add SAP repository: https://tools.hana.ondemand.com/latest
4. Install "ABAP Development Tools"
Project setup:
1. File → New → ABAP Project
2. Enter system connection details
3. Provide SAP credentials
4. Select package for development
* When creating any object, you specify:
* 1. Package - logical container for related objects
* 2. Transport request - moves objects between systems
* Package types:
* $TMP - Local, temporary (not transportable)
* ZPACKAGE - Customer package (Z/Y namespace)
* /NAMESPACE/ - Registered namespace
* Transport request format: <SID>K9<number>
* Example: DEVK900123
* Best practice:
* - Use meaningful package names (Z_FI_REPORTING)
* - One transport per logical change
* - Document transport descriptions clearly
* Every ABAP statement ends with a period
DATA lv_name TYPE string.
* Statements can span multiple lines
DATA lv_long_variable_name
TYPE string
VALUE 'Initial value'.
* Chain statements (classic, less preferred now)
DATA: lv_var1 TYPE i,
lv_var2 TYPE string,
lv_var3 TYPE c LENGTH 10.
* Comments
* This is a full-line comment (asterisk in column 1)
DATA lv_x TYPE i. "This is an end-of-line comment
* Case insensitivity
* DATA, Data, data are all the same
* But convention: KEYWORDS uppercase, variables lowercase
* Classic declaration
DATA lv_counter TYPE i. "Integer
DATA lv_amount TYPE p DECIMALS 2. "Packed decimal
DATA lv_name TYPE string. "Variable length string
DATA lv_flag TYPE c LENGTH 1. "Fixed character
* With initial value
DATA lv_status TYPE c LENGTH 1 VALUE 'A'.
DATA lv_pi TYPE p DECIMALS 5 VALUE '3.14159'.
* Modern inline declaration (ABAP 7.40+)
DATA(lv_result) = 100. "Type inferred as i
DATA(lv_text) = |Hello World|. "Type inferred as string
* Constants
CONSTANTS c_max_items TYPE i VALUE 999.
CONSTANTS c_company TYPE c LENGTH 4 VALUE 'SAP'.
* Field symbols (like pointers/references)
FIELD-SYMBOLS <fs_any> TYPE any.
FIELD-SYMBOLS <fs_line> TYPE mara. "Typed field symbol
| Type | Description | Default Length | Example |
|---|---|---|---|
C |
Fixed-length character | 1 | 'ABCD' |
N |
Numeric text (digits only) | 1 | '001234' |
I |
Integer (4 bytes) | 4 | 12345 |
P |
Packed decimal | 8 | 123.45 |
F |
Floating point (8 bytes) | 8 | '3.14E+00' |
D |
Date (YYYYMMDD) | 8 | '20241215' |
T |
Time (HHMMSS) | 6 | '143052' |
STRING |
Variable-length string | Variable | `Any length` |
XSTRING |
Variable-length byte sequence | Variable | Binary data |
* String templates use | delimiters
DATA(lv_greeting) = |Hello World|.
* Embedded expressions in { }
DATA(lv_name) = 'Alice'.
DATA(lv_msg) = |Hello { lv_name }!|. "Hello Alice!
* Formatting options
DATA(lv_num) = 1234567.
DATA(lv_formatted) = |Amount: { lv_num NUMBER = USER }|.
* Date formatting
DATA(lv_date) = sy-datum.
DATA(lv_date_str) = |Today: { lv_date DATE = USER }|.
* Width and alignment
DATA(lv_padded) = |{ lv_name WIDTH = 10 ALIGN = LEFT PAD = '_' }|.
" Result: 'Alice_____'
* Combining multiple expressions
DATA(lv_full) = |Name: { lv_name }, Date: { sy-datum DATE = ISO }|.
* IF statement
IF lv_amount > 1000.
lv_status = 'H'. "High
ELSEIF lv_amount > 100.
lv_status = 'M'. "Medium
ELSE.
lv_status = 'L'. "Low
ENDIF.
* CASE statement
CASE lv_status.
WHEN 'H'.
WRITE: / 'High value'.
WHEN 'M'.
WRITE: / 'Medium value'.
WHEN 'L'.
WRITE: / 'Low value'.
WHEN OTHERS.
WRITE: / 'Unknown'.
ENDCASE.
* DO loop (fixed iterations)
DO 10 TIMES.
WRITE: / sy-index. "sy-index = current iteration (1-based)
ENDDO.
* WHILE loop
lv_counter = 0.
WHILE lv_counter < 10.
lv_counter = lv_counter + 1.
ENDWHILE.
* LOOP AT (for internal tables - see later section)
LOOP AT lt_data INTO ls_line.
WRITE: / ls_line-field1.
ENDLOOP.
* Conditional expression (COND)
DATA(lv_grade) = COND string(
WHEN lv_score >= 90 THEN 'A'
WHEN lv_score >= 80 THEN 'B'
WHEN lv_score >= 70 THEN 'C'
ELSE 'F' ).
* Switch expression (SWITCH)
DATA(lv_day_name) = SWITCH string( lv_day_num
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
ELSE 'Weekend' ).
* Constructor expressions
DATA(lt_numbers) = VALUE int4_table( ( 1 ) ( 2 ) ( 3 ) ).
* Iteration expression
DATA(lt_doubled) = VALUE int4_table(
FOR wa IN lt_numbers ( wa * 2 ) ).
* Corresponding - map fields by name
ls_target = CORRESPONDING #( ls_source ).
REPORT zmy_first_program.
* Global data declarations
DATA: gv_message TYPE string.
* Initialization event (runs once at start)
INITIALIZATION.
gv_message = 'Program started'.
* Start of selection (main processing)
START-OF-SELECTION.
WRITE: / gv_message.
WRITE: / 'Hello, ABAP World!'.
WRITE: / 'Current date:', sy-datum.
WRITE: / 'Current time:', sy-uzeit.
REPORT zmy_report.
* Single parameters
PARAMETERS: p_matnr TYPE matnr, "Material number
p_date TYPE sy-datum DEFAULT sy-datum,
p_check AS CHECKBOX DEFAULT 'X'.
* Select-options (ranges)
SELECT-OPTIONS: s_werks FOR mara-werks, "Plant range
s_mtart FOR mara-mtart. "Material type range
* Selection screen blocks
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001.
PARAMETERS p_name TYPE string LOWER CASE.
SELECTION-SCREEN END OF BLOCK b1.
* Initialization - set defaults
INITIALIZATION.
s_mtart-sign = 'I'.
s_mtart-option = 'EQ'.
s_mtart-low = 'FERT'.
APPEND s_mtart.
* Validate input
AT SELECTION-SCREEN.
IF p_matnr IS INITIAL.
MESSAGE 'Please enter material number' TYPE 'E'.
ENDIF.
START-OF-SELECTION.
" Use parameters and select-options in queries
SELECT * FROM mara
WHERE matnr = @p_matnr
AND werks IN @s_werks
INTO TABLE @DATA(lt_mara).
REPORT zmodular_program.
DATA: gv_result TYPE i.
START-OF-SELECTION.
PERFORM calculate_sum USING 10 20 CHANGING gv_result.
WRITE: / 'Sum:', gv_result.
PERFORM display_message USING 'Processing complete'.
*&---------------------------------------------------------------------*
*& Form calculate_sum
*&---------------------------------------------------------------------*
FORM calculate_sum USING pv_a TYPE i
pv_b TYPE i
CHANGING pv_result TYPE i.
pv_result = pv_a + pv_b.
ENDFORM.
*&---------------------------------------------------------------------*
*& Form display_message
*&---------------------------------------------------------------------*
FORM display_message USING pv_text TYPE string.
WRITE: / pv_text.
ENDFORM.
REPORT zmethod_example.
* Local class definition
CLASS lcl_calculator DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
add IMPORTING iv_a TYPE i
iv_b TYPE i
RETURNING VALUE(rv_result) TYPE i,
multiply IMPORTING iv_a TYPE i
iv_b TYPE i
RETURNING VALUE(rv_result) TYPE i.
ENDCLASS.
* Local class implementation
CLASS lcl_calculator IMPLEMENTATION.
METHOD add.
rv_result = iv_a + iv_b.
ENDMETHOD.
METHOD multiply.
rv_result = iv_a * iv_b.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA(lv_sum) = lcl_calculator=>add( iv_a = 10 iv_b = 20 ).
DATA(lv_product) = lcl_calculator=>multiply( iv_a = 5 iv_b = 6 ).
WRITE: / 'Sum:', lv_sum.
WRITE: / 'Product:', lv_product.
* Internal tables = dynamic arrays in memory
* Fundamental ABAP data structure for handling multiple records
* Three components:
* 1. Line type - structure of each row
* 2. Table type - how data is organized (standard/sorted/hashed)
* 3. Key - fields for identification
* Memory visualization:
* +--------+--------+--------+
* | MATNR | MAKTX | MEINS | <-- Header (optional, deprecated)
* +--------+--------+--------+
* | MAT001 | Widget | EA | <-- Line 1
* | MAT002 | Gadget | PC | <-- Line 2
* | MAT003 | Part | KG | <-- Line 3
* +--------+--------+--------+
| Type | Access | Key | Duplicates | Use Case |
|---|---|---|---|---|
| STANDARD | Index or key (linear search) | Non-unique | Allowed | General purpose, small tables |
| SORTED | Index or key (binary search) | Unique or non-unique | Depends on key | Frequent key access, medium tables |
| HASHED | Key only (hash algorithm) | Unique only | Not allowed | Large tables, fast key lookup |
* Work area (structure) for single line
DATA: ls_material TYPE mara.
* Classic declaration - internal table with header line (obsolete)
DATA: lt_old TYPE TABLE OF mara WITH HEADER LINE.
* Modern declaration - separate work area
DATA: lt_materials TYPE TABLE OF mara, "Standard table
ls_mat TYPE mara. "Work area
* Sorted table with unique key
DATA: lt_sorted TYPE SORTED TABLE OF mara
WITH UNIQUE KEY matnr.
* Hashed table
DATA: lt_hashed TYPE HASHED TABLE OF mara
WITH UNIQUE KEY matnr.
* Inline declaration (7.40+)
SELECT * FROM mara INTO TABLE @DATA(lt_mara_new).
* Table type definition (reusable)
TYPES: tt_materials TYPE STANDARD TABLE OF mara
WITH NON-UNIQUE KEY matnr.
DATA: lt_mat TYPE tt_materials.
* Append a line
ls_material-matnr = 'MAT001'.
ls_material-mtart = 'FERT'.
APPEND ls_material TO lt_materials.
* Modern append with VALUE
APPEND VALUE #( matnr = 'MAT002' mtart = 'HALB' ) TO lt_materials.
* Insert at specific index
INSERT ls_material INTO lt_materials INDEX 1.
* Read by index
READ TABLE lt_materials INTO ls_material INDEX 1.
IF sy-subrc = 0.
" Found
ENDIF.
* Read by key
READ TABLE lt_materials INTO ls_material
WITH KEY matnr = 'MAT001'.
* Modern read with inline result (7.40+)
DATA(ls_found) = lt_materials[ matnr = 'MAT001' ].
* Safe read with line_exists (avoid dump on not found)
IF line_exists( lt_materials[ matnr = 'MAT001' ] ).
DATA(ls_safe) = lt_materials[ matnr = 'MAT001' ].
ENDIF.
* Modify
ls_material-mtart = 'ROH'.
MODIFY TABLE lt_materials FROM ls_material.
* Delete
DELETE lt_materials WHERE mtart = 'ROH'.
DELETE lt_materials INDEX 1.
* Clear table
CLEAR lt_materials. "Keeps memory allocated
FREE lt_materials. "Releases memory
* Classic loop with work area
LOOP AT lt_materials INTO ls_material.
WRITE: / ls_material-matnr, ls_material-mtart.
ENDLOOP.
* Loop with field symbol (better performance, direct access)
LOOP AT lt_materials ASSIGNING FIELD-SYMBOL(<fs_mat>).
<fs_mat>-mtart = 'NEW'. "Directly modifies table
WRITE: / <fs_mat>-matnr.
ENDLOOP.
* Loop with inline declaration (7.40+)
LOOP AT lt_materials INTO DATA(ls_line).
WRITE: / ls_line-matnr.
ENDLOOP.
* Loop with index
LOOP AT lt_materials INTO ls_material.
WRITE: / sy-tabix, ls_material-matnr. "sy-tabix = current index
ENDLOOP.
* Loop with condition
LOOP AT lt_materials INTO ls_material
WHERE mtart = 'FERT'.
WRITE: / ls_material-matnr.
ENDLOOP.
* Parallel cursor (efficient nested loops)
LOOP AT lt_header INTO DATA(ls_header).
LOOP AT lt_item INTO DATA(ls_item)
WHERE docnum = ls_header-docnum.
" Process item
ENDLOOP.
ENDLOOP.
* VALUE constructor - create table with data
DATA(lt_numbers) = VALUE int4_table(
( 10 ) ( 20 ) ( 30 ) ( 40 ) ).
* Create table of structures
DATA(lt_persons) = VALUE tt_person(
( name = 'Alice' age = 30 )
( name = 'Bob' age = 25 )
( name = 'Carol' age = 35 ) ).
* CORRESPONDING - copy matching fields
DATA(lt_target) = CORRESPONDING tt_target( lt_source ).
* FOR iteration expression
DATA(lt_doubled) = VALUE int4_table(
FOR wa IN lt_numbers ( wa * 2 ) ).
* FOR with WHERE condition
DATA(lt_adults) = VALUE tt_person(
FOR wa IN lt_persons WHERE ( age >= 18 )
( wa ) ).
* FILTER - extract matching entries
DATA(lt_filtered) = FILTER #( lt_persons
WHERE age > 25 ).
* REDUCE - aggregate values
DATA(lv_sum) = REDUCE i(
INIT sum = 0
FOR wa IN lt_numbers
NEXT sum = sum + wa ).
" Result: 100
* Line exists and line index
IF line_exists( lt_persons[ name = 'Alice' ] ).
DATA(lv_idx) = line_index( lt_persons[ name = 'Alice' ] ).
ENDIF.
* Open SQL = SAP's database abstraction layer
* Translates to native SQL for any supported database
* Provides syntax checking at compile time
* Key features:
* - Database-independent syntax
* - Integrated with ABAP type system
* - Automatic client handling (multi-tenant)
* - Buffer integration
* Supported statements:
* SELECT, INSERT, UPDATE, DELETE, MODIFY
* Select single record
SELECT SINGLE * FROM mara
WHERE matnr = 'MAT001'
INTO @DATA(ls_material).
IF sy-subrc = 0.
WRITE: / 'Found:', ls_material-matnr.
ENDIF.
* Select into internal table
SELECT * FROM mara
WHERE mtart = 'FERT'
INTO TABLE @DATA(lt_materials).
* Select specific fields
SELECT matnr, mtart, meins
FROM mara
WHERE mtart IN @s_mtart
INTO TABLE @DATA(lt_result).
* Select with join
SELECT m~matnr, m~mtart, t~maktx
FROM mara AS m
INNER JOIN makt AS t ON t~matnr = m~matnr
WHERE m~mtart = 'FERT'
AND t~spras = @sy-langu
INTO TABLE @DATA(lt_with_text).
* Select with aggregation
SELECT mtart, COUNT(*) AS count
FROM mara
GROUP BY mtart
INTO TABLE @DATA(lt_counts).
* Classic syntax (still valid)
SELECT * FROM mara INTO TABLE lt_materials
WHERE mtart = 'FERT'.
* Modern syntax (7.40+) - @ escapes host variables
SELECT * FROM mara
INTO TABLE @lt_materials
WHERE mtart = @lv_type.
* Classic field list
SELECT matnr mtart meins FROM mara INTO TABLE lt_result.
* Modern field list (comma separated)
SELECT matnr, mtart, meins FROM mara INTO TABLE @lt_result.
* Classic - work area loop
SELECT * FROM mara INTO ls_material
WHERE mtart = 'FERT'.
WRITE: / ls_material-matnr.
ENDSELECT.
* Modern - package processing (better for large data)
SELECT * FROM mara
WHERE mtart = 'FERT'
INTO TABLE @DATA(lt_package) PACKAGE SIZE 1000.
" Process lt_package
ENDSELECT.
* Insert single row
DATA(ls_new) = VALUE zmytable(
key_field = 'KEY01'
data_field = 'Some value'
created_on = sy-datum ).
INSERT zmytable FROM @ls_new.
IF sy-subrc = 0.
WRITE: / 'Insert successful'.
ENDIF.
* Insert multiple rows
INSERT zmytable FROM TABLE @lt_new_entries.
" sy-dbcnt = number of rows inserted
* Update by key
ls_new-data_field = 'Updated value'.
UPDATE zmytable FROM @ls_new.
* Update with SET
UPDATE zmytable SET data_field = 'New value'
WHERE key_field = 'KEY01'.
* Delete by key
DELETE zmytable FROM @ls_new.
* Delete with condition
DELETE FROM zmytable WHERE created_on < @lv_cutoff_date.
* Modify = Insert or Update (upsert)
MODIFY zmytable FROM @ls_data.
MODIFY zmytable FROM TABLE @lt_data.
* Get related data for entries in an internal table
* CRITICAL: Always check if driver table is not empty!
IF lt_orders IS NOT INITIAL.
SELECT vbeln, posnr, matnr, kwmeng
FROM vbap
FOR ALL ENTRIES IN @lt_orders
WHERE vbeln = @lt_orders-vbeln
INTO TABLE @DATA(lt_items).
ENDIF.
* How it works:
* 1. ABAP sends multiple queries to database
* 2. Results are collected and duplicates removed
* 3. If driver table is empty, ALL records are returned!
* Best practices:
* - Always check IF ... IS NOT INITIAL
* - Remove duplicates from driver table
* - Use for mass data retrieval
* - Consider JOINs for better performance with SAP HANA
| Operation | Syntax | sy-subrc |
|---|---|---|
| Select single | SELECT SINGLE ... WHERE ... |
0=found, 4=not found |
| Select multiple | SELECT ... INTO TABLE ... |
0=found, 4=empty |
| Insert | INSERT <table> FROM ... |
0=success, 4=duplicate key |
| Update | UPDATE <table> FROM/SET ... |
0=success, 4=not found |
| Delete | DELETE <table> FROM/WHERE ... |
0=deleted, 4=not found |
| Modify | MODIFY <table> FROM ... |
Always 0 (insert or update) |
* System fields are in structure SYST, accessed via SY-*
* Return codes
sy-subrc "Return code (0 = success, others = various errors)
sy-dbcnt "Number of database rows processed
* Loop counters
sy-tabix "Current line index in LOOP AT
sy-index "Current iteration in DO/WHILE
* Date and time
sy-datum "Current date (YYYYMMDD)
sy-uzeit "Current time (HHMMSS)
sy-timezone "User's time zone
* User and system
sy-uname "Current username
sy-langu "User's logon language
sy-mandt "Current client (tenant)
sy-sysid "System ID (DEV, QAS, PRD)
* Program information
sy-repid "Current program name
sy-tcode "Current transaction code
sy-cprog "Calling program name
* Screen-related
sy-dynnr "Current screen number
sy-pfkey "Current GUI status
* Message handling
sy-msgty "Message type (E/W/I/S/A)
sy-msgid "Message class
sy-msgno "Message number
sy-msgv1-4 "Message variables
* Check operation success
SELECT * FROM mara INTO TABLE @DATA(lt_mara)
WHERE mtart = 'FERT'.
IF sy-subrc <> 0.
WRITE: / 'No materials found'.
ELSE.
WRITE: / 'Found', sy-dbcnt, 'materials'.
ENDIF.
* Loop index usage
LOOP AT lt_mara INTO DATA(ls_mara).
IF sy-tabix = 1.
WRITE: / 'First item:', ls_mara-matnr.
ENDIF.
WRITE: / sy-tabix, ls_mara-matnr.
ENDLOOP.
* Date calculations
DATA(lv_yesterday) = sy-datum - 1.
DATA(lv_next_month) = sy-datum + 30.
* User and system info
WRITE: / 'User:', sy-uname.
WRITE: / 'Client:', sy-mandt.
WRITE: / 'System:', sy-sysid.
WRITE: / 'Program:', sy-repid.
* Message types
* E - Error (stops processing, requires correction)
* W - Warning (can be overridden)
* I - Info (popup, continues after OK)
* S - Success (status bar, continues)
* A - Abend (terminates transaction)
* X - Exit (short dump with message)
* Direct message
MESSAGE 'Material not found' TYPE 'E'.
* Message from message class (SE91)
MESSAGE e001(zmsg_class) WITH lv_matnr.
* Modern syntax
MESSAGE e001(zmsg_class) WITH lv_matnr INTO DATA(lv_msg).
* Inline message (often used with exceptions)
MESSAGE |Material { lv_matnr } not found| TYPE 'E'.
* Class-based exceptions (modern approach)
TRY.
DATA(lv_result) = 100 / lv_divisor.
WRITE: / 'Result:', lv_result.
CATCH cx_sy_zerodivide INTO DATA(lx_error).
WRITE: / 'Error:', lx_error->get_text( ).
CATCH cx_root INTO DATA(lx_any).
" Catch any exception
WRITE: / 'Unexpected error:', lx_any->get_text( ).
CLEANUP.
" Always executed before leaving TRY block on exception
" Use for resource cleanup
ENDTRY.
* Raise exception
IF lv_input IS INITIAL.
RAISE EXCEPTION TYPE cx_sy_illegal_argument
EXPORTING textid = cx_sy_illegal_argument=>value_missing.
ENDIF.
* Propagate exception (in method signature)
METHODS process
IMPORTING iv_data TYPE string
RAISING cx_custom_exception.
* CATCH SYSTEM-EXCEPTIONS (classic, limited)
DATA lv_result TYPE i.
CATCH SYSTEM-EXCEPTIONS arithmetic_errors = 1
others = 2.
lv_result = 100 / 0.
ENDCATCH.
CASE sy-subrc.
WHEN 1.
WRITE: / 'Arithmetic error'.
WHEN 2.
WRITE: / 'Other error'.
ENDCASE.
* Function module exceptions (classic)
CALL FUNCTION 'CONVERSION_EXIT_MATN1_INPUT'
EXPORTING
input = lv_input
IMPORTING
output = lv_output
EXCEPTIONS
not_found = 1
OTHERS = 2.
IF sy-subrc <> 0.
" Handle error
ENDIF.
* Every statement ends with a period. No exceptions.
DATA lv_count TYPE i.
lv_count = 42.
WRITE lv_count.
DATA lv_very_long_variable_name
TYPE string
VALUE 'Hello'.
* Instead of:
DATA lv_a TYPE i.
DATA lv_b TYPE i.
DATA lv_c TYPE i.
* You can write:
DATA: lv_a TYPE i,
lv_b TYPE i,
lv_c TYPE i.
* Also works with WRITE:
WRITE: / lv_a, / lv_b, / lv_c.
* Asterisk in column 1 = full line comment
DATA lv_x TYPE i. "Double quote = end of line comment
* These are identical:
DATA lv_name TYPE string.
data lv_name type string.
Data Lv_Name Type String.
* Convention: KEYWORDS uppercase, variables lowercase
DATA lv_count TYPE i. "integer (4 bytes)
DATA lv_amount TYPE p DECIMALS 2. "packed decimal (BCD)
DATA lv_name TYPE string. "variable length
DATA lv_code TYPE c LENGTH 10. "fixed length char
DATA lv_date TYPE d. "date: YYYYMMDD
DATA lv_time TYPE t. "time: HHMMSS
| Type | Meaning | Similar to |
|---|---|---|
i |
Integer | int32 |
int8 |
64-bit integer | int64 |
p |
Packed decimal (BCD) | decimal (C#) |
f |
Floating point | double |
c |
Fixed-length character | char[n] |
n |
Numeric text (digits only) | — |
string |
Variable-length string | std::string |
d |
Date (8 chars: YYYYMMDD) | — |
t |
Time (6 chars: HHMMSS) | — |
x |
Fixed-length byte | byte[n] |
xstring |
Variable-length byte | byte[] |
DATA lv_status TYPE c LENGTH 1 VALUE 'A'.
DATA lv_pi TYPE p DECIMALS 5 VALUE '3.14159'.
CONSTANTS c_max TYPE i VALUE 100.
* Check if variable has default value
IF lv_count IS INITIAL.
" i is initial when = 0, string when empty, etc.
ENDIF.
* Type inferred from right side
DATA(lv_num) = 42. "i
DATA(lv_txt) = 'Hello'. "c (length 5)
DATA(lv_str) = |Hello|. "string
lv_result = lv_a + lv_b.
lv_result = lv_a - lv_b.
lv_result = lv_a * lv_b.
lv_result = lv_a / lv_b.
lv_result = lv_a MOD lv_b. "modulo
lv_result = lv_a DIV lv_b. "integer division
lv_result = lv_a ** 2. "power
* Symbolic or Keyword
lv_a = lv_b lv_a EQ lv_b
lv_a <> lv_b lv_a NE lv_b
lv_a < lv_b lv_a LT lv_b
lv_a > lv_b lv_a GT lv_b
lv_a <= lv_b lv_a LE lv_b
lv_a >= lv_b lv_a GE lv_b
* String specific
lv_s CS 'abc' "contains string
lv_s CP '*test*' "covers pattern (wildcard)
lv_s CO '0123456789' "contains only
IF lv_a > 0 AND lv_b > 0.
IF lv_a > 0 OR lv_b > 0.
IF NOT lv_flag IS INITIAL.
IF lv_amount > 1000.
lv_status = 'H'.
ELSEIF lv_amount > 100.
lv_status = 'M'.
ELSE.
lv_status = 'L'.
ENDIF.
CASE lv_status.
WHEN 'H'.
WRITE 'High'.
WHEN 'M' OR 'L'.
WRITE 'Not high'.
WHEN OTHERS.
WRITE 'Unknown'.
ENDCASE.
* Fixed count
DO 10 TIMES.
WRITE sy-index. "1, 2, 3, ... 10
ENDDO.
* Conditional
WHILE lv_count < 10.
lv_count = lv_count + 1.
ENDWHILE.
* Exit and continue
DO 100 TIMES.
IF sy-index = 5.
CONTINUE. "skip to next iteration
ENDIF.
IF sy-index = 50.
EXIT. "break out of loop
ENDIF.
ENDDO.
'Text literal' "type c, trailing spaces trimmed
`Text literal` "type string, spaces preserved
|Text literal| "string template (see below)
DATA(lv_name) = 'Alice'.
DATA(lv_age) = 30.
* Embedded expressions with { }
DATA(lv_msg) = |Hello { lv_name }, you are { lv_age }|.
* Formatting
DATA(lv_formatted) = |Date: { sy-datum DATE = USER }|.
DATA(lv_padded) = |{ lv_name WIDTH = 10 ALIGN = RIGHT }|.
* Classic
CONCATENATE lv_first lv_last INTO lv_full SEPARATED BY ' '.
* Modern
lv_full = lv_first && ' ' && lv_last.
lv_full = |{ lv_first } { lv_last }|.
DATA(lv_len) = strlen( lv_str ).
DATA(lv_upper) = to_upper( lv_str ).
DATA(lv_lower) = to_lower( lv_str ).
DATA(lv_part) = substring( val = lv_str off = 0 len = 5 ).
* Find and replace
FIND 'abc' IN lv_str MATCH OFFSET DATA(lv_pos).
REPLACE ALL OCCURRENCES OF 'old' IN lv_str WITH 'new'.
* Accessed as sy-fieldname, always available
sy-subrc "return code of last operation (0 = success)
sy-datum "current date
sy-uzeit "current time
sy-uname "current user
sy-index "loop counter in DO/WHILE
sy-tabix "current row in LOOP AT internal table
sy-dbcnt "rows affected by database operation
READ TABLE lt_data INTO ls_row INDEX 5.
IF sy-subrc <> 0.
WRITE 'Not found'.
ENDIF.
SELECT SINGLE * FROM mara WHERE matnr = 'X' INTO @DATA(ls_mat).
IF sy-subrc <> 0.
WRITE 'No such material'.
ENDIF.
TYPES: BEGIN OF ty_person,
name TYPE string,
age TYPE i,
city TYPE string,
END OF ty_person.
DATA ls_person TYPE ty_person.
ls_person-name = 'Alice'.
ls_person-age = 30.
ls_person-city = 'Berlin'.
* Instead of defining your own structure,
* use existing table structures from the database
DATA ls_material TYPE mara. "structure of MARA table
ls_material-matnr = 'MAT001'.
ls_material-mtart = 'FERT'.
* COND - like ternary operator, but chainable
DATA(lv_grade) = COND string(
WHEN lv_score >= 90 THEN 'A'
WHEN lv_score >= 80 THEN 'B'
WHEN lv_score >= 70 THEN 'C'
ELSE 'F' ).
* SWITCH - pattern matching on single value
DATA(lv_name) = SWITCH string( lv_day
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
ELSE 'Other' ).
Classic syntax: 1980s-2012
Modern syntax: ABAP 7.40 (2013) onwards
Existing SAP systems have millions of lines of classic code.
You'll read classic, but write modern when your system supports it.
.) - statement terminator:DATA lv_a TYPE i.
DATA lv_b TYPE i.
lv_a = 10.
Every ABAP statement ends with a period. Forgetting it causes syntax errors.
:) and comma (,) - chain statements:* Without chaining:
DATA lv_a TYPE i.
DATA lv_b TYPE i.
DATA lv_c TYPE i.
* With chaining (equivalent):
DATA: lv_a TYPE i,
lv_b TYPE i,
lv_c TYPE i.
Colon after keyword, commas between items, period at end.
/) in WRITE - new line:WRITE 'One'.
WRITE 'Two'.
WRITE 'Three'.
OneTwoThree
WRITE / 'One'.
WRITE / 'Two'.
WRITE / 'Three'.
One
Two
Three
/ means "start new line before printing".
DATA lv_a TYPE i VALUE 10.
DATA lv_b TYPE i VALUE 20.
WRITE: / lv_a, lv_b. "new line, then both values
WRITE: / 'Sum:', lv_a + lv_b.
10 20
Sum: 30
Elementary types - single values (integer, string, date)
Complex types - structures (records) and internal tables (arrays)
Reference types - pointers to data or objects
* Built-in types (i, c, n, string, d, t, etc.)
DATA lv_num TYPE i.
* DDIC types (defined in Data Dictionary, shared across system)
DATA lv_matnr TYPE matnr. "material number - defined in SE11
DATA ls_material TYPE mara. "structure of table MARA
DATA <name> TYPE i. "32-bit signed
DATA <name> TYPE int8. "64-bit signed (ABAP 7.40+)
DATA lv_num TYPE i.
lv_num = 2147483647. "max value
WRITE: / lv_num.
lv_num = lv_num + 1. "overflow
WRITE: / lv_num.
2147483647
-2147483648
Range: -2,147,483,648 to 2,147,483,647. Overflow wraps around.
DATA lv_big TYPE int8.
lv_big = 9223372036854775807. "max value
WRITE: / lv_big.
9223372036854775807
Range: -9.2×10¹⁸ to 9.2×10¹⁸. Use for very large integers.
DATA <name> TYPE p [LENGTH <bytes>] [DECIMALS <places>].
LENGTH = storage size in bytes (1-16, default 8)
DECIMALS = digits after decimal point (0-14, default 0)
Each byte holds 2 digits (BCD encoding). Max digits ≈ LENGTH × 2 - 1.
DATA lv_p1 TYPE p LENGTH 4 DECIMALS 2. "max ~7 digits, 2 after decimal
DATA lv_p2 TYPE p LENGTH 8 DECIMALS 2. "max ~15 digits, 2 after decimal
lv_p1 = '12345.67'.
lv_p2 = '1234567890123.45'.
WRITE: / lv_p1.
WRITE: / lv_p2.
12345.67
1234567890123.45
DATA lv_money TYPE p LENGTH 8 DECIMALS 2.
lv_money = '100.456'.
WRITE: / lv_money.
lv_money = '100.454'.
WRITE: / lv_money.
100.46
100.45
Rounds to specified decimal places. Banker's rounding (round half to even).
DATA lv_float TYPE f.
DATA lv_packed TYPE p DECIMALS 2.
lv_float = '0.1'.
lv_packed = '0.1'.
DO 10 TIMES.
lv_float = lv_float + '0.1'.
lv_packed = lv_packed + '0.1'.
ENDDO.
WRITE: / 'Float:', lv_float.
WRITE: / 'Packed:', lv_packed.
Float: 1.0999999999999999E+00
Packed: 1.10
Floating point has precision errors. Packed decimal is exact. Use packed for money.
DATA <name> TYPE f. "64-bit IEEE 754 binary
DATA <name> TYPE decfloat16. "IEEE 754 decimal, 16 significant digits
DATA <name> TYPE decfloat34. "IEEE 754 decimal, 34 significant digits
DATA lv_f TYPE f.
lv_f = '3.14159265358979'.
WRITE: / lv_f.
lv_f = '1.23E+10'. "scientific notation
WRITE: / lv_f.
3.1415926535897900E+00
1.2300000000000000E+10
~15-17 significant digits. Has precision errors like C/Java double. Use for scientific calculations only.
DATA lv_d16 TYPE decfloat16.
DATA lv_d34 TYPE decfloat34.
lv_d16 = '1234567890123456'. "16 significant digits
lv_d34 = '1234567890123456789012345678901234'. "34 significant digits
WRITE: / lv_d16.
WRITE: / lv_d34.
1234567890123456
1234567890123456789012345678901234
"Significant digits" means total digits that matter, regardless of decimal position:
DATA lv_d16 TYPE decfloat16.
lv_d16 = '0.0000000000000001'. "16th digit after zeros - OK
WRITE: / lv_d16.
lv_d16 = '1234567890.123456'. "10 before + 6 after = 16 - OK
WRITE: / lv_d16.
0.0000000000000001
1234567890.123456
DATA <name> TYPE c [LENGTH <n>].
LENGTH = exact number of characters (1-262143, default 1)
DATA lv_c TYPE c LENGTH 10.
lv_c = 'Hello'.
WRITE: / '>', lv_c, '<'.
lv_c = 'Hello World!!!'.
WRITE: / '>', lv_c, '<'.
>Hello
>Hello Worl
Short values are right-padded with spaces. Long values are truncated.
DATA <name> TYPE string.
DATA lv_str TYPE string.
lv_str = 'Hello'.
WRITE: / '>', lv_str, '<'.
lv_str = 'This can be any length without truncation or padding'.
WRITE: / lv_str.
>Hello
This can be any length without truncation or padding
No padding, no truncation. Preferred for text processing.
DATA <name> TYPE n [LENGTH <n>].
LENGTH = exact number of digits (1-262143, default 1)
DATA lv_n TYPE n LENGTH 10.
lv_n = '123'.
WRITE: / lv_n.
lv_n = 456. "numeric also works
WRITE: / lv_n.
lv_n = 'ABC123'. "non-digits become 0
WRITE: / lv_n.
0000000123
0000000456
0000000123
Left-padded with zeros. Only stores digits 0-9. Used for document numbers, IDs.
DATA lv_c TYPE c LENGTH 10.
DATA lv_str TYPE string.
lv_c = 'Hello'. "type c, trailing spaces trimmed
lv_str = 'Hello'. "converted to string
lv_str = `Hello `. "type string, spaces preserved
WRITE: / '>', lv_str, '<'.
lv_str = |Hello|. "string template, allows expressions
DATA(lv_name) = 'World'.
lv_str = |Hello { lv_name }|.
WRITE: / lv_str.
>Hello
Hello World
| Literal | Type | Spaces | Expressions |
|---|---|---|---|
'...' |
c | Trailing trimmed | No |
`...` |
string | Preserved | No |
|...| |
string | Preserved | Yes { } |
DATA <name> TYPE d.
Always 8 characters: YYYYMMDD
DATA lv_date TYPE d.
lv_date = sy-datum. "current date
WRITE: / 'Today:', lv_date.
lv_date = '20241225'. "Christmas 2024
WRITE: / 'Christmas:', lv_date.
Today: 20241215
Christmas: 20241225
DATA lv_date TYPE d VALUE '20241215'.
lv_date = lv_date + 7. "add 7 days
WRITE: / '+7 days:', lv_date.
lv_date = lv_date - 30. "subtract 30 days
WRITE: / '-30 days:', lv_date.
+7 days: 20241222
-30 days: 20241122
DATA lv_date TYPE d VALUE '20241215'.
DATA(lv_year) = lv_date+0(4). "offset 0, length 4
DATA(lv_month) = lv_date+4(2). "offset 4, length 2
DATA(lv_day) = lv_date+6(2). "offset 6, length 2
WRITE: / 'Year:', lv_year, 'Month:', lv_month, 'Day:', lv_day.
Year: 2024 Month: 12 Day: 15
Syntax: variable+offset(length) extracts substring.
DATA <name> TYPE t.
Always 6 characters: HHMMSS
DATA lv_time TYPE t.
lv_time = sy-uzeit. "current time
WRITE: / 'Now:', lv_time.
lv_time = '143052'. "14:30:52
WRITE: / 'Set:', lv_time.
lv_time = lv_time + 3600. "add 1 hour (3600 seconds)
WRITE: / '+1 hour:', lv_time.
Now: 102345
Set: 143052
+1 hour: 153052
DATA <name> TYPE timestamp. "YYYYMMDDhhmmss as packed decimal
DATA <name> TYPE timestampl. "with microseconds
DATA lv_ts TYPE timestamp.
GET TIME STAMP FIELD lv_ts.
WRITE: / lv_ts.
20241215102345
DATA <name> TYPE x [LENGTH <n>]. "fixed length bytes
DATA <name> TYPE xstring. "variable length bytes
DATA lv_x TYPE x LENGTH 4.
DATA lv_xs TYPE xstring.
lv_x = 'DEADBEEF'. "hex literal
lv_xs = '48454C4C4F'. "hex for 'HELLO'
WRITE: / lv_x.
WRITE: / lv_xs.
DEADBEEF
48454C4C4F
For binary data: files, hashes, encoded content. Each byte shown as 2 hex digits.
TYPES: BEGIN OF <type_name>,
<field1> TYPE <type>,
<field2> TYPE <type>,
...
END OF <type_name>.
DATA <variable> TYPE <type_name>.
TYPES: BEGIN OF ty_person,
name TYPE string,
age TYPE i,
city TYPE string,
END OF ty_person.
DATA ls_person TYPE ty_person.
ls_person-name = 'Alice'.
ls_person-age = 30.
ls_person-city = 'Berlin'.
WRITE: / ls_person-name, ls_person-age, ls_person-city.
Alice 30 Berlin
DATA: BEGIN OF <name>,
<field1> TYPE <type>,
...
END OF <name>.
DATA: BEGIN OF ls_point,
x TYPE i,
y TYPE i,
END OF ls_point.
ls_point-x = 10.
ls_point-y = 20.
WRITE: / ls_point-x, ls_point-y.
10 20
Inline is quick but type not reusable. Prefer TYPES for shared definitions.
DATA ls_material TYPE mara. "structure from table MARA
ls_material-matnr = 'MAT001'.
ls_material-mtart = 'FERT'.
WRITE: / ls_material-matnr, ls_material-mtart.
MAT001 FERT
TYPES: BEGIN OF ty_address,
street TYPE string,
city TYPE string,
END OF ty_address.
TYPES: BEGIN OF ty_person,
name TYPE string,
address TYPE ty_address,
END OF ty_person.
DATA ls_person TYPE ty_person.
ls_person-name = 'Bob'.
ls_person-address-street = '123 Main St'.
ls_person-address-city = 'Munich'.
WRITE: / ls_person-name.
WRITE: / ls_person-address-city.
Bob
Munich
Dynamic arrays in memory. Can grow/shrink. Most important ABAP data structure.
DATA <name> TYPE [STANDARD|SORTED|HASHED] TABLE OF <row_type>
[WITH <key_spec>].
DATA lt_names TYPE TABLE OF string.
APPEND 'Alice' TO lt_names.
APPEND 'Bob' TO lt_names.
APPEND 'Carol' TO lt_names.
LOOP AT lt_names INTO DATA(lv_name).
WRITE: / sy-tabix, lv_name.
ENDLOOP.
1 Alice
2 Bob
3 Carol
Access by index: O(1). Access by key: O(n) linear scan. Duplicates allowed.
TYPES: BEGIN OF ty_emp,
id TYPE i,
name TYPE string,
END OF ty_emp.
DATA lt_emp TYPE SORTED TABLE OF ty_emp WITH UNIQUE KEY id.
INSERT VALUE #( id = 3 name = 'Carol' ) INTO TABLE lt_emp.
INSERT VALUE #( id = 1 name = 'Alice' ) INTO TABLE lt_emp.
INSERT VALUE #( id = 2 name = 'Bob' ) INTO TABLE lt_emp.
LOOP AT lt_emp INTO DATA(ls_emp).
WRITE: / ls_emp-id, ls_emp-name.
ENDLOOP.
1 Alice
2 Bob
3 Carol
Automatically sorted by key. Access by key: O(log n) binary search. UNIQUE KEY prevents duplicates.
DATA lt_emp_hash TYPE HASHED TABLE OF ty_emp WITH UNIQUE KEY id.
INSERT VALUE #( id = 100 name = 'Alice' ) INTO TABLE lt_emp_hash.
INSERT VALUE #( id = 200 name = 'Bob' ) INTO TABLE lt_emp_hash.
READ TABLE lt_emp_hash INTO DATA(ls_found) WITH KEY id = 200.
WRITE: / ls_found-name.
Bob
Access by key: O(1) hash lookup. No index access. Must have UNIQUE KEY.
| Type | Key access | Index access | Duplicates | Best for |
|---|---|---|---|---|
| STANDARD | O(n) | O(1) | Yes | Small tables, sequential |
| SORTED | O(log n) | O(1) | Optional | Medium, need both |
| HASHED | O(1) | No | No | Large, key-only |
DATA <name> TYPE REF TO <type>. "reference to specific type
DATA <name> TYPE REF TO data. "reference to any data
DATA <name> TYPE REF TO object. "reference to any object
DATA lv_value TYPE i VALUE 10.
DATA lr_ref TYPE REF TO i.
lr_ref = REF #( lv_value ). "get reference
WRITE: / 'Original:', lv_value.
WRITE: / 'Via ref:', lr_ref->*.
lr_ref->* = 99. "modify through reference
WRITE: / 'After:', lv_value.
Original: 10
Via ref: 10
After: 99
REF #( ) gets address. ->* dereferences.
DATA lo_obj TYPE REF TO zcl_myclass.
lo_obj = NEW zcl_myclass( ). "create instance
lo_obj->some_method( ). "call method
FIELD-SYMBOLS <<name>> TYPE <type>.
ASSIGN <variable> TO <<name>>.
Like a pointer that's always dereferenced. Changes affect original variable.
DATA lv_num TYPE i VALUE 10.
FIELD-SYMBOLS TYPE i.
ASSIGN lv_num TO .
WRITE: / 'Original:', lv_num.
WRITE: / 'Field symbol:', .
= 99.
WRITE: / 'After:', lv_num.
Original: 10
Field symbol: 10
After: 99
TYPES: BEGIN OF ty_data,
id TYPE i,
name TYPE string,
END OF ty_data.
DATA lt_data TYPE TABLE OF ty_data.
APPEND VALUE #( id = 1 name = 'alice' ) TO lt_data.
APPEND VALUE #( id = 2 name = 'bob' ) TO lt_data.
* With INTO - copies each row (slow, changes don't affect table)
LOOP AT lt_data INTO DATA(ls_row).
ls_row-name = to_upper( ls_row-name ). "only changes the copy!
ENDLOOP.
LOOP AT lt_data INTO ls_row.
WRITE: / ls_row-name. "still lowercase
ENDLOOP.
alice
bob
* With ASSIGNING - direct access (fast, changes affect table)
LOOP AT lt_data ASSIGNING FIELD-SYMBOL().
-name = to_upper( -name ). "modifies table directly
ENDLOOP.
LOOP AT lt_data INTO ls_row.
WRITE: / ls_row-name. "now uppercase
ENDLOOP.
ALICE
BOB
FIELD-SYMBOLS TYPE any.
IF IS ASSIGNED.
" safe to use
ELSE.
" not pointing to anything
ENDIF.
DATA lv_int TYPE i.
DATA lv_str TYPE string.
lv_int = '123'. "string to int
lv_str = 456. "int to string
WRITE: / lv_int, lv_str.
123 456
ABAP converts between compatible types automatically. Can cause silent truncation or unexpected results.
CONV <type>( <value> )
DATA lv_int TYPE i VALUE 42.
DATA(lv_str) = CONV string( lv_int ).
WRITE: / lv_str.
DATA(lv_back) = CONV i( '999' ).
WRITE: / lv_back.
42
999
CORRESPONDING <type>( <source> )
TYPES: BEGIN OF ty_source,
a TYPE i,
b TYPE i,
c TYPE i,
END OF ty_source.
TYPES: BEGIN OF ty_target,
a TYPE i,
b TYPE i,
x TYPE i,
END OF ty_target.
DATA ls_source TYPE ty_source.
ls_source-a = 1.
ls_source-b = 2.
ls_source-c = 3.
DATA(ls_target) = CORRESPONDING ty_target( ls_source ).
WRITE: / 'a:', ls_target-a.
WRITE: / 'b:', ls_target-b.
WRITE: / 'x:', ls_target-x.
a: 1
b: 2
x: 0
Fields a and b copied (same name). Field c ignored (not in target). Field x initial (not in source).
DATA <name> TYPE <type> [LENGTH <n>] [DECIMALS <d>] [VALUE <initial>].
DATA lv_count TYPE i.
DATA lv_name TYPE string.
lv_count = 42.
lv_name = 'Alice'.
WRITE: / lv_count, lv_name.
42 Alice
DATA lv_count TYPE i VALUE 100.
DATA lv_name TYPE string VALUE 'Bob'.
WRITE: / lv_count, lv_name.
100 Bob
DATA lv_code TYPE c LENGTH 5.
DATA lv_docnum TYPE n LENGTH 10.
lv_code = 'ABCDEFGH'. "too long
lv_docnum = '123'. "too short
WRITE: / lv_code, / lv_docnum.
ABCDE
0000000123
c truncates from right. n pads with zeros from left.
DATA lv_amount TYPE p LENGTH 8 DECIMALS 2.
lv_amount = '1234.567'.
WRITE: / lv_amount.
1234.57
Rounded to 2 decimal places. LENGTH 8 means 8 bytes storage (up to 15 digits).
DATA: lv_a TYPE i VALUE 1,
lv_b TYPE i VALUE 2,
lv_c TYPE i VALUE 3.
WRITE: / lv_a, lv_b, lv_c.
1 2 3
DATA(<name>) = <expression>.
Type is inferred from the right side. Cannot specify type or initial value separately.
DATA(lv_int) = 42.
DATA(lv_char) = 'Hello'.
DATA(lv_str) = |Hello|.
WRITE: / lv_int, / lv_char, / lv_str.
42
Hello
Hello
'Hello' becomes type c LENGTH 5. |Hello| becomes type string.
SELECT * FROM mara WHERE mtart = 'FERT' INTO TABLE @DATA(lt_materials).
WRITE: / lines( lt_materials ), 'rows'.
127 rows
lt_materials gets type TABLE OF mara automatically.
LOOP AT lt_materials INTO DATA(ls_mat).
WRITE: / ls_mat-matnr.
ENDLOOP.
MAT001
MAT002
MAT003
...
ls_mat gets the row type of lt_materials.
CONSTANTS <name> TYPE <type> VALUE <value>.
VALUE is mandatory. Cannot be changed after declaration.
CONSTANTS lc_max TYPE i VALUE 100.
CONSTANTS lc_pi TYPE p DECIMALS 5 VALUE '3.14159'.
WRITE: / lc_max, / lc_pi.
100
3.14159
lc_max = 200.
Syntax error: "LC_MAX" cannot be changed.
DATA <name> TYPE <type>.
DATA lv_num TYPE i. "built-in type
DATA lv_matnr TYPE matnr. "DDIC data element
DATA ls_mat TYPE mara. "DDIC structure
DATA lt_mat TYPE TABLE OF mara. "table of DDIC structure
DATA <name> LIKE <variable>.
DATA lv_original TYPE i VALUE 100.
DATA lv_copy LIKE lv_original.
WRITE: / 'original:', lv_original.
WRITE: / 'copy:', lv_copy.
original: 100
copy: 0
Same type (i), but VALUE is not copied. lv_copy gets initial value 0.
DATA <name> LIKE LINE OF <internal_table>.
DATA lt_materials TYPE TABLE OF mara.
DATA ls_single LIKE LINE OF lt_materials.
ls_single-matnr = 'TEST01'.
WRITE: / ls_single-matnr.
TEST01
ls_single has type mara (the row type of the table).
TYPES <name> TYPE <type> [LENGTH <n>] [DECIMALS <d>].
Defines a reusable type. No memory allocated. No VALUE allowed.
TYPES ty_money TYPE p LENGTH 8 DECIMALS 2.
DATA lv_price TYPE ty_money VALUE '19.99'.
DATA lv_tax TYPE ty_money VALUE '1.60'.
DATA lv_total TYPE ty_money.
lv_total = lv_price + lv_tax.
WRITE: / lv_total.
21.59
TYPES: BEGIN OF ty_person,
name TYPE string,
age TYPE i,
END OF ty_person.
DATA ls_person TYPE ty_person.
ls_person-name = 'Alice'.
ls_person-age = 30.
WRITE: / ls_person-name, ls_person-age.
Alice 30
TYPES ty_person_tab TYPE TABLE OF ty_person WITH DEFAULT KEY.
DATA lt_people TYPE ty_person_tab.
APPEND VALUE #( name = 'Alice' age = 30 ) TO lt_people.
APPEND VALUE #( name = 'Bob' age = 25 ) TO lt_people.
WRITE: / lines( lt_people ), 'people'.
2 people
STATICS <name> TYPE <type> [VALUE <initial>].
Like DATA, but retains value between calls. Only inside FORM/METHOD.
FORM count_calls.
STATICS sv_count TYPE i.
sv_count = sv_count + 1.
WRITE: / 'Call number:', sv_count.
ENDFORM.
PERFORM count_calls.
PERFORM count_calls.
PERFORM count_calls.
Call number: 1
Call number: 2
Call number: 3
FORM count_calls_data.
DATA lv_count TYPE i.
lv_count = lv_count + 1.
WRITE: / 'Call number:', lv_count.
ENDFORM.
PERFORM count_calls_data.
PERFORM count_calls_data.
PERFORM count_calls_data.
Call number: 1
Call number: 1
Call number: 1
DATA resets to initial value (0) each call. STATICS persists.
REPORT ztest.
DATA gv_global TYPE i VALUE 10.
START-OF-SELECTION.
WRITE: / 'Before:', gv_global.
PERFORM change_it.
WRITE: / 'After:', gv_global.
FORM change_it.
gv_global = 99.
ENDFORM.
Before: 10
After: 99
FORM my_form.
DATA lv_local TYPE i VALUE 5.
WRITE: / lv_local.
ENDFORM.
PERFORM my_form.
WRITE: / lv_local. "syntax error
Syntax error: "LV_LOCAL" is unknown.
FORM test.
DATA lv_outside TYPE i VALUE 1.
IF lv_outside = 1.
DATA lv_inside TYPE i VALUE 100.
ENDIF.
WRITE: / lv_inside. "works!
ENDFORM.
100
DATA is hoisted to the procedure level. There is no block scope in ABAP. The variable exists throughout the entire FORM, regardless of where it's declared.
| Type | Initial value | Example |
|---|---|---|
i, int8, p, f |
0 | 0 |
c |
spaces | ' ' |
n |
zeros | '0000' |
string |
empty | '' |
d |
zero date | '00000000' |
t |
zero time | '000000' |
| reference | unbound (null) | — |
DATA lv_num TYPE i.
DATA lv_str TYPE string.
IF lv_num IS INITIAL.
WRITE: / 'lv_num is initial'.
ENDIF.
lv_str = 'Hello'.
IF lv_str IS NOT INITIAL.
WRITE: / 'lv_str has value'.
ENDIF.
lv_num is initial
lv_str has value
CLEAR <variable>.
DATA lv_num TYPE i VALUE 100.
WRITE: / 'Before:', lv_num.
CLEAR lv_num.
WRITE: / 'After:', lv_num.
Before: 100
After: 0
DATA: BEGIN OF ls_person,
name TYPE string VALUE 'Alice',
age TYPE i VALUE 30,
END OF ls_person.
WRITE: / ls_person-name, ls_person-age.
CLEAR ls_person.
WRITE: / ls_person-name, ls_person-age.
Alice 30
0
DATA lt_data TYPE TABLE OF i.
APPEND 1 TO lt_data.
APPEND 2 TO lt_data.
CLEAR lt_data. "rows removed, memory still allocated
FREE lt_data. "rows removed, memory released
Use CLEAR for tables you'll refill. Use FREE when done with the table entirely.
Predefined read-only variables in structure SYST. Accessed as sy-<field>. Automatically filled by the runtime.
DATA lt_mat TYPE TABLE OF mara.
SELECT * FROM mara WHERE mtart = 'XXXX' INTO TABLE @lt_mat.
WRITE: / 'sy-subrc:', sy-subrc.
WRITE: / 'sy-dbcnt:', sy-dbcnt.
sy-subrc: 4
sy-dbcnt: 0
sy-subrc = 0 means success. Non-zero means something went wrong or nothing found. sy-dbcnt = rows affected.
WRITE: / 'Date:', sy-datum.
WRITE: / 'Time:', sy-uzeit.
WRITE: / 'User:', sy-uname.
Date: 20241215
Time: 143052
User: JSMITH
DO 3 TIMES.
WRITE: / 'DO iteration:', sy-index.
ENDDO.
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
APPEND 20 TO lt_nums.
LOOP AT lt_nums INTO DATA(lv_num).
WRITE: / 'LOOP row:', sy-tabix, 'value:', lv_num.
ENDLOOP.
DO iteration: 1
DO iteration: 2
DO iteration: 3
LOOP row: 1 value: 10
LOOP row: 2 value: 20
sy-index for DO/WHILE. sy-tabix for LOOP AT.
READ TABLE lt_nums INTO DATA(lv_found) INDEX 99.
IF sy-subrc <> 0.
WRITE: / 'Not found'.
ENDIF.
Not found
- Maximum 30 characters
- Must start with letter
- Only letters, digits, underscore
- Case insensitive (LV_NAME = lv_name)
- Cannot use ABAP reserved words
| Prefix | Meaning |
|---|---|
lv_ |
Local variable |
gv_ |
Global variable |
ls_ |
Local structure |
gs_ |
Global structure |
lt_ |
Local table |
gt_ |
Global table |
lr_ |
Local reference (data) |
lo_ |
Local object reference |
lc_ |
Local constant |
iv_ |
Importing parameter (value) |
ev_ |
Exporting parameter |
cv_ |
Changing parameter |
rv_ |
Returning value |
ty_ |
Local type |
tt_ |
Table type |
Literal = a value written directly in code (no name)
Constant = a named value that cannot change
DATA lv_x TYPE i.
lv_x = 42. "42 is a literal
CONSTANTS lc_max TYPE i VALUE 100.
lv_x = lc_max. "lc_max is a constant
DATA lv_i TYPE i.
lv_i = 42.
lv_i = -100.
lv_i = 0.
WRITE: / lv_i.
0
No quotes. Just the digits. Negative with minus sign.
DATA lv_p TYPE p DECIMALS 2.
lv_p = '123.45'. "quotes required for decimal
lv_p = '-99.99'.
WRITE: / lv_p.
99.99-
Decimal point requires quotes. Without quotes, 123.45 is a syntax error.
DATA lv_f TYPE f.
lv_f = '1.5E+03'. "1500
lv_f = '2.5E-02'. "0.025
WRITE: / lv_f.
2.5000000000000001E-02
| Syntax | Type | Trailing spaces | Expressions |
|---|---|---|---|
'...' |
c (fixed length) | Trimmed | No |
`...` |
string | Preserved | No |
|...| |
string | Preserved | Yes { } |
'...') - text field literal:DATA lv_c TYPE c LENGTH 10.
DATA lv_str TYPE string.
lv_c = 'Hello'.
lv_str = 'Hello'.
WRITE: / '>', lv_c, '<'.
WRITE: / '>', lv_str, '<'.
>Hello
>Hello
Type c pads to declared length. When assigned to string, trailing spaces are trimmed.
DATA lv_str TYPE string.
lv_str = 'Hello '. "3 trailing spaces
WRITE: / '>', lv_str, '<'.
WRITE: / 'Length:', strlen( lv_str ).
>Hello
Length: 5
Spaces at end disappear. This is often unexpected.
`...`) - string literal:DATA lv_str TYPE string.
lv_str = `Hello `. "3 trailing spaces
WRITE: / '>', lv_str, '<'.
WRITE: / 'Length:', strlen( lv_str ).
>Hello
Length: 8
Spaces preserved. Use backticks when spaces matter.
|...|) - string template:DATA lv_name TYPE string VALUE 'Alice'.
DATA lv_age TYPE i VALUE 30.
DATA(lv_msg) = |Name: { lv_name }, Age: { lv_age }|.
WRITE: / lv_msg.
Name: Alice, Age: 30
Expressions inside { } are evaluated. Like f-strings in Python or template literals in JavaScript.
DATA lv_num TYPE p DECIMALS 2 VALUE '1234.50'.
DATA lv_date TYPE d VALUE '20241215'.
WRITE: / |Number: { lv_num }|.
WRITE: / |Formatted: { lv_num NUMBER = USER }|.
WRITE: / |Date: { lv_date }|.
WRITE: / |Formatted: { lv_date DATE = USER }|.
Number: 1234.50
Formatted: 1,234.50
Date: 20241215
Formatted: 12/15/2024
NUMBER = USER and DATE = USER format according to user settings.
DATA lv_name TYPE string VALUE 'Bob'.
WRITE: / |>{ lv_name WIDTH = 10 }<|.
WRITE: / |>{ lv_name WIDTH = 10 ALIGN = LEFT }<|.
WRITE: / |>{ lv_name WIDTH = 10 ALIGN = RIGHT }<|.
WRITE: / |>{ lv_name WIDTH = 10 ALIGN = CENTER }<|.
WRITE: / |>{ lv_name WIDTH = 10 PAD = '_' }<|.
>Bob
>Bob
> Bob
> Bob
>Bob_______
* In single quotes - double the quote
DATA(lv_1) = 'It''s OK'.
WRITE: / lv_1.
* In backticks - double the backtick
DATA(lv_2) = `Say ``hello``.
WRITE: / lv_2.
* In pipes - use backslash
DATA(lv_3) = |Pipe: \|, Brace: \{, Backslash: \\|.
WRITE: / lv_3.
It's OK
Say `hello`
Pipe: |, Brace: {, Backslash: \
X'<hex_digits>' "for type x (fixed bytes)
DATA lv_x TYPE x LENGTH 4.
DATA lv_xs TYPE xstring.
lv_x = 'DEADBEEF'.
lv_xs = '48454C4C4F'. "hex for ASCII 'HELLO'
WRITE: / lv_x.
WRITE: / lv_xs.
DEADBEEF
48454C4C4F
Each byte is 2 hex characters. For binary data, raw bytes, hashes.
CONSTANTS <name> TYPE <type> VALUE <value>.
CONSTANTS <name> TYPE <type> LENGTH <n> VALUE <value>.
VALUE is mandatory. Cannot be changed after declaration.
CONSTANTS lc_pi TYPE p DECIMALS 5 VALUE '3.14159'.
CONSTANTS lc_max_rows TYPE i VALUE 1000.
CONSTANTS lc_company TYPE c LENGTH 4 VALUE 'SAP'.
WRITE: / lc_pi.
WRITE: / lc_max_rows.
WRITE: / lc_company.
3.14159
1000
SAP
CONSTANTS lc_max TYPE i VALUE 100.
lc_max = 200.
Syntax error: "LC_MAX" cannot be changed.
CONSTANTS: lc_status_new TYPE c VALUE 'N',
lc_status_open TYPE c VALUE 'O',
lc_status_closed TYPE c VALUE 'C'.
CONSTANTS: BEGIN OF lc_defaults,
max_items TYPE i VALUE 100,
currency TYPE c LENGTH 3 VALUE 'EUR',
END OF lc_defaults.
WRITE: / lc_defaults-max_items.
WRITE: / lc_defaults-currency.
100
EUR
DATA lv_flag TYPE abap_bool.
lv_flag = abap_true.
WRITE: / 'True:', lv_flag.
lv_flag = abap_false.
WRITE: / 'False:', lv_flag.
True: X
False:
abap_true = 'X', abap_false = ' ' (space). Type abap_bool is c LENGTH 1.
DATA lv_success TYPE abap_bool.
lv_success = abap_true.
IF lv_success = abap_true.
WRITE: / 'Success'.
ENDIF.
* Also works (but less readable)
IF lv_success = 'X'.
WRITE: / 'Also success'.
ENDIF.
Success
Also success
| Constant | Value | Use |
|---|---|---|
abap_true |
'X' | Boolean true |
abap_false |
' ' | Boolean false |
abap_undefined |
'-' | Undefined/not applicable |
space |
' ' | Single space character |
DATA lv_c TYPE c LENGTH 1 VALUE 'X'.
IF lv_c = space.
WRITE: / 'Empty'.
ELSE.
WRITE: / 'Has value'.
ENDIF.
lv_c = space. "clear to space
WRITE: / '>', lv_c, '<'.
Has value
>
DATA lv_c TYPE c LENGTH 1.
DATA lv_str TYPE string.
* For type c, initial value IS space
lv_c = space.
IF lv_c IS INITIAL.
WRITE: / 'c is initial'.
ENDIF.
IF lv_c = space.
WRITE: / 'c = space'.
ENDIF.
* For string, initial is empty (not space)
lv_str = ``.
IF lv_str IS INITIAL.
WRITE: / 'string is initial'.
ENDIF.
lv_str = ` `. "one space
IF lv_str IS INITIAL.
WRITE: / 'string with space is initial'.
ELSE.
WRITE: / 'string with space is NOT initial'.
ENDIF.
c is initial
c = space
string is initial
string with space is NOT initial
For c: initial = filled with spaces.
For string: initial = zero length (empty).
* Integer literal → type i
DATA(lv_1) = 42.
cl_abap_typedescr=>describe_by_data( lv_1 )->get_relative_name( ). "I
* Quoted number → type c
DATA(lv_2) = '42'.
cl_abap_typedescr=>describe_by_data( lv_2 )->get_relative_name( ). "C
* Backtick → type string
DATA(lv_3) = `42`.
cl_abap_typedescr=>describe_by_data( lv_3 )->get_relative_name( ). "STRING
* Pipe → type string
DATA(lv_4) = |42|.
cl_abap_typedescr=>describe_by_data( lv_4 )->get_relative_name( ). "STRING
* These are NOT the same
DATA(lv_a) = 42. "type i (integer)
DATA(lv_b) = '42'. "type c LENGTH 2 (character)
* Comparison works but types differ
IF lv_a = lv_b.
WRITE: / 'Equal value'.
ENDIF.
* But behavior differs
DATA(lv_sum_a) = lv_a + 8. "50 (math)
DATA(lv_sum_b) = lv_b + 8. "50 (converted then math)
DATA(lv_cat_a) = |{ lv_a }X|. "'42X'
DATA(lv_cat_b) = lv_b && 'X'. "'42X'
WRITE: / lv_sum_a, lv_sum_b.
WRITE: / lv_cat_a, lv_cat_b.
50 50
42X 42X
- Value is used multiple times
- Value has meaning (give it a name)
- Value might change in future (single place to update)
* Bad - magic numbers
IF lv_status = 'A'.
IF lv_count > 100.
* Good - named constants
CONSTANTS lc_status_active TYPE c VALUE 'A'.
CONSTANTS lc_max_items TYPE i VALUE 100.
IF lv_status = lc_status_active.
IF lv_count > lc_max_items.
- Value is obvious (0, 1, '', etc.)
- Used only once
- In string templates
lv_count = 0. "obvious reset
lv_msg = |Error in line { sy-tabix }|. "template
=):DATA lv_a TYPE i.
DATA lv_b TYPE i.
lv_a = 10.
lv_b = lv_a.
WRITE: / lv_a, lv_b.
10 10
In ABAP, = is both assignment and comparison. Context determines which.
* This does NOT work in ABAP
lv_a = lv_b = lv_c = 10. "syntax error
* Must assign separately
lv_a = 10.
lv_b = 10.
lv_c = 10.
MOVE 10 TO lv_a. "same as: lv_a = 10.
MOVE lv_a TO lv_b. "same as: lv_b = lv_a.
WRITE: / lv_a, lv_b.
10 10
MOVE is older syntax. = is preferred in modern code.
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ |
Addition | 5 + 3 |
8 |
- |
Subtraction | 5 - 3 |
2 |
* |
Multiplication | 5 * 3 |
15 |
/ |
Division | 7 / 2 |
3.5 |
DIV |
Integer division | 7 DIV 2 |
3 |
MOD |
Modulo (remainder) | 7 MOD 2 |
1 |
** |
Power | 2 ** 3 |
8 |
DATA lv_result TYPE p DECIMALS 2.
lv_result = 5 + 3.
WRITE: / '5 + 3 =', lv_result.
lv_result = 5 - 3.
WRITE: / '5 - 3 =', lv_result.
lv_result = 5 * 3.
WRITE: / '5 * 3 =', lv_result.
lv_result = 7 / 2.
WRITE: / '7 / 2 =', lv_result.
5 + 3 = 8.00
5 - 3 = 2.00
5 * 3 = 15.00
7 / 2 = 3.50
DATA lv_i TYPE i.
DATA lv_p TYPE p DECIMALS 2.
lv_i = 7 / 2. "result truncated to integer
lv_p = 7 / 2. "result kept as decimal
WRITE: / '7 / 2 into i:', lv_i.
WRITE: / '7 / 2 into p:', lv_p.
lv_i = 7 DIV 2. "explicit integer division
WRITE: / '7 DIV 2:', lv_i.
7 / 2 into i: 3
7 / 2 into p: 3.50
7 DIV 2: 3
/ does decimal division, result depends on target type. DIV always integer division.
DATA lv_i TYPE i.
lv_i = 7 MOD 2.
WRITE: / '7 MOD 2 =', lv_i.
lv_i = 10 MOD 3.
WRITE: / '10 MOD 3 =', lv_i.
lv_i = 8 MOD 4.
WRITE: / '8 MOD 4 =', lv_i.
* Common use: check if even/odd
IF 7 MOD 2 = 0.
WRITE: / '7 is even'.
ELSE.
WRITE: / '7 is odd'.
ENDIF.
7 MOD 2 = 1
10 MOD 3 = 1
8 MOD 4 = 0
7 is odd
DATA lv_result TYPE p DECIMALS 2.
lv_result = 2 ** 3.
WRITE: / '2 ** 3 =', lv_result.
lv_result = 10 ** 2.
WRITE: / '10 ** 2 =', lv_result.
lv_result = 9 ** '0.5'. "square root
WRITE: / '9 ** 0.5 =', lv_result.
2 ** 3 = 8.00
10 ** 2 = 100.00
9 ** 0.5 = 3.00
Highest to lowest:
1. ** (power)
2. *, /, DIV, MOD
3. +, -
DATA lv_i TYPE i.
lv_i = 2 + 3 * 4. "3 * 4 first, then + 2
WRITE: / '2 + 3 * 4 =', lv_i.
lv_i = ( 2 + 3 ) * 4. "parentheses first
WRITE: / '(2 + 3) * 4 =', lv_i.
2 + 3 * 4 = 14
(2 + 3) * 4 = 20
* ABAP requires spaces around operators
lv_i = 5+3. "syntax error
lv_i = 5 + 3. "correct
lv_i = 10/2. "syntax error
lv_i = 10 / 2. "correct
| Symbol | Keyword | Meaning |
|---|---|---|
= |
EQ |
Equal |
<> |
NE |
Not equal |
< |
LT |
Less than |
> |
GT |
Greater than |
<= |
LE |
Less than or equal |
>= |
GE |
Greater than or equal |
Both forms are equivalent. Use whichever is clearer.
DATA lv_a TYPE i VALUE 10.
DATA lv_b TYPE i VALUE 20.
IF lv_a = lv_b.
WRITE: / 'a = b'.
ENDIF.
IF lv_a <> lv_b.
WRITE: / 'a <> b'.
ENDIF.
IF lv_a < lv_b.
WRITE: / 'a < b'.
ENDIF.
IF lv_a <= lv_b.
WRITE: / 'a <= b'.
ENDIF.
a <> b
a < b
a <= b
IF lv_a EQ lv_b.
WRITE: / 'Equal'.
ENDIF.
IF lv_a NE lv_b.
WRITE: / 'Not equal'.
ENDIF.
IF lv_a LT lv_b.
WRITE: / 'Less than'.
ENDIF.
Not equal
Less than
DATA lv_s1 TYPE string VALUE 'ABC'.
DATA lv_s2 TYPE string VALUE 'ABD'.
IF lv_s1 < lv_s2.
WRITE: / 'ABC < ABD'.
ENDIF.
IF 'apple' < 'banana'.
WRITE: / 'apple < banana'.
ENDIF.
ABC < ABD
apple < banana
Strings compared lexicographically (character by character).
DATA lv_c1 TYPE c LENGTH 10 VALUE 'ABC'.
DATA lv_c2 TYPE c LENGTH 5 VALUE 'ABC'.
IF lv_c1 = lv_c2.
WRITE: / 'Equal (spaces ignored)'.
ENDIF.
Equal (spaces ignored)
For type c, trailing spaces are ignored in comparison.
| Operator | Meaning |
|---|---|
AND |
Both must be true |
OR |
At least one must be true |
NOT |
Negation |
EQUIV |
Both same (both true or both false) |
DATA lv_a TYPE i VALUE 10.
DATA lv_b TYPE i VALUE 20.
IF lv_a > 5 AND lv_b > 15.
WRITE: / 'Both conditions true'.
ENDIF.
IF lv_a > 5 AND lv_b > 25.
WRITE: / 'This will not print'.
ENDIF.
Both conditions true
DATA lv_status TYPE c VALUE 'A'.
IF lv_status = 'A' OR lv_status = 'B'.
WRITE: / 'Status is A or B'.
ENDIF.
IF lv_status = 'X' OR lv_status = 'Y'.
WRITE: / 'This will not print'.
ENDIF.
Status is A or B
DATA lv_flag TYPE abap_bool VALUE abap_false.
IF NOT lv_flag = abap_true.
WRITE: / 'Flag is not true'.
ENDIF.
* Cleaner way
IF lv_flag <> abap_true.
WRITE: / 'Flag is not true (cleaner)'.
ENDIF.
Flag is not true
Flag is not true (cleaner)
DATA lv_age TYPE i VALUE 25.
DATA lv_status TYPE c VALUE 'A'.
IF ( lv_age >= 18 AND lv_age <= 65 ) AND lv_status = 'A'.
WRITE: / 'Adult with active status'.
ENDIF.
IF lv_age < 18 OR lv_age > 65 OR lv_status <> 'A'.
WRITE: / 'Does not qualify'.
ELSE.
WRITE: / 'Qualifies'.
ENDIF.
Adult with active status
Qualifies
Highest to lowest:
1. NOT
2. AND
3. OR
* These are different:
IF a OR b AND c. "means: a OR (b AND c)
IF ( a OR b ) AND c. "means: (a OR b) AND c
* Use parentheses to be explicit
DATA lv_i TYPE i.
DATA lv_s TYPE string.
IF lv_i IS INITIAL.
WRITE: / 'Integer is initial (0)'.
ENDIF.
IF lv_s IS INITIAL.
WRITE: / 'String is initial (empty)'.
ENDIF.
lv_s = 'Hello'.
IF lv_s IS NOT INITIAL.
WRITE: / 'String has value'.
ENDIF.
Integer is initial (0)
String is initial (empty)
String has value
DATA lr_ref TYPE REF TO i.
IF lr_ref IS INITIAL.
WRITE: / 'Reference is initial (null)'.
ENDIF.
IF lr_ref IS NOT BOUND.
WRITE: / 'Reference is not bound'.
ENDIF.
DATA lv_num TYPE i VALUE 10.
lr_ref = REF #( lv_num ).
IF lr_ref IS BOUND.
WRITE: / 'Reference is bound'.
ENDIF.
Reference is initial (null)
Reference is not bound
Reference is bound
FIELD-SYMBOLS TYPE i.
IF IS NOT ASSIGNED.
WRITE: / 'Field symbol not assigned'.
ENDIF.
DATA lv_num TYPE i VALUE 42.
ASSIGN lv_num TO .
IF IS ASSIGNED.
WRITE: / 'Field symbol assigned, value:', .
ENDIF.
Field symbol not assigned
Field symbol assigned, value: 42
DATA lo_obj TYPE REF TO object.
lo_obj = NEW zcl_child( ).
IF lo_obj IS INSTANCE OF zcl_parent.
WRITE: / 'Object is instance of zcl_parent'.
ENDIF.
IF lo_obj IS INSTANCE OF zcl_child.
WRITE: / 'Object is instance of zcl_child'.
ENDIF.
DATA lv_num TYPE i VALUE 50.
IF lv_num BETWEEN 1 AND 100.
WRITE: / 'Number is between 1 and 100'.
ENDIF.
IF lv_num NOT BETWEEN 60 AND 70.
WRITE: / 'Number is not between 60 and 70'.
ENDIF.
Number is between 1 and 100
Number is not between 60 and 70
BETWEEN is inclusive on both ends.
DATA lt_range TYPE RANGE OF i.
lt_range = VALUE #(
( sign = 'I' option = 'BT' low = 1 high = 10 )
( sign = 'I' option = 'EQ' low = 50 )
).
DATA lv_num TYPE i VALUE 5.
IF lv_num IN lt_range.
WRITE: / '5 is in range'.
ENDIF.
lv_num = 50.
IF lv_num IN lt_range.
WRITE: / '50 is in range'.
ENDIF.
lv_num = 25.
IF lv_num NOT IN lt_range.
WRITE: / '25 is not in range'.
ENDIF.
5 is in range
50 is in range
25 is not in range
&&):DATA lv_full TYPE string.
lv_full = 'Hello' && ' ' && 'World'.
WRITE: / lv_full.
DATA lv_first TYPE string VALUE 'John'.
DATA lv_last TYPE string VALUE 'Doe'.
lv_full = lv_first && | | && lv_last.
WRITE: / lv_full.
Hello World
John Doe
| Operator | Meaning | True when |
|---|---|---|
CO |
Contains Only | Left contains only characters from right |
CN |
Contains Not only | Left contains characters not in right |
CA |
Contains Any | Left contains at least one char from right |
NA |
contains No Any | Left contains no characters from right |
CS |
Contains String | Left contains right as substring |
NS |
contains No String | Left does not contain right |
CP |
Covers Pattern | Left matches pattern in right |
NP |
No Pattern | Left does not match pattern |
DATA lv_str TYPE string.
lv_str = '12345'.
IF lv_str CO '0123456789'.
WRITE: / 'Contains only digits'.
ENDIF.
lv_str = '123A5'.
IF lv_str CN '0123456789'.
WRITE: / 'Contains non-digit characters'.
ENDIF.
Contains only digits
Contains non-digit characters
DATA lv_str TYPE string VALUE 'Hello World'.
IF lv_str CA 'aeiou'.
WRITE: / 'Contains at least one vowel'.
ENDIF.
IF lv_str NA 'xyz'.
WRITE: / 'Contains no x, y, or z'.
ENDIF.
Contains at least one vowel
Contains no x, y, or z
DATA lv_str TYPE string VALUE 'Hello World'.
IF lv_str CS 'World'.
WRITE: / 'Contains "World"'.
ENDIF.
IF lv_str CS 'world'. "case insensitive!
WRITE: / 'Contains "world" (case insensitive)'.
ENDIF.
IF lv_str NS 'xyz'.
WRITE: / 'Does not contain "xyz"'.
ENDIF.
Contains "World"
Contains "world" (case insensitive)
Does not contain "xyz"
CS is case-insensitive by default.
Pattern wildcards:
* = any sequence of characters (including empty)
+ = exactly one character
# = escape next character (to match literal * or +)
DATA lv_str TYPE string VALUE 'HELLO123'.
IF lv_str CP 'HELLO*'.
WRITE: / 'Starts with HELLO'.
ENDIF.
IF lv_str CP '*123'.
WRITE: / 'Ends with 123'.
ENDIF.
IF lv_str CP 'HELLO+++'.
WRITE: / 'HELLO followed by exactly 3 chars'.
ENDIF.
IF lv_str CP '*LO*'.
WRITE: / 'Contains LO somewhere'.
ENDIF.
Starts with HELLO
Ends with 123
HELLO followed by exactly 3 chars
Contains LO somewhere
DATA lv_str TYPE string VALUE 'Hello World'.
IF lv_str CS 'World'.
WRITE: / 'Found at position:', sy-fdpos.
ENDIF.
IF lv_str CA 'W'.
WRITE: / 'W found at position:', sy-fdpos.
ENDIF.
Found at position: 6
W found at position: 6
After CO, CA, CS, CP, sy-fdpos contains the position (0-based).
| Operator | Meaning |
|---|---|
BIT-AND |
Bitwise AND |
BIT-OR |
Bitwise OR |
BIT-XOR |
Bitwise exclusive OR |
BIT-NOT |
Bitwise NOT (complement) |
DATA lv_a TYPE x LENGTH 1 VALUE 'F0'. "11110000
DATA lv_b TYPE x LENGTH 1 VALUE '0F'. "00001111
DATA lv_result TYPE x LENGTH 1.
lv_result = lv_a BIT-AND lv_b.
WRITE: / 'F0 AND 0F =', lv_result.
lv_result = lv_a BIT-OR lv_b.
WRITE: / 'F0 OR 0F =', lv_result.
lv_result = lv_a BIT-XOR lv_b.
WRITE: / 'F0 XOR 0F =', lv_result.
lv_result = BIT-NOT lv_a.
WRITE: / 'NOT F0 =', lv_result.
F0 AND 0F = 00
F0 OR 0F = FF
F0 XOR 0F = FF
NOT F0 = 0F
CONSTANTS: lc_flag_read TYPE x VALUE '01', "00000001
lc_flag_write TYPE x VALUE '02', "00000010
lc_flag_delete TYPE x VALUE '04'. "00000100
DATA lv_permissions TYPE x VALUE '00'.
* Set read and write flags
lv_permissions = lv_permissions BIT-OR lc_flag_read.
lv_permissions = lv_permissions BIT-OR lc_flag_write.
WRITE: / 'Permissions:', lv_permissions.
* Check if read flag is set
DATA lv_check TYPE x.
lv_check = lv_permissions BIT-AND lc_flag_read.
IF lv_check <> '00'.
WRITE: / 'Read permission is set'.
ENDIF.
Permissions: 03
Read permission is set
DATA lv_num TYPE i VALUE 10.
lv_num += 5. "same as: lv_num = lv_num + 5.
WRITE: / '+= 5:', lv_num.
lv_num -= 3. "same as: lv_num = lv_num - 3.
WRITE: / '-= 3:', lv_num.
lv_num *= 2. "same as: lv_num = lv_num * 2.
WRITE: / '*= 2:', lv_num.
lv_num /= 4. "same as: lv_num = lv_num / 4.
WRITE: / '/= 4:', lv_num.
+= 5: 15
-= 3: 12
*= 2: 24
/= 4: 6
DATA lv_str TYPE string VALUE 'Hello'.
lv_str &&= ' World'.
WRITE: / lv_str.
Hello World
* Use explicit form
lv_num = lv_num + 5.
lv_str = lv_str && ' World'.
| Category | Operators |
|---|---|
| Arithmetic | + - * / DIV MOD ** |
| Comparison | = <> < > <= >= |
| Comparison (keyword) | EQ NE LT GT LE GE |
| Logical | AND OR NOT EQUIV |
| Special comparison | BETWEEN IN IS INITIAL IS BOUND IS ASSIGNED |
| String | && CO CN CA NA CS NS CP NP |
| Bit | BIT-AND BIT-OR BIT-XOR BIT-NOT |
| Assignment | = += -= *= /= &&= |
IF <condition>.
<statements>
ENDIF.
IF <condition>.
<statements>
ELSE.
<statements>
ENDIF.
IF <condition1>.
<statements>
ELSEIF <condition2>.
<statements>
ELSEIF <condition3>.
<statements>
ELSE.
<statements>
ENDIF.
DATA lv_num TYPE i VALUE 10.
IF lv_num > 5.
WRITE: / 'Greater than 5'.
ENDIF.
Greater than 5
DATA lv_num TYPE i VALUE 3.
IF lv_num > 5.
WRITE: / 'Greater than 5'.
ELSE.
WRITE: / 'Not greater than 5'.
ENDIF.
Not greater than 5
DATA lv_score TYPE i VALUE 75.
IF lv_score >= 90.
WRITE: / 'Grade: A'.
ELSEIF lv_score >= 80.
WRITE: / 'Grade: B'.
ELSEIF lv_score >= 70.
WRITE: / 'Grade: C'.
ELSEIF lv_score >= 60.
WRITE: / 'Grade: D'.
ELSE.
WRITE: / 'Grade: F'.
ENDIF.
Grade: C
Conditions checked top to bottom. First true branch executes, rest skipped.
DATA lv_age TYPE i VALUE 25.
DATA lv_status TYPE c VALUE 'A'.
IF lv_age >= 18.
IF lv_status = 'A'.
WRITE: / 'Adult, Active'.
ELSE.
WRITE: / 'Adult, Inactive'.
ENDIF.
ELSE.
WRITE: / 'Minor'.
ENDIF.
Adult, Active
DATA lv_age TYPE i VALUE 25.
DATA lv_status TYPE c VALUE 'A'.
IF lv_age >= 18 AND lv_status = 'A'.
WRITE: / 'Adult and Active'.
ENDIF.
IF lv_age < 18 OR lv_status = 'I'.
WRITE: / 'Minor or Inactive'.
ELSE.
WRITE: / 'Adult and not Inactive'.
ENDIF.
Adult and Active
Adult and not Inactive
CASE <variable>.
WHEN <value1>.
<statements>
WHEN <value2>.
<statements>
WHEN <value3> OR <value4>.
<statements>
WHEN OTHERS.
<statements>
ENDCASE.
DATA lv_day TYPE i VALUE 3.
CASE lv_day.
WHEN 1.
WRITE: / 'Monday'.
WHEN 2.
WRITE: / 'Tuesday'.
WHEN 3.
WRITE: / 'Wednesday'.
WHEN 4.
WRITE: / 'Thursday'.
WHEN 5.
WRITE: / 'Friday'.
WHEN 6 OR 7.
WRITE: / 'Weekend'.
WHEN OTHERS.
WRITE: / 'Invalid day'.
ENDCASE.
Wednesday
DATA lv_char TYPE c VALUE 'A'.
CASE lv_char.
WHEN 'A' OR 'E' OR 'I' OR 'O' OR 'U'.
WRITE: / 'Vowel'.
WHEN OTHERS.
WRITE: / 'Consonant or other'.
ENDCASE.
Vowel
DATA lv_status TYPE string VALUE 'ACTIVE'.
CASE lv_status.
WHEN 'ACTIVE'.
WRITE: / 'Status is active'.
WHEN 'INACTIVE'.
WRITE: / 'Status is inactive'.
WHEN 'PENDING'.
WRITE: / 'Status is pending'.
WHEN OTHERS.
WRITE: / 'Unknown status'.
ENDCASE.
Status is active
DATA lv_x TYPE i VALUE 99.
CASE lv_x.
WHEN 1.
WRITE: / 'One'.
WHEN 2.
WRITE: / 'Two'.
ENDCASE.
WRITE: / 'Done'.
Done
No match, no output from CASE. Program continues.
DO [<n> TIMES].
<statements>
ENDDO.
DO 5 TIMES.
WRITE: / 'Iteration:', sy-index.
ENDDO.
Iteration: 1
Iteration: 2
Iteration: 3
Iteration: 4
Iteration: 5
sy-index is the current iteration number (1-based).
DATA lv_count TYPE i VALUE 0.
DO.
lv_count = lv_count + 1.
WRITE: / 'Count:', lv_count.
IF lv_count >= 3.
EXIT. "break out of loop
ENDIF.
ENDDO.
WRITE: / 'Loop ended'.
Count: 1
Count: 2
Count: 3
Loop ended
DO. without TIMES loops forever until EXIT.
DATA lv_sum TYPE i VALUE 0.
DO 10 TIMES.
lv_sum = lv_sum + sy-index.
ENDDO.
WRITE: / 'Sum 1-10:', lv_sum.
Sum 1-10: 55
WHILE <condition>.
<statements>
ENDWHILE.
DATA lv_count TYPE i VALUE 1.
WHILE lv_count <= 5.
WRITE: / 'Count:', lv_count.
lv_count = lv_count + 1.
ENDWHILE.
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
DATA lv_num TYPE i VALUE 1.
WHILE lv_num < 100.
lv_num = lv_num * 2.
WRITE: / 'Iteration:', sy-index, 'Value:', lv_num.
ENDWHILE.
Iteration: 1 Value: 2
Iteration: 2 Value: 4
Iteration: 3 Value: 8
Iteration: 4 Value: 16
Iteration: 5 Value: 32
Iteration: 6 Value: 64
Iteration: 7 Value: 128
DATA lv_x TYPE i VALUE 10.
WHILE lv_x < 5.
WRITE: / 'This never prints'.
ENDWHILE.
WRITE: / 'Done'.
Done
If condition is false initially, loop body never executes.
LOOP AT <itab> INTO <work_area>.
<statements>
ENDLOOP.
LOOP AT <itab> ASSIGNING FIELD-SYMBOL(<<fs>>).
<statements>
ENDLOOP.
LOOP AT <itab> REFERENCE INTO DATA(<lr_ref>).
<statements>
ENDLOOP.
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
APPEND 20 TO lt_nums.
APPEND 30 TO lt_nums.
LOOP AT lt_nums INTO DATA(lv_num).
WRITE: / 'Row:', sy-tabix, 'Value:', lv_num.
ENDLOOP.
Row: 1 Value: 10
Row: 2 Value: 20
Row: 3 Value: 30
sy-tabix is the current row index (1-based).
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
APPEND 20 TO lt_nums.
APPEND 30 TO lt_nums.
LOOP AT lt_nums ASSIGNING FIELD-SYMBOL(<fs_num>).
<fs_num> = <fs_num> * 2. "modifies table directly
ENDLOOP.
LOOP AT lt_nums INTO DATA(lv_num).
WRITE: / lv_num.
ENDLOOP.
20
40
60
ASSIGNING is faster (no copy) and changes affect the table.
| Aspect | INTO | ASSIGNING |
|---|---|---|
| Creates copy | Yes | No |
| Changes affect table | No | Yes |
| Performance | Slower | Faster |
| Use when | Read only, need separate copy | Modify in place, large rows |
TYPES: BEGIN OF ty_person,
name TYPE string,
age TYPE i,
END OF ty_person.
DATA lt_people TYPE TABLE OF ty_person.
APPEND VALUE #( name = 'Alice' age = 30 ) TO lt_people.
APPEND VALUE #( name = 'Bob' age = 17 ) TO lt_people.
APPEND VALUE #( name = 'Carol' age = 25 ) TO lt_people.
LOOP AT lt_people INTO DATA(ls_person) WHERE age >= 18.
WRITE: / ls_person-name, 'is an adult'.
ENDLOOP.
Alice is an adult
Carol is an adult
DATA lt_nums TYPE TABLE OF i.
DO 10 TIMES.
APPEND sy-index TO lt_nums.
ENDDO.
LOOP AT lt_nums INTO DATA(lv_num) FROM 3 TO 6.
WRITE: / 'Index:', sy-tabix, 'Value:', lv_num.
ENDLOOP.
Index: 3 Value: 3
Index: 4 Value: 4
Index: 5 Value: 5
Index: 6 Value: 6
TYPES: BEGIN OF ty_order,
customer TYPE string,
amount TYPE i,
END OF ty_order.
DATA lt_orders TYPE TABLE OF ty_order.
APPEND VALUE #( customer = 'A' amount = 100 ) TO lt_orders.
APPEND VALUE #( customer = 'B' amount = 200 ) TO lt_orders.
APPEND VALUE #( customer = 'A' amount = 150 ) TO lt_orders.
APPEND VALUE #( customer = 'B' amount = 50 ) TO lt_orders.
LOOP AT lt_orders INTO DATA(ls_order)
GROUP BY ls_order-customer INTO DATA(lv_customer).
DATA(lv_total) = 0.
LOOP AT GROUP lv_customer INTO DATA(ls_member).
lv_total = lv_total + ls_member-amount.
ENDLOOP.
WRITE: / 'Customer:', lv_customer, 'Total:', lv_total.
ENDLOOP.
Customer: A Total: 250
Customer: B Total: 250
DO 10 TIMES.
IF sy-index = 5.
EXIT.
ENDIF.
WRITE: / sy-index.
ENDDO.
WRITE: / 'After loop'.
1
2
3
4
After loop
EXIT immediately leaves the innermost loop.
DO 5 TIMES.
IF sy-index = 3.
CONTINUE.
ENDIF.
WRITE: / sy-index.
ENDDO.
1
2
4
5
CONTINUE skips rest of current iteration, goes to next.
CHECK <condition>.
If condition is false, acts like CONTINUE.
If condition is true, continues execution.
DO 5 TIMES.
CHECK sy-index <> 3. "skip when index = 3
WRITE: / sy-index.
ENDDO.
1
2
4
5
Same result as CONTINUE example. CHECK is shorter for simple conditions.
* These are equivalent:
* Using CHECK
DO 5 TIMES.
CHECK sy-index MOD 2 = 0. "only even numbers
WRITE: / sy-index.
ENDDO.
* Using IF-CONTINUE
DO 5 TIMES.
IF sy-index MOD 2 <> 0.
CONTINUE.
ENDIF.
WRITE: / sy-index.
ENDDO.
2
4
2
4
DO 3 TIMES.
DATA(lv_outer) = sy-index.
DO 3 TIMES.
DATA(lv_inner) = sy-index.
IF lv_inner = 2.
EXIT. "only exits inner loop
ENDIF.
WRITE: / 'Outer:', lv_outer, 'Inner:', lv_inner.
ENDDO.
ENDDO.
Outer: 1 Inner: 1
Outer: 2 Inner: 1
Outer: 3 Inner: 1
EXIT only exits the innermost loop. Outer loop continues.
START-OF-SELECTION.
WRITE: / 'Before EXIT'.
EXIT.
WRITE: / 'After EXIT'. "never reached
Before EXIT
DATA lv_flag TYPE c VALUE ' '.
START-OF-SELECTION.
WRITE: / 'Before CHECK'.
CHECK lv_flag = 'X'.
WRITE: / 'After CHECK'. "skipped because flag is not X
Before CHECK
FORM my_subroutine.
WRITE: / 'Before RETURN'.
RETURN.
WRITE: / 'After RETURN'. "never reached
ENDFORM.
START-OF-SELECTION.
PERFORM my_subroutine.
WRITE: / 'Back in main'.
Before RETURN
Back in main
COND <type>(
WHEN <condition1> THEN <value1>
WHEN <condition2> THEN <value2>
...
ELSE <default_value> ).
Returns a value based on conditions. Like a conditional expression.
DATA lv_num TYPE i VALUE 75.
DATA(lv_grade) = COND string(
WHEN lv_num >= 90 THEN 'A'
WHEN lv_num >= 80 THEN 'B'
WHEN lv_num >= 70 THEN 'C'
WHEN lv_num >= 60 THEN 'D'
ELSE 'F' ).
WRITE: / 'Grade:', lv_grade.
Grade: C
DATA lv_result TYPE string.
lv_result = COND #(
WHEN sy-datum+4(2) <= '03' THEN 'Q1'
WHEN sy-datum+4(2) <= '06' THEN 'Q2'
WHEN sy-datum+4(2) <= '09' THEN 'Q3'
ELSE 'Q4' ).
WRITE: / 'Quarter:', lv_result.
Quarter: Q4
# infers type from target variable (lv_result is string).
DATA lv_count TYPE i VALUE 1.
WRITE: / |{ lv_count } item{ COND string( WHEN lv_count <> 1 THEN 's' ) }|.
lv_count = 5.
WRITE: / |{ lv_count } item{ COND string( WHEN lv_count <> 1 THEN 's' ) }|.
1 item
5 items
SWITCH <type>( <variable>
WHEN <value1> THEN <result1>
WHEN <value2> THEN <result2>
...
ELSE <default_result> ).
Like CASE but returns a value.
DATA lv_day TYPE i VALUE 3.
DATA(lv_name) = SWITCH string( lv_day
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
WHEN 7 THEN 'Sunday'
ELSE 'Invalid' ).
WRITE: / 'Day:', lv_name.
Day: Wednesday
DATA lv_day TYPE i VALUE 6.
DATA(lv_type) = SWITCH string( lv_day
WHEN 1 OR 2 OR 3 OR 4 OR 5 THEN 'Weekday'
WHEN 6 OR 7 THEN 'Weekend'
ELSE 'Invalid' ).
WRITE: / lv_type.
Weekend
| Aspect | COND | SWITCH |
|---|---|---|
| Tests | Any conditions | Single variable against values |
| Like | IF-ELSEIF-ELSE | CASE-WHEN |
| Use when | Complex conditions | Matching against discrete values |
* COND - any conditions allowed
DATA(lv_a) = COND string(
WHEN lv_x > 10 AND lv_y < 5 THEN 'A'
WHEN lv_x BETWEEN 5 AND 10 THEN 'B'
ELSE 'C' ).
* SWITCH - only equality checks on one variable
DATA(lv_b) = SWITCH string( lv_x
WHEN 1 THEN 'One'
WHEN 2 THEN 'Two'
ELSE 'Other' ).
TYPES: BEGIN OF ty_item,
id TYPE i,
status TYPE c,
amount TYPE i,
END OF ty_item.
DATA lt_items TYPE TABLE OF ty_item.
APPEND VALUE #( id = 1 status = 'A' amount = 100 ) TO lt_items.
APPEND VALUE #( id = 2 status = 'I' amount = 200 ) TO lt_items.
APPEND VALUE #( id = 3 status = 'A' amount = 150 ) TO lt_items.
APPEND VALUE #( id = 4 status = 'A' amount = 50 ) TO lt_items.
* Filter by status
LOOP AT lt_items INTO DATA(ls_item) WHERE status = 'A'.
WRITE: / 'Active item:', ls_item-id, ls_item-amount.
ENDLOOP.
Active item: 1 100
Active item: 3 150
Active item: 4 50
LOOP AT lt_items INTO DATA(ls_item) WHERE status = 'A' AND amount > 75.
WRITE: / 'Filtered:', ls_item-id.
ENDLOOP.
Filtered: 1
Filtered: 3
DATA lt_nums TYPE TABLE OF i.
DO 10 TIMES.
APPEND sy-index TO lt_nums.
ENDDO.
* Every second row
LOOP AT lt_nums INTO DATA(lv_num) STEP 2.
WRITE: / lv_num.
ENDLOOP.
1
3
5
7
9
LOOP AT lt_nums INTO DATA(lv_num) STEP -1.
WRITE: / lv_num.
ENDLOOP.
10
9
8
7
6
5
4
3
2
1
| Statement | Purpose | Counter |
|---|---|---|
IF...ENDIF |
Conditional execution | — |
CASE...ENDCASE |
Match value against options | — |
DO...ENDDO |
Fixed or infinite loop | sy-index |
WHILE...ENDWHILE |
Conditional loop | sy-index |
LOOP AT...ENDLOOP |
Iterate internal table | sy-tabix |
| Statement | In loop | Outside loop |
|---|---|---|
EXIT |
Break out of loop | Exit processing block |
CONTINUE |
Skip to next iteration | Skip rest of block |
CHECK <cond> |
CONTINUE if false | Exit block if false |
RETURN |
Exit subroutine/method | |
| Expression | Like statement | Returns |
|---|---|---|
COND |
IF-ELSEIF-ELSE | Value based on conditions |
SWITCH |
CASE-WHEN | Value based on match |
| Type | Length | Padding | Use case |
|---|---|---|---|
c |
Fixed | Spaces (right) | Fixed-format fields, flags |
string |
Variable | None | General text processing |
n |
Fixed | Zeros (left) | Numeric IDs, document numbers |
DATA <name> TYPE c [LENGTH <n>].
Default length is 1. Maximum is 262143.
DATA lv_c TYPE c LENGTH 10.
lv_c = 'Hello'.
WRITE: / '>', lv_c, '<'.
WRITE: / 'Length:', strlen( lv_c ).
lv_c = 'Hello World!!!'.
WRITE: / '>', lv_c, '<'.
>Hello
Length: 5
>Hello Worl
Short values padded with spaces. Long values truncated. strlen() returns length without trailing spaces.
DATA <name> TYPE string.
No length limit. No padding. No truncation.
DATA lv_str TYPE string.
lv_str = 'Hello'.
WRITE: / '>', lv_str, '<'.
WRITE: / 'Length:', strlen( lv_str ).
lv_str = 'This can be any length without truncation'.
WRITE: / lv_str.
>Hello
Length: 5
This can be any length without truncation
DATA <name> TYPE n [LENGTH <n>].
Only digits 0-9. Left-padded with zeros.
DATA lv_n TYPE n LENGTH 10.
lv_n = '123'.
WRITE: / lv_n.
lv_n = 456.
WRITE: / lv_n.
lv_n = 'ABC123'. "non-digits become 0
WRITE: / lv_n.
0000000123
0000000456
0000000123
| Syntax | Type | Trailing spaces | Embedded expressions |
|---|---|---|---|
'...' |
c | Trimmed | No |
`...` |
string | Preserved | No |
|...| |
string | Preserved | Yes { } |
'...'):DATA lv_str TYPE string.
lv_str = 'Hello '. "3 trailing spaces
WRITE: / '>', lv_str, '<'.
WRITE: / 'Length:', strlen( lv_str ).
>Hello
Length: 5
Trailing spaces removed when assigned to string.
`...`):DATA lv_str TYPE string.
lv_str = `Hello `. "3 trailing spaces
WRITE: / '>', lv_str, '<'.
WRITE: / 'Length:', strlen( lv_str ).
>Hello
Length: 8
Spaces preserved. Use when whitespace matters.
|...|) - string templates:DATA lv_name TYPE string VALUE 'Alice'.
DATA lv_age TYPE i VALUE 30.
DATA(lv_msg) = |Name: { lv_name }, Age: { lv_age }|.
WRITE: / lv_msg.
Name: Alice, Age: 30
Expressions inside { } are evaluated and inserted.
* Single quote in single quotes - double it
DATA(lv_1) = 'It''s OK'.
WRITE: / lv_1.
* Backtick in backticks - double it
DATA(lv_2) = `Say ``hello`` `.
WRITE: / lv_2.
* In pipes - use backslash
DATA(lv_3) = |Pipe: \|, Brace: \{, Backslash: \\|.
WRITE: / lv_3.
It's OK
Say `hello`
Pipe: |, Brace: {, Backslash: \
DATA(lv_multiline) = |Line 1\nLine 2\nLine 3|.
WRITE: / lv_multiline.
DATA(lv_tabbed) = |Col1\tCol2\tCol3|.
WRITE: / lv_tabbed.
Line 1
Line 2
Line 3
Col1 Col2 Col3
| Escape | Meaning |
|---|---|
\n |
Newline |
\r |
Carriage return |
\t |
Tab |
\\ |
Backslash |
\| |
Pipe |
\{ |
Open brace |
\} |
Close brace |
|{ <expression> [<formatting_options>] }|
DATA lv_name TYPE string VALUE 'Bob'.
WRITE: / |>{ lv_name }<|.
WRITE: / |>{ lv_name WIDTH = 10 }<|.
WRITE: / |>{ lv_name WIDTH = 10 ALIGN = LEFT }<|.
WRITE: / |>{ lv_name WIDTH = 10 ALIGN = RIGHT }<|.
WRITE: / |>{ lv_name WIDTH = 10 ALIGN = CENTER }<|.
>Bob
>Bob
>Bob
> Bob
> Bob
DATA lv_num TYPE i VALUE 42.
WRITE: / |>{ lv_num WIDTH = 6 PAD = '0' }<|.
WRITE: / |>{ lv_num WIDTH = 6 PAD = '_' ALIGN = RIGHT }<|.
>000042
>____42
DATA lv_str TYPE string VALUE 'Hello World'.
WRITE: / |{ lv_str CASE = UPPER }|.
WRITE: / |{ lv_str CASE = LOWER }|.
WRITE: / |{ lv_str CASE = RAW }|.
HELLO WORLD
hello world
Hello World
DATA lv_num TYPE p DECIMALS 2 VALUE '1234567.89'.
WRITE: / |{ lv_num }|.
WRITE: / |{ lv_num NUMBER = RAW }|.
WRITE: / |{ lv_num NUMBER = USER }|.
WRITE: / |{ lv_num NUMBER = ENVIRONMENT }|.
WRITE: / |{ lv_num DECIMALS = 1 }|.
WRITE: / |{ lv_num SIGN = LEFT }|.
WRITE: / |{ lv_num SIGN = LEFTPLUS }|.
1234567.89
1234567.89
1,234,567.89
1,234,567.89
1234567.9
1234567.89
+1234567.89
NUMBER = USER formats according to user's locale settings.
DATA lv_date TYPE d VALUE '20241215'.
WRITE: / |{ lv_date }|.
WRITE: / |{ lv_date DATE = RAW }|.
WRITE: / |{ lv_date DATE = USER }|.
WRITE: / |{ lv_date DATE = ISO }|.
WRITE: / |{ lv_date DATE = ENVIRONMENT }|.
20241215
20241215
12/15/2024
2024-12-15
12/15/2024
DATA lv_time TYPE t VALUE '143052'.
WRITE: / |{ lv_time }|.
WRITE: / |{ lv_time TIME = RAW }|.
WRITE: / |{ lv_time TIME = USER }|.
WRITE: / |{ lv_time TIME = ISO }|.
143052
143052
2:30:52 PM
14:30:52
DATA lv_ts TYPE timestamp VALUE '20241215143052'.
WRITE: / |{ lv_ts TIMESTAMP = ISO }|.
WRITE: / |{ lv_ts TIMESTAMP = USER }|.
WRITE: / |{ lv_ts TIMESTAMP = SPACE }|.
2024-12-15T14:30:52
12/15/2024 2:30:52 PM
2024-12-15 14:30:52
DATA lv_matnr TYPE c LENGTH 18 VALUE '000000000001234567'.
WRITE: / |{ lv_matnr }|.
WRITE: / |{ lv_matnr ALPHA = OUT }|. "remove leading zeros
DATA lv_short TYPE c LENGTH 10 VALUE '1234567'.
WRITE: / |{ lv_short ALPHA = IN WIDTH = 18 }|. "add leading zeros
000000000001234567
1234567
000000000001234567
&& operator:DATA lv_first TYPE string VALUE 'Hello'.
DATA lv_second TYPE string VALUE 'World'.
DATA(lv_result) = lv_first && ' ' && lv_second.
WRITE: / lv_result.
Hello World
DATA lv_first TYPE string VALUE 'Hello'.
DATA lv_second TYPE string VALUE 'World'.
DATA(lv_result) = |{ lv_first } { lv_second }|.
WRITE: / lv_result.
Hello World
CONCATENATE <s1> <s2> ... INTO <result> [SEPARATED BY <sep>].
DATA lv_result TYPE string.
CONCATENATE 'Hello' 'World' INTO lv_result SEPARATED BY ' '.
WRITE: / lv_result.
CONCATENATE 'A' 'B' 'C' INTO lv_result.
WRITE: / lv_result.
Hello World
ABC
DATA lt_words TYPE TABLE OF string.
APPEND 'One' TO lt_words.
APPEND 'Two' TO lt_words.
APPEND 'Three' TO lt_words.
DATA(lv_joined) = concat_lines_of( table = lt_words sep = ', ' ).
WRITE: / lv_joined.
One, Two, Three
<string>+<offset>(<length>)
offset = starting position (0-based)
length = number of characters
DATA lv_str TYPE string VALUE 'Hello World'.
WRITE: / lv_str+0(5). "first 5 chars
WRITE: / lv_str+6(5). "chars 6-10
WRITE: / lv_str+6(*). "from position 6 to end
Hello
World
World
substring( val = <string> off = <offset> len = <length> )
DATA lv_str TYPE string VALUE 'Hello World'.
DATA(lv_sub1) = substring( val = lv_str off = 0 len = 5 ).
WRITE: / lv_sub1.
DATA(lv_sub2) = substring( val = lv_str off = 6 len = 5 ).
WRITE: / lv_sub2.
Hello
World
DATA lv_str TYPE string VALUE 'Hello World'.
DATA(lv_from) = substring_from( val = lv_str sub = 'Wo' ).
WRITE: / 'From "Wo":', lv_from.
DATA(lv_after) = substring_after( val = lv_str sub = 'o ' ).
WRITE: / 'After "o ":', lv_after.
DATA(lv_before) = substring_before( val = lv_str sub = ' Wo' ).
WRITE: / 'Before " Wo":', lv_before.
DATA(lv_to) = substring_to( val = lv_str sub = 'o W' ).
WRITE: / 'To "o W":', lv_to.
From "Wo": World
After "o ": World
Before " Wo": Hell
To "o W": Hello W
DATA lv_str TYPE string VALUE 'Hello'.
DATA lv_c TYPE c LENGTH 10 VALUE 'Hello'.
WRITE: / 'string length:', strlen( lv_str ).
WRITE: / 'c length:', strlen( lv_c ).
string length: 5
c length: 5
For type c, strlen() excludes trailing spaces.
DATA lv_c TYPE c LENGTH 10 VALUE 'Hello'.
WRITE: / 'strlen:', strlen( lv_c ).
WRITE: / 'numofchar:', numofchar( lv_c ).
strlen: 5
numofchar: 5
Same for most cases. numofchar() handles multi-byte characters.
DATA lv_str TYPE string VALUE 'Hello World'.
DATA(lv_upper) = to_upper( lv_str ).
WRITE: / lv_upper.
DATA(lv_lower) = to_lower( lv_str ).
WRITE: / lv_lower.
DATA(lv_mixed) = to_mixed( val = 'HELLO_WORLD' sep = '_' ).
WRITE: / lv_mixed.
HELLO WORLD
hello world
HelloWorld
DATA lv_str TYPE string VALUE 'Hello World'.
TRANSLATE lv_str TO UPPER CASE.
WRITE: / lv_str.
TRANSLATE lv_str TO LOWER CASE.
WRITE: / lv_str.
HELLO WORLD
hello world
DATA lv_str TYPE string VALUE ' Hello World '.
DATA(lv_condensed) = condense( lv_str ).
WRITE: / '>', lv_condensed, '<'.
DATA(lv_no_gaps) = condense( val = lv_str del = ' ' ).
WRITE: / '>', lv_no_gaps, '<'.
>Hello World
>>HelloWorld
Default: trims ends and reduces multiple spaces to single space.
DATA lv_str TYPE string VALUE ' Hello '.
DATA(lv_left) = shift_left( val = lv_str sub = ' ' ).
WRITE: / '>', lv_left, '<'.
DATA(lv_right) = shift_right( val = lv_str sub = ' ' ).
WRITE: / '>', lv_right, '<'.
>Hello
> Hello
DATA lv_str TYPE c LENGTH 10 VALUE ' Hello'.
SHIFT lv_str LEFT DELETING LEADING ' '.
WRITE: / '>', lv_str, '<'.
lv_str = 'Hello '.
SHIFT lv_str RIGHT DELETING TRAILING ' '.
WRITE: / '>', lv_str, '<'.
>Hello
> Hello
DATA lv_str TYPE c LENGTH 10 VALUE 'ABCDEFGHIJ'.
SHIFT lv_str BY 3 PLACES LEFT.
WRITE: / lv_str.
lv_str = 'ABCDEFGHIJ'.
SHIFT lv_str BY 3 PLACES RIGHT.
WRITE: / lv_str.
lv_str = 'ABCDEFGHIJ'.
SHIFT lv_str BY 3 PLACES CIRCULAR.
WRITE: / lv_str.
DEFGHIJ
ABCDEFG
DEFGHIJABC
DATA lv_str TYPE string VALUE 'Hello World'.
DATA(lv_pos) = find( val = lv_str sub = 'World' ).
WRITE: / 'Position of "World":', lv_pos.
lv_pos = find( val = lv_str sub = 'xyz' ).
WRITE: / 'Position of "xyz":', lv_pos.
Position of "World": 6
Position of "xyz": -1
Returns 0-based position. -1 if not found.
DATA lv_str TYPE string VALUE 'Hello World World'.
DATA(lv_pos) = find( val = lv_str sub = 'World' occ = 2 ).
WRITE: / 'Second "World" at:', lv_pos.
lv_pos = find( val = lv_str sub = 'WORLD' case = abap_false ).
WRITE: / 'Case insensitive:', lv_pos.
Second "World" at: 12
Case insensitive: 6
DATA lv_str TYPE string VALUE 'abracadabra'.
DATA(lv_count) = count( val = lv_str sub = 'a' ).
WRITE: / 'Count of "a":', lv_count.
lv_count = count( val = lv_str sub = 'bra' ).
WRITE: / 'Count of "bra":', lv_count.
Count of "a": 5
Count of "bra": 2
DATA lv_str TYPE string VALUE 'Hello World'.
DATA(lv_new) = replace( val = lv_str sub = 'World' with = 'ABAP' ).
WRITE: / lv_new.
lv_str = 'aaa bbb aaa'.
DATA(lv_all) = replace( val = lv_str sub = 'aaa' with = 'xxx' occ = 0 ).
WRITE: / lv_all.
Hello ABAP
xxx bbb xxx
occ = 0 replaces all occurrences.
DATA lv_str TYPE string VALUE 'Hello World'.
FIND 'World' IN lv_str MATCH OFFSET DATA(lv_off) MATCH LENGTH DATA(lv_len).
IF sy-subrc = 0.
WRITE: / 'Found at offset:', lv_off, 'length:', lv_len.
ENDIF.
Found at offset: 6 length: 5
DATA lv_str TYPE string VALUE 'Hello World World'.
REPLACE 'World' IN lv_str WITH 'ABAP'.
WRITE: / lv_str.
lv_str = 'Hello World World'.
REPLACE ALL OCCURRENCES OF 'World' IN lv_str WITH 'ABAP'.
WRITE: / lv_str.
Hello ABAP World
Hello ABAP ABAP
DATA lv_str TYPE string.
lv_str = '12345'.
IF lv_str CO '0123456789'.
WRITE: / 'Only digits'.
ENDIF.
lv_str = '123A5'.
IF lv_str CN '0123456789'.
WRITE: / 'Contains non-digits at position:', sy-fdpos.
ENDIF.
Only digits
Contains non-digits at position: 3
DATA lv_str TYPE string VALUE 'Hello World'.
IF lv_str CA 'aeiou'.
WRITE: / 'Has vowel at position:', sy-fdpos.
ENDIF.
IF lv_str NA '0123456789'.
WRITE: / 'Contains no digits'.
ENDIF.
Has vowel at position: 1
Contains no digits
DATA lv_str TYPE string VALUE 'Hello World'.
IF lv_str CS 'World'.
WRITE: / 'Contains "World" at:', sy-fdpos.
ENDIF.
IF lv_str CS 'WORLD'. "case insensitive!
WRITE: / 'CS is case insensitive'.
ENDIF.
IF lv_str NS 'xyz'.
WRITE: / 'Does not contain "xyz"'.
ENDIF.
Contains "World" at: 6
CS is case insensitive
Does not contain "xyz"
Pattern wildcards:
* = any characters (including none)
+ = exactly one character
# = escape (to match literal * + #)
DATA lv_str TYPE string VALUE 'HELLO123'.
IF lv_str CP 'HELLO*'.
WRITE: / 'Starts with HELLO'.
ENDIF.
IF lv_str CP '*123'.
WRITE: / 'Ends with 123'.
ENDIF.
IF lv_str CP 'HELLO+++'.
WRITE: / 'HELLO + exactly 3 chars'.
ENDIF.
IF lv_str CP '*LO*'.
WRITE: / 'Contains LO'.
ENDIF.
Starts with HELLO
Ends with 123
HELLO + exactly 3 chars
Contains LO
| Operator | True when | Negative |
|---|---|---|
CO |
Contains Only chars from right | CN |
CA |
Contains Any char from right | NA |
CS |
Contains String (case insensitive) | NS |
CP |
Covers Pattern (wildcards) | NP |
After these operators, sy-fdpos contains the position found (or first mismatch for CO).
DATA lv_str TYPE string VALUE 'Order 12345 confirmed'.
DATA(lv_pos) = find( val = lv_str regex = '\d+' ).
WRITE: / 'Digits found at:', lv_pos.
Digits found at: 6
DATA lv_str TYPE string VALUE 'Price: $100 or $200'.
DATA(lv_new) = replace( val = lv_str regex = '\$\d+' with = 'XXX' occ = 0 ).
WRITE: / lv_new.
Price: XXX or XXX
DATA lv_str TYPE string VALUE 'Email: test@example.com'.
DATA lv_email TYPE string.
FIND REGEX '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
IN lv_str MATCH OFFSET DATA(lv_off) MATCH LENGTH DATA(lv_len).
IF sy-subrc = 0.
lv_email = lv_str+lv_off(lv_len).
WRITE: / 'Email found:', lv_email.
ENDIF.
Email found: test@example.com
DATA lv_str TYPE string VALUE 'ABC123'.
IF contains( val = lv_str regex = '^\d+$' ).
WRITE: / 'Only digits'.
ELSE.
WRITE: / 'Not only digits'.
ENDIF.
IF matches( val = lv_str regex = '^[A-Z]+\d+$' ).
WRITE: / 'Letters followed by digits'.
ENDIF.
Not only digits
Letters followed by digits
contains checks if regex matches anywhere. matches checks if entire string matches.
SPLIT <string> AT <separator> INTO <v1> <v2> ... .
SPLIT <string> AT <separator> INTO TABLE <itab>.
DATA lv_str TYPE string VALUE 'One,Two,Three'.
DATA lt_parts TYPE TABLE OF string.
SPLIT lv_str AT ',' INTO TABLE lt_parts.
LOOP AT lt_parts INTO DATA(lv_part).
WRITE: / lv_part.
ENDLOOP.
One
Two
Three
DATA lv_str TYPE string VALUE '2024-12-15'.
DATA: lv_year TYPE string,
lv_month TYPE string,
lv_day TYPE string.
SPLIT lv_str AT '-' INTO lv_year lv_month lv_day.
WRITE: / 'Year:', lv_year.
WRITE: / 'Month:', lv_month.
WRITE: / 'Day:', lv_day.
Year: 2024
Month: 12
Day: 15
DATA lt_parts TYPE TABLE OF string.
APPEND 'One' TO lt_parts.
APPEND 'Two' TO lt_parts.
APPEND 'Three' TO lt_parts.
DATA(lv_joined) = concat_lines_of( table = lt_parts sep = ',' ).
WRITE: / lv_joined.
DATA(lv_lines) = concat_lines_of( table = lt_parts sep = cl_abap_char_utilities=>newline ).
WRITE: / lv_lines.
One,Two,Three
One
Two
Three
DATA lv_str TYPE string VALUE 'Hello'.
DATA(lv_rev) = reverse( lv_str ).
WRITE: / lv_rev.
* Check palindrome
DATA lv_word TYPE string VALUE 'radar'.
IF lv_word = reverse( lv_word ).
WRITE: / lv_word, 'is a palindrome'.
ENDIF.
olleH
radar is a palindrome
DATA(lv_line) = repeat( val = '-' occ = 20 ).
WRITE: / lv_line.
DATA(lv_pattern) = repeat( val = 'AB' occ = 5 ).
WRITE: / lv_pattern.
--------------------
ABABABABAB
* Newline
DATA(lv_with_newline) = |Line1{ cl_abap_char_utilities=>newline }Line2|.
WRITE: / lv_with_newline.
* Carriage return + line feed (Windows)
DATA(lv_crlf) = cl_abap_char_utilities=>cr_lf.
* Tab
DATA(lv_with_tab) = |Col1{ cl_abap_char_utilities=>horizontal_tab }Col2|.
WRITE: / lv_with_tab.
* Check for digits only
IF cl_abap_char_utilities=>str_is_digit( '12345' ) = abap_true.
WRITE: / 'Only digits'.
ENDIF.
Line1
Line2
Col1 Col2
Only digits
| Function | Purpose |
|---|---|
strlen( ) |
Length excluding trailing spaces |
substring( ) |
Extract part of string |
to_upper( ) |
Convert to uppercase |
to_lower( ) |
Convert to lowercase |
to_mixed( ) |
Convert to mixed case |
condense( ) |
Remove extra spaces |
shift_left( ) |
Remove leading characters |
shift_right( ) |
Remove trailing characters |
find( ) |
Find substring position |
count( ) |
Count occurrences |
replace( ) |
Replace substring |
reverse( ) |
Reverse string |
repeat( ) |
Repeat string n times |
concat_lines_of( ) |
Join table rows |
contains( ) |
Check if contains substring/regex |
matches( ) |
Check if entire string matches regex |
DATA <name> TYPE d.
Always 8 characters. Format: YYYYMMDD.
Stored as character but supports date arithmetic.
DATA lv_date TYPE d.
lv_date = '20241215'. "direct assignment
WRITE: / lv_date.
lv_date = sy-datum. "current date
WRITE: / 'Today:', lv_date.
20241215
Today: 20241215
DATA lv_date TYPE d.
WRITE: / 'Initial:', lv_date.
IF lv_date IS INITIAL.
WRITE: / 'Date is initial (00000000)'.
ENDIF.
Initial: 00000000
Date is initial (00000000)
Date format: YYYYMMDD
01234567 (positions)
lv_date+0(4) = year (positions 0-3)
lv_date+4(2) = month (positions 4-5)
lv_date+6(2) = day (positions 6-7)
DATA lv_date TYPE d VALUE '20241215'.
DATA(lv_year) = lv_date+0(4).
DATA(lv_month) = lv_date+4(2).
DATA(lv_day) = lv_date+6(2).
WRITE: / 'Year:', lv_year.
WRITE: / 'Month:', lv_month.
WRITE: / 'Day:', lv_day.
Year: 2024
Month: 12
Day: 15
DATA lv_date TYPE d VALUE '20241215'.
lv_date+4(2) = '06'. "change month to June
WRITE: / lv_date.
lv_date+6(2) = '01'. "change day to 1st
WRITE: / lv_date.
20240615
20240601
DATA lv_date TYPE d.
lv_date = '20241315'. "month 13 - invalid but accepted
WRITE: / lv_date.
lv_date = '20240230'. "Feb 30 - invalid but accepted
WRITE: / lv_date.
20241315
20240230
ABAP stores any 8 digits. Validation is your responsibility.
DATA <name> TYPE t.
Always 6 characters. Format: HHMMSS.
24-hour format. Stored as character.
DATA lv_time TYPE t.
lv_time = '143052'. "14:30:52
WRITE: / lv_time.
lv_time = sy-uzeit. "current time
WRITE: / 'Now:', lv_time.
143052
Now: 102345
DATA lv_time TYPE t.
WRITE: / 'Initial:', lv_time.
IF lv_time IS INITIAL.
WRITE: / 'Time is initial (000000)'.
ENDIF.
Initial: 000000
Time is initial (000000)
Time format: HHMMSS
012345 (positions)
lv_time+0(2) = hours (positions 0-1)
lv_time+2(2) = minutes (positions 2-3)
lv_time+4(2) = seconds (positions 4-5)
DATA lv_time TYPE t VALUE '143052'.
DATA(lv_hour) = lv_time+0(2).
DATA(lv_min) = lv_time+2(2).
DATA(lv_sec) = lv_time+4(2).
WRITE: / 'Hour:', lv_hour.
WRITE: / 'Minute:', lv_min.
WRITE: / 'Second:', lv_sec.
Hour: 14
Minute: 30
Second: 52
DATA lv_date TYPE d VALUE '20241215'.
lv_date = lv_date + 7. "add 7 days
WRITE: / '+7 days:', lv_date.
lv_date = lv_date - 20. "subtract 20 days
WRITE: / '-20 days:', lv_date.
+7 days: 20241222
-20 days: 20241202
Arithmetic is in days. Handles month/year boundaries automatically.
DATA lv_date TYPE d VALUE '20241231'.
lv_date = lv_date + 1.
WRITE: / 'Dec 31 + 1:', lv_date.
lv_date = '20240101'.
lv_date = lv_date - 1.
WRITE: / 'Jan 1 - 1:', lv_date.
Dec 31 + 1: 20250101
Jan 1 - 1: 20231231
DATA lv_date1 TYPE d VALUE '20241201'.
DATA lv_date2 TYPE d VALUE '20241215'.
DATA(lv_diff) = lv_date2 - lv_date1.
WRITE: / 'Difference:', lv_diff, 'days'.
lv_diff = lv_date1 - lv_date2.
WRITE: / 'Reversed:', lv_diff, 'days'.
Difference: 14 days
Reversed: -14 days
DATA lv_date TYPE d VALUE '20241215'.
DATA lv_month TYPE i.
lv_month = lv_date+4(2). "get month
lv_month = lv_month + 3. "add 3 months
IF lv_month > 12.
lv_month = lv_month - 12.
lv_date+0(4) = lv_date+0(4) + 1. "increment year
ENDIF.
lv_date+4(2) = lv_month.
WRITE: / '+3 months:', lv_date.
+3 months: 20250315
Month arithmetic requires manual handling. Use function modules for complex cases.
DATA lv_time TYPE t VALUE '143052'.
lv_time = lv_time + 60. "add 60 seconds (1 minute)
WRITE: / '+60 sec:', lv_time.
lv_time = lv_time + 3600. "add 3600 seconds (1 hour)
WRITE: / '+1 hour:', lv_time.
lv_time = lv_time - 7200. "subtract 2 hours
WRITE: / '-2 hours:', lv_time.
+60 sec: 143152
+1 hour: 153152
-2 hours: 133152
Arithmetic is in seconds.
DATA lv_time TYPE t VALUE '235900'.
lv_time = lv_time + 120. "add 2 minutes
WRITE: / '23:59 + 2min:', lv_time.
23:59 + 2min: 000100
Time wraps at midnight. No date change (type t is time only).
DATA lv_time1 TYPE t VALUE '090000'.
DATA lv_time2 TYPE t VALUE '173000'.
DATA(lv_diff) = lv_time2 - lv_time1.
WRITE: / 'Difference:', lv_diff, 'seconds'.
DATA(lv_hours) = lv_diff / 3600.
WRITE: / 'That is', lv_hours, 'hours'.
Difference: 30600 seconds
That is 8.50000000000000E+00 hours
| Type | Format | Precision |
|---|---|---|
timestamp |
YYYYMMDDhhmmss | Seconds |
timestampl |
YYYYMMDDhhmmss.mmmuuun | Nanoseconds (7 decimals) |
Both stored as packed decimal. Represent UTC time.
DATA lv_ts TYPE timestamp.
DATA lv_tsl TYPE timestampl.
GET TIME STAMP FIELD lv_ts.
WRITE: / 'Timestamp:', lv_ts.
GET TIME STAMP FIELD lv_tsl.
WRITE: / 'Long timestamp:', lv_tsl.
Timestamp: 20241215102345
Long timestamp: 20241215102345.1234567
DATA lv_date TYPE d VALUE '20241215'.
DATA lv_time TYPE t VALUE '143052'.
DATA lv_ts TYPE timestamp.
CONVERT DATE lv_date TIME lv_time INTO TIME STAMP lv_ts TIME ZONE 'UTC'.
WRITE: / 'Timestamp:', lv_ts.
Timestamp: 20241215143052
DATA lv_ts TYPE timestamp VALUE '20241215143052'.
DATA lv_date TYPE d.
DATA lv_time TYPE t.
CONVERT TIME STAMP lv_ts TIME ZONE 'UTC' INTO DATE lv_date TIME lv_time.
WRITE: / 'Date:', lv_date.
WRITE: / 'Time:', lv_time.
Date: 20241215
Time: 143052
DATA lv_ts TYPE timestamp.
GET TIME STAMP FIELD lv_ts.
WRITE: / 'Now:', lv_ts.
lv_ts = lv_ts + 3600. "add 1 hour (3600 seconds)
WRITE: / '+1 hour:', lv_ts.
Now: 20241215102345
+1 hour: 20241215112345
DATA lv_ts1 TYPE timestamp VALUE '20241215090000'.
DATA lv_ts2 TYPE timestamp VALUE '20241215173000'.
DATA(lv_diff) = cl_abap_tstmp=>subtract( tstmp1 = lv_ts2 tstmp2 = lv_ts1 ).
WRITE: / 'Difference:', lv_diff, 'seconds'.
Difference: 30600 seconds
* sy-zonlo = user's time zone
WRITE: / 'User time zone:', sy-zonlo.
* Common time zones: UTC, CET, EST, PST, etc.
DATA lv_ts TYPE timestamp VALUE '20241215120000'. "12:00 UTC
DATA lv_date TYPE d.
DATA lv_time TYPE t.
* Convert to different time zones
CONVERT TIME STAMP lv_ts TIME ZONE 'UTC' INTO DATE lv_date TIME lv_time.
WRITE: / 'UTC:', lv_date, lv_time.
CONVERT TIME STAMP lv_ts TIME ZONE 'CET' INTO DATE lv_date TIME lv_time.
WRITE: / 'CET:', lv_date, lv_time.
CONVERT TIME STAMP lv_ts TIME ZONE 'EST' INTO DATE lv_date TIME lv_time.
WRITE: / 'EST:', lv_date, lv_time.
UTC: 20241215 120000
CET: 20241215 130000
EST: 20241215 070000
* sy-datum, sy-uzeit are in server time
* sy-datlo, sy-timlo are in user's local time
WRITE: / 'Server date:', sy-datum.
WRITE: / 'Server time:', sy-uzeit.
WRITE: / 'Local date:', sy-datlo.
WRITE: / 'Local time:', sy-timlo.
Server date: 20241215
Server time: 102345
Local date: 20241215
Local time: 112345
DATA lv_date1 TYPE d VALUE '20241215'.
DATA lv_date2 TYPE d VALUE '20241220'.
IF lv_date1 < lv_date2.
WRITE: / 'date1 is earlier'.
ENDIF.
IF lv_date1 = lv_date2.
WRITE: / 'Same date'.
ELSE.
WRITE: / 'Different dates'.
ENDIF.
date1 is earlier
Different dates
DATA lv_date TYPE d VALUE '20241215'.
DATA lv_start TYPE d VALUE '20241201'.
DATA lv_end TYPE d VALUE '20241231'.
IF lv_date BETWEEN lv_start AND lv_end.
WRITE: / 'Date is in December 2024'.
ENDIF.
Date is in December 2024
DATA lv_date TYPE d.
IF lv_date IS INITIAL.
WRITE: / 'No date set'.
ENDIF.
IF lv_date = '00000000'.
WRITE: / 'Also means no date'.
ENDIF.
No date set
Also means no date
DATA lv_date TYPE d VALUE '20241215'.
WRITE: / |{ lv_date }|. "raw
WRITE: / |{ lv_date DATE = RAW }|. "raw
WRITE: / |{ lv_date DATE = ISO }|. "ISO format
WRITE: / |{ lv_date DATE = USER }|. "user settings
WRITE: / |{ lv_date DATE = ENVIRONMENT }|. "environment
20241215
20241215
2024-12-15
12/15/2024
12/15/2024
DATA lv_time TYPE t VALUE '143052'.
WRITE: / |{ lv_time }|. "raw
WRITE: / |{ lv_time TIME = RAW }|. "raw
WRITE: / |{ lv_time TIME = ISO }|. "ISO format
WRITE: / |{ lv_time TIME = USER }|. "user settings
143052
143052
14:30:52
2:30:52 PM
DATA lv_ts TYPE timestamp VALUE '20241215143052'.
WRITE: / |{ lv_ts TIMESTAMP = ISO }|.
WRITE: / |{ lv_ts TIMESTAMP = USER }|.
WRITE: / |{ lv_ts TIMESTAMP = SPACE }|.
2024-12-15T14:30:52
12/15/2024 2:30:52 PM
2024-12-15 14:30:52
DATA lv_date TYPE d VALUE '20241215'.
DATA(lv_formatted) = |{ lv_date+6(2) }.{ lv_date+4(2) }.{ lv_date+0(4) }|.
WRITE: / lv_formatted.
lv_formatted = |{ lv_date+4(2) }/{ lv_date+6(2) }/{ lv_date+0(4) }|.
WRITE: / lv_formatted.
15.12.2024
12/15/2024
DATA lv_date TYPE d.
lv_date = '20241215'.
CALL FUNCTION 'DATE_CHECK_PLAUSIBILITY'
EXPORTING
date = lv_date
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
WRITE: / lv_date, 'is valid'.
ELSE.
WRITE: / lv_date, 'is invalid'.
ENDIF.
lv_date = '20240230'. "Feb 30
CALL FUNCTION 'DATE_CHECK_PLAUSIBILITY'
EXPORTING
date = lv_date
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
WRITE: / lv_date, 'is valid'.
ELSE.
WRITE: / lv_date, 'is invalid'.
ENDIF.
20241215 is valid
20240230 is invalid
DATA lv_date TYPE d VALUE '20240230'.
TRY.
cl_abap_datfm=>conv_date_int_to_ext(
im_datint = lv_date
im_datfmdes = '1' ).
WRITE: / 'Valid date'.
CATCH cx_abap_datfm_no_date.
WRITE: / 'Invalid date'.
ENDTRY.
Invalid date
DATA lv_date TYPE d VALUE '20241215'.
DATA lv_day TYPE c.
CALL FUNCTION 'DATE_COMPUTE_DAY'
EXPORTING
date = lv_date
IMPORTING
day = lv_day.
WRITE: / 'Day of week:', lv_day. "1=Mon, 7=Sun
CASE lv_day.
WHEN 1. WRITE: / 'Monday'.
WHEN 2. WRITE: / 'Tuesday'.
WHEN 3. WRITE: / 'Wednesday'.
WHEN 4. WRITE: / 'Thursday'.
WHEN 5. WRITE: / 'Friday'.
WHEN 6. WRITE: / 'Saturday'.
WHEN 7. WRITE: / 'Sunday'.
ENDCASE.
Day of week: 7
Sunday
DATA lv_date TYPE d VALUE '20241215'.
DATA lv_first TYPE d.
DATA lv_last TYPE d.
* First day of month
lv_first = lv_date.
lv_first+6(2) = '01'.
WRITE: / 'First day:', lv_first.
* Last day of month
CALL FUNCTION 'RP_LAST_DAY_OF_MONTHS'
EXPORTING
day_in = lv_date
IMPORTING
last_day_of_month = lv_last.
WRITE: / 'Last day:', lv_last.
First day: 20241201
Last day: 20241231
DATA lv_date TYPE d VALUE '20241215'.
DATA lv_result TYPE d.
CALL FUNCTION 'RP_CALC_DATE_IN_INTERVAL'
EXPORTING
date = lv_date
days = 0
months = 3
sigession = '+'
years = 0
IMPORTING
calc_date = lv_result.
WRITE: / 'Original:', lv_date.
WRITE: / '+3 months:', lv_result.
Original: 20241215
+3 months: 20250315
DATA lv_date1 TYPE d VALUE '20230115'.
DATA lv_date2 TYPE d VALUE '20241215'.
DATA: lv_days TYPE i,
lv_months TYPE i,
lv_years TYPE i.
CALL FUNCTION 'FIMA_DAYS_AND_MONTHS_AND_YEARS'
EXPORTING
i_date_from = lv_date1
i_date_to = lv_date2
IMPORTING
e_days = lv_days
e_months = lv_months
e_years = lv_years.
WRITE: / 'Years:', lv_years.
WRITE: / 'Months:', lv_months.
WRITE: / 'Days:', lv_days.
Years: 1
Months: 11
Days: 0
Factory calendar = defines working days for a location/plant.
Contains: holidays, weekends, special closing days.
Configured in SAP (transaction SCAL).
DATA lv_date TYPE d VALUE '20241213'. "Friday
DATA lv_result TYPE d.
CALL FUNCTION 'BKK_ADD_WORKINGDAY'
EXPORTING
i_date = lv_date
i_days = 3
i_calendar = '01' "factory calendar ID
IMPORTING
e_date = lv_result.
WRITE: / 'Original:', lv_date.
WRITE: / '+3 working days:', lv_result.
Original: 20241213
+3 working days: 20241218
Skips weekends and holidays defined in factory calendar.
DATA lv_date TYPE d VALUE '20241215'. "Sunday
DATA lv_workday TYPE c.
CALL FUNCTION 'DATE_CONVERT_TO_FACTORYDATE'
EXPORTING
date = lv_date
factory_calendar_id = '01'
IMPORTING
workingday_indicator = lv_workday
EXCEPTIONS
OTHERS = 1.
IF lv_workday = 'X'.
WRITE: / lv_date, 'is a working day'.
ELSE.
WRITE: / lv_date, 'is not a working day'.
ENDIF.
20241215 is not a working day
| Field | Type | Meaning |
|---|---|---|
sy-datum |
d | Current date (server) |
sy-uzeit |
t | Current time (server) |
sy-datlo |
d | Current date (user local) |
sy-timlo |
t | Current time (user local) |
sy-zonlo |
c | User's time zone |
WRITE: / 'Server date:', sy-datum.
WRITE: / 'Server time:', sy-uzeit.
WRITE: / 'Local date:', sy-datlo.
WRITE: / 'Local time:', sy-timlo.
WRITE: / 'Time zone:', sy-zonlo.
Server date: 20241215
Server time: 102345
Local date: 20241215
Local time: 112345
Time zone: CET
DATA lv_ts1 TYPE timestamp.
DATA lv_ts2 TYPE timestamp.
GET TIME STAMP FIELD lv_ts1.
* Add seconds
lv_ts2 = cl_abap_tstmp=>add(
tstmp = lv_ts1
secs = 3600 ). "add 1 hour
WRITE: / 'Original:', lv_ts1.
WRITE: / '+1 hour:', lv_ts2.
* Subtract timestamps (get difference in seconds)
DATA(lv_diff) = cl_abap_tstmp=>subtract( tstmp1 = lv_ts2 tstmp2 = lv_ts1 ).
WRITE: / 'Difference:', lv_diff, 'seconds'.
Original: 20241215102345
+1 hour: 20241215112345
Difference: 3600 seconds
DATA lv_ts TYPE timestamp.
lv_ts = cl_abap_tstmp=>get_utc_timestamp( ).
WRITE: / 'UTC timestamp:', lv_ts.
UTC timestamp: 20241215092345
DATA lv_start TYPE d.
DATA lv_end TYPE d.
* First day of current month
lv_start = sy-datum.
lv_start+6(2) = '01'.
* Last day of current month
CALL FUNCTION 'RP_LAST_DAY_OF_MONTHS'
EXPORTING
day_in = sy-datum
IMPORTING
last_day_of_month = lv_end.
WRITE: / 'Month start:', lv_start.
WRITE: / 'Month end:', lv_end.
Month start: 20241201
Month end: 20241231
DATA lv_birthdate TYPE d VALUE '19900315'.
DATA: lv_days TYPE i, lv_months TYPE i, lv_years TYPE i.
CALL FUNCTION 'FIMA_DAYS_AND_MONTHS_AND_YEARS'
EXPORTING
i_date_from = lv_birthdate
i_date_to = sy-datum
IMPORTING
e_days = lv_days
e_months = lv_months
e_years = lv_years.
WRITE: / 'Age:', lv_years, 'years'.
Age: 34 years
DATA lv_date TYPE d VALUE '20241220'.
IF lv_date < sy-datum.
WRITE: / 'Date is in the past'.
ELSEIF lv_date > sy-datum.
WRITE: / 'Date is in the future'.
ELSE.
WRITE: / 'Date is today'.
ENDIF.
Date is in the future
DATA lv_target TYPE d VALUE '20241225'. "Christmas
DATA(lv_days) = lv_target - sy-datum.
IF lv_days > 0.
WRITE: / lv_days, 'days until', lv_target.
ELSEIF lv_days < 0.
WRITE: / 'Date has passed'.
ELSE.
WRITE: / 'That is today'.
ENDIF.
10 days until 20241225
| Type | Format | Arithmetic unit |
|---|---|---|
d |
YYYYMMDD (8 chars) | Days |
t |
HHMMSS (6 chars) | Seconds |
timestamp |
YYYYMMDDhhmmss | Seconds |
timestampl |
YYYYMMDDhhmmss.nnnnnnn | Seconds |
| Function module | Purpose |
|---|---|
DATE_CHECK_PLAUSIBILITY |
Validate date |
DATE_COMPUTE_DAY |
Get day of week |
RP_LAST_DAY_OF_MONTHS |
Last day of month |
RP_CALC_DATE_IN_INTERVAL |
Add days/months/years |
FIMA_DAYS_AND_MONTHS_AND_YEARS |
Difference between dates |
BKK_ADD_WORKINGDAY |
Add working days |
ABAP has two exception systems:
1. Classic exceptions (pre-OOP)
- Uses sy-subrc return codes
- Used in function modules and older code
- Still common in standard SAP code
2. Class-based exceptions (modern)
- Uses TRY-CATCH blocks
- Exception objects with details
- Recommended for new development
1. Call a function/perform an operation
2. Check sy-subrc immediately after
3. sy-subrc = 0 means success
4. sy-subrc <> 0 means error (value indicates type)
DATA lt_data TYPE TABLE OF mara.
SELECT * FROM mara INTO TABLE @lt_data WHERE mtart = 'XXXX'.
IF sy-subrc = 0.
WRITE: / 'Found', lines( lt_data ), 'rows'.
ELSE.
WRITE: / 'No data found'.
ENDIF.
No data found
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
APPEND 20 TO lt_nums.
READ TABLE lt_nums INTO DATA(lv_num) INDEX 5.
CASE sy-subrc.
WHEN 0.
WRITE: / 'Found:', lv_num.
WHEN 4.
WRITE: / 'Index not found'.
WHEN 8.
WRITE: / 'Table is empty'.
ENDCASE.
Index not found
DATA lv_date TYPE d VALUE '20240230'. "Feb 30 - invalid
CALL FUNCTION 'DATE_CHECK_PLAUSIBILITY'
EXPORTING
date = lv_date
EXCEPTIONS
plausibility_check_failed = 1
OTHERS = 2.
CASE sy-subrc.
WHEN 0.
WRITE: / 'Date is valid'.
WHEN 1.
WRITE: / 'Date is invalid'.
WHEN 2.
WRITE: / 'Other error'.
ENDCASE.
Date is invalid
EXCEPTIONS maps exception names to sy-subrc values.
| Operation | sy-subrc = 0 | sy-subrc <> 0 |
|---|---|---|
| SELECT | Data found | 4 = no data |
| READ TABLE | Entry found | 4 = not found, 8 = empty |
| FIND | Found | 4 = not found |
| OPEN DATASET | Opened | 8 = failed |
| Function module | Success | As defined in EXCEPTIONS |
TRY.
<statements that might raise exception>
CATCH <exception_class> [INTO <variable>].
<handle exception>
ENDTRY.
DATA lv_result TYPE i.
TRY.
lv_result = 10 / 0. "division by zero
WRITE: / 'Result:', lv_result.
CATCH cx_sy_zerodivide.
WRITE: / 'Error: Division by zero'.
ENDTRY.
WRITE: / 'Program continues'.
Error: Division by zero
Program continues
TRY.
DATA(lv_result) = 10 / 0.
CATCH cx_sy_zerodivide INTO DATA(lx_error).
WRITE: / 'Error:', lx_error->get_text( ).
ENDTRY.
Error: Division by zero.
INTO captures the exception object. get_text( ) returns error message.
DATA lv_num TYPE i.
DATA lv_str TYPE string VALUE 'ABC'.
TRY.
lv_num = lv_str. "conversion error
CATCH cx_sy_zerodivide.
WRITE: / 'Division by zero'.
CATCH cx_sy_conversion_no_number.
WRITE: / 'Cannot convert to number'.
CATCH cx_root INTO DATA(lx_error).
WRITE: / 'Other error:', lx_error->get_text( ).
ENDTRY.
Cannot convert to number
CATCH blocks checked top to bottom. First match handles exception.
TRY.
DATA(lv_result) = 10 / 0.
CATCH cx_sy_zerodivide cx_sy_conversion_no_number INTO DATA(lx_error).
WRITE: / 'Math or conversion error:', lx_error->get_text( ).
ENDTRY.
Math or conversion error: Division by zero.
CX_ROOT (abstract base)
├── CX_STATIC_CHECK - must be declared or caught
├── CX_DYNAMIC_CHECK - should be caught, not enforced by compiler
└── CX_NO_CHECK - runtime errors, cannot declare
TRY.
DATA(lv_result) = 10 / 0.
CATCH cx_sy_arithmetic_error INTO DATA(lx_error).
"catches cx_sy_zerodivide and other arithmetic errors
WRITE: / 'Arithmetic error:', lx_error->get_text( ).
ENDTRY.
Arithmetic error: Division by zero.
Catching parent class catches all child exceptions.
TRY.
"any risky operation
DATA(lv_result) = some_method( ).
CATCH cx_root INTO DATA(lx_error).
WRITE: / 'Error:', lx_error->get_text( ).
ENDTRY.
cx_root catches any exception. Use as last resort.
| Class | Cause |
|---|---|
cx_sy_zerodivide |
Division by zero |
cx_sy_conversion_no_number |
String to number conversion failed |
cx_sy_itab_line_not_found |
Table line not found (table expressions) |
cx_sy_range_out_of_bounds |
Index out of bounds |
cx_sy_ref_is_initial |
Dereferencing null reference |
cx_sy_move_cast_error |
Invalid type cast |
cx_sy_open_sql_db |
Database error in Open SQL |
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
* This dumps if not found!
DATA(lv_val) = lt_nums[ 5 ].
Runtime error: CX_SY_ITAB_LINE_NOT_FOUND
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
TRY.
DATA(lv_val) = lt_nums[ 5 ].
WRITE: / 'Found:', lv_val.
CATCH cx_sy_itab_line_not_found.
WRITE: / 'Index not found'.
ENDTRY.
Index not found
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
DATA(lv_val) = VALUE #( lt_nums[ 5 ] OPTIONAL ).
IF lv_val IS INITIAL.
WRITE: / 'Not found (or value was 0)'.
ELSE.
WRITE: / 'Found:', lv_val.
ENDIF.
Not found (or value was 0)
OPTIONAL returns initial value if not found. No exception.
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
DATA(lv_val) = VALUE #( lt_nums[ 5 ] DEFAULT -1 ).
WRITE: / 'Value:', lv_val.
Value: -1
DEFAULT returns specified value if not found.
DATA lt_nums TYPE TABLE OF i.
APPEND 10 TO lt_nums.
IF line_exists( lt_nums[ 5 ] ).
DATA(lv_val) = lt_nums[ 5 ].
WRITE: / 'Found:', lv_val.
ELSE.
WRITE: / 'Not found'.
ENDIF.
Not found
RAISE EXCEPTION TYPE <exception_class>
[EXPORTING <parameter> = <value> ...].
RAISE EXCEPTION <exception_object>.
DATA lv_divisor TYPE i VALUE 0.
TRY.
IF lv_divisor = 0.
RAISE EXCEPTION TYPE cx_sy_zerodivide.
ENDIF.
DATA(lv_result) = 10 / lv_divisor.
CATCH cx_sy_zerodivide.
WRITE: / 'Cannot divide by zero'.
ENDTRY.
Cannot divide by zero
TRY.
RAISE EXCEPTION TYPE cx_sy_zerodivide
EXPORTING
textid = cx_sy_zerodivide=>cx_sy_zerodivide.
CATCH cx_sy_zerodivide INTO DATA(lx_error).
WRITE: / lx_error->get_text( ).
ENDTRY.
Division by zero.
1. Create class inheriting from:
- CX_STATIC_CHECK (must catch or declare)
- CX_DYNAMIC_CHECK (should catch)
- CX_NO_CHECK (cannot declare)
2. Add attributes for additional information
3. Define exception texts (messages)
CLASS lcx_invalid_input DEFINITION INHERITING FROM cx_static_check.
ENDCLASS.
CLASS lcx_invalid_input IMPLEMENTATION.
ENDCLASS.
START-OF-SELECTION.
TRY.
RAISE EXCEPTION TYPE lcx_invalid_input.
CATCH lcx_invalid_input.
WRITE: / 'Invalid input error'.
ENDTRY.
Invalid input error
CLASS lcx_validation_error DEFINITION INHERITING FROM cx_static_check.
PUBLIC SECTION.
INTERFACES if_t100_message.
CONSTANTS:
BEGIN OF invalid_amount,
msgid TYPE symsgid VALUE 'ZMYMSGS',
msgno TYPE symsgno VALUE '001',
attr1 TYPE scx_attrname VALUE 'MV_AMOUNT',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF invalid_amount.
DATA mv_amount TYPE p DECIMALS 2.
METHODS constructor
IMPORTING
textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
amount TYPE p OPTIONAL.
ENDCLASS.
CLASS lcx_validation_error IMPLEMENTATION.
METHOD constructor.
super->constructor( previous = previous ).
mv_amount = amount.
if_t100_message~t100key = textid.
ENDMETHOD.
ENDCLASS.
CLASS lcx_app_error DEFINITION INHERITING FROM cx_dynamic_check.
PUBLIC SECTION.
DATA mv_message TYPE string.
METHODS constructor
IMPORTING
message TYPE string OPTIONAL
previous LIKE previous OPTIONAL.
METHODS get_text REDEFINITION.
ENDCLASS.
CLASS lcx_app_error IMPLEMENTATION.
METHOD constructor.
super->constructor( previous = previous ).
mv_message = message.
ENDMETHOD.
METHOD get_text.
result = mv_message.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
TRY.
RAISE EXCEPTION TYPE lcx_app_error
EXPORTING
message = 'Customer not found'.
CATCH lcx_app_error INTO DATA(lx_error).
WRITE: / lx_error->get_text( ).
ENDTRY.
Customer not found
TRY.
<statements>
CLEANUP.
<cleanup code - runs if exception not caught here>
CATCH ...
ENDTRY.
CLEANUP runs when exception propagates out (not caught locally).
CLASS lcx_outer DEFINITION INHERITING FROM cx_dynamic_check.
ENDCLASS.
CLASS lcx_outer IMPLEMENTATION.
ENDCLASS.
CLASS lcx_inner DEFINITION INHERITING FROM cx_dynamic_check.
ENDCLASS.
CLASS lcx_inner IMPLEMENTATION.
ENDCLASS.
START-OF-SELECTION.
TRY.
TRY.
WRITE: / 'Inside inner TRY'.
RAISE EXCEPTION TYPE lcx_outer.
CLEANUP.
WRITE: / 'Cleanup runs - exception propagating'.
CATCH lcx_inner.
WRITE: / 'Inner catch - not reached'.
ENDTRY.
CATCH lcx_outer.
WRITE: / 'Outer catch handles it'.
ENDTRY.
Inside inner TRY
Cleanup runs - exception propagating
Outer catch handles it
CLEANUP runs because lcx_outer was not caught in inner TRY.
TRY.
OPEN DATASET lv_filename FOR INPUT IN TEXT MODE ENCODING UTF-8.
"process file...
RAISE EXCEPTION TYPE cx_sy_file_io.
CLEANUP.
"always close file if exception propagates
CLOSE DATASET lv_filename.
CATCH cx_sy_file_open.
WRITE: / 'Could not open file'.
ENDTRY.
TRY.
<statements>
CATCH <exception>.
<fix problem>
RETRY. "restart TRY block
ENDTRY.
DATA lv_divisor TYPE i VALUE 0.
DATA lv_attempts TYPE i VALUE 0.
TRY.
lv_attempts = lv_attempts + 1.
WRITE: / 'Attempt:', lv_attempts.
DATA(lv_result) = 10 / lv_divisor.
WRITE: / 'Result:', lv_result.
CATCH cx_sy_zerodivide.
IF lv_attempts < 3.
lv_divisor = 2. "fix the problem
RETRY. "try again
ELSE.
WRITE: / 'Giving up after', lv_attempts, 'attempts'.
ENDIF.
ENDTRY.
Attempt: 1
Attempt: 2
Result: 5
RETRY restarts the TRY block from beginning.
RESUME continues execution after the RAISE EXCEPTION statement.
Only works with RESUMABLE exceptions.
* Raising resumable exception
RAISE RESUMABLE EXCEPTION TYPE <class>.
* Catching and resuming
TRY.
...
CATCH BEFORE UNWIND <class>.
RESUME.
ENDTRY.
CLASS lcx_warning DEFINITION INHERITING FROM cx_dynamic_check.
ENDCLASS.
CLASS lcx_warning IMPLEMENTATION.
ENDCLASS.
FORM process_with_warning.
WRITE: / 'Before warning'.
RAISE RESUMABLE EXCEPTION TYPE lcx_warning.
WRITE: / 'After warning - only reached if RESUME called'.
ENDFORM.
START-OF-SELECTION.
TRY.
PERFORM process_with_warning.
CATCH BEFORE UNWIND lcx_warning.
WRITE: / 'Warning caught - resuming'.
RESUME.
ENDTRY.
WRITE: / 'Done'.
Before warning
Warning caught - resuming
After warning - only reached if RESUME called
Done
BEFORE UNWIND keeps stack intact for RESUME.
METHODS <name>
RAISING <exception_class1> <exception_class2> ...
CLASS lcl_calculator DEFINITION.
PUBLIC SECTION.
METHODS divide
IMPORTING
iv_a TYPE i
iv_b TYPE i
RETURNING
VALUE(rv_result) TYPE i
RAISING
cx_sy_zerodivide.
ENDCLASS.
CLASS lcl_calculator IMPLEMENTATION.
METHOD divide.
IF iv_b = 0.
RAISE EXCEPTION TYPE cx_sy_zerodivide.
ENDIF.
rv_result = iv_a / iv_b.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA(lo_calc) = NEW lcl_calculator( ).
TRY.
DATA(lv_result) = lo_calc->divide( iv_a = 10 iv_b = 0 ).
WRITE: / 'Result:', lv_result.
CATCH cx_sy_zerodivide.
WRITE: / 'Division by zero'.
ENDTRY.
Division by zero
CLASS lcx_my_error DEFINITION INHERITING FROM cx_static_check.
ENDCLASS.
CLASS lcx_my_error IMPLEMENTATION.
ENDCLASS.
CLASS lcl_test DEFINITION.
PUBLIC SECTION.
"must declare RAISING or catch internally
METHODS do_something RAISING lcx_my_error.
ENDCLASS.
CLASS lcl_test IMPLEMENTATION.
METHOD do_something.
RAISE EXCEPTION TYPE lcx_my_error.
ENDMETHOD.
ENDCLASS.
Compiler enforces: either catch CX_STATIC_CHECK or declare in RAISING.
| Method | Returns |
|---|---|
get_text( ) |
Error message as string |
get_longtext( ) |
Detailed error description |
get_source_position( ) |
Program/include/line where raised |
TRY.
DATA(lv_result) = 10 / 0.
CATCH cx_sy_zerodivide INTO DATA(lx_error).
lx_error->get_source_position(
IMPORTING
program_name = DATA(lv_program)
include_name = DATA(lv_include)
source_line = DATA(lv_line) ).
WRITE: / 'Error:', lx_error->get_text( ).
WRITE: / 'Program:', lv_program.
WRITE: / 'Line:', lv_line.
ENDTRY.
Error: Division by zero.
Program: ZTEST_PROGRAM
Line: 5
CLASS lcx_app_error DEFINITION INHERITING FROM cx_dynamic_check.
PUBLIC SECTION.
METHODS constructor
IMPORTING previous LIKE previous OPTIONAL.
ENDCLASS.
CLASS lcx_app_error IMPLEMENTATION.
METHOD constructor.
super->constructor( previous = previous ).
ENDMETHOD.
ENDCLASS.
TRY.
TRY.
DATA(lv_result) = 10 / 0.
CATCH cx_sy_zerodivide INTO DATA(lx_inner).
"wrap original exception
RAISE EXCEPTION TYPE lcx_app_error
EXPORTING
previous = lx_inner.
ENDTRY.
CATCH lcx_app_error INTO DATA(lx_outer).
WRITE: / 'App error'.
IF lx_outer->previous IS BOUND.
WRITE: / 'Caused by:', lx_outer->previous->get_text( ).
ENDIF.
ENDTRY.
App error
Caused by: Division by zero.
MESSAGE <type><number>(<message_class>) [WITH <v1> <v2> ...].
Types:
A = Abend (terminates)
E = Error
W = Warning
I = Information
S = Success (status bar)
X = Exit (short dump)
MESSAGE s001(zmymsgs) WITH 'Operation completed'.
MESSAGE i002(zmymsgs) WITH 'Info message'.
MESSAGE e003(zmymsgs) WITH 'Error occurred'.
"Show as error but behave as success message
MESSAGE e001(zmymsgs) WITH 'Error text' DISPLAY LIKE 'S'.
DATA lv_msg TYPE string.
MESSAGE e001(00) WITH 'Test' INTO lv_msg.
WRITE: / 'Message:', lv_msg.
WRITE: / 'ID:', sy-msgid.
WRITE: / 'Number:', sy-msgno.
WRITE: / 'Type:', sy-msgty.
Message: Test
ID: 00
Number: 001
Type: E
INTO captures message without displaying. Sets sy-msg* fields.
FORM validate USING iv_value TYPE i
RAISING cx_static_check.
IF iv_value < 0.
MESSAGE e001(zmymsgs) WITH 'Negative value' RAISING cx_static_check.
ENDIF.
ENDFORM.
ASSERT <condition>.
If condition is false, program terminates with dump.
For debugging and catching "impossible" states.
DATA lv_ptr TYPE REF TO i.
lv_ptr = NEW #( 10 ).
ASSERT lv_ptr IS BOUND. "OK, continues
lv_ptr = VALUE #( ). "set to null
ASSERT lv_ptr IS BOUND. "fails - dumps
Runtime error: ASSERTION_FAILED
ASSERT ID zmycheck SUBKEY 'VALIDATION' CONDITION lv_valid = abap_true.
Can be activated/deactivated via transaction SAAB.
| Aspect | Classic (sy-subrc) | Class-based (TRY-CATCH) |
|---|---|---|
| Use for | Function modules, old APIs | Methods, new development |
| Error details | Limited (just a number) | Rich (object with text, stack) |
| Propagation | Manual (pass sy-subrc up) | Automatic (RAISING) |
| Multiple errors | Only one at a time | Can chain (previous) |
| Compiler check | No | Yes (CX_STATIC_CHECK) |
"Call function module, convert to exception
CALL FUNCTION 'DATE_CHECK_PLAUSIBILITY'
EXPORTING
date = lv_date
EXCEPTIONS
plausibility_check_failed = 1
OTHERS = 2.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE lcx_invalid_date
EXPORTING
message = |Invalid date: { lv_date }|.
ENDIF.
1. Use class-based exceptions for new code
2. Catch specific exceptions, not cx_root (usually)
3. Always check sy-subrc after:
- SELECT
- READ TABLE
- Function module calls
- File operations
4. Don't use exceptions for normal control flow
5. Include meaningful information in custom exceptions
6. Use CX_STATIC_CHECK for recoverable errors
Use CX_DYNAMIC_CHECK for programming errors
Use CX_NO_CHECK for system errors
7. Clean up resources in CLEANUP block
METHOD process_order.
TRY.
validate_order( is_order ).
save_order( is_order ).
COMMIT WORK.
CATCH lcx_validation_error INTO DATA(lx_val).
"handle validation error
log_error( lx_val->get_text( ) ).
RAISE EXCEPTION lx_val. "re-throw
CATCH lcx_database_error INTO DATA(lx_db).
"handle database error
ROLLBACK WORK.
log_error( lx_db->get_text( ) ).
RAISE EXCEPTION TYPE lcx_order_failed
EXPORTING
previous = lx_db.
CATCH cx_root INTO DATA(lx_other).
"unexpected error
ROLLBACK WORK.
log_error( |Unexpected: { lx_other->get_text( ) }| ).
RAISE EXCEPTION TYPE lcx_order_failed
EXPORTING
previous = lx_other.
ENDTRY.
ENDMETHOD.
| Statement | Purpose |
|---|---|
TRY...ENDTRY |
Define exception handling block |
CATCH |
Handle specific exception(s) |
CLEANUP |
Run when exception propagates out |
RAISE EXCEPTION |
Throw exception |
RETRY |
Restart TRY block |
RESUME |
Continue after RAISE RESUMABLE |
RAISING |
Declare exceptions method can throw |
ASSERT |
Verify condition, dump if false |
MESSAGE |
Display/capture message |
Data Dictionary (DDIC) = central repository for data definitions.
- Defines database tables, views, data types
- Shared across all ABAP programs
- Managed via transaction SE11
- Changes automatically update database schema
1. Consistency - same type definition everywhere
2. Reusability - define once, use in many programs
3. Documentation - descriptions, labels, help texts
4. Database independence - SAP handles DB-specific SQL
5. Referential integrity - foreign keys, constraints
| Transaction | Purpose |
|---|---|
SE11 |
ABAP Dictionary (main) |
SE12 |
Dictionary display (read-only) |
SE13 |
Technical settings |
SE14 |
Database utility |
SE16 |
Data browser (view table contents) |
Domain
↓ (technical attributes: type, length)
Data Element
↓ (semantic meaning: labels, documentation)
Table Field / Structure Field
↓ (where data is stored)
Domain: MATNR_D
Type: CHAR
Length: 18
↓
Data Element: MATNR
Short text: "Material Number"
Labels: "Material", "Mat. No."
↓
Table MARA, field MATNR
Table MARC, field MATNR
Table MSEG, field MATNR
... (all use same definition)
Change domain length → all data elements updated
Change data element label → all screens updated
One definition, consistent everywhere
- Data type (CHAR, NUMC, DEC, INT, etc.)
- Length
- Decimal places (for numeric)
- Value range (fixed values or value table)
- Conversion routine (input/output formatting)
| Type | Description | ABAP equivalent |
|---|---|---|
CHAR |
Character | c |
NUMC |
Numeric text | n |
DATS |
Date (YYYYMMDD) | d |
TIMS |
Time (HHMMSS) | t |
DEC |
Packed decimal | p |
INT1 |
1-byte integer | b |
INT2 |
2-byte integer | s |
INT4 |
4-byte integer | i |
INT8 |
8-byte integer | int8 |
FLTP |
Floating point | f |
STRING |
Variable string | string |
RAWSTRING |
Variable bytes | xstring |
RAW |
Fixed bytes | x |
Domain: ZSTATUS_D
Type: CHAR, Length: 1
Fixed values:
'A' = Active
'I' = Inactive
'P' = Pending
'C' = Closed
→ Dropdown list appears in screens
→ Input validated automatically
Domain: MATNR_D
Conversion routine: MATN1
Input: User enters "123"
Output: Stored as "000000000000000123" (18 chars, left-padded)
Display: Shown as "123" (leading zeros removed)
DATA lv_matnr TYPE matnr VALUE '000000000000000123'.
* CONVERSION_EXIT_MATN1_OUTPUT removes leading zeros
WRITE: / |External: { lv_matnr ALPHA = OUT }|.
* CONVERSION_EXIT_MATN1_INPUT adds leading zeros
DATA lv_input TYPE matnr.
lv_input = |{ '123' ALPHA = IN WIDTH = 18 }|.
WRITE: / |Internal: { lv_input }|.
External: 123
Internal: 000000000000000123
- Reference to domain (or direct type)
- Field labels (short, medium, long, heading)
- Documentation (F1 help text)
- Search help assignment
| Label type | Max length | Used in |
|---|---|---|
| Short | 10 chars | Narrow columns |
| Medium | 20 chars | Standard fields |
| Long | 40 chars | Wide displays |
| Heading | 55 chars | Report headers |
* Reference data element type
DATA lv_matnr TYPE matnr. "material number
DATA lv_bukrs TYPE bukrs. "company code
DATA lv_werks TYPE werks_d. "plant
lv_matnr = '000000000001234567'.
lv_bukrs = '1000'.
lv_werks = 'P001'.
WRITE: / lv_matnr, lv_bukrs, lv_werks.
000000000001234567 1000 P001
Data element can either:
1. Reference a domain (recommended)
→ inherits technical attributes from domain
2. Define direct type (built-in type)
→ standalone, no domain reuse
Structure = collection of fields (no database table).
Used for: work areas, parameters, temporary data.
Transaction SE11 → Data type → Create → Structure
Fields:
CUSTOMER_ID TYPE KUNNR
NAME TYPE NAME1
CITY TYPE ORT01
COUNTRY TYPE LAND1
* Declare variable with DDIC structure type
DATA ls_customer TYPE zcustomer_str.
ls_customer-customer_id = '0000001234'.
ls_customer-name = 'Acme Corp'.
ls_customer-city = 'Berlin'.
ls_customer-country = 'DE'.
WRITE: / ls_customer-name, ls_customer-city.
Acme Corp Berlin
Structure ZADDR_STR:
STREET TYPE CHAR40
CITY TYPE CHAR40
COUNTRY TYPE LAND1
Structure ZCUSTOMER_STR:
CUSTOMER_ID TYPE KUNNR
NAME TYPE NAME1
.INCLUDE ZADDR_STR ← includes all fields from ZADDR_STR
DATA ls_cust TYPE zcustomer_str.
ls_cust-customer_id = '123'.
ls_cust-name = 'Test'.
ls_cust-street = '123 Main St'. "from included structure
ls_cust-city = 'Munich'. "from included structure
Append structure = add custom fields to SAP standard structure.
Used for enhancements without modifying original.
Standard structure: VBAK (sales order header)
Append structure: ZZVBAK_APPEND
ZZ_CUSTOM_FIELD TYPE CHAR10
→ ZZ_CUSTOM_FIELD appears in VBAK
Table type = definition of internal table structure.
Defines: line type, key, table kind.
Transaction SE11 → Data type → Create → Table type
Line type: ZCUSTOMER_STR (or data element, or built-in)
Access: STANDARD TABLE / SORTED TABLE / HASHED TABLE
Key: DEFAULT KEY / specific fields
* DDIC table type
DATA lt_customers TYPE zcustomer_tab.
APPEND VALUE #( customer_id = '001' name = 'Alice' ) TO lt_customers.
APPEND VALUE #( customer_id = '002' name = 'Bob' ) TO lt_customers.
LOOP AT lt_customers INTO DATA(ls_cust).
WRITE: / ls_cust-customer_id, ls_cust-name.
ENDLOOP.
001 Alice
002 Bob
Table type: ZCUST_SORTED_TAB
Line type: ZCUSTOMER_STR
Access: SORTED TABLE
Key: UNIQUE KEY CUSTOMER_ID
DATA lt_sorted TYPE zcust_sorted_tab.
"automatically sorted by customer_id
INSERT VALUE #( customer_id = '003' name = 'Carol' ) INTO TABLE lt_sorted.
INSERT VALUE #( customer_id = '001' name = 'Alice' ) INTO TABLE lt_sorted.
INSERT VALUE #( customer_id = '002' name = 'Bob' ) INTO TABLE lt_sorted.
LOOP AT lt_sorted INTO DATA(ls_cust).
WRITE: / ls_cust-customer_id.
ENDLOOP.
001
002
003
| Type | Description | Use |
|---|---|---|
| Transparent | 1:1 with database table | Most application tables |
| Pooled | Multiple tables in one DB table | Small tables (obsolete) |
| Cluster | Compressed storage | Large data (obsolete) |
Modern SAP uses transparent tables almost exclusively.
Table ZCUSTOMERS:
Fields:
MANDT TYPE MANDT (client - key)
CUSTOMER_ID TYPE KUNNR (key)
NAME TYPE NAME1
CITY TYPE ORT01
CREATED_AT TYPE TIMESTAMP
Primary key: MANDT + CUSTOMER_ID
MANDT = client (tenant) field.
SAP is multi-tenant - data separated by client.
Table definition:
Delivery class: A (application data)
Client-dependent: Yes → MANDT field required
Open SQL automatically filters by sy-mandt.
* These are equivalent (client handled automatically):
SELECT * FROM zcustomers INTO TABLE @DATA(lt_cust).
SELECT * FROM zcustomers INTO TABLE @DATA(lt_cust)
WHERE mandt = @sy-mandt.
| Class | Meaning | Transport behavior |
|---|---|---|
A |
Application table | Data not transported |
C |
Customizing table | Data transported |
L |
Temporary data | Data deleted on import |
G |
Customer customizing | Protected from SAP upgrades |
S |
System table | SAP controlled |
Data class:
APPL0 = Master data (few changes)
APPL1 = Transaction data (frequent changes)
APPL2 = Org and customizing
Size category:
0 = 0-100 rows
1 = 100-1000 rows
2 = 1000-10000 rows
...
Buffering:
Not buffered
Fully buffered (small tables)
Generic buffering (by key fields)
Single record buffered
* Use table name as type (structure)
DATA ls_customer TYPE zcustomers.
* Internal table of table rows
DATA lt_customers TYPE TABLE OF zcustomers.
* Select into internal table
SELECT * FROM zcustomers INTO TABLE @lt_customers.
* Insert row
ls_customer-customer_id = '0000001234'.
ls_customer-name = 'Test Customer'.
INSERT zcustomers FROM ls_customer.
IF sy-subrc = 0.
WRITE: / 'Inserted successfully'.
ENDIF.
Inserted successfully
- Uniquely identifies each row
- Always includes MANDT (if client-dependent)
- Created automatically in database
- Cannot be changed after activation (with data)
Purpose: Speed up queries on non-key fields.
Table ZCUSTOMERS:
Primary key: MANDT, CUSTOMER_ID
Index Z01: CITY (queries by city)
Index Z02: NAME, CITY (queries by name+city)
1. Open table in SE11
2. Menu: Goto → Indexes
3. Create index
4. Add fields
5. Activate
Index naming: Z + table name + number (e.g., ZCUSTOMERS~Z01)
Create index when:
- Field frequently in WHERE clause
- Field frequently in JOIN condition
- Significant performance improvement measured
Avoid:
- Too many indexes (slows INSERT/UPDATE)
- Indexes on small tables
- Indexes on frequently changing fields
- Define relationships between tables
- Enable value check (input validation)
- Enable search helps (F4)
- Document data model
Table ZORDERS:
ORDER_ID (key)
CUSTOMER_ID → foreign key to ZCUSTOMERS-CUSTOMER_ID
ORDER_DATE
When entering CUSTOMER_ID:
- F4 shows list from ZCUSTOMERS
- Invalid values rejected
| Type | Meaning |
|---|---|
| 1:1 | One foreign row per dependent row |
| 1:N | One foreign row, many dependent rows |
| 1:C | Zero or one foreign row |
| 1:CN | Zero, one, or many dependent rows |
1. Open table
2. Click on field
3. Button: Foreign keys
4. Enter check table
5. Define key fields mapping
6. Set cardinality
7. Activate
| Type | Purpose | Database object |
|---|---|---|
| Database view | Inner join of tables | Yes (created in DB) |
| Projection view | Subset of fields from one table | Yes |
| Maintenance view | Data maintenance (SM30) | No |
| Help view | Search help (outer join) | No |
View ZCUST_ORDER_V:
Join: ZCUSTOMERS + ZORDERS
Join condition: ZCUSTOMERS-CUSTOMER_ID = ZORDERS-CUSTOMER_ID
Fields:
CUSTOMER_ID (from ZCUSTOMERS)
NAME (from ZCUSTOMERS)
ORDER_ID (from ZORDERS)
ORDER_DATE (from ZORDERS)
* Use view like a table
SELECT * FROM zcust_order_v INTO TABLE @DATA(lt_data)
WHERE name LIKE 'A%'.
LOOP AT lt_data INTO DATA(ls_row).
WRITE: / ls_row-name, ls_row-order_id, ls_row-order_date.
ENDLOOP.
Acme Corp 0000000001 20241215
Acme Corp 0000000002 20241218
Alpha Inc 0000000003 20241220
Core Data Services = modern view technology.
Defined in ADT (Eclipse), not SE11.
More powerful than DDIC views.
Features:
- Annotations
- Expressions
- Associations
- Parameters
- Aggregations
@AbapCatalog.sqlViewName: 'ZCUSTORDERV'
@AbapCatalog.compiler.compareFilter: true
define view Z_Cust_Order as select from zcustomers as c
inner join zorders as o on c.customer_id = o.customer_id
{
key c.customer_id,
c.name,
o.order_id,
o.order_date
}
Search help = F4 value selection.
Shows list of valid values for a field.
User can search and select.
| Type | Description |
|---|---|
| Elementary | Single search path |
| Collective | Multiple search paths (tabs) |
| Append | Add to existing search help |
Search help: ZCUST_SH
Selection method: ZCUSTOMERS (table or view)
Parameters:
CUSTOMER_ID IMP, EXP, LPos 1, SPos 1
NAME IMP, LPos 2, SPos 2
CITY LPos 3
IMP = can be used for filtering (import)
EXP = returned to screen (export)
LPos = position in list
SPos = position in search screen
Attach at:
1. Data element level (applies everywhere)
2. Table field level (applies to that table)
3. Screen field level (applies to that screen)
4. ABAP code (PARAMETERS, SELECT-OPTIONS)
* Search help in selection screen
PARAMETERS p_cust TYPE kunnr MATCHCODE OBJECT zcust_sh.
* Alternative: attach via data element
PARAMETERS p_cust TYPE kunnr. "uses search help from data element
DATA lt_return TYPE TABLE OF ddshretval.
DATA ls_return TYPE ddshretval.
CALL FUNCTION 'F4IF_FIELD_VALUE_REQUEST'
EXPORTING
tabname = 'ZCUSTOMERS'
fieldname = 'CUSTOMER_ID'
TABLES
return_tab = lt_return
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
READ TABLE lt_return INTO ls_return INDEX 1.
IF sy-subrc = 0.
WRITE: / 'Selected:', ls_return-fieldval.
ENDIF.
ENDIF.
Lock object = logical lock on database records.
Prevents concurrent modification.
SAP-specific (not database locks).
Transaction SE11 → Lock object → Create
Name: EZZCUSTOMERS (must start with E)
Tables: ZCUSTOMERS
Lock mode: Write lock / Read lock / Enhanced write lock
→ Generates function modules:
ENQUEUE_EZZCUSTOMERS (acquire lock)
DEQUEUE_EZZCUSTOMERS (release lock)
DATA lv_customer_id TYPE kunnr VALUE '0000001234'.
* Acquire lock
CALL FUNCTION 'ENQUEUE_EZZCUSTOMERS'
EXPORTING
customer_id = lv_customer_id
EXCEPTIONS
foreign_lock = 1 "locked by another user
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
WRITE: / 'Lock acquired'.
"... do work ...
* Release lock
CALL FUNCTION 'DEQUEUE_EZZCUSTOMERS'
EXPORTING
customer_id = lv_customer_id.
WRITE: / 'Lock released'.
ELSE.
WRITE: / 'Could not acquire lock'.
ENDIF.
Lock acquired
Lock released
| Mode | Description | Collisions with |
|---|---|---|
| E (Exclusive) | Write lock | E, S, X |
| S (Shared) | Read lock | E, X |
| X (Exclusive) | Enhanced write | E, S, X (cumulative) |
* Release all locks held by current program
CALL FUNCTION 'DEQUEUE_ALL'.
Generates screens for maintaining table data.
No ABAP coding required.
Access via SM30 or SE16.
1. SE11 → Table → Display
2. Menu: Utilities → Table Maintenance Generator
3. Authorization group: &NC&
4. Function group: ZZCUST_MNT
5. Maintenance type: One step / Two step
6. Generate
→ Can now maintain data via SM30
Transaction SM30:
Table/View: ZCUSTOMERS
Button: Maintain
→ Opens generated maintenance screen
→ Can add, change, delete records
Authorization group in table maintenance.
Object: S_TABU_DIS
Field: DICBERCLS (table class)
Control who can maintain which tables.
Type groups = collections of type definitions.
Used before ABAP Objects for shared types.
Still exists but obsolete in modern ABAP.
* Old style - required TYPE-POOLS statement
TYPE-POOLS: slis. "ALV types
DATA ls_layout TYPE slis_layout_alv.
* Modern - type groups available without TYPE-POOLS
DATA ls_layout TYPE slis_layout_alv.
Since ABAP 7.40, TYPE-POOLS statement optional.
* Data element
DATA lv_matnr TYPE matnr.
* Table as structure
DATA ls_material TYPE mara.
* Internal table of table
DATA lt_materials TYPE TABLE OF mara.
* Specific structure
DATA ls_data TYPE zmystructure.
* Table type
DATA lt_data TYPE zmytabletype.
* Reference to DDIC type
DATA lr_data TYPE REF TO mara.
* Describe a DDIC type
DATA lo_type TYPE REF TO cl_abap_typedescr.
DATA lo_struct TYPE REF TO cl_abap_structdescr.
lo_type = cl_abap_typedescr=>describe_by_name( 'MARA' ).
IF lo_type->kind = cl_abap_typedescr=>kind_struct.
lo_struct ?= lo_type.
LOOP AT lo_struct->components INTO DATA(ls_comp).
WRITE: / ls_comp-name, ls_comp-type_kind.
ENDLOOP.
ENDIF.
MANDT C
MATNR C
ERSDA D
ERNAM C
...
DATA lr_data TYPE REF TO data.
FIELD-SYMBOLS <fs_table> TYPE ANY TABLE.
* Create internal table from DDIC table name
CREATE DATA lr_data TYPE TABLE OF (p_tabname).
ASSIGN lr_data->* TO <fs_table>.
SELECT * FROM (p_tabname) INTO TABLE @<fs_table>.
WRITE: / 'Rows:', lines( <fs_table> ).
After creating/changing DDIC object:
1. Save (stores in database)
2. Activate (makes available for use)
Inactive objects cannot be used in programs.
Activation creates/updates database objects.
Transaction SE11 menu: Utilities → Activate multiple
Or transaction: SACT
DDIC objects assigned to package + transport request.
Z* / Y* = customer namespace
/NAMESPACE/ = registered namespace
Changes transported: DEV → QAS → PRD
Transaction SE14: Database Utility
When table structure changes:
- Add field: ALTER TABLE (usually)
- Remove field: requires conversion
- Change key: requires new table + data migration
- Change field type: may require conversion
SE14 can:
- Activate and adjust database
- Delete data
- Convert table
| Object | Purpose | Create in |
|---|---|---|
| Domain | Technical attributes | SE11 |
| Data element | Semantic meaning | SE11 |
| Structure | Field collection | SE11 |
| Table type | Internal table definition | SE11 |
| Database table | Persistent storage | SE11 |
| View | Table join/projection | SE11 |
| Search help | F4 value selection | SE11 |
| Lock object | Logical locking | SE11 |
| CDS View | Modern views | ADT |
| Prefix | Object type |
|---|---|
Z* / Y* |
Customer objects |
E* |
Lock objects |
*_D |
Domain (convention) |
*_STR |
Structure (convention) |
*_TAB |
Table type (convention) |
*_SH |
Search help (convention) |
*_V |
View (convention) |
Domain = technical definition of a field's value range.
Defines:
- Data type (CHAR, NUMC, DEC, etc.)
- Length
- Decimal places (for numeric types)
- Fixed values (valid entries)
- Value table (check table)
- Conversion routine (input/output formatting)
- Output characteristics (lowercase, sign)
Domain ← technical attributes (you are here)
↓
Data Element ← semantic meaning (labels, docs)
↓
Table Field ← actual storage
1. Reusability
One domain → many data elements → many fields
Change domain → all fields updated
2. Consistency
All fields using domain have same type/length
3. Validation
Fixed values enforced automatically
4. Formatting
Conversion routines applied consistently
1. Transaction SE11
2. Select "Domain"
3. Enter name (e.g., ZSTATUS_D)
4. Click Create
5. Fill in:
- Short description
- Data type
- Length (Number of characters)
- Decimal places (if numeric)
- Output length
6. Optionally add:
- Fixed values (Value Range tab)
- Conversion routine
7. Save → Assign to package/transport
8. Activate
Z*_D or Y*_D
Examples:
ZSTATUS_D status domain
ZQUANTITY_D quantity domain
ZCURRENCY_D currency amount domain
| Type | Description | Length | ABAP type |
|---|---|---|---|
CHAR |
Character | 1-30000 | c |
NUMC |
Numeric text (digits only) | 1-255 | n |
DATS |
Date (YYYYMMDD) | 8 (fixed) | d |
TIMS |
Time (HHMMSS) | 6 (fixed) | t |
DEC |
Packed decimal | 1-31 | p |
CURR |
Currency amount | 1-31 | p |
QUAN |
Quantity | 1-31 | p |
INT1 |
1-byte integer | 3 (fixed) | b (0-255) |
INT2 |
2-byte integer | 5 (fixed) | s |
INT4 |
4-byte integer | 10 (fixed) | i |
INT8 |
8-byte integer | 19 (fixed) | int8 |
FLTP |
Floating point | 16 (fixed) | f |
STRING |
Variable string | variable | string |
RAWSTRING |
Variable bytes | variable | xstring |
RAW |
Fixed bytes | 1-32000 | x |
CLNT |
Client | 3 (fixed) | c |
LANG |
Language key | 1 (fixed) | c |
Text that can contain any characters → CHAR
Text with only digits (IDs, codes) → NUMC
Dates → DATS
Times → TIMS
Money amounts → CURR (with reference to currency)
Quantities → QUAN (with reference to unit)
Other decimals → DEC
Whole numbers → INT4 (or INT8 for large)
Yes/no flags → CHAR length 1
Binary data → RAW or RAWSTRING
Length = number of characters
Domain ZCODE_D:
Type: CHAR
Length: 10
→ Can store up to 10 characters
→ Shorter values right-padded with spaces
→ Longer values truncated
Length = total significant digits
Decimal places = digits after decimal point
Domain ZAMOUNT_D:
Type: DEC
Length: 15
Decimal places: 2
→ Max value: 9999999999999.99 (13 integer + 2 decimal = 15 total)
→ Stored as packed decimal (BCD)
Length: 7, Decimals: 2
Integer part: 5 digits
Decimal part: 2 digits
Max: 99999.99
Length: 11, Decimals: 4
Integer part: 7 digits
Decimal part: 4 digits
Max: 9999999.9999
Length: 5, Decimals: 0
Integer part: 5 digits
No decimal
Max: 99999
* Domain ZAMOUNT_D: DEC 15,2
DATA lv_amount TYPE zamount. "data element using domain
lv_amount = '12345678901234.56'. "too large - error
lv_amount = '9999999999999.99'. "max value - OK
lv_amount = '1234.567'. "rounded to 1234.57
WRITE: / lv_amount.
1234.57
Output length = display width on screens/reports.
Can be different from storage length.
Includes: sign, decimal point, thousands separator.
For DEC with length 15, decimals 2:
Digits: 15
Decimal point: 1
Sign: 1 (optional)
Thousands separators: 4 (for 13 integer digits)
Output length: ~21
SAP calculates default, but you can override.
DATA lv_amount TYPE zamount VALUE '1234567.89'.
* WRITE uses output length for formatting
WRITE: / lv_amount.
1,234,567.89
Formatted according to output length and user settings.
Fixed values = enumeration of allowed values.
Restricts what can be entered.
Provides dropdown list on screens.
Domain: ZSTATUS_D
Type: CHAR
Length: 1
Value Range tab → Single values:
Value Short description
A Active
I Inactive
P Pending
C Closed
Can also define ranges:
Domain: ZPRIORITY_D
Type: NUMC
Length: 2
Intervals:
From To Description
01 03 Low priority
04 06 Medium priority
07 09 High priority
Screen field using data element with this domain:
1. F4 (dropdown) shows:
A - Active
I - Inactive
P - Pending
C - Closed
2. Invalid values rejected:
User enters "X" → Error "Value X is not valid"
* Fixed values are NOT automatically checked in ABAP code
* Only enforced on screens
DATA lv_status TYPE zstatus VALUE 'X'. "compiles fine!
* Manual check
DATA lt_values TYPE TABLE OF dd07v.
CALL FUNCTION 'DD_DOMVALUES_GET'
EXPORTING
domname = 'ZSTATUS_D'
text = 'X'
TABLES
dd07v_tab = lt_values.
READ TABLE lt_values WITH KEY domvalue_l = lv_status TRANSPORTING NO FIELDS.
IF sy-subrc <> 0.
WRITE: / 'Invalid status value'.
ENDIF.
Invalid status value
DATA lt_values TYPE TABLE OF dd07v.
CALL FUNCTION 'DD_DOMVALUES_GET'
EXPORTING
domname = 'ZSTATUS_D'
text = 'X' "X = get texts too
langu = sy-langu
TABLES
dd07v_tab = lt_values.
LOOP AT lt_values INTO DATA(ls_val).
WRITE: / ls_val-domvalue_l, '-', ls_val-ddtext.
ENDLOOP.
A - Active
I - Inactive
P - Pending
C - Closed
Value table = database table that contains valid values.
Alternative to fixed values for dynamic/large value lists.
Domain: ZCOUNTRY_D
Type: CHAR
Length: 3
Value table: T005 (SAP country table)
→ Valid values come from T005-LAND1
| Aspect | Fixed values | Value table |
|---|---|---|
| Storage | In domain definition | In database table |
| Maintenance | Change domain | Change table data |
| Size | Small lists | Large lists |
| Dynamic | No (requires transport) | Yes (data changes) |
Value table alone does NOT enforce validation.
Only suggests which table contains valid values.
For actual enforcement:
Create foreign key on table field level.
Foreign key points to value table.
Conversion routine = transforms value between:
- Internal format (stored in database)
- External format (shown to user)
Two function modules:
CONVERSION_EXIT_xxxxx_INPUT (external → internal)
CONVERSION_EXIT_xxxxx_OUTPUT (internal → external)
| Routine | Purpose | Example |
|---|---|---|
ALPHA |
Add/remove leading zeros | 123 ↔ 0000000123 |
MATN1 |
Material number format | 123 ↔ 000000000000000123 |
CUNIT |
Unit of measure | KG ↔ KG (ISO conversion) |
ISOLA |
Language key | EN ↔ E |
GJAHR |
Fiscal year | 2024 ↔ 2024 |
Domain: ZORDERNUM_D
Type: NUMC
Length: 10
Conversion routine: ALPHA
User enters: 123
Stored as: 0000000123
Displayed as: 123
DATA lv_matnr TYPE matnr. "has MATN1 conversion
* Stored internally with leading zeros
lv_matnr = '000000000001234567'.
* Display: conversion output applied
WRITE: / lv_matnr.
* In string template with ALPHA = OUT
WRITE: / |Material: { lv_matnr ALPHA = OUT }|.
000000000001234567
Material: 1234567
DATA lv_external TYPE char18 VALUE '123'.
DATA lv_internal TYPE matnr.
* External → Internal (add zeros)
CALL FUNCTION 'CONVERSION_EXIT_MATN1_INPUT'
EXPORTING
input = lv_external
IMPORTING
output = lv_internal.
WRITE: / 'Internal:', lv_internal.
* Internal → External (remove zeros)
CALL FUNCTION 'CONVERSION_EXIT_MATN1_OUTPUT'
EXPORTING
input = lv_internal
IMPORTING
output = lv_external.
WRITE: / 'External:', lv_external.
Internal: 000000000000000123
External: 123
1. Create function module: CONVERSION_EXIT_ZTEST_INPUT
- Import: INPUT
- Export: OUTPUT
- Code: transform external → internal
2. Create function module: CONVERSION_EXIT_ZTEST_OUTPUT
- Import: INPUT
- Export: OUTPUT
- Code: transform internal → external
3. Assign to domain:
Conversion routine: ZTEST
* Example: CONVERSION_EXIT_ZTEST_INPUT
FUNCTION conversion_exit_ztest_input.
"Add prefix 'X' to all values
output = 'X' && input.
ENDFUNCTION.
* Example: CONVERSION_EXIT_ZTEST_OUTPUT
FUNCTION conversion_exit_ztest_output.
"Remove prefix 'X'
IF input(1) = 'X'.
output = input+1.
ELSE.
output = input.
ENDIF.
ENDFUNCTION.
In SE11 domain → Output Characteristics tab:
- Lowercase letters: allow lowercase (default: convert to upper)
- Sign: display sign for negative numbers
Domain ZDESC_D:
Type: CHAR
Length: 40
Lowercase: Yes (checked)
→ User can enter: "Hello World"
→ Stored as: "Hello World"
Domain ZCODE_D:
Type: CHAR
Length: 10
Lowercase: No (unchecked)
→ User enters: "Hello"
→ Stored as: "HELLO"
* Domain with lowercase = No
DATA lv_code TYPE zcode VALUE 'Hello'.
WRITE: / lv_code. "depends on domain setting
* Domain with lowercase = Yes
DATA lv_desc TYPE zdesc VALUE 'Hello World'.
WRITE: / lv_desc.
HELLO
Hello World
Uppercase conversion happens on screen input, not in ABAP assignment.
Domain ZBALANCE_D:
Type: DEC
Length: 15
Decimals: 2
Sign: checked
→ Negative values display with sign: -1234.56
→ Without: 1234.56- (trailing minus)
Domain ZAMOUNT_D:
Type: CURR
Length: 15
Decimals: 2
Reference table: ZCURRENCY_TAB
Reference field: CURRENCY
→ Decimals depend on currency
→ JPY has 0 decimals, EUR has 2
Domain ZQUANTITY_D:
Type: QUAN
Length: 13
Decimals: 3
Reference table: ZQUANT_TAB
Reference field: UNIT
→ Decimals depend on unit of measure
Currency/quantity fields need unit field reference.
Otherwise SAP doesn't know how to:
- Format decimals correctly
- Convert between currencies/units
- Validate entries
Safe changes (no data loss):
- Increase length
- Add fixed values
- Change output length
- Add/change conversion routine
- Change description
Risky changes (may lose data):
- Decrease length
- Change data type
- Remove fixed values in use
- Change decimal places
When domain used in table with data:
1. Increase length → ALTER TABLE (usually OK)
2. Decrease length → may truncate data
3. Type change → requires table conversion
SE14 (Database Utility) handles adjustments.
SE11 → Domain → Display
Menu: Where-Used List (Ctrl+Shift+F3)
Shows:
- Data elements using this domain
- Tables (indirectly via data elements)
Domain: ZORDER_STATUS_D
Type: CHAR
Length: 2
Conversion routine: (none)
Fixed values:
01 = New
02 = In Progress
03 = Completed
04 = Cancelled
99 = Error
Domain: ZDOCUMENT_ID_D
Type: NUMC
Length: 10
Conversion routine: ALPHA
User enters: 12345
Stored: 0000012345
Displayed: 12345
Domain: ZPRICE_D
Type: CURR
Length: 11
Decimals: 2
Domain: ZFLAG_D
Type: CHAR
Length: 1
Fixed values:
X = Yes
(space) = No
* After creating data elements for each domain
DATA lv_status TYPE zorder_status VALUE '02'.
DATA lv_doc_id TYPE zdocument_id VALUE '0000012345'.
DATA lv_price TYPE zprice VALUE '199.99'.
DATA lv_active TYPE zflag VALUE 'X'.
WRITE: / 'Status:', lv_status.
WRITE: / 'Doc ID:', |{ lv_doc_id ALPHA = OUT }|.
WRITE: / 'Price:', lv_price.
WRITE: / 'Active:', lv_active.
Status: 02
Doc ID: 12345
Price: 199.99
Active: X
DATA ls_domain TYPE dd01v.
CALL FUNCTION 'DDIF_DOMA_GET'
EXPORTING
name = 'MATNR_D'
langu = sy-langu
IMPORTING
dd01v_wa = ls_domain
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
WRITE: / 'Domain:', ls_domain-domname.
WRITE: / 'Type:', ls_domain-datatype.
WRITE: / 'Length:', ls_domain-leng.
WRITE: / 'Decimals:', ls_domain-decimals.
WRITE: / 'Conversion:', ls_domain-convexit.
ENDIF.
Domain: MATNR_D
Type: CHAR
Length: 18
Decimals: 0
Conversion: MATN1
DATA lt_values TYPE TABLE OF dd07v.
DATA lv_status TYPE c LENGTH 2 VALUE '05'.
CALL FUNCTION 'DD_DOMVALUES_GET'
EXPORTING
domname = 'ZORDER_STATUS_D'
TABLES
dd07v_tab = lt_values.
READ TABLE lt_values WITH KEY domvalue_l = lv_status TRANSPORTING NO FIELDS.
IF sy-subrc = 0.
WRITE: / lv_status, 'is valid'.
ELSE.
WRITE: / lv_status, 'is NOT valid'.
ENDIF.
05 is NOT valid
1. Create domain when:
- Multiple data elements share same technical properties
- Need fixed values (enumeration)
- Need conversion routine
2. Skip domain when:
- One-off field with unique properties
- Using built-in type directly is clearer
3. Use meaningful names:
- ZSTATUS_D not ZDOM1
- Include _D suffix for clarity
4. Document fixed values:
- Clear descriptions for each value
- Consider future additions
5. Plan for changes:
- Slightly larger length than current need
- Room for new fixed values
| Property | Purpose | Tab in SE11 |
|---|---|---|
| Data type | Base type (CHAR, DEC, etc.) | Definition |
| Length | Storage size | Definition |
| Decimals | Decimal places | Definition |
| Output length | Display width | Definition |
| Conversion routine | Format transformation | Definition |
| Fixed values | Allowed values list | Value Range |
| Value table | Reference table for values | Value Range |
| Lowercase | Allow lowercase input | Output Characteristics |
| Sign | Display sign for negatives | Output Characteristics |
Data element = semantic definition of a field.
Defines:
- Field labels (short, medium, long, heading)
- Documentation (F1 help text)
- Search help (F4)
- Parameter ID (screen memory)
- Technical type (from domain or direct)
Domain ← technical attributes (type, length, values)
↓
Data Element ← semantic meaning (you are here)
↓
Table Field ← actual storage location
1. Consistent labeling
Same field label everywhere in the system.
Change once → updates all screens/reports.
2. Built-in documentation
F1 help available on any field using this element.
3. Search help integration
F4 dropdown automatically attached.
4. Screen field memory
Parameter ID remembers last entered value.
5. Reusability
One definition → many table fields.
| Aspect | Domain | Data Element |
|---|---|---|
| Focus | Technical (how stored) | Semantic (what it means) |
| Defines | Type, length, values | Labels, docs, help |
| Reuse | By multiple data elements | By multiple fields |
| Example | CHAR 18 with ALPHA | "Material Number" |
1. Transaction SE11
2. Select "Data type"
3. Enter name (e.g., ZORDER_ID)
4. Click Create
5. Choose "Data element"
6. Fill in:
- Short description
- Domain OR Built-in type
7. Field Label tab:
- Short, Medium, Long, Heading texts
8. Optionally:
- Documentation (Change Documentation button)
- Search help
- Parameter ID
9. Save → Assign to package/transport
10. Activate
Z* or Y* prefix for custom objects.
Examples:
ZORDER_ID order identifier
ZCUSTOMER_NAME customer name
ZSTATUS status field
ZQUANTITY quantity field
Option 1: Reference to Domain
- Inherits type, length, decimals from domain
- Inherits conversion routine
- Inherits fixed values
- Recommended when domain exists
Option 2: Direct Type (Built-in Type)
- Specify type and length directly
- No domain needed
- Less reusable
- OK for one-off fields
Data Element: ZMATNR
Domain: MATNR_D
→ Inherits: CHAR 18, conversion routine MATN1
→ Change domain → data element updated automatically
Data Element: ZDESCRIPTION
Built-in type: CHAR
Length: 60
→ No domain
→ Type defined directly in data element
┌─────────────────────────────────────┐
│ Data Type │
├─────────────────────────────────────┤
│ ○ Domain [MATNR_D ] │
│ ○ Built-in Type [ ] │
│ - Data Type [CHAR ] │
│ - Length [18 ] │
│ - Decimals [0 ] │
└─────────────────────────────────────┘
Select one option:
- Domain: enter domain name
- Built-in Type: enter type details
Use Domain when:
- Multiple data elements share same technical definition
- Need fixed values or conversion routine
- Type might change (change domain once)
Use Direct Type when:
- Unique field with no reuse
- Simple type, no special formatting
- No suitable domain exists and not worth creating one
| Label | Max Length | Used In |
|---|---|---|
| Short | 10 characters | Narrow columns, compact screens |
| Medium | 20 characters | Standard screen fields |
| Long | 40 characters | Wide displays, reports |
| Heading | 55 characters | Column headers, report titles |
Data Element: MATNR (Material Number)
Short: Material
Medium: Material Number
Long: Material Number
Heading: Material Number
Data Element: ERDAT (Created Date)
Short: Created
Medium: Created On
Long: Date Created
Heading: Date Record Was Created
┌───────────────────────────────────────────────┐
│ Field Label │
├───────────────────────────────────────────────┤
│ Short [10] [Order ] │
│ Medium [20] [Order Number ] │
│ Long [40] [Sales Order Number ]│
│ Heading [55] [Sales Order Document Number ]│
└───────────────────────────────────────────────┘
Numbers in brackets = max length for that label.
Screen Painter:
- Pulls label based on available space
- Short for narrow fields
- Medium for standard fields
ALV Reports:
- Uses Heading for column header
- Uses Medium/Short if space limited
Selection Screens:
- Uses Medium by default
DATA ls_dfies TYPE dfies.
CALL FUNCTION 'DDIF_FIELDINFO_GET'
EXPORTING
tabname = 'MARA'
fieldname = 'MATNR'
langu = sy-langu
IMPORTING
dfies_wa = ls_dfies
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
WRITE: / 'Short:', ls_dfies-scrtext_s.
WRITE: / 'Medium:', ls_dfies-scrtext_m.
WRITE: / 'Long:', ls_dfies-scrtext_l.
ENDIF.
Short: Material
Medium: Material
Long: Material Number
User presses F1 on a field → Documentation displayed.
Contains:
- Field description
- Valid values explanation
- Business context
- Usage guidelines
In SE11:
1. Display data element
2. Menu: Goto → Documentation
Or: Button "Documentation" (Ctrl+F7)
3. Choose: "Create" or "Change"
4. Write documentation using SAPscript editor
5. Save and activate
Documentation is language-dependent.
Create for each supported language.
Typical sections:
Definition
What this field represents.
Use
When and how to use this field.
Valid Values (if not obvious from fixed values)
A = Active order
I = Inactive order
etc.
Example
Sample value: 0000001234
Dependencies
Related fields that affect this one.
DATA lt_docu TYPE TABLE OF tline.
CALL FUNCTION 'DOCU_READ'
EXPORTING
id = 'DE' "DE = data element
langu = sy-langu
object = 'MATNR' "data element name
TABLES
line = lt_docu
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
LOOP AT lt_docu INTO DATA(ls_line).
WRITE: / ls_line-tdline.
ENDLOOP.
ENDIF.
User presses F4 on field → Search help displayed.
Shows list of valid values for selection.
Search help can be attached at:
1. Data element (applies everywhere)
2. Table field (overrides data element)
3. Screen field (overrides all)
In SE11 - Further Characteristics tab:
Search help: [ZORDER_SH ]
→ Any field using this data element gets F4 help.
If domain has value table:
→ Basic search help auto-generated
If search help explicitly assigned:
→ Takes precedence over domain value table
Best practice:
→ Assign search help at data element level
→ Most consistent experience
User on screen field ORDER_ID (uses data element ZORDER_ID):
Press F4:
┌─────────────────────────────────────┐
│ Order Number │
├─────────────────────────────────────┤
│ Order Customer Date │
├─────────────────────────────────────┤
│ 0000000001 CUST001 2024-12-01 │
│ 0000000002 CUST002 2024-12-05 │
│ 0000000003 CUST001 2024-12-10 │
└─────────────────────────────────────┘
Parameter ID = memory ID for screen field values.
When user enters value in one screen:
→ Value remembered (SET parameter)
When user opens another screen with same parameter ID:
→ Value pre-filled (GET parameter)
Example: Enter company code 1000 in one transaction
→ Automatically filled in next transaction
In SE11 - Further Characteristics tab:
Parameter ID: [BUK]
Common parameter IDs:
BUK = Company code
MAT = Material
KUN = Customer
WRK = Plant
VKO = Sales organization
Transaction SM30 → Table TPARA
Or SE11 → Table TPARA → Maintain
Add entry:
Parameter ID: ZORD
Description: Custom Order ID
DATA lv_value TYPE c LENGTH 10.
* Get value from memory
GET PARAMETER ID 'BUK' FIELD lv_value.
WRITE: / 'Company code from memory:', lv_value.
* Set value in memory
lv_value = '2000'.
SET PARAMETER ID 'BUK' FIELD lv_value.
WRITE: / 'Set company code to:', lv_value.
Company code from memory: 1000
Set company code to: 2000
Screen field with parameter ID needs flag set:
Screen Painter → Field attributes:
☑ SET parameter
☑ GET parameter
SET: Save entered value to memory
GET: Retrieve value from memory on screen load
Change document = audit trail of field changes.
If enabled:
→ System logs old value, new value, who, when
→ Viewable in change history
Used for:
- Compliance/audit requirements
- Tracking who changed what
In SE11 - Further Characteristics tab:
☑ Change document
When checked:
→ Changes to this field logged (if table supports it)
For change documents to work:
1. Data element has flag checked
2. Table has change document object
3. Application calls change document function modules
Just checking the flag doesn't automatically log changes.
Application must implement change document handling.
* Use data element as type
DATA lv_matnr TYPE matnr.
DATA lv_order_id TYPE vbeln.
DATA lv_customer TYPE kunnr.
lv_matnr = '000000000001234567'.
lv_order_id = '0000000001'.
lv_customer = '0000001234'.
WRITE: / lv_matnr, lv_order_id, lv_customer.
000000000001234567 0000000001 0000001234
CLASS lcl_order DEFINITION.
PUBLIC SECTION.
METHODS get_order
IMPORTING
iv_order_id TYPE vbeln "sales order number
RETURNING
VALUE(rs_order) TYPE vbak. "order header
ENDCLASS.
PARAMETERS: p_matnr TYPE matnr, "Material number
p_werks TYPE werks_d, "Plant
p_date TYPE erdat. "Date
* Labels come from data element automatically
* F4 help comes from data element/domain
TYPES: BEGIN OF ty_order,
order_id TYPE vbeln,
customer TYPE kunnr,
order_date TYPE erdat,
amount TYPE netwr,
END OF ty_order.
DATA lt_orders TYPE TABLE OF ty_order.
* Without data element - no semantic info
DATA lv_id TYPE c LENGTH 10.
* With data element - semantic info available
DATA lv_order TYPE vbeln.
* Both work technically, but vbeln carries:
* - Proper labels
* - F1 documentation
* - F4 search help
* - Conversion routine
* - Correct type/length
DATA ls_dtel TYPE dd04v.
CALL FUNCTION 'DDIF_DTEL_GET'
EXPORTING
name = 'MATNR'
langu = sy-langu
IMPORTING
dd04v_wa = ls_dtel
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
WRITE: / 'Data element:', ls_dtel-rollname.
WRITE: / 'Domain:', ls_dtel-domname.
WRITE: / 'Data type:', ls_dtel-datatype.
WRITE: / 'Length:', ls_dtel-leng.
WRITE: / 'Short text:', ls_dtel-scrtext_s.
WRITE: / 'Medium text:', ls_dtel-scrtext_m.
WRITE: / 'Long text:', ls_dtel-scrtext_l.
WRITE: / 'Heading:', ls_dtel-reptext.
ENDIF.
Data element: MATNR
Domain: MATNR_D
Data type: CHAR
Length: 18
Short text: Material
Medium text: Material
Long text: Material Number
Heading: Material Number
DATA lv_matnr TYPE matnr.
DATA lo_elem TYPE REF TO cl_abap_elemdescr.
lo_elem ?= cl_abap_typedescr=>describe_by_data( lv_matnr ).
WRITE: / 'Type kind:', lo_elem->type_kind.
WRITE: / 'Length:', lo_elem->length.
WRITE: / 'Decimals:', lo_elem->decimals.
* Get DDIC info
DATA ls_ddic TYPE dfies.
lo_elem->get_ddic_field( RECEIVING p_flddescr = ls_ddic ).
WRITE: / 'Domain:', ls_ddic-domname.
WRITE: / 'Short text:', ls_ddic-scrtext_s.
Type kind: C
Length: 18
Decimals: 0
Domain: MATNR_D
Short text: Material
| Data Element | Description | Type |
|---|---|---|
MATNR |
Material Number | CHAR 40 |
KUNNR |
Customer Number | CHAR 10 |
LIFNR |
Vendor Number | CHAR 10 |
VBELN |
Sales Document Number | CHAR 10 |
BUKRS |
Company Code | CHAR 4 |
WERKS_D |
Plant | CHAR 4 |
ERDAT |
Created Date | DATS |
ERNAM |
Created By | CHAR 12 |
NETWR |
Net Value | CURR 15,2 |
WAERS |
Currency Key | CHAR 5 |
MENGE |
Quantity | QUAN 13,3 |
MEINS |
Unit of Measure | UNIT 3 |
SPRAS |
Language Key | LANG 1 |
MANDT |
Client | CLNT 3 |
| Data Element | Description | ABAP Type |
|---|---|---|
ABAP_BOOL |
Boolean | c LENGTH 1 |
STRING |
String | string |
XSTRING |
Byte string | xstring |
TIMESTAMP |
Timestamp | p LENGTH 8 |
TIMESTAMPL |
Long timestamp | p LENGTH 11 DECIMALS 7 |
I |
Integer | i |
INT8 |
8-byte integer | int8 |
* Using data element (recommended)
DATA lv_order TYPE vbeln.
* Using direct type (less semantic info)
DATA lv_order2 TYPE c LENGTH 10.
| Feature | Data Element | Direct Type |
|---|---|---|
| Field labels | ✓ Yes | ✗ No |
| F1 documentation | ✓ Yes | ✗ No |
| F4 search help | ✓ Yes | ✗ No |
| Conversion routine | ✓ Yes (via domain) | ✗ No |
| Fixed values | ✓ Yes (via domain) | ✗ No |
| Parameter ID | ✓ Yes | ✗ No |
| Type safety | ✓ Named type | ~ Generic type |
| Refactoring | ✓ Change central definition | ✗ Change everywhere |
- Temporary/local variables
- Loop counters
- Internal calculations
- One-off processing variables
* OK to use direct type for internal work
DATA lv_count TYPE i.
DATA lv_flag TYPE abap_bool.
DATA lv_temp TYPE string.
* Use data element for business data
DATA lv_order TYPE vbeln.
DATA lv_customer TYPE kunnr.
Step 1: Create domain (if needed)
Name: ZORDERID_D
Type: NUMC
Length: 10
Conversion routine: ALPHA
Step 2: Create data element
Name: ZORDER_ID
Domain: ZORDERID_D
Field Labels:
Short: Order
Medium: Order ID
Long: Customer Order ID
Heading: Customer Order Identification Number
Search help: ZORDER_SH (if exists)
Parameter ID: ZORD (if exists)
Documentation:
"Unique identifier for customer orders.
Generated automatically when order is created.
Format: 10-digit number with leading zeros."
Name: ZORDER_STATUS
Built-in type: CHAR
Length: 2
Field Labels:
Short: Status
Medium: Order Status
Long: Order Processing Status
Heading: Order Processing Status
Documentation:
"Current status of the order.
Valid values:
01 - New
02 - In Progress
03 - Completed
04 - Cancelled"
Table: ZORDERS
Fields:
MANDT TYPE MANDT (key)
ORDER_ID TYPE ZORDER_ID (key)
CUSTOMER TYPE KUNNR
STATUS TYPE ZORDER_STATUS
CREATED_AT TYPE TIMESTAMP
CREATED_BY TYPE ERNAM
DATA: ls_order TYPE zorders,
lt_orders TYPE TABLE OF zorders.
ls_order-order_id = '0000000001'.
ls_order-customer = '0000001234'.
ls_order-status = '01'.
GET TIME STAMP FIELD ls_order-created_at.
ls_order-created_by = sy-uname.
APPEND ls_order TO lt_orders.
SELECT * FROM zorders INTO TABLE @lt_orders
WHERE status = '01'.
LOOP AT lt_orders INTO ls_order.
WRITE: / |Order: { ls_order-order_id ALPHA = OUT }|,
|Status: { ls_order-status }|.
ENDLOOP.
Order: 1 Status: 01
Order: 2 Status: 01
Cannot modify SAP standard data elements.
But can extend their search help via append.
Rare use case - usually just create own data element.
In SE11:
1. Display data element
2. Menu: Where-Used List (Ctrl+Shift+F3)
Shows:
- Tables using this element
- Structures using this element
- Programs referencing this element
Changing data element affects:
- All tables using it (reactivation needed)
- All screens using those tables
- All programs using the type
Safe changes:
- Labels (no table impact)
- Documentation (no table impact)
- Search help (no table impact)
Careful changes (via domain):
- Length increase (usually OK)
- Length decrease (may truncate data)
- Type change (requires conversion)
Data element labels are language-dependent.
Each language has its own texts.
SE11 → Data element → Menu: Goto → Translation
Or: Transaction SE63
Translate:
- Short text
- Medium text
- Long text
- Heading text
- Documentation
* User logged in with English
* → Labels appear in English
* User logged in with German
* → Labels appear in German (if translated)
* sy-langu contains current language
WRITE: / 'Language:', sy-langu.
1. Always use data elements for business fields
Not direct types (c, n, i, etc.)
2. Create domain when:
- Multiple data elements share same technical properties
- Need fixed values or conversion routine
3. Meaningful names:
- ZORDER_ID not ZFLD01
- Reflect business meaning
4. Complete all labels:
- Don't leave any blank
- Short should be meaningful even if abbreviated
5. Write documentation:
- Explain business meaning
- List valid values if applicable
- Include examples
6. Assign search help:
- At data element level (most reusable)
- Consistent F4 experience
7. Use parameter IDs:
- For frequently used fields
- Improves user experience
8. Plan for translation:
- Keep texts simple
- Avoid technical jargon in labels
| Property | Purpose | Location |
|---|---|---|
| Domain/Built-in type | Technical definition | Data Type tab |
| Short label | 10-char label | Field Label tab |
| Medium label | 20-char label | Field Label tab |
| Long label | 40-char label | Field Label tab |
| Heading | 55-char header | Field Label tab |
| Search help | F4 value help | Further Characteristics |
| Parameter ID | Screen memory | Further Characteristics |
| Change document | Audit logging | Further Characteristics |
| Documentation | F1 help text | Goto → Documentation |
| Function | Purpose |
|---|---|
DDIF_DTEL_GET |
Get data element info |
DDIF_FIELDINFO_GET |
Get field info including labels |
DD_DOMVALUES_GET |
Get domain fixed values |
DOCU_READ |
Get documentation text |
Database table = persistent storage structure in SAP.
Defined in ABAP Dictionary (SE11).
Creates actual table in underlying database.
Holds application data (customers, orders, materials, etc.).
| Aspect | Database Table | Internal Table |
|---|---|---|
| Storage | Database (persistent) | Memory (temporary) |
| Lifespan | Permanent | Program runtime only |
| Access | SQL (SELECT, INSERT...) | ABAP statements (LOOP, READ...) |
| Definition | SE11 (DDIC) | ABAP code (DATA, TYPES) |
| Shared | All programs | Single program instance |
Domain ← technical type
↓
Data Element ← semantic meaning
↓
Table Field ← you are here (uses data element)
↓
Database ← physical storage
| Type | Description | Usage |
|---|---|---|
| Transparent | 1:1 mapping to database table | Most tables (99%+) |
| Pooled | Multiple DDIC tables in one DB table | Obsolete |
| Cluster | Compressed storage in DB table | Obsolete |
Modern SAP uses transparent tables exclusively. Pooled/cluster tables exist only in legacy systems.
DDIC Table: ZCUSTOMERS
↓ (1:1 mapping)
Database Table: ZCUSTOMERS
Same name, same structure.
Each DDIC field = one database column.
- Direct SQL access possible (native SQL)
- Standard database tools work
- Better performance
- Indexes work normally
- Database-level constraints apply
1. Transaction SE11
2. Enter table name (e.g., ZCUSTOMERS)
3. Click Create
4. Fill in:
- Short description
- Delivery class
- Fields (name, data element, key flag)
5. Technical settings (Goto → Technical Settings)
6. Save → Assign package/transport
7. Activate
Z* or Y* = customer namespace
/NAMESPACE/* = registered namespace
Examples:
ZCUSTOMERS customer master
ZORDERS order header
ZORDER_ITEMS order line items
ZMM_STOCK inventory data
┌─────────────────────────────────────────────────┐
│ Dictionary: Maintain Table │
├─────────────────────────────────────────────────┤
│ Table: ZCUSTOMERS │
│ Short Description: Customer Master Data │
│ │
│ Attributes: │
│ Delivery Class: [A ] (Application table) │
│ Table Category: [Transparent table] │
└─────────────────────────────────────────────────┘
Each field has:
- Field name (column name)
- Key flag (part of primary key?)
- Data element (type and semantics)
- Or: Direct type (built-in type without data element)
Table: ZCUSTOMERS
Field Key Data Element Description
─────────────────────────────────────────────
MANDT X MANDT Client
CUSTOMER_ID X ZCUSTOMER_ID Customer Number
NAME NAME1 Name
CITY ORT01 City
COUNTRY LAND1 Country
CREATED_AT TIMESTAMP Created Timestamp
CREATED_BY ERNAM Created By
First field in most tables.
Always key field.
Enables multi-tenancy (data separation by client).
Without MANDT = cross-client table (rare).
┌────────────────────────────────────────────────────────┐
│ Fields │
├──────┬─────────────┬──────┬──────────────┬─────────────┤
│ Key │ Field │ Init │ Data Element │ Description │
├──────┼─────────────┼──────┼──────────────┼─────────────┤
│ X │ MANDT │ │ MANDT │ Client │
│ X │ CUSTOMER_ID │ │ ZCUSTOMER_ID │ Customer No │
│ │ NAME │ │ NAME1 │ Name │
│ │ CITY │ │ ORT01 │ City │
└──────┴─────────────┴──────┴──────────────┴─────────────┘
Key = primary key field
Init = initial value required (rare)
Click "Predefined Type" button.
Field Key Type Length Decimals
─────────────────────────────────────────────
TEMP_FLAG CHAR 1
COUNTER INT4
Not recommended - loses semantic information.
Use data elements when possible.
- Uniquely identifies each row
- Created as database index automatically
- Cannot have duplicate combinations
- Cannot be NULL
1. Key fields must be first in field list
2. Key fields must be contiguous (no gaps)
3. MANDT typically first key field
4. Cannot change key after table has data
Valid:
MANDT (key), ORDER_ID (key), ITEM_NO (key), MATERIAL, QTY
Invalid:
MANDT (key), ORDER_ID (key), MATERIAL, ITEM_NO (key)
↑ non-key before key
Header table (ZORDERS):
Key: MANDT + ORDER_ID
Item table (ZORDER_ITEMS):
Key: MANDT + ORDER_ID + ITEM_NO
Config table (ZCONFIG):
Key: MANDT + CONFIG_KEY
* Full key access - fastest
SELECT SINGLE * FROM zcustomers
INTO @DATA(ls_cust)
WHERE mandt = @sy-mandt
AND customer_id = '0000001234'.
* Partial key - uses index partially
SELECT * FROM zorder_items
INTO TABLE @DATA(lt_items)
WHERE mandt = @sy-mandt
AND order_id = '0000000001'.
SE11 → Table → Menu: Goto → Technical Settings
Or: Button "Technical Settings"
Must be maintained before activation.
| Class | Description | Use for |
|---|---|---|
APPL0 |
Master data | Customers, materials, vendors |
APPL1 |
Transaction data | Orders, invoices, postings |
APPL2 |
Org/customizing | Config tables |
USR |
User data | User settings |
USR1 |
User data (large) | Large user data |
Data class affects database storage location (tablespace).
| Category | Expected rows |
|---|---|
0 |
0 to 100 |
1 |
100 to 1,000 |
2 |
1,000 to 10,000 |
3 |
10,000 to 100,000 |
4 |
100,000+ |
Affects initial storage allocation. Can be changed later.
Buffering = cache table data in application server memory.
Options:
- Not buffered (default for large/frequently changing)
- Fully buffered (entire table in memory)
- Generic buffered (by key fields)
- Single record buffered
Benefits: Faster reads, reduced database load
Risks: Stale data if modified by other means
| Buffering type | Best for |
|---|---|
| Not buffered | Large tables, frequent changes |
| Fully buffered | Small config tables (<100 rows) |
| Generic | Medium tables, access by partial key |
| Single record | Large tables, single row access |
┌─────────────────────────────────────────────┐
│ Technical Settings │
├─────────────────────────────────────────────┤
│ Data class: [APPL1 ] │
│ Size category: [2 ] │
│ │
│ Buffering: │
│ ○ Buffering not allowed │
│ ○ Buffering allowed but switched off │
│ ● Buffering switched on │
│ │
│ Buffering type: │
│ ○ Full │
│ ● Generic (number of key fields: 2) │
│ ○ Single records │
└─────────────────────────────────────────────┘
Delivery class = how table data is handled during:
- Transport (moved between systems)
- Upgrades (SAP updates)
- Client copy
| Class | Description | Data transported? |
|---|---|---|
A |
Application table | No (data stays in each system) |
C |
Customizing table | Yes (data moved with transport) |
L |
Temporary data | No (deleted on import) |
G |
Customer customizing | Protected from SAP changes |
E |
System table (control) | SAP controls |
S |
System table (data) | SAP controls |
W |
System table (protected) | SAP controls |
A - Application table
Orders, invoices, master data
Data created by users
Different data in DEV/QAS/PRD
C - Customizing table
Config settings, mappings
Same data needed in all systems
Transported DEV → QAS → PRD
Foreign key = relationship between tables.
Provides:
- Input validation (only valid values allowed)
- F4 help (dropdown from related table)
- Documentation of data model
Table ZORDERS:
ORDER_ID (key)
CUSTOMER_ID → foreign key to ZCUSTOMERS-CUSTOMER_ID
When entering CUSTOMER_ID:
- F4 shows customers from ZCUSTOMERS
- Invalid values rejected (if check enabled)
1. Position cursor on field
2. Menu: Goto → Foreign Keys
Or: Button "Foreign keys"
3. Enter check table
4. System proposes field assignment
5. Set cardinality
6. Save and activate
Check table = table containing valid values
| Setting | Meaning | Example |
|---|---|---|
| 1:1 | Exactly one related row | Order → One customer |
| 1:N | One or more related rows | Customer → Many orders |
| 1:C | Zero or one related row | Optional relationship |
| 1:CN | Zero, one, or many | Optional, multiple |
┌───────────────────────────────────────────────┐
│ Foreign Key for Field CUSTOMER_ID │
├───────────────────────────────────────────────┤
│ Check table: ZCUSTOMERS │
│ │
│ Foreign key fields: │
│ ZCUSTOMERS-MANDT = ZORDERS-MANDT │
│ ZCUSTOMERS-CUSTOMER_ID = ZORDERS-CUSTOMER_ID│
│ │
│ Cardinality: │
│ Foreign key table: [1 ] │
│ Check table: [CN ] │
└───────────────────────────────────────────────┘
Foreign keys do NOT enforce referential integrity in database.
Only provides:
- Screen validation
- F4 help
- Where-used documentation
For actual enforcement:
- Application logic must check
- Or use database triggers (rare in SAP)
Index = database structure for faster lookups.
Primary key index: created automatically
Secondary indexes: created manually for non-key fields
Create index when:
- Field frequently used in WHERE clause
- Field used in JOIN conditions
- Query performance is slow
Do NOT create when:
- Table is small (< 1000 rows)
- Field has few unique values
- Field changes frequently
- Too many indexes already (slows writes)
1. SE11 → Display table
2. Menu: Goto → Indexes
3. Click Create
4. Enter index ID (3 chars, e.g., Z01)
5. Add fields
6. Save and activate
Index name in DB: tablename~indexid
Example: ZCUSTOMERS~Z01
Table: ZCUSTOMERS
Index: Z01
Fields:
CITY
COUNTRY
→ Speeds up: SELECT * FROM zcustomers WHERE city = 'Berlin'
☑ Unique index
Enforces uniqueness at database level.
Duplicate combinations rejected.
Alternative to primary key for business keys.
* Query uses index Z01 automatically
SELECT * FROM zcustomers
INTO TABLE @DATA(lt_cust)
WHERE city = 'Berlin'.
* Explain plan shows index usage
* Transaction ST05 (SQL trace) to verify
Include = embed all fields from another structure.
Reuse common field groups across tables.
Structure ZADDRESS:
STREET TYPE CHAR40
CITY TYPE ORT01
COUNTRY TYPE LAND1
POSTAL TYPE PSTLZ
Table ZCUSTOMERS:
MANDT (key)
CUSTOMER_ID (key)
NAME
.INCLUDE ZADDRESS ← all address fields added
Result - ZCUSTOMERS has:
MANDT, CUSTOMER_ID, NAME, STREET, CITY, COUNTRY, POSTAL
1. Position cursor in field list
2. Menu: Edit → Include → Insert
3. Enter structure name
4. Optionally add suffix/prefix to field names
Alternative: Type ".INCLUDE" in field name column
.INCLUDE ZADDRESS AS ADDR GROUP ADDR
Adds fields with prefix:
ADDR-STREET
ADDR-CITY
etc.
Useful when including same structure multiple times.
Append structure = add custom fields to SAP table.
No modification of original table.
Fields added at end of table.
Adding fields to SAP standard tables:
VBAK (sales order header) → add ZZ_CUSTOM_FIELD
MARA (material master) → add ZZ_DIVISION
Cannot modify SAP tables directly.
Append structure is the official enhancement method.
1. SE11 → Table (e.g., VBAK) → Display
2. Menu: Goto → Append Structures
3. Click Create
4. Enter name (e.g., ZZVBAK_APPEND)
5. Add fields (must start with ZZ or YY)
6. Save and activate
Field naming: ZZ* or YY* (customer namespace in field names)
Append Structure: ZZVBAK_APPEND
For Table: VBAK
Fields:
ZZPROJECT_ID TYPE ZPROJECT_ID
ZZCUSTOM_FLAG TYPE FLAG
→ VBAK now has these additional fields
→ Survives SAP upgrades
* Append fields accessible like regular fields
DATA ls_vbak TYPE vbak.
SELECT SINGLE * FROM vbak INTO @ls_vbak
WHERE vbeln = '0000000001'.
WRITE: / 'Custom field:', ls_vbak-zzproject_id.
Enhancement category = rules for extending the table.
Determines if/how append structures can be added.
Must be set for all tables (activation warning if missing).
| Category | Append allowed | Use for |
|---|---|---|
| Cannot be enhanced | No | System tables |
| Can be enhanced (char) | Character-like only | Tables with structure issues |
| Can be enhanced (char/num) | Char and numeric | Most custom tables |
| Can be enhanced (deep) | All types incl. strings | Flexible tables |
Menu: Extras → Enhancement Category
Select appropriate option.
"Can be enhanced (character-type or numeric)" is typical.
* Structure (one row)
DATA ls_customer TYPE zcustomers.
* Internal table (multiple rows)
DATA lt_customers TYPE TABLE OF zcustomers.
* With explicit key
DATA lt_customers TYPE SORTED TABLE OF zcustomers
WITH UNIQUE KEY mandt customer_id.
* Single row
SELECT SINGLE * FROM zcustomers
INTO @DATA(ls_cust)
WHERE customer_id = '0000001234'.
IF sy-subrc = 0.
WRITE: / 'Found:', ls_cust-name.
ENDIF.
* Multiple rows
SELECT * FROM zcustomers
INTO TABLE @DATA(lt_cust)
WHERE country = 'DE'.
WRITE: / 'Found:', lines( lt_cust ), 'customers'.
* Specific fields
SELECT customer_id, name, city
FROM zcustomers
INTO TABLE @DATA(lt_partial)
WHERE country = 'US'.
Found: Acme Corp
Found: 42 customers
DATA ls_new TYPE zcustomers.
ls_new-mandt = sy-mandt.
ls_new-customer_id = '0000009999'.
ls_new-name = 'New Customer'.
ls_new-city = 'Berlin'.
ls_new-country = 'DE'.
GET TIME STAMP FIELD ls_new-created_at.
ls_new-created_by = sy-uname.
INSERT zcustomers FROM ls_new.
IF sy-subrc = 0.
WRITE: / 'Inserted successfully'.
ELSE.
WRITE: / 'Insert failed (duplicate key?)'.
ENDIF.
Inserted successfully
DATA lt_new TYPE TABLE OF zcustomers.
APPEND VALUE #(
mandt = sy-mandt
customer_id = '0000009997'
name = 'Customer A'
) TO lt_new.
APPEND VALUE #(
mandt = sy-mandt
customer_id = '0000009998'
name = 'Customer B'
) TO lt_new.
INSERT zcustomers FROM TABLE lt_new.
WRITE: / 'Inserted:', sy-dbcnt, 'rows'.
Inserted: 2 rows
* Update specific fields
UPDATE zcustomers
SET city = 'Munich'
country = 'DE'
WHERE customer_id = '0000001234'.
IF sy-subrc = 0.
WRITE: / 'Updated:', sy-dbcnt, 'rows'.
ENDIF.
* Update from structure (full row)
ls_cust-city = 'Hamburg'.
UPDATE zcustomers FROM ls_cust.
Updated: 1 rows
* If key exists: UPDATE
* If key doesn't exist: INSERT
DATA ls_cust TYPE zcustomers.
ls_cust-mandt = sy-mandt.
ls_cust-customer_id = '0000001234'.
ls_cust-name = 'Modified Name'.
ls_cust-city = 'Frankfurt'.
MODIFY zcustomers FROM ls_cust.
WRITE: / 'Modified:', sy-dbcnt, 'rows'.
Modified: 1 rows
* Delete by condition
DELETE FROM zcustomers
WHERE customer_id = '0000009999'.
IF sy-subrc = 0.
WRITE: / 'Deleted:', sy-dbcnt, 'rows'.
ENDIF.
* Delete from structure (by key)
DELETE zcustomers FROM ls_cust.
* Delete all (dangerous!)
DELETE FROM zcustomers.
Deleted: 1 rows
Changes are not permanent until COMMIT.
INSERT, UPDATE, DELETE → held in database buffer
COMMIT WORK → writes to database permanently
ROLLBACK WORK → discards all changes
TRY.
INSERT zcustomers FROM ls_cust1.
INSERT zorders FROM ls_order.
INSERT zorder_items FROM TABLE lt_items.
COMMIT WORK AND WAIT.
WRITE: / 'All changes committed'.
CATCH cx_root INTO DATA(lx_error).
ROLLBACK WORK.
WRITE: / 'Error - changes rolled back:', lx_error->get_text( ).
ENDTRY.
* Asynchronous (faster, less safe)
COMMIT WORK.
* Synchronous (waits for DB confirmation)
COMMIT WORK AND WAIT.
Table maintenance generator = auto-generated screens.
For maintaining table data without coding.
Used for config/customizing tables.
1. SE11 → Table → Display
2. Menu: Utilities → Table Maintenance Generator
3. Fill in:
- Authorization group: &NC& (or custom)
- Function group: ZZCUST_MNT
- Maintenance type: One step or Two step
4. Click Create
One step:
Single screen showing all records.
Good for simple tables.
Two step:
Overview screen → Detail screen.
Good for tables with many fields.
Transaction SM30:
Table/View: ZCUSTOMERS
Click: Maintain
Opens generated maintenance screen.
Can add, change, delete records.
Authorization group controls access.
Object: S_TABU_DIS
Field: DICBERCLS (authorization group)
Create custom group in SM30 → table TBRG.
Save → stores in DDIC repository
Activate → creates/updates database table
First activation:
→ CREATE TABLE in database
Field added:
→ ALTER TABLE ADD COLUMN
Field removed:
→ Requires table conversion
Transaction SE14 for:
- Activate and adjust database
- Delete table data
- Convert table structure
- Check table consistency
Use when normal activation fails.
Safe changes (table has data):
- Add new field
- Increase field length
- Add index
Requires conversion:
- Delete field
- Decrease field length
- Change data type
- Change key fields
SE14 → "Activate and adjust database"
| Transaction | Purpose | Edit? |
|---|---|---|
SE16 |
Data Browser | No (display only) |
SE16N |
General Table Display | No (display only) |
SM30 |
Table Maintenance | Yes (if generated) |
SE11 |
Dictionary (Contents button) | No |
- Selection screen with all fields
- Output formatting options
- Download to Excel
- Record count
- Field documentation (F1)
| Table | Description |
|---|---|
MARA |
Material master (general) |
MARC |
Material master (plant) |
KNA1 |
Customer master (general) |
LFA1 |
Vendor master (general) |
T001 |
Company codes |
T001W |
Plants |
| Table | Description |
|---|---|
VBAK |
Sales order header |
VBAP |
Sales order items |
EKKO |
Purchase order header |
EKPO |
Purchase order items |
BKPF |
Accounting document header |
BSEG |
Accounting document items |
| Table | Description |
|---|---|
DD02L |
DDIC table definitions |
DD03L |
DDIC field definitions |
TADIR |
Repository objects |
USR02 |
User logon data |
1. Always include MANDT as first key field
(unless cross-client table is required)
2. Use data elements, not direct types
→ Better documentation and reuse
3. Set appropriate technical settings
→ Data class matches usage
→ Size category realistic
4. Create indexes for frequent queries
→ But don't over-index
5. Use foreign keys for relationships
→ Documents data model
→ Enables F4 help
6. Set enhancement category
→ Allows future extensions
7. Choose correct delivery class
→ A for application data
→ C for config data
Table name:
Z + module + description
ZMMSTOCK, ZSDORDERS, ZFICUSTOM
Fields:
Use standard SAP names when applicable
MATNR for material, KUNNR for customer
Indexes:
Z01, Z02, Z03...
Append structures:
ZZ + table + _APPEND
ZZVBAK_APPEND
| Property | Where to set |
|---|---|
| Delivery class | Attributes tab |
| Data class | Technical Settings |
| Size category | Technical Settings |
| Buffering | Technical Settings |
| Enhancement category | Extras menu |
| Indexes | Goto → Indexes |
| Foreign keys | Field → Foreign keys |
| Append structures | Goto → Append Structures |
| Statement | Purpose | sy-subrc |
|---|---|---|
SELECT |
Read data | 0=found, 4=not found |
INSERT |
Add rows | 0=OK, 4=duplicate |
UPDATE |
Modify rows | 0=OK, 4=not found |
MODIFY |
Insert or update | 0=OK |
DELETE |
Remove rows | 0=OK, 4=not found |
| Transaction | Purpose |
|---|---|
SE11 |
DDIC - create/maintain tables |
SE14 |
Database utility |
SE16 |
Data Browser |
SE16N |
General Table Display |
SM30 |
Table Maintenance |
ST05 |
SQL Trace (performance) |
Structure = composite data type containing multiple fields.
Groups related fields into a single unit.
Like a record, row, or object without methods.
No database storage (unlike tables).
Used for: work areas, parameters, temporary data.
| Aspect | Structure | Database Table |
|---|---|---|
| Storage | Memory only | Database (persistent) |
| Rows | Single row definition | Multiple rows |
| Primary key | None | Required |
| Technical settings | None | Required |
| Use | Work areas, parameters | Data persistence |
1. DDIC Structure (SE11)
- Reusable across programs
- Visible system-wide
- Has documentation, search helps
2. Local Structure (ABAP code)
- Defined in program
- Only visible in that program
- Quick and flexible
1. Transaction SE11
2. Select "Data type"
3. Enter name (e.g., ZADDRESS_STR)
4. Click Create
5. Choose "Structure"
6. Add components (fields)
7. Save → Assign package/transport
8. Activate
Z*_STR or Z*_S
Examples:
ZADDRESS_STR address structure
ZCUSTOMER_STR customer data structure
ZORDER_HEADER_S order header structure
┌────────────────────────────────────────────────────┐
│ Components │
├─────────────┬──────────────┬───────────────────────┤
│ Component │ Type │ Description │
├─────────────┼──────────────┼───────────────────────┤
│ CUSTOMER_ID │ KUNNR │ Customer Number │
│ NAME │ NAME1 │ Name │
│ STREET │ STRAS │ Street │
│ CITY │ ORT01 │ City │
│ COUNTRY │ LAND1 │ Country │
│ POSTAL_CODE │ PSTLZ │ Postal Code │
└─────────────┴──────────────┴───────────────────────┘
Component = field name
Type = data element (or direct type)
* Declare variable with DDIC structure type
DATA ls_address TYPE zaddress_str.
ls_address-customer_id = '0000001234'.
ls_address-name = 'Acme Corporation'.
ls_address-street = '123 Main Street'.
ls_address-city = 'Berlin'.
ls_address-country = 'DE'.
ls_address-postal_code = '10115'.
WRITE: / ls_address-name, ls_address-city.
Acme Corporation Berlin
* Any database table can be used as structure type
DATA ls_customer TYPE kna1. "customer master
DATA ls_material TYPE mara. "material master
DATA ls_order TYPE vbak. "sales order header
* Structure has all fields from the table
SELECT SINGLE * FROM kna1 INTO @ls_customer
WHERE kunnr = '0000001234'.
WRITE: / ls_customer-name1.
TYPES: BEGIN OF <name>,
<field1> TYPE <type1>,
<field2> TYPE <type2>,
...
END OF <name>.
Defines a type. No memory allocated.
Use with DATA to create variables.
* Define structure type
TYPES: BEGIN OF ty_person,
id TYPE i,
name TYPE string,
birthdate TYPE d,
salary TYPE p DECIMALS 2,
END OF ty_person.
* Create variable of that type
DATA ls_person TYPE ty_person.
ls_person-id = 1.
ls_person-name = 'Alice'.
ls_person-birthdate = '19900315'.
ls_person-salary = '75000.00'.
WRITE: / ls_person-name, ls_person-salary.
Alice 75,000.00
* Mix data elements and built-in types
TYPES: BEGIN OF ty_order,
order_id TYPE vbeln, "data element
customer TYPE kunnr, "data element
order_date TYPE d, "built-in
status TYPE c LENGTH 2, "built-in
amount TYPE netwr, "data element
END OF ty_order.
DATA ls_order TYPE ty_order.
ty_* = type definition (structure, table type)
Examples:
ty_customer customer structure type
ty_order order structure type
ty_address address structure type
DATA: BEGIN OF <name>,
<field1> TYPE <type1>,
<field2> TYPE <type2>,
...
END OF <name>.
Creates variable directly. No separate type.
Type cannot be reused.
* Define and create in one step
DATA: BEGIN OF ls_config,
setting_name TYPE c LENGTH 30,
setting_value TYPE c LENGTH 100,
is_active TYPE abap_bool,
END OF ls_config.
ls_config-setting_name = 'MAX_RETRIES'.
ls_config-setting_value = '3'.
ls_config-is_active = abap_true.
WRITE: / ls_config-setting_name, ls_config-setting_value.
MAX_RETRIES 3
| Aspect | TYPES BEGIN OF | DATA BEGIN OF |
|---|---|---|
| Creates | Type definition | Variable |
| Memory | None | Allocated |
| Reusable | Yes (multiple variables) | No (single variable) |
| For tables | Yes (TABLE OF ty_x) | Limited |
Use TYPES when:
- Need multiple variables of same structure
- Need internal table of structure
- Structure is complex
Use DATA BEGIN OF when:
- One-off structure
- Quick temporary variable
- Simple configuration
DATA: BEGIN OF ls_person,
name TYPE string,
age TYPE i,
END OF ls_person.
* Access with hyphen
ls_person-name = 'Bob'.
ls_person-age = 30.
WRITE: / ls_person-name, 'is', ls_person-age, 'years old'.
Bob is 30 years old
TYPES: BEGIN OF ty_address,
street TYPE string,
city TYPE string,
END OF ty_address.
TYPES: BEGIN OF ty_person,
name TYPE string,
address TYPE ty_address,
END OF ty_person.
DATA ls_person TYPE ty_person.
* Access nested fields
ls_person-name = 'Alice'.
ls_person-address-street = '123 Main St'.
ls_person-address-city = 'Berlin'.
WRITE: / ls_person-name, 'lives in', ls_person-address-city.
Alice lives in Berlin
DATA: BEGIN OF ls_data,
field1 TYPE string VALUE 'Value1',
field2 TYPE string VALUE 'Value2',
field3 TYPE string VALUE 'Value3',
END OF ls_data.
FIELD-SYMBOLS TYPE any.
DATA lv_fieldname TYPE string VALUE 'FIELD2'.
ASSIGN COMPONENT lv_fieldname OF STRUCTURE ls_data TO .
IF sy-subrc = 0.
WRITE: / 'Dynamic access:', .
ENDIF.
Dynamic access: Value2
FIELD-SYMBOLS TYPE any.
* Access by position (1-based)
ASSIGN COMPONENT 2 OF STRUCTURE ls_data TO .
IF sy-subrc = 0.
WRITE: / 'Component 2:', .
ENDIF.
Component 2: Value2
DATA ls_source TYPE zaddress_str.
DATA ls_target TYPE zaddress_str.
ls_source-city = 'Berlin'.
ls_source-country = 'DE'.
* Copy entire structure
ls_target = ls_source.
WRITE: / ls_target-city, ls_target-country.
Berlin DE
TYPES: BEGIN OF ty_person,
name TYPE string,
age TYPE i,
city TYPE string,
END OF ty_person.
* Initialize with values
DATA(ls_person) = VALUE ty_person(
name = 'Alice'
age = 30
city = 'Munich'
).
WRITE: / ls_person-name, ls_person-age.
Alice 30
DATA ls_person TYPE ty_person.
ls_person-name = 'Bob'.
ls_person-age = 25.
ls_person-city = 'Berlin'.
* Copy and modify
DATA(ls_updated) = VALUE ty_person(
BASE ls_person
age = 26 "only change age
).
WRITE: / ls_updated-name, ls_updated-age, ls_updated-city.
Bob 26 Berlin
DATA ls_person TYPE ty_person.
ls_person-name = 'Alice'.
ls_person-age = 30.
CLEAR ls_person.
WRITE: / 'Name:', ls_person-name.
WRITE: / 'Age:', ls_person-age.
Name:
Age: 0
CORRESPONDING copies fields with matching names.
Different structures, same field names → copied.
Non-matching fields → initial or unchanged.
TYPES: BEGIN OF ty_source,
id TYPE i,
name TYPE string,
extra TYPE string,
END OF ty_source.
TYPES: BEGIN OF ty_target,
id TYPE i,
name TYPE string,
other TYPE string,
END OF ty_target.
DATA ls_source TYPE ty_source.
ls_source-id = 1.
ls_source-name = 'Test'.
ls_source-extra = 'Source only'.
* Copy matching fields
DATA(ls_target) = CORRESPONDING ty_target( ls_source ).
WRITE: / 'ID:', ls_target-id.
WRITE: / 'Name:', ls_target-name.
WRITE: / 'Other:', ls_target-other. "initial - no match
ID: 1
Name: Test
Other:
DATA ls_target TYPE ty_target.
ls_target-id = 999.
ls_target-other = 'Keep this'.
* Copy matching, keep non-matching
ls_target = CORRESPONDING #( BASE ( ls_target ) ls_source ).
WRITE: / 'ID:', ls_target-id. "from source
WRITE: / 'Name:', ls_target-name. "from source
WRITE: / 'Other:', ls_target-other. "kept from target
ID: 1
Name: Test
Other: Keep this
TYPES: BEGIN OF ty_old,
customer_number TYPE kunnr,
customer_name TYPE string,
END OF ty_old.
TYPES: BEGIN OF ty_new,
id TYPE kunnr,
name TYPE string,
END OF ty_new.
DATA ls_old TYPE ty_old.
ls_old-customer_number = '0000001234'.
ls_old-customer_name = 'Acme Corp'.
* Map different field names
DATA(ls_new) = CORRESPONDING ty_new(
ls_old MAPPING id = customer_number
name = customer_name
).
WRITE: / 'ID:', ls_new-id.
WRITE: / 'Name:', ls_new-name.
ID: 0000001234
Name: Acme Corp
DATA(ls_partial) = CORRESPONDING ty_target(
ls_source EXCEPT extra
).
MOVE-CORRESPONDING <source> TO <target>.
Classic statement. Same as CORRESPONDING but:
- Modifies existing variable
- No inline declaration
DATA ls_source TYPE ty_source.
DATA ls_target TYPE ty_target.
ls_source-id = 1.
ls_source-name = 'Test'.
ls_target-other = 'Preserved'.
MOVE-CORRESPONDING ls_source TO ls_target.
WRITE: / 'ID:', ls_target-id.
WRITE: / 'Name:', ls_target-name.
WRITE: / 'Other:', ls_target-other. "preserved
ID: 1
Name: Test
Other: Preserved
| Aspect | CORRESPONDING | MOVE-CORRESPONDING |
|---|---|---|
| Style | Modern (7.40+) | Classic |
| Inline declaration | Yes | No |
| Existing data | BASE option | Always preserved |
| MAPPING | Yes | No |
| EXCEPT | Yes | No |
TYPES: BEGIN OF ty_address,
street TYPE string,
city TYPE string,
country TYPE land1,
END OF ty_address.
TYPES: BEGIN OF ty_contact,
phone TYPE string,
email TYPE string,
END OF ty_contact.
TYPES: BEGIN OF ty_customer,
id TYPE kunnr,
name TYPE string,
address TYPE ty_address, "nested structure
contact TYPE ty_contact, "nested structure
END OF ty_customer.
DATA ls_customer TYPE ty_customer.
ls_customer-id = '0000001234'.
ls_customer-name = 'Acme Corp'.
ls_customer-address-street = '123 Main St'.
ls_customer-address-city = 'Berlin'.
ls_customer-address-country = 'DE'.
ls_customer-contact-phone = '+49 30 12345'.
ls_customer-contact-email = 'info@acme.de'.
WRITE: / ls_customer-name.
WRITE: / ls_customer-address-city, ls_customer-address-country.
WRITE: / ls_customer-contact-email.
Acme Corp
Berlin DE
info@acme.de
DATA(ls_cust) = VALUE ty_customer(
id = '0000005678'
name = 'Beta Inc'
address = VALUE #(
street = '456 Oak Ave'
city = 'Munich'
country = 'DE'
)
contact = VALUE #(
phone = '+49 89 99999'
email = 'hello@beta.de'
)
).
WRITE: / ls_cust-name, ls_cust-address-city.
Beta Inc Munich
Structure ZCUSTOMER_STR:
CUSTOMER_ID TYPE KUNNR
NAME TYPE NAME1
.INCLUDE ZADDRESS_STR ← embeds all fields
Result:
CUSTOMER_ID
NAME
STREET (from ZADDRESS_STR)
CITY (from ZADDRESS_STR)
COUNTRY (from ZADDRESS_STR)
TYPES: BEGIN OF ty_address,
street TYPE string,
city TYPE string,
country TYPE land1,
END OF ty_address.
TYPES: BEGIN OF ty_customer,
id TYPE kunnr,
name TYPE string,
END OF ty_customer.
* Include adds fields inline
TYPES: BEGIN OF ty_full_customer.
INCLUDE TYPE ty_customer.
INCLUDE TYPE ty_address.
TYPES: END OF ty_full_customer.
DATA ls_full TYPE ty_full_customer.
* All fields at top level
ls_full-id = '0000001234'.
ls_full-name = 'Test'.
ls_full-street = '123 Main St'. "from ty_address
ls_full-city = 'Berlin'. "from ty_address
TYPES: BEGIN OF ty_transfer.
INCLUDE TYPE ty_address AS ship_addr RENAMING WITH SUFFIX _ship.
INCLUDE TYPE ty_address AS bill_addr RENAMING WITH SUFFIX _bill.
TYPES: END OF ty_transfer.
DATA ls_transfer TYPE ty_transfer.
* Fields renamed with suffix
ls_transfer-street_ship = 'Shipping Street'.
ls_transfer-city_ship = 'Ship City'.
ls_transfer-street_bill = 'Billing Street'.
ls_transfer-city_bill = 'Bill City'.
| Aspect | Include | Nested |
|---|---|---|
| Field access | ls-field |
ls-sub-field |
| Hierarchy | Flat | Hierarchical |
| Name conflicts | Possible (use RENAMING) | No (separate namespace) |
| Database mapping | Direct | Deep structure |
Deep structure = contains variable-length or reference types.
Deep types:
- STRING
- XSTRING
- Internal tables
- References (REF TO)
- Nested structures with deep types
Flat structure = only fixed-length elementary types.
TYPES: BEGIN OF ty_item,
item_no TYPE i,
description TYPE string,
END OF ty_item.
TYPES ty_items TYPE TABLE OF ty_item WITH EMPTY KEY.
TYPES: BEGIN OF ty_order,
order_id TYPE vbeln,
notes TYPE string, "deep (string)
items TYPE ty_items, "deep (table)
END OF ty_order.
DATA ls_order TYPE ty_order.
ls_order-order_id = '0000000001'.
ls_order-notes = 'Rush delivery requested'.
APPEND VALUE ty_item( item_no = 10 description = 'Widget A' ) TO ls_order-items.
APPEND VALUE ty_item( item_no = 20 description = 'Widget B' ) TO ls_order-items.
WRITE: / 'Order:', ls_order-order_id.
WRITE: / 'Items:', lines( ls_order-items ).
Order: 0000000001
Items: 2
DATA(ls_order2) = VALUE ty_order(
order_id = '0000000002'
notes = 'Standard delivery'
items = VALUE #(
( item_no = 10 description = 'Part X' )
( item_no = 20 description = 'Part Y' )
( item_no = 30 description = 'Part Z' )
)
).
LOOP AT ls_order2-items INTO DATA(ls_item).
WRITE: / ls_item-item_no, ls_item-description.
ENDLOOP.
10 Part X
20 Part Y
30 Part Z
Flat structures:
- Can be used in database operations directly
- Fixed memory size
- Byte-level operations allowed
Deep structures:
- Cannot be used directly in some DB operations
- Variable memory size
- Must handle components separately for some operations
DATA ls_a TYPE ty_person.
DATA ls_b TYPE ty_person.
ls_a-name = 'Alice'.
ls_a-age = 30.
ls_b-name = 'Alice'.
ls_b-age = 30.
IF ls_a = ls_b.
WRITE: / 'Structures are equal'.
ENDIF.
ls_b-age = 31.
IF ls_a <> ls_b.
WRITE: / 'Structures are different'.
ENDIF.
Structures are equal
Structures are different
DATA ls_person TYPE ty_person.
IF ls_person IS INITIAL.
WRITE: / 'Structure is initial (all fields initial)'.
ENDIF.
ls_person-name = 'Test'.
IF ls_person IS NOT INITIAL.
WRITE: / 'Structure has data'.
ENDIF.
Structure is initial (all fields initial)
Structure has data
IF ls_a-name = ls_b-name AND ls_a-age = ls_b-age.
WRITE: / 'Name and age match'.
ENDIF.
TYPES: BEGIN OF ty_employee,
id TYPE i,
name TYPE string,
dept TYPE string,
salary TYPE p DECIMALS 2,
END OF ty_employee.
* Table of structures
DATA lt_employees TYPE TABLE OF ty_employee.
APPEND VALUE #( id = 1 name = 'Alice' dept = 'IT' salary = 75000 ) TO lt_employees.
APPEND VALUE #( id = 2 name = 'Bob' dept = 'HR' salary = 65000 ) TO lt_employees.
APPEND VALUE #( id = 3 name = 'Carol' dept = 'IT' salary = 80000 ) TO lt_employees.
LOOP AT lt_employees INTO DATA(ls_emp).
WRITE: / ls_emp-id, ls_emp-name, ls_emp-dept.
ENDLOOP.
1 Alice IT
2 Bob HR
3 Carol IT
DATA ls_employee TYPE ty_employee. "work area
* Read into work area
READ TABLE lt_employees INTO ls_employee INDEX 2.
WRITE: / 'Row 2:', ls_employee-name.
* Loop with work area
LOOP AT lt_employees INTO ls_employee WHERE dept = 'IT'.
WRITE: / 'IT employee:', ls_employee-name.
ENDLOOP.
Row 2: Bob
IT employee: Alice
IT employee: Carol
* Work area (copy)
LOOP AT lt_employees INTO ls_employee.
ls_employee-salary = ls_employee-salary * '1.10'. "doesn't update table
ENDLOOP.
* Field symbol (direct reference)
LOOP AT lt_employees ASSIGNING FIELD-SYMBOL().
-salary = -salary * '1.10'. "updates table directly
ENDLOOP.
| Structure | Description |
|---|---|
SYST |
System fields (sy-*) |
BAPIRET2 |
BAPI return message |
BDCDATA |
Batch input data |
BDCMSGCOLL |
Batch input messages |
RSPARAMS |
Selection parameters |
DATA ls_return TYPE bapiret2.
DATA lt_return TYPE TABLE OF bapiret2.
ls_return-type = 'E'. "E=Error, S=Success, W=Warning, I=Info
ls_return-id = 'ZMYMSGS'.
ls_return-number = '001'.
ls_return-message = 'Customer not found'.
APPEND ls_return TO lt_return.
* Standard structure for BAPI error handling
LOOP AT lt_return INTO ls_return WHERE type = 'E'.
WRITE: / 'Error:', ls_return-message.
ENDLOOP.
Error: Customer not found
* Range table for selection criteria
DATA lr_matnr TYPE RANGE OF matnr.
lr_matnr = VALUE #(
( sign = 'I' option = 'EQ' low = '000000000000000001' )
( sign = 'I' option = 'BT' low = '000000000000000010' high = '000000000000000020' )
).
* Range structure has: SIGN, OPTION, LOW, HIGH
SELECT * FROM mara INTO TABLE @DATA(lt_mara)
WHERE matnr IN @lr_matnr.
Two structures are compatible if:
- Same sequence of components
- Each component has compatible type
Compatible = can assign directly (ls_a = ls_b)
TYPES: BEGIN OF ty_a,
field1 TYPE i,
field2 TYPE string,
END OF ty_a.
TYPES: BEGIN OF ty_b,
field1 TYPE i,
field2 TYPE string,
END OF ty_b.
DATA ls_a TYPE ty_a.
DATA ls_b TYPE ty_b.
ls_a-field1 = 1.
ls_a-field2 = 'Test'.
ls_b = ls_a. "OK - compatible structures
WRITE: / ls_b-field1, ls_b-field2.
1 Test
TYPES: BEGIN OF ty_c,
field1 TYPE i,
field3 TYPE string, "different name - still compatible
END OF ty_c.
TYPES: BEGIN OF ty_d,
field1 TYPE i,
field2 TYPE c LENGTH 10, "different type
END OF ty_d.
DATA ls_c TYPE ty_c.
DATA ls_d TYPE ty_d.
ls_a-field1 = 1.
ls_a-field2 = 'Test'.
ls_c = ls_a. "OK - same types, different names
* ls_d = ls_a. "Error - string vs c LENGTH 10
* Use CORRESPONDING for incompatible
ls_d = CORRESPONDING #( ls_a ).
Menu: Extras → Enhancement Category
Options:
- Cannot be enhanced
- Can be enhanced (character-type)
- Can be enhanced (character-type or numeric)
- Can be enhanced (deep)
Determines if append structures can be added.
If structure contains other structures:
Each nested structure needs enhancement category.
Activation warning if not set.
TYPES: BEGIN OF ty_api_response,
success TYPE abap_bool,
message TYPE string,
code TYPE i,
data TYPE string,
END OF ty_api_response.
DATA(ls_response) = VALUE ty_api_response(
success = abap_true
message = 'Operation completed'
code = 200
data = '{"result": "ok"}'
).
IF ls_response-success = abap_true.
WRITE: / 'Success:', ls_response-message.
ELSE.
WRITE: / 'Error:', ls_response-code, ls_response-message.
ENDIF.
Success: Operation completed
TYPES: BEGIN OF ty_config,
max_retries TYPE i,
timeout_sec TYPE i,
debug_mode TYPE abap_bool,
log_level TYPE c LENGTH 1,
endpoint_url TYPE string,
END OF ty_config.
DATA(gs_config) = VALUE ty_config(
max_retries = 3
timeout_sec = 30
debug_mode = abap_false
log_level = 'E' "E=Error, W=Warning, I=Info, D=Debug
endpoint_url = 'https://api.example.com/v1'
).
* Structure for moving data between layers
TYPES: BEGIN OF ty_customer_dto,
customer_id TYPE kunnr,
customer_name TYPE name1,
city TYPE ort01,
country TYPE land1,
is_active TYPE abap_bool,
END OF ty_customer_dto.
* Load from database
DATA ls_kna1 TYPE kna1.
SELECT SINGLE * FROM kna1 INTO @ls_kna1
WHERE kunnr = '0000001234'.
* Map to DTO
DATA(ls_dto) = VALUE ty_customer_dto(
customer_id = ls_kna1-kunnr
customer_name = ls_kna1-name1
city = ls_kna1-ort01
country = ls_kna1-land1
is_active = COND #( WHEN ls_kna1-sperr IS INITIAL THEN abap_true ELSE abap_false )
).
1. Use DDIC structures for:
- Shared across multiple programs
- Public APIs (function modules, BAPIs)
- Database table work areas
2. Use local TYPES for:
- Program-specific structures
- Internal processing
- Quick prototyping
3. Use data elements in DDIC structures
- Better documentation
- Search helps, labels
4. Keep structures focused
- Single responsibility
- Don't mix unrelated data
5. Use meaningful names
- ty_* for local types
- Z*_STR for DDIC structures
- Describe content, not usage
6. Consider flat vs deep
- Flat for database operations
- Deep when needed (strings, tables)
7. Set enhancement category
- Avoid activation warnings
| Method | Syntax | Scope |
|---|---|---|
| DDIC | SE11 → Data type → Structure | Global |
| TYPES | TYPES: BEGIN OF ty_x... |
Program |
| DATA | DATA: BEGIN OF ls_x... |
Program (one variable) |
| Operation | Syntax |
|---|---|
| Declare | DATA ls_x TYPE ty_x. |
| Access field | ls_x-field |
| Initialize | VALUE ty_x( field = value ) |
| Clear | CLEAR ls_x. |
| Copy | ls_target = ls_source. |
| Map fields | CORRESPONDING #( ls_source ) |
| Compare | IF ls_a = ls_b. |
| Check initial | IF ls_x IS INITIAL. |
| Statement | Purpose |
|---|---|
CORRESPONDING |
Copy matching fields (modern) |
MOVE-CORRESPONDING |
Copy matching fields (classic) |
INCLUDE TYPE |
Embed structure fields |
ASSIGN COMPONENT |
Dynamic field access |