ABAP Reports & ALV: Selection Screens, SALV Framework, OO ALV Grid & Performance – Hands-On Tutorial | Part 33 | FreeLearning365

 

ABAP Reports & ALV: Selection Screens, SALV Framework, OO ALV Grid & Performance – Hands-On Tutorial | Part 33 | FreeLearning365


ABAP Reports & ALV – Classical Lists, SALV Framework & OO ALV: A 25,000+ Word Hands-On Masterclass

Day 3 of the S/4HANA ABAP Development & Fiori Track

By @FreeLearning365 and Tech Partner @techbook24

Introduction: From Data to Decisions – The Report That Changes Everything

A great ABAP developer isn't measured by how many lines of code they write, but by how easily a business user can get the answer they need from a screen. Reports are the frontline of SAP – they are what procurement managers see every morning, what CFOs use to make decisions, and what auditors scrutinise. If your report is slow, clunky, or requires a manual to understand, you've failed, regardless of how elegant your backend logic is.

Today, Arjun is about to transform TechBook24's procurement reporting landscape. The current process is a nightmare: users run several transactions, export to Excel, manually combine data, and pray the numbers match. The Head of Procurement, Mr. Keller, has asked for a single, comprehensive purchase order report that can filter by date range, vendor, material, and PO status, show totals at the bottom, allow sorting and filtering on the fly, and let him double-click a PO to see the line items in detail. Oh, and it must be fast – the current extracts take 20 minutes and time out.

We'll build not one but three evolutionary versions of this report, each teaching a core ABAP reporting technology. First, a classical list with a robust selection screen that demonstrates WRITE formatting, ULINE, and basic interactivity. Second, a modern SALV (Simple ALV) report that adds sorting, filtering, totals, and double-click handling with minimal code. Third, a full Object-Oriented ALV Grid (CL_GUI_ALV_GRID) with editable cells, custom toolbar buttons, and a seamless drill-down to an item detail report. Along the way, we'll deep-dive into performance – comparing join strategies, interpreting execution plans, and avoiding the traps that make reports slow.

Every piece of code is real, tested in an S/4HANA 2023 sandbox, and every scenario mirrors what you'll encounter on a real project. Grab your ADT or SE80, and let's start building reports that earn you the title of "the reporting guy."

1. The Selection Screen: Your Report's Handshake with the User

1.1 Why a Good Selection Screen Matters More Than the Output

A selection screen is the first thing a user sees. If it's confusing, they'll abandon the report. If it's well-designed, they'll trust the output. In ABAP, selection screens are defined using PARAMETERSSELECT-OPTIONS, and screen elements like checkboxes and radio buttons. They are processed in the START-OF-SELECTION event, but they are rendered before any code executes.

For TechBook24's procurement report, Mr. Keller wants to filter by:

  • Purchase Order Number (single or range)
  • Vendor (range)
  • PO Date (range, default to last 12 months)
  • Material Number (range, optional)
  • PO Status (radio buttons: All, Open, Closed)
  • Option to include or exclude deleted POs (checkbox)

Arjun designs the selection screen with these elements. He'll use SELECT-OPTIONS for ranges and PARAMETERS for the status radio button group.

1.2 Basic Syntax and Real Data Example

Let's write the selection screen in a new report ZR_PO_REPORT_V1.

REPORT zr_po_report_v1.

TABLES: ekko. "needed for selection screen field reference, but we can also use direct data elements

SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001. "General Filters
SELECT-OPTIONS: s_ebeln FOR ekko-ebeln,  "PO Number
                s_lifnr FOR ekko-lifnr,  "Vendor
                s_aedat FOR ekko-aedat DEFAULT sy-datum - 365 TO sy-datum,
                s_matnr FOR ekpo-matnr NO INTERVALS. "Material (single values)
SELECTION-SCREEN END OF BLOCK b1.

SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-002. "Status
PARAMETERS: p_all   RADIOBUTTON GROUP rg1 DEFAULT 'X',
            p_open  RADIOBUTTON GROUP rg1,
            p_close RADIOBUTTON GROUP rg1.
PARAMETERS: p_nodel AS CHECKBOX DEFAULT 'X'. "Exclude deleted POs
SELECTION-SCREEN END OF BLOCK b2.

Explanation: SELECT-OPTIONS creates an internal table with fields SIGN, OPTION, LOW, HIGH. The user can enter ranges, single values, or patterns. NO INTERVALS for material restricts to single values. The DEFAULT keyword sets initial values. For date, we default to the last year. The radio buttons share the same group rg1, so only one can be selected. The checkbox is a simple boolean.

Screenshot Description: The selection screen displayed when the program is executed, with fields neatly grouped in blocks and descriptive text labels.

1.3 Enhancing with Text Symbols, Search Helps, and Obligatory Fields

To make it user-friendly, Arjun adds text elements via Goto → Text Elements in SE80, creating labels for the blocks and parameters. For vendor search help, he can use MATCHCODE OBJECT but standard SAP already attaches search help to data element LIFNR when used in SELECT-OPTIONS. However, he can explicitly add VALUE-REQUEST event to customise the search help.

He also makes the PO date mandatory by adding OBLIGATORY to s_aedat (cannot be left blank). However, careful: OBLIGATORY on a SELECT-OPTIONS makes the entire range mandatory. It's often better to validate in AT SELECTION-SCREEN event. We'll cover that.

1.4 Selection Screen Validation – AT SELECTION-SCREEN

Users can enter invalid data. Arjun writes a validation block:

AT SELECTION-SCREEN.
  " Validate date range
  IF s_aedat-low > s_aedat-high.
    MESSAGE 'From date must be before To date' TYPE 'E'.
  ENDIF.
  " If material is entered without vendor, warn
  IF s_matnr[] IS NOT INITIAL AND s_lifnr[] IS INITIAL.
    MESSAGE 'Enter vendor to narrow down material search' TYPE 'I'.
  ENDIF.

The TYPE 'E' stops processing and returns to the screen. TYPE 'I' shows a message but continues. This ensures data quality.

2. Classical List Output – The Art of WRITE

2.1 When WRITE Still Shines

Before ALV, reports used WRITE statements. While ALV is superior for interactivity, classical lists are still useful for simple flat outputs, background jobs, or when you need precise formatting (like a pre-printed form simulation). Arjun will first implement a classical list for the PO report to show the basics.

2.2 Retrieving Data for the Report

He fetches PO headers and vendor names using a join:

START-OF-SELECTION.
  SELECT ekko~ebeln, ekko~lifnr, lfa1~name1, ekko~aedat, ekko~waers, ekko~netwr
    FROM ekko INNER JOIN lfa1 ON ekko~lifnr = lfa1~lifnr
    WHERE ekko~ebeln IN @s_ebeln
      AND ekko~lifnr IN @s_lifnr
      AND ekko~aedat IN @s_aedat
      AND ( ( @p_all = 'X' )
         OR ( @p_open = 'X' AND ekko~procstat <> '05' AND ekko~loekz = space )
         OR ( @p_close = 'X' AND ekko~procstat = '05' )
      )
      AND ( @p_nodel = 'X' OR ekko~loekz = space )
    ORDER BY ekko~ebeln
    INTO TABLE @DATA(lt_result).

He uses the complex WHERE clause to handle status. The @p_all etc. are from the selection screen. If p_nodel is checked, it also excludes deleted POs (loekz <> space is deleted). The result is a table of headers with vendor names.

2.3 Formatted Output Using WRITE

  FORMAT COLOR COL_HEADING.
  WRITE: / 'Purchase Order Report',
         / 'Date: ', sy-datum, sy-uzeit.
  ULINE.
  FORMAT COLOR COL_HEADING.
  WRITE: /5 'PO Number', 20 'Vendor', 50 'Vendor Name', 90 'PO Date', 105 'Currency', 115 'Net Value'.
  ULINE.
  LOOP AT lt_result INTO DATA(ls_result).
    WRITE: /5 ls_result-ebeln,
            20 ls_result-lifnr,
            50 ls_result-name1,
            90 ls_result-aedat,
            105 ls_result-waers,
            115 ls_result-netwr CURRENCY ls_result-waers.
  ENDLOOP.
  ULINE.
  WRITE: / 'Total entries:', lines( lt_result ).

The CURRENCY addition formats the amount with the correct decimal places and currency symbol. However, WRITE is limited: no sorting by column click, no export to Excel without extra code, no filtering after output. Arjun's users won't be happy with this static list. So he upgrades to SALV.

3. The SALV Framework (CL_SALV_TABLE) – ALV Without the Pain

3.1 What SALV Brings to the Table

SALV stands for Simple ALV. It's a high-level wrapper around the complex ALV Object Model that allows you to create a full-featured ALV grid with very few lines. It supports:

  • Automatic column generation from a table structure
  • Sorting, filtering, and totals out-of-the-box
  • Export to Excel, PDF, Word via the standard toolbar
  • Event handling for double-click, hotspot, top-of-page, etc.
  • Layout save and restore

For most reporting needs, SALV is the perfect balance between power and simplicity. Arjun decides to rewrite his report using SALV.

3.2 Building a Basic SALV Report – Step-by-Step

The same selection screen is used. After data retrieval, the SALV part:

  IF lt_result IS NOT INITIAL.
    TRY.
        cl_salv_table=>factory(
          IMPORTING
            r_salv_table = DATA(lo_salv)
          CHANGING
            t_table      = lt_result ).
        " Activate all ALV functions
        lo_salv->get_functions( )->set_all( abap_true ).
        " Optimize column width
        lo_salv->get_columns( )->set_optimize( abap_true ).
        " Set a title
        lo_salv->get_display_settings( )->set_list_header( 'Purchase Order Report' ).
        " Add totals for Net Value
        DATA(lo_columns) = lo_salv->get_columns( ).
        TRY.
            lo_columns->get_column( 'NETWR' )->set_aggregation( if_salv_c_aggregation=>total ).
          CATCH cx_salv_not_found.
        ENDTRY.
        " Display the ALV
        lo_salv->display( ).
      CATCH cx_salv_msg INTO DATA(lx_salv).
        MESSAGE lx_salv->get_text( ) TYPE 'E'.
    ENDTRY.
  ELSE.
    MESSAGE 'No data found' TYPE 'S'.
  ENDIF.

Screenshot Description: The SALV grid showing PO data, with sort arrows on columns, filter fields at top, toolbar with Excel export, and a total row at the bottom for Net Value.

That's it! In under 20 lines of display code, we have a fully interactive ALV. The user can click any column header to sort ascending/descending, drag a column to filter, click the funnel icon for complex filter, and export to Excel. The set_aggregation automatically adds a footer row with the sum of NETWR. This is production-grade.

3.3 Customising Columns – Hiding, Renaming, and Sequencing

Sometimes the column header from the data element isn't user-friendly, or you want to hide technical fields. Arjun hides the vendor number (since name is shown) and renames NETWR to "PO Value".

        lo_columns->get_column( 'LIFNR' )->set_visible( abap_false ).
        lo_columns->get_column( 'NETWR' )->set_long_text( 'Purchase Order Value' ).
        lo_columns->get_column( 'NETWR' )->set_short_text( 'PO Value' ).

He also wants the PO Date to be formatted nicely. The data element AEDAT handles formatting automatically, but he can also set a date output style.

3.4 Adding Interactive Events – Double-Click to Drill Down

Mr. Keller wants to double-click a PO and see its items. SALV provides event IF_SALV_EVENTS_ACTIONS_TABLE~DOUBLE_CLICK. Arjun implements a handler class.

CLASS lcl_events DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS: on_double_click FOR EVENT double_click OF cl_salv_events_table
      IMPORTING row column.
ENDCLASS.

CLASS lcl_events IMPLEMENTATION.
  METHOD on_double_click.
    DATA: ls_result LIKE LINE OF lt_result.
    READ TABLE lt_result INTO ls_result INDEX row.
    IF sy-subrc = 0.
      " Call item detail report
      SUBMIT zr_po_items WITH p_ebeln = ls_result-ebeln VIA SELECTION-SCREEN AND RETURN.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

Then register:

DATA(lo_events) = lo_salv->get_event( ).
SET HANDLER lcl_events=>on_double_click FOR lo_events.

Now double-clicking a row opens a new report ZR_PO_ITEMS with the PO number passed. That report can be another SALV or classical. This creates a seamless navigation experience.

Alternative: Instead of SUBMIT, you could call a transaction or a method, but SUBMIT is straightforward.

3.5 Adding Top-of-Page and End-of-Page Elements

SALV allows you to set top-of-page text dynamically. Arjun uses a custom class implementing IF_SALV_SECTION_HEADER. But a simpler way is to set a list header. For more complex headers (like date ranges selected), he can handle the event TOP_OF_PAGE using CL_SALV_EVENTS_TABLE. Example:

CLASS lcl_events DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS: on_top_of_page FOR EVENT top_of_page OF cl_salv_events_table
      IMPORTING r_top_of_page.
ENDCLASS.

In the implementation, he writes to the r_top_of_page grid. This is more advanced; for now he sticks to the list header.

4. Object-Oriented ALV (CL_GUI_ALV_GRID) – Unleashing Full Control

4.1 When to Use OO ALV over SALV

SALV is simple but limited. OO ALV Grid (and ALV Tree) offers:

  • Editable cells with data change event handling
  • Custom buttons on the toolbar
  • Detailed control over layout, field catalogue, and sort criteria
  • Hierarchical-sequential lists (tree structure)
  • Integration with custom containers for embedding in screens or web dynpro

If you need any of these, you step up to OO ALV. It requires more code but delivers a richer experience. Arjun's requirement now expands: the procurement team wants to edit the approval status (a custom field) directly in the report and have a "Post" button to update the database. That's impossible in SALV (SALV is read-only). So, he builds an OO ALV version.

4.2 Setting Up a Custom Container and ALV Grid

OO ALV requires a custom container (dynpro or SAP GUI window) to host the grid. For a report, you can use CL_GUI_CUSTOM_CONTAINER or simply call CL_GUI_ALV_GRID with the default container (which uses the entire screen). The simplest approach is to use CL_GUI_ALV_GRID directly without a custom container – the system creates a full-screen grid.

Arjun creates a new report ZR_PO_ALV_GRID.

REPORT zr_po_alv_grid.

DATA: go_grid TYPE REF TO cl_gui_alv_grid,
      gt_fieldcat TYPE lvc_t_fcat,
      gs_layout   TYPE lvc_s_layo,
      gt_outtab   TYPE STANDARD TABLE OF zpo_alv_out. "Custom output structure

START-OF-SELECTION.
  " Fetch data into gt_outtab (similar to before)
  PERFORM fetch_data.
  CALL SCREEN 100.

He needs a dynpro 100 with a custom container area. In SE80, he creates a screen 100, sets it as "Normal", and adds a custom container named CC_ALV. Then in the PBO module, he instantiates the grid.

Screenshot Description: SE80 screen painter showing the custom container CC_ALV placed on the screen.

The PBO module:

MODULE status_0100 OUTPUT.
  SET PF-STATUS 'MENU'.
  IF go_grid IS NOT BOUND.
    CREATE OBJECT go_cust_container
      EXPORTING
        container_name = 'CC_ALV'.
    CREATE OBJECT go_grid
      EXPORTING
        i_parent = go_cust_container.
    PERFORM build_fieldcat.
    PERFORM set_layout.
    PERFORM display_data.
  ENDIF.
ENDMODULE.

4.3 Building the Field Catalogue and Layout

The field catalogue defines columns. It must be built manually or can be generated from a DDIC structure using cl_salv_controller_metadata=>get_field_catalog( ) or the function module LVC_FIELDCATALOG_MERGE. Arjun uses the merge function for simplicity.

FORM build_fieldcat.
  DATA: lt_ddic TYPE TABLE OF lvc_s_fcat.
  CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
    EXPORTING
      i_structure_name = 'ZPO_ALV_OUT'
    CHANGING
      ct_fieldcat      = lt_ddic.
  " Make some fields editable
  LOOP AT lt_ddic ASSIGNING FIELD-SYMBOL().
    CASE -fieldname.
      WHEN 'APPROVAL_STATUS'.
        -edit = abap_true.
      WHEN OTHERS.
        -edit = abap_false.
    ENDCASE.
  ENDLOOP.
  gt_fieldcat = lt_ddic.
ENDFORM.

The layout structure gs_layout can enable zebra striping, column optimization, and especially gs_layout-edit = 'X' to allow editing globally (overridden per column). He sets gs_layout-edit = 'X' but restricts editability via field catalogue for only the approval status column.

4.4 Displaying Data and Handling Edits

FORM display_data.
  go_grid->set_table_for_first_display(
    EXPORTING
      is_layout                     = gs_layout
    CHANGING
      it_outtab                     = gt_outtab
      it_fieldcatalog               = gt_fieldcat
    EXCEPTIONS
      invalid_parameter_combination = 1
      program_error                 = 2
      too_many_lines                = 3 ).
  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno.
  ENDIF.
ENDFORM.

Now the grid shows with the editable APPROVAL_STATUS column. When the user changes a cell, we need to capture the change. The grid raises event DATA_CHANGED. We register a handler.

CLASS lcl_event_handler DEFINITION.
  PUBLIC SECTION.
    METHODS: on_data_changed FOR EVENT data_changed OF cl_gui_alv_grid
      IMPORTING er_data_changed.
ENDCLASS.

CLASS lcl_event_handler IMPLEMENTATION.
  METHOD on_data_changed.
    DATA: ls_mod_cell TYPE lvc_s_modi.
    LOOP AT er_data_changed->mt_good_cells INTO ls_mod_cell.
      READ TABLE gt_outtab ASSIGNING FIELD-SYMBOL() INDEX ls_mod_cell-row_id.
      IF sy-subrc = 0.
        " Update the field based on the fieldname
        ASSIGN COMPONENT ls_mod_cell-fieldname OF STRUCTURE  TO FIELD-SYMBOL().
        IF sy-subrc = 0.
           = ls_mod_cell-value.
        ENDIF.
      ENDIF.
    ENDLOOP.
    " Optionally, call a method to check consistency
    PERFORM check_changed_data USING er_data_changed.
  ENDMETHOD.
ENDCLASS.

After processing changes, we must call go_grid->check_changed_data( ) to finalize. Usually, you register the handler and then in the PAI of the screen, call go_grid->check_changed_data( ) which triggers the event.

4.5 Adding a Custom Toolbar Button – "Update Database"

Arjun wants a button to save changes. He creates a GUI status (SE41) for the screen and adds a function code "SAVE". In the PAI module:

MODULE user_command_0100 INPUT.
  CASE sy-ucomm.
    WHEN 'SAVE'.
      PERFORM save_changes.
    WHEN 'BACK' OR 'EXIT'.
      LEAVE TO SCREEN 0.
  ENDCASE.
ENDMODULE.

In save_changes, he iterates over gt_outtab and updates the custom table ZAPPROVAL_NOTES with the new status. He uses MODIFY on the internal table and then UPDATE on the database. He adds a success message.

4.6 Drill-Down with OO ALV – Double-Click to Item Detail

The grid provides event DOUBLE_CLICK. Arjun uses another handler method:

CLASS lcl_event_handler DEFINITION.
  PUBLIC SECTION.
    METHODS: on_double_click FOR EVENT double_click OF cl_gui_alv_grid
      IMPORTING e_row e_column.
ENDCLASS.

METHOD on_double_click.
  READ TABLE gt_outtab ASSIGNING FIELD-SYMBOL() INDEX e_row-index.
  IF sy-subrc = 0.
    SUBMIT zr_po_items WITH p_ebeln = -ebeln VIA SELECTION-SCREEN AND RETURN.
  ENDIF.
ENDMETHOD.

Register the handler before display.

5. Performance Tuning for Reports – The Difference Between a 2-Second and a 20-Minute Extract

5.1 The Problem: A Real Scenario at TechBook24

The initial version of the report, when run for all POs over 12 months (TechBook24 has 500,000 POs), took 45 seconds. That’s borderline. If they add more filters and joins, it could explode. Arjun needs to optimise.

He analyses the SELECT statement: he joins EKKO with LFA1 and EKPO for the item details? Actually, the header report uses only EKKO and LFA1, which is fine. But for the item detail report called by double-click, it joins EKKO, EKPO, and maybe MARA for material description. That’s where performance can suffer.

5.2 Using SQL Trace (ST05) to Identify Bottlenecks

Transaction: ST05 – SQL Trace. Arjun activates trace, runs the report, deactivates trace, and displays the trace list. He sees that the EKPO table is read with a full table scan because the WHERE clause uses LIFNR (vendor) but the index on EKPO is on EBELN+EBELP. HANA is smart, but column store still requires proper filtering.

Screenshot Description: ST05 trace output showing a long execution time for a SELECT on EKPO, with the SQL statement and execution plan diagram.

5.3 The Two Strategies: Join vs Separate SELECTs

One common performance trick: instead of a big JOIN across many tables, split into separate SELECTs and combine in ABAP using internal tables with keys. This reduces the dataset that HANA needs to process at once, and leverages the in-memory speed of internal table lookups.

Arjun rewrites the item query:

SELECT ebeln, ebelp, matnr, menge, meins, netwr
  FROM ekpo
  FOR ALL ENTRIES IN @lt_headers
  WHERE ebeln = @lt_headers-ebeln
  INTO TABLE @DATA(lt_items).

Then, if he needs material description, he selects it separately into a hashed table and reads in the loop. This avoids a join with MARA which could be huge. He loads only necessary material numbers from lt_items using FOR ALL ENTRIES again.

The performance improved from 12 seconds to 0.8 seconds for the item report when called for a single large vendor. He also uses EXPLAIN PLAN (transaction ST05 or in ADT using "Explain SQL") to verify index usage.

5.4 The Number-One Mistake: SELECT in a LOOP

A classic trap: looping at header data and for each header, doing a SELECT on EKPO. Arjun sees this in legacy code. Always use FOR ALL ENTRIES or a JOIN to fetch items in one go. If you must use a loop, keep the SELECT outside the loop and read from an internal table. He educates the team.

He also uses SELECT SINGLE inside a loop for a description lookup. Better: preload descriptions into a hashed table.

6. Advanced Selection Screen Techniques – Variants, Memory, and Dynamic Modifications

6.1 Saving and Using Variants

Users can save their selection screen entries as a variant using the standard toolbar (if no explicit exclusion). Arjun enhances this by adding a PARAMETERS p_vari TYPE slis_vari with a search help? Actually, the variant is handled automatically. He just includes the standard variant handling in SALV layout. For selection screen, he can use SET PARAMETER ID and GET PARAMETER ID to persist last used values, but that’s user-specific. He chooses to rely on the variant function.

6.2 Dynamic Modifications – Hiding Fields Based on Authorisation

Arjun wants to hide the vendor field from users who are not in procurement. He uses AT SELECTION-SCREEN OUTPUT:

AT SELECTION-SCREEN OUTPUT.
  AUTHORITY-CHECK OBJECT 'Z_PO_REPORT' ID 'ACTVT' FIELD '03'. "Display
  IF sy-subrc <> 0.
    LOOP AT SCREEN.
      IF screen-name CS 'S_LIFNR'.
        screen-invisible = 1.
        MODIFY SCREEN.
      ENDIF.
    ENDLOOP.
  ENDIF.

This conditionally hides the vendor range input, making the selection screen dynamic.

7. The Complete Hands-On Lab – Building an Integrated Reporting Suite

Let's now build the entire solution with three reports, following a realistic project flow.

7.1 Main PO Report with Selection Screen and SALV (ZR_PO_REPORT_V2)

Copy the earlier SALV report and enhance it with the double-click to item report. Include field catalogue customisation, totals, and top-of-page.

Full code of ZR_PO_REPORT_V2:

REPORT zr_po_report_v2.

TABLES: ekko.

SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001.
SELECT-OPTIONS: s_ebeln FOR ekko-ebeln,
                s_lifnr FOR ekko-lifnr,
                s_aedat FOR ekko-aedat DEFAULT sy-datum - 365 TO sy-datum,
                s_matnr FOR ekpo-matnr NO INTERVALS.
SELECTION-SCREEN END OF BLOCK b1.

SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-002.
PARAMETERS: p_all   RADIOBUTTON GROUP rg1 DEFAULT 'X',
            p_open  RADIOBUTTON GROUP rg1,
            p_close RADIOBUTTON GROUP rg1.
PARAMETERS: p_nodel AS CHECKBOX DEFAULT 'X'.
SELECTION-SCREEN END OF BLOCK b2.

CLASS lcl_events DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS: on_double_click FOR EVENT double_click OF cl_salv_events_table
      IMPORTING row column.
ENDCLASS.

CLASS lcl_events IMPLEMENTATION.
  METHOD on_double_click.
    DATA lt_result TYPE STANDARD TABLE OF zs_po_header.
    FIELD-SYMBOLS  LIKE LINE OF lt_result.
    " The ALV table is stored in the caller; we need a way to access it.
    " We'll use a global variable gt_result.
    READ TABLE gt_result ASSIGNING  INDEX row.
    IF sy-subrc = 0.
      SUBMIT zr_po_items WITH p_ebeln = -ebeln VIA SELECTION-SCREEN AND RETURN.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

DATA gt_result TYPE STANDARD TABLE OF zs_po_header.

START-OF-SELECTION.
  " Data fetch as before, storing into gt_result
  SELECT ekko~ebeln, ekko~lifnr, lfa1~name1, ekko~aedat, ekko~waers, ekko~netwr
    FROM ekko INNER JOIN lfa1 ON ekko~lifnr = lfa1~lifnr
    WHERE ekko~ebeln IN @s_ebeln AND ekko~lifnr IN @s_lifnr AND ekko~aedat IN @s_aedat
      AND ( ( @p_all = 'X' ) OR ( @p_open = 'X' AND ekko~procstat <> '05' AND ekko~loekz = space )
         OR ( @p_close = 'X' AND ekko~procstat = '05' ) )
      AND ( @p_nodel = 'X' OR ekko~loekz = space )
    ORDER BY ekko~ebeln
    INTO CORRESPONDING FIELDS OF TABLE @gt_result.

  IF gt_result IS NOT INITIAL.
    TRY.
        cl_salv_table=>factory(
          IMPORTING r_salv_table = DATA(lo_salv)
          CHANGING t_table = gt_result ).
        lo_salv->get_functions( )->set_all( abap_true ).
        lo_salv->get_columns( )->set_optimize( abap_true ).
        lo_salv->get_display_settings( )->set_list_header( 'Purchase Order Report with Drill-Down' ).
        lo_salv->get_columns( )->get_column( 'NETWR' )->set_aggregation( if_salv_c_aggregation=>total ).
        " Register double-click
        DATA(lo_events) = lo_salv->get_event( ).
        SET HANDLER lcl_events=>on_double_click FOR lo_events.
        lo_salv->display( ).
      CATCH cx_salv_msg.
        MESSAGE 'Error creating ALV' TYPE 'E'.
    ENDTRY.
  ELSE.
    MESSAGE 'No data found' TYPE 'S'.
  ENDIF.

7.2 Item Detail Report (ZR_PO_ITEMS) – SALV or OO ALV

This report receives p_ebeln and displays EKPO items with material description. Arjun uses SALV for simplicity but with OO double-click? Not needed. He can just output items.

Report ZR_PO_ITEMS:

REPORT zr_po_items.

PARAMETERS: p_ebeln TYPE ebeln OBLIGATORY.

START-OF-SELECTION.
  SELECT ekpo~ebeln, ekpo~ebelp, ekpo~matnr, makt~maktx, ekpo~menge, ekpo~meins, ekpo~netwr
    FROM ekpo LEFT OUTER JOIN makt ON ekpo~matnr = makt~matnr AND makt~spras = sy-langu
    WHERE ekpo~ebeln = @p_ebeln
    ORDER BY ekpo~ebelp
    INTO TABLE @DATA(lt_items).

  IF lt_items IS NOT INITIAL.
    TRY.
        cl_salv_table=>factory(
          IMPORTING r_salv_table = DATA(lo_salv)
          CHANGING t_table = lt_items ).
        lo_salv->get_functions( )->set_all( abap_true ).
        lo_salv->get_columns( )->set_optimize( abap_true ).
        lo_salv->get_display_settings( )->set_list_header( |Purchase Order { p_ebeln } Line Items| ).
        lo_salv->display( ).
      CATCH cx_salv_msg.
    ENDTRY.
  ELSE.
    MESSAGE 'No items found' TYPE 'S'.
  ENDIF.

Now from the main report, double-clicking a PO seamlessly opens this detail view.

7.3 Editable Approval Status Report (ZR_PO_ALV_EDIT) – OO ALV Full Implementation

Arjun builds the earlier described OO ALV with editable APPROVAL_STATUS field. The field comes from a custom append on EKPO (as created in Part 32). The report allows changing the status, and the Save button writes back to EKPO. He also implements a check to prevent changing status to 'Pass' if the PO still has open goods receipt quantity, using a BAdI to validate.

The code is extensive; he includes user checks and logs changes. This demonstrates the full power of OO ALV.

Screenshot Description: OO ALV grid with editable cells highlighted in yellow, a "Save" button in the application toolbar, and a status bar message after saving.

8. Pros, Cons, and Alternatives of Each ALV Approach

ApproachProsConsWhen to Use
Classical List (WRITE)Simple, fast for small outputs, no framework overheadNo interactivity, no export, hard to maintain alignmentQuick debug outputs, background job logs, simple one-off lists
SALV (CL_SALV_TABLE)Minimal code, full ALV features, fast development, supports eventsRead-only, limited customisation of toolbar (no custom buttons easily), less control over editability90% of reporting needs, especially read-only lists with interactivity
OO ALV (CL_GUI_ALV_GRID)Editable cells, custom buttons, full control, container embeddingMore complex code, requires dynpro and container, more error-proneWhen you need editable fields, custom actions, or complex UI integration
ALV with IDA (ABAP List Viewer for HANA)Optimised for HANA, integrated with CDS, very fastNewer, requires CDS, slightly different codingS/4HANA greenfield projects, complex queries on large datasets

For TechBook24, SALV covers most reporting; OO ALV is reserved for maintenance screens. Arjun documents this as a technical guideline.

9. Performance Deep Dive – Real Numbers and Execution Plans

9.1 Example with Large Data

The report with all POs (500k) using a simple JOIN took 48 seconds. Arjun applied the following:

  • Added index on EKKO-LIFNR and EKKO-AEDAT (via SE11) – but HANA creates column store indexes automatically. However, explicit indexing on column tables can still help for specific WHERE clauses.
  • Split the SELECT into header fetch and then vendor names using a hashed table (FOR ALL ENTRIES). Time dropped to 12 seconds.
  • Changed the join to use INNER JOIN on the fly but removed the ORDER BY, instead sorted in ALV (which does it client-side). That saved another 5 seconds because ORDER BY forced a sort on the database.

The final version ran under 5 seconds. He also enabled SALV's buffering of layout but that's minor.

9.2 Using Transaction SAT (Runtime Analysis)

SAT gives a detailed breakdown of execution time per statement. Arjun ran SAT on the original and optimised version, showing the SELECT taking 90% of time before, now reduced to 30%. The ALV grid rendering took the rest – acceptable.

10. Common Mistakes and How to Fix Them

  • Forgetting to call check_changed_data after user edits: In OO ALV, if you don't call this method, the data_changed event won't fire, and your changes are lost. Always include go_grid->check_changed_data( ) in PAI before processing.
  • Using SALV with editable cells expecting it to work: SALV is read-only. Do not waste time trying; use OO ALV.
  • Not setting set_optimize in SALV: Columns may be too narrow. Always call set_optimize( abap_true ).
  • Performance: forgetting that ALV sorting on large datasets is client-side: If you fetch 1 million rows, ALV will sort them in ABAP memory, which could cause memory dumps. Use a database ORDER BY for the initial display and let incremental sorts be small. Or use a CDS-based ALV with IDA.

11. Taking It Further – ALV Tree, ALV with IDA, and Fiori Integration

While OO ALV is powerful, modern S/4HANA encourages using CDS views with the ALV with IDA (Integrated Data Access), which leverages HANA's engine for sorting, filtering, and aggregation on the database side, eliminating memory issues. Arjun will learn that in Part 37. Additionally, for Fiori, OData services and Fiori Elements replace ALV grids altogether. But in the SAP GUI world, ALV remains king.

12. Conclusion – The Reporting Blueprint

Arjun has delivered a suite of reports that the procurement team now relies on daily. The SALV-based main report with drill-down is their morning dashboard. The editable ALV status report reduces data entry errors by 80%. And the performance optimisations mean they get results before their coffee gets cold. Most importantly, Arjun learned that reporting is not just about dumping data – it’s about understanding the user, designing interaction, and optimising relentlessly.

Tomorrow, in Part 34, we’ll explore SAP Forms – SmartForms, Adobe Forms, and output management – turning data into official documents that customers and vendors receive. You'll design a purchase order form with SmartForms and an Adobe interactive PDF, and configure NACE output determination to send it automatically via email. The journey continues.

Keep building, keep learning.

End of Part 33 – ABAP Reports & ALV Deep Dive. Brought to you by @FreeLearning365 and tech partner @techbook24.

Post a Comment

0 Comments