ABAP Foundations – Syntax, Data Types, Internal Tables & Work Areas: A Hands-On Developer Tutorial
Day 1 of the S/4HANA ABAP Development & Fiori Track
By @FreeLearning365 and Tech Partner @techbook24
Introduction: Why ABAP Still Reigns in S/4HANA
If you think ABAP is a relic of the R/3 past, you’re in for a surprise. S/4HANA might have pushed the database layer to HANA and exposed OData services for Fiori, but every custom business logic, every enhancement, every complex report still runs on ABAP. Not just any ABAP – the modern, clean, and incredibly powerful ABAP 7.5x that comes with inline declarations, new internal table expressions, and a robust development environment in Eclipse. Today, we start from the ground up. Whether you’re a functional consultant aiming to read code or a budding developer ready to build, this tutorial will give you the bedrock you need.
We’re not going to drown in theory. I’ll take you on a journey alongside Arjun, a new ABAP developer at TechBook24, the online retailer we’ve been following through their S/4HANA implementation. Arjun just got his first assignment: write a program that lists all open purchase orders with vendor names and total order values. He’s sitting in front of an S/4HANA 2023 sandbox, staring at the SAP GUI and Eclipse. By the end of this article, you’ll have written that program together, but you’ll also understand every piece of the ABAP puzzle that makes it work. From data types to internal tables, from field symbols to modern inline declarations, we’ll cover it all with live code, screen descriptions, and the kind of practical insight you’d otherwise get only from years of debugging other people’s code.
Get your coffee, open your system, and let’s code. This is going to be long, detailed, and deeply rewarding.
1. The Battle of the Workbenches: SE80 vs Eclipse ADT
1.1 The Classic Giant – ABAP Workbench (SE80)
For decades, transaction SE80 has been the home of ABAP developers. It’s a massive integrated environment where you can browse packages, create programs, function modules, classes, and even test your code. When Arjun logs into the SAP GUI and types /nSE80, he sees the familiar object navigator. The left panel is a tree: you can select “Program”, enter a name like ZARJUN_OPEN_PO, and hit create. A wizard opens, asking for attributes: type (Executable program, Module pool, etc.), status, and package. For our report, it’s type ‘1’ (Executable program). The right panel then turns into a full-fledged editor with syntax highlighting. You can write code, activate, and run directly with F8.
What’s great about SE80: It’s deeply integrated with the SAP system. You don’t need any installation. All tools like Debugger, SQL Trace, and Performance Analyzer are just a menu away. For quick fixes or legacy system access, SE80 remains indispensable. Arjun uses it often when he wants to check something in production (with appropriate read-only access, of course).
What’s not great: The editor feels dated. Multiple tabs are clumsy. No native Git integration. Refactoring tools are minimal. And it enforces an older ABAP style that doesn’t nudge you toward modern syntax. Many experienced developers still code in SE80 out of habit, but they miss out on the productivity boost that Eclipse ADT offers.
1.2 The Modern Powerhouse – ABAP Development Tools in Eclipse (ADT)
Arjun’s mentor, Priya (our functional consultant friend, but she also codes a bit), insisted he set up Eclipse with ADT. The installation is straightforward: download Eclipse IDE for Java Developers, then install the ABAP Development Tools from the update site. Once configured with the SAP system connection (via RFC), you log in and see the exact same objects, but in a modern IDE.
Eclipse ADT brings:
- Code completion that actually works: press Ctrl+Space and you get suggestions not just for keywords but also for your local variables, class methods, and even table fields if you’ve declared a structure.
- Inline syntax checking: as you type, errors are underlined. No need to syntax-check manually.
- Refactoring: rename variables, extract methods, move code – all without breaking references.
- ABAP Unit integration: run tests directly, see green/red bars.
- Modern ABAP templates: snippets for inline declarations, SELECT loops with inline data, and CDS views.
Arjun opens Eclipse, creates a new ABAP project, connects to the TechBook24 development system (client 200). He creates his program ZARJUN_OPEN_PO directly from the project explorer. The editor is clean, dark-themed, and ready. He’ll use ADT for most of this tutorial, but I’ll note when he flips back to SE80 for debugging.
Pros/Cons comparison:
| Feature | SE80 | Eclipse ADT |
|---|---|---|
| Setup | None – built into SAP GUI | Requires local installation, connection config |
| Code Assistance | Basic, limited | Smart, context-aware, templates |
| Version Control | Via SAPlink or external tools, awkward | Native Git staging, diff views |
| Modern Syntax Support | No wizards for 7.4+ inline declarations | Templates, quick fixes, style guides |
| Offline Capability | No | Possible with abapGit and local objects |
| Memory Consumption | Low (within SAP GUI) | Higher, but manageable |
Best Practice: Use ADT as your primary IDE. Keep SE80 for emergency situations, quick checks, and some legacy tools that haven’t been ported to ADT. The productivity gain is enormous.
2. ABAP Data Types – The Vocabulary of Your Program
2.1 Elementary Data Types – Fixed-Length and Variable-Length
Before you can store anything, you need to declare variables. ABAP has a rich set of built-in data types. The complete list is:
- c – character (default length 1, max 65535). Use for text.
- n – numeric text (digits 0-9, but treated as characters). Often used for account numbers.
- i – integer (4 bytes). Fast for counts, loop indices.
- f – floating point. Rarely used directly; use type
decfloat16ordecfloat34for high precision. - p – packed decimal (length and decimals defined). Use for currency, quantity amounts. Example:
p length 8 decimals 2. - d – date (YYYYMMDD, 8 characters).
- t – time (HHMMSS, 6 characters).
- string – dynamic-length character string. Use when length is unknown.
- xstring – dynamic-length byte string. For binary data.
Arjun writes his first line in ADT:
DATA: lv_name TYPE string,
lv_count TYPE i,
lv_amount TYPE p LENGTH 12 DECIMALS 2.
Here lv_ prefix is a naming convention (local variable). He could also use the classic DATA lv_name(50) TYPE c. but string is safer. Let's talk about TYPE vs LIKE.
2.2 TYPE vs LIKE – Referring to Existing Objects
When you declare a variable, you can either use a predefined data type (TYPE i) or refer to the data type of another variable or data element (LIKE). For example, if you have a structure from the data dictionary, say table EKKO (purchasing document header), you can declare:
DATA: wa_ekko TYPE ekko, "Structure matching DB table
lv_ebeln LIKE wa_ekko-ebeln. "lv_ebeln will have same type as field EBELN
LIKE is handy when you want to refer to a field’s type without looking up the data element. However, in modern ABAP, TYPE is preferred for clarity and because LIKE can lead to subtle errors if the referenced variable changes. The best practice: use TYPE with a data element or table component whenever possible. For instance:
DATA lv_ebeln TYPE ekko-ebeln.
Arjun will use this style extensively in his report.
2.3 Complex Data Types – Structures and Table Types
A structure is a compound data object that contains multiple fields. You can define a structure locally using TYPES and then DATA:
TYPES: BEGIN OF ty_order_summary,
ebeln TYPE ekko-ebeln,
lifnr TYPE ekko-lifnr,
netwr TYPE ekpo-netwr,
vname TYPE lfa1-name1,
END OF ty_order_summary.
DATA: gs_order TYPE ty_order_summary.
Here gs_order is a work area (global structure variable). The prefix gs_ stands for global structure. You can also refer to a DDIC structure directly: DATA wa_ekpo TYPE ekpo. But often you want a subset.
A table type defines an internal table based on a structure. Arjun needs an internal table to hold the list of open POs. He can define:
DATA: gt_orders TYPE TABLE OF ty_order_summary.
This creates a standard internal table with no key. Later we’ll explore sorted and hashed tables with explicit keys.
3. Internal Tables Deep Dive – Standard, Sorted, Hashed & Their Secrets
3.1 Standard Tables – The Default Workhorse
The most common internal table type is standard. It’s an index-based table; you access rows primarily by their index (1,2,3...). Standard tables allow duplicate keys (if a key is defined) and can be searched linearly with LOOP AT or READ TABLE. They’re best when you don’t need frequent key-based access and the number of rows is moderate.
Arjun writes the first draft of his data retrieval:
DATA: lt_orders TYPE TABLE OF ty_order_summary,
ls_order LIKE LINE OF lt_orders.
SELECT ebeln lifnr netwr
FROM ekpo
INTO TABLE @DATA(lt_ekpo_raw)
WHERE loekz = space. "loekz blank means not deleted
" Then loop to add vendor names...
He uses the inline declaration @DATA(lt_ekpo_raw) – we’ll dissect that later. For now, assume it’s a standard internal table of fields from EKPO. Standard tables are fine for this use case, but as Arjun will discover, reading a specific order by number would be faster with a sorted or hashed table.
3.2 Sorted Tables – Unique or Non-Unique Keys, Binary Search
A sorted table is always sorted by its key. You define it with TYPE SORTED TABLE OF ... WITH UNIQUE/NON-UNIQUE KEY .... Because it’s sorted, you can use READ TABLE ... WITH TABLE KEY ... BINARY SEARCH, which is an O(log n) operation instead of O(n) for a standard table. That’s a huge performance gain for large tables.
Arjun creates a sorted table of vendors:
TYPES: BEGIN OF ty_vendor,
lifnr TYPE lfa1-lifnr,
name1 TYPE lfa1-name1,
END OF ty_vendor.
DATA: gt_vendors TYPE SORTED TABLE OF ty_vendor WITH UNIQUE KEY lifnr.
He loads vendors with SELECT lifnr name1 FROM lfa1 INTO TABLE gt_vendors. Then to get a vendor name, he does:
READ TABLE gt_vendors INTO DATA(ls_vendor) WITH TABLE KEY lifnr = '12345'.
No loop, no manual search. Fast. The system uses the sorted order.
When to use sorted tables: Any time you need random access by key and the key can be sorted naturally (e.g., number, code). If the key is not unique, you can use NON-UNIQUE KEY; then you might need to read in a loop or use an index.
3.3 Hashed Tables – Key-Based Access Only, No Index
A hashed table has no index. You cannot access rows by integer position. You can only use READ TABLE with the exact key. The key must be unique. The internal implementation uses a hash algorithm, making access extremely fast even for millions of records. They’re perfect for lookup tables where you’re mapping a code to a description.
Arjun could have defined his vendor table as hashed:
DATA: gt_vendors_hashed TYPE HASHED TABLE OF ty_vendor WITH UNIQUE KEY lifnr.
But since vendor numbers are naturally sortable and he might want to display them in order, sorted is better. Use hashed when the key is something like a GUID or a concatenated key where sort order doesn’t matter.
Comparison of internal table types:
| Type | Access Method | Key Uniqueness | Best Use |
|---|---|---|---|
| Standard | Index (primary), linear search | Optional, non-unique allowed | Small lists, sequential processing |
| Sorted | Key (binary search), index | Unique or non-unique | Master data, ordered output, mixed access |
| Hashed | Key only, hash algorithm | Unique mandatory | Large lookup tables, fast single-read |
3.4 Essential Operations: LOOP AT, READ TABLE, MODIFY, APPEND, INSERT
Arjun’s program will heavily use these. Let’s illustrate with his raw EKPO data.
- APPEND adds a row to the end of a standard table. For sorted/hashed, you use INSERT because the position is determined by the key.
- INSERT ... INTO TABLE works for all types; for sorted/hashed, it places the row according to key.
- LOOP AT ... INTO ... iterates over rows. Modern ABAP allows inline declaration of the work area:
LOOP AT lt_orders INTO DATA(ls_order). WRITE: / ls_order-ebeln, ls_order-vname. ENDLOOP. - READ TABLE with key for single row. For standard table, you need to specify
BINARY SEARCHonly if the table is sorted; otherwise it does a slow sequential search. For sorted/hashed, the search is always optimized. - MODIFY TABLE changes an existing row. If the table has a key, you modify using the key; otherwise use index. For standard tables,
MODIFY lt_orders FROM ls_order INDEX 3.For key-based,MODIFY TABLE lt_orders FROM ls_order.if key matches. - DELETE removes rows by index or key.
Arjun will use LOOP AT heavily to process the order lines and sum the net value per order header.
4. Field Symbols vs Reference Variables – Pointers Without the Pain
4.1 Field Symbols – The Lightweight Pointers
A field symbol is a placeholder that points to any data object at runtime. You declare it with FIELD-SYMBOLS: <fs> TYPE any or more specifically, TYPE ekko. Then you assign it using ASSIGN ... TO <fs>. It’s extremely fast because no data is copied; you work directly on the memory area.
Arjun’s mentor shows him a common pattern when processing internal tables:
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<ls_order>).
<ls_order>-netwr = <ls_order>-netwr * '1.1'. "increase by 10%
ENDLOOP.No work area is needed. The field symbol directly points to the table row, so changes modify the table instantly. This avoids the overhead of copying the row to a work area and back, making it ideal for large tables.
When to use field symbols: When you need to modify data in a loop, or when you want to avoid data copying for performance. Be cautious: if you assign a field symbol and the original variable goes out of scope, the symbol becomes invalid (runtime error). Always ensure lifetime management.
4.2 Reference Variables – The More Formal Pointer
ABAP Objects introduced reference variables. You declare DATA: lo_ref TYPE REF TO data or TYPE REF TO zcl_something. To create data dynamically, use CREATE DATA. Then dereference with ->*.
Example:
DATA lo_data TYPE REF TO data.
CREATE DATA lo_data TYPE ekko.
ASSIGN lo_data->* TO FIELD-SYMBOL(<wa>).
This is more verbose than field symbols, but it’s safer in many OOP contexts. Arjun won’t need reference variables today, but they’re essential when working with dynamic programming and the Runtime Type Services (RTTS).
5. String Operations, Date/Time Handling, and Numeric Functions
5.1 Strings – The ABAP Way
ABAP provides many string functions that are easy to use. Arjun wants to display a message concatenating vendor name and order number. Instead of CONCATENATE, which still works, he uses the string template (new in 7.4):
DATA(lv_message) = |Vendor { ls_order-vname } has PO { ls_order-ebeln }|.
The pipe characters |...| allow you to embed expressions inside curly braces. This is cleaner than CONCATENATE and handles type conversions automatically.
Other useful string functions:
strlen( )– length of stringcondense( )– remove extra spacesto_upper( ),to_lower( )shift_left( ),shift_right( )substring( val = ... off = ... len = ... )contains( ),matches( )for pattern matching with regex.
5.2 Date and Time – Not Just Character Fields
ABAP stores dates as d (character 8) and times as t (character 6). But you can use arithmetic directly:
DATA: lv_today TYPE d VALUE sy-datum,
lv_tomorrow TYPE d.
lv_tomorrow = lv_today + 1. "adds one day
Similarly, you can calculate date differences with functions like days_between( ) from class cl_abap_date_utility or simply subtract two dates and get the number of days (since they’re internally stored as number of days since 01.01.0001).
Arjun wants to display only orders created in the last 90 days. He does:
SELECT ebeln, aedat FROM ekko
WHERE aedat >= @( sy-datum - 90 )
INTO TABLE @DATA(lt_recent_orders).
The expression sy-datum - 90 is perfectly valid and yields a date.
5.3 Numeric Functions – Precision Work
For financial calculations, ABAP offers decfloat types to avoid rounding issues. But for most reporting, packed decimal is fine. You can use standard arithmetic + - * / along with built-in functions like abs( ), ceil( ), floor( ), round( ).
When Arjun sums the net values of order items, he’ll accumulate into a variable lv_total_netwr of type ekpo-netwr. Multiplication or division of currency amounts requires careful handling of decimal places. He’ll use the function cl_abap_math=>round if needed.
6. ABAP 7.4+ Inline Declarations – The Game Changer
The biggest improvement in modern ABAP is the ability to declare variables inline at the point of use. This reduces the number of separate DATA statements and makes code much more readable. Arjun’s entire program will leverage this.
Forms of inline declaration:
DATA(lv_var) = 5.– type derived from right side (integer).DATA(ls_header) = VALUE ekko( ebeln = '12345' ... )SELECT ... INTO TABLE @DATA(lt_result)LOOP AT itab INTO DATA(ls_row)READ TABLE itab ASSIGNING FIELD-SYMBOL(<row>)– can also beASSIGNING FIELD-SYMBOL(<row>)without prior declaration.
The old code:
DATA: lt_ekko TYPE TABLE OF ekko,
ls_ekko LIKE LINE OF lt_ekko.
SELECT * FROM ekko INTO TABLE lt_ekko.
LOOP AT lt_ekko INTO ls_ekko.
WRITE ls_ekko-ebeln.
ENDLOOP.
Becomes:
SELECT * FROM ekko INTO TABLE @DATA(lt_ekko).
LOOP AT lt_ekko INTO DATA(ls_ekko).
WRITE ls_ekko-ebeln.
ENDLOOP.
No need to declare lt_ekko or ls_ekko beforehand. The system infers the types from the SELECT clause (because we used *, it creates a structure matching the database table). With specific field lists, it creates a new anonymous structure type. This is extremely convenient and reduces boilerplate.
Arjun’s initial PO selection used:
SELECT ebeln, lifnr, netwr
FROM ekpo
INTO TABLE @DATA(lt_ekpo_raw)
WHERE loekz = space.
Here lt_ekpo_raw becomes a standard table with columns EBELN, LIFNR, NETWR. This is a tabular result with the field names from the table. It works beautifully.
7. Lab Task: Build the Open Purchase Order Report Step-by-Step
Now we come to the centerpiece. Arjun’s task: “Read all open purchase orders from EKKO/EKPO and display vendor name + total value in a list.” Let’s do it in a clean, modern way. We’ll build it incrementally, and I’ll explain every decision.
7.1 Requirement Refinement and Data Model
Open POs are those with delivery not yet completed, meaning EKKO-ESTKZ (Creation indicator) is not needed, but rather the PO status. Simplest definition: POs that are not finally deleted (LOEKZ = space in EKKO) and have remaining quantity in EKPO (or simply we consider a PO open if its header status is not ‘COMPLETED’). For learning purposes, Arjun’s mentor tells him to consider a PO open if its EKKO-PROCSTAT is not ‘05’ (Complete) and not deleted. He also wants only POs from the past year. The output: vendor name (from LFA1), PO number, PO date, total net value (sum of NETWR from all items of that PO), and currency. Grouped by PO.
7.2 Step 1: Select Open PO Headers
SELECT ebeln, lifnr, aedat, waers
FROM ekko
WHERE aedat >= @( sy-datum - 365 )
AND loekz = space
AND procstat <> '05'
INTO TABLE @DATA(lt_headers).
lt_headers is now an internal table with four fields: EBELN, LIFNR, AEDAT, WAERS. But we need vendor name. We could join LFA1 in the SELECT, but let’s first get headers and then fetch vendor names separately using a sorted table for lookup, which is a common pattern when you need to avoid expensive outer joins on many rows.
7.3 Step 2: Collect Vendor Master Data into a Hashed Table
" Get distinct vendors from header
SELECT DISTINCT lifnr FROM @lt_headers AS h
INTO TABLE @DATA(lt_vendor_ids).
IF lt_vendor_ids IS NOT INITIAL.
SELECT lifnr, name1 FROM lfa1
FOR ALL ENTRIES IN @lt_vendor_ids
WHERE lifnr = @lt_vendor_ids-lifnr
INTO TABLE @DATA(lt_vendors).
ENDIF.
We use the new SELECT ... FROM @internal_table syntax (7.4 SP08) or the classic FOR ALL ENTRIES. I’ll use FOR ALL ENTRIES because it’s familiar and still widely used. The result lt_vendors is a standard table. We’ll convert it to a hashed table for faster lookup:
DATA: gt_vendor_map TYPE HASHED TABLE OF lfa1 WITH UNIQUE KEY lifnr.
gt_vendor_map = lt_vendors.
7.4 Step 3: Select Items for Those Headers and Aggregate
SELECT ebeln, netwr, menge
FROM ekpo
FOR ALL ENTRIES IN @lt_headers
WHERE ebeln = @lt_headers-ebeln
AND loekz = space
INTO TABLE @DATA(lt_items).
Now we have all item lines. We need to sum NETWR per EBELN. We can use a simple LOOP AT with COLLECT, or use modern VALUE expressions. For clarity, I’ll show a loop with a work area and accumulation into an internal table using a sorted table with unique key EBELN to hold the totals.
TYPES: BEGIN OF ty_po_total,
ebeln TYPE ekko-ebeln,
netwr TYPE ekpo-netwr,
END OF ty_po_total.
DATA: gt_totals TYPE SORTED TABLE OF ty_po_total WITH UNIQUE KEY ebeln.
LOOP AT lt_items INTO DATA(ls_item).
DATA(ls_total) = VALUE ty_po_total( ebeln = ls_item-ebeln
netwr = ls_item-netwr ).
READ TABLE gt_totals ASSIGNING FIELD-SYMBOL(<total>) WITH TABLE KEY ebeln = ls_item-ebeln.
IF sy-subrc = 0.
<total>-netwr = <total>-netwr + ls_item-netwr.
ELSE.
INSERT ls_total INTO TABLE gt_totals.
ENDIF.
ENDLOOP.
Alternatively, we could use COLLECT statement, which is designed for this: COLLECT ls_total INTO gt_totals. — simpler but less control over the logic. I’ll use COLLECT for brevity in the final program. We’ll stick to explicit READ TABLE for teaching.
7.5 Step 4: Build the Final Output List
DATA: gt_final TYPE STANDARD TABLE OF ty_order_summary.
LOOP AT lt_headers INTO DATA(ls_head).
READ TABLE gt_totals ASSIGNING FIELD-SYMBOL(<tot>) WITH TABLE KEY ebeln = ls_head-ebeln.
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
READ TABLE gt_vendor_map ASSIGNING FIELD-SYMBOL(<vend>) WITH TABLE KEY lifnr = ls_head-lifnr.
APPEND VALUE ty_order_summary(
ebeln = ls_head-ebeln
lifnr = ls_head-lifnr
vname = COND #( WHEN <vend> IS ASSIGNED THEN <vend>-name1 ELSE 'Unknown' )
netwr = <tot>-netwr
waers = ls_head-waers
) TO gt_final.
ENDLOOP.
Here we used COND #( ... ) to handle missing vendor gracefully. Now gt_final holds the report data. Next, output.
7.6 Step 5: Output – Classic List or ALV?
For a simple report, a classical list with WRITE is fine, but ALV gives sorting, filtering, and export. Arjun wants to impress his team, so he’ll use the SALV framework (simple ALV). It requires minimal code.
TRY.
cl_salv_table=>factory(
IMPORTING
r_salv_table = DATA(lo_salv)
CHANGING
t_table = gt_final ).
lo_salv->get_functions( )->set_all( abap_true ).
lo_salv->get_columns( )->set_optimize( abap_true ).
lo_salv->display( ).
CATCH cx_salv_msg INTO DATA(lx_salv).
MESSAGE lx_salv->get_text( ) TYPE 'E'.
ENDTRY.
That’s it. The system displays an interactive ALV grid with all columns. Sorting, filtering, totals are automatically available.
7.7 Complete Source Code (Copy-Paste Ready)
REPORT zarjun_open_po.
TYPES: BEGIN OF ty_order_summary,
ebeln TYPE ekko-ebeln,
lifnr TYPE ekko-lifnr,
vname TYPE lfa1-name1,
netwr TYPE ekpo-netwr,
waers TYPE ekko-waers,
END OF ty_order_summary.
START-OF-SELECTION.
* Step 1: Open PO headers
SELECT ebeln, lifnr, aedat, waers
FROM ekko
WHERE aedat >= @( sy-datum - 365 )
AND loekz = space
AND procstat <> '05'
INTO TABLE @DATA(lt_headers).
IF lt_headers IS INITIAL.
MESSAGE 'No open POs found' TYPE 'S'.
RETURN.
ENDIF.
* Step 2: Vendor master lookup (hashed table)
SELECT DISTINCT lifnr FROM @lt_headers AS h INTO TABLE @DATA(lt_vendor_ids).
IF lt_vendor_ids IS NOT INITIAL.
SELECT lifnr, name1 FROM lfa1
FOR ALL ENTRIES IN @lt_vendor_ids
WHERE lifnr = @lt_vendor_ids-lifnr
INTO TABLE @DATA(lt_vendors).
ENDIF.
DATA(gt_vendor_map) = VALUE HASHED TABLE OF lfa1 WITH UNIQUE KEY lifnr( FOR ls IN lt_vendors ( ls ) ).
* Step 3: Item aggregation
SELECT ebeln, netwr
FROM ekpo
FOR ALL ENTRIES IN @lt_headers
WHERE ebeln = @lt_headers-ebeln
AND loekz = space
INTO TABLE @DATA(lt_items).
DATA: gt_totals TYPE SORTED TABLE OF ty_order_summary WITH UNIQUE KEY ebeln.
LOOP AT lt_items INTO DATA(ls_item).
DATA(ls_tot) = VALUE ty_order_summary( ebeln = ls_item-ebeln netwr = ls_item-netwr ).
COLLECT ls_tot INTO gt_totals.
ENDLOOP.
* Step 4: Build final output
DATA: gt_final TYPE STANDARD TABLE OF ty_order_summary.
LOOP AT lt_headers INTO DATA(ls_head).
READ TABLE gt_totals ASSIGNING FIELD-SYMBOL(<tot>) WITH TABLE KEY ebeln = ls_head-ebeln.
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
READ TABLE gt_vendor_map ASSIGNING FIELD-SYMBOL(<vend>) WITH TABLE KEY lifnr = ls_head-lifnr.
APPEND VALUE ty_order_summary(
ebeln = ls_head-ebeln
lifnr = ls_head-lifnr
vname = COND #( WHEN <vend> IS ASSIGNED THEN <vend>-name1 ELSE 'Unknown' )
netwr = <tot>-netwr
waers = ls_head-waers
) TO gt_final.
ENDLOOP.
* Step 5: Display ALV
IF gt_final IS NOT INITIAL.
TRY.
cl_salv_table=>factory(
IMPORTING r_salv_table = DATA(lo_salv)
CHANGING t_table = gt_final ).
lo_salv->get_functions( )->set_all( abap_true ).
lo_salv->get_columns( )->set_optimize( abap_true ).
lo_salv->display( ).
CATCH cx_salv_msg.
MESSAGE 'Error displaying ALV' TYPE 'I'.
ENDTRY.
ELSE.
WRITE 'No data to display.'.
ENDIF.
Screenshot Description: The ALV output showing columns: Purchase Order, Vendor, Vendor Name, Net Value, Currency, with standard toolbar for sorting, filtering, and export.
7.8 Debugging and Enhancing the Report
Arjun sets a breakpoint in ADT (or SE80) before the ALV display. He inspects gt_final in the debugger: it shows 152 rows. He notices one vendor is “Unknown”. He checks the vendor master table LFA1 and realizes that vendor ID is missing because of a leading zero issue. He uses CONVERSION_EXIT_ALPHA_INPUT to normalize LIFNR. He adds a function module call or uses |{ ls_head-lifnr ALPHA = IN }| in the query. This is a real-world edge case that ABAP developers constantly face.
He also refactors the code to use LOOP AT ... GROUP BY for the aggregation part, which is even more modern:
LOOP AT lt_items INTO DATA(ls_item)
GROUP BY ls_item-ebeln
ASSIGNING FIELD-SYMBOL(<group>).
DATA(ls_total) = VALUE ty_order_summary( ebeln = <group>-ebeln ).
LOOP AT GROUP <group> ASSIGNING FIELD-SYMBOL(<item>).
ls_total-netwr = ls_total-netwr + <item>-netwr.
ENDLOOP.
INSERT ls_total INTO TABLE gt_totals.
ENDLOOP.
But the original COLLECT remains simple and efficient for this case. Both are valid; the group by approach is more explicit and does not rely on sorting.
8. Best Practices, Pitfalls, and Performance Tips for ABAP Foundations
8.1 Always Declare Variables with Proper Types
Avoid DATA lv_count TYPE i. if it should be of a specific field type. Instead, use TYPE ekko-ebeln. This prevents truncation and type mismatches. If you use inline declaration, the type is automatically derived correctly.
8.2 Prefer Internal Table with Key over LOOP AT … WHERE
When you need to find a row, READ TABLE with a key is far faster than looping and using an IF condition. Especially for sorted/hashed tables. Arjun used sorted and hashed tables exactly for this.
8.3 Use FIELD-SYMBOLS for Large Tables in Loops
If you’re processing thousands of rows and need to modify them, use ASSIGNING FIELD-SYMBOL(<row>) instead of INTO DATA(ls_row). It reduces memory copying and improves speed.
8.4 Avoid SELECT * in Production, But Use It Judiciously
SELECT * fetches all columns, which on a wide table can be wasteful. Always specify only the fields you need. Arjun’s code selects only EBELN, NETWR, etc. Good habit.
8.5 Leverage Inline Declarations but Watch Scope
Inline declarations create variables in the current scope. If you need a variable across multiple subroutines, you still need a global DATA. Use them freely within a method or subroutine, but avoid creating dozens of unnamed structures that confuse future readers. Name your inline variable clearly: DATA(lt_orders) is better than DATA(lt).
8.6 Debug Like a Pro
Set breakpoints, use /h in OK code to activate debugging from anywhere. In debugger, you can double-click any variable to see its content, create watchpoints, and even change values on the fly to test “what if” scenarios. Arjun spent extra time exploring the ALV object in debugger to understand how SALV sets columns.
9. Common Mistakes and How to Fix Them
- Mixing TYPE and LIKE incorrectly:
DATA lv TYPE ekko-ebeln.works;DATA lv LIKE ekko-ebeln.also works but is less clear. If you later change the data element, both adapt. However, if you usedDATA lv LIKE wa_ekko-ebeln.andwa_ekkostructure changes, your variable type follows. It’s a style choice, but modern ABAP guidelines recommendTYPEwith data element. - Using standard table for key-based access without BINARY SEARCH: If the table is sorted but declared as standard, you must use
BINARY SEARCHin READ TABLE. Forgetting it causes linear search, which can kill performance. Always use sorted table type if you intend to read by key frequently. - Overlooking cleared selection screen values: In a report, if you use PARAMETERS, always check if the user left it empty. Arjun didn’t use selection screen today, but in real reports, it’s essential.
10. Wrap-Up and What’s Next
Arjun’s program is complete, activated, and tested. He learned:
- The difference between SE80 and Eclipse ADT, and why he’ll stick to ADT.
- Data types from elementary to complex, TYPE vs LIKE.
- Internal tables – standard, sorted, hashed – and their operations.
- Field symbols vs reference variables.
- String, date, and numeric functions.
- The power of inline declarations (ABAP 7.4+).
- How to build a full practical report from scratch.
Tomorrow, in Part 32, we’ll dive into the ABAP Data Dictionary (SE11): tables, views, domains, data elements, and structures – the backbone that makes programs like this work. You’ll learn how to design a database table, create foreign keys, and build views that your code can consume. The foundation is set; now we’ll build on it.
Keep coding, keep learning.
This article is Part 31 of the “S/4HANA ABAP Development & Fiori” track. Provided by @FreeLearning365 with tech partner @techbook24.

0 Comments
thanks for your comments!