Processing the Contents of Internal Tables

Objective

After completing this lesson, you will be able to Process the contents of an internal table.

The SORT Statement

In a standard internal table, the contents are unsorted. New entries that you add to the table using the APPEND statement are added to the end of the table.

Sorting a Standard Internal Table

In this video, you will see how you can sort a table by its key, by any field, and in an ascending and descending order.

Try It Out: The SORT Statement

  1. Create a new class that implements the interface IF_OO_ADT_CLASSRUN.
  2. Copy the following code snippet to the implementation part of method if_oo_adt_classrun~main( ):
    Code Snippet
    Copy code
    Switch to dark mode
    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
    TYPES t_flights TYPE STANDARD TABLE OF /dmo/flight WITH NON-UNIQUE KEY carrier_id connection_id flight_date. DATA flights TYPE t_flights. flights = VALUE #( ( client = sy-mandt carrier_id = 'LH' connection_id = '0400' flight_date = '20230201' plane_type_id = '747-400' price = '600' currency_code = 'EUR' ) ( client = sy-mandt carrier_id = 'LH' connection_id = '0400' flight_date = '20230115' plane_type_id = '747-400' price = '600' currency_code = 'EUR' ) ( client = sy-mandt carrier_id = 'QF' connection_id = '0006' flight_date = '20230112' plane_type_id = 'A380' price = '1600' currency_code = 'AUD' ) ( client = sy-mandt carrier_id = 'AA' connection_id = '0017' flight_date = '20230110' plane_type_id = '747-400' price = '600' currency_code = 'USD' ) ( client = sy-mandt carrier_id = 'UA' connection_id = '0900' flight_date = '20230201' plane_type_id = '777-200' price = '600' currency_code = 'USD' ) ). out->write( 'Contents Before Sort' ). out->write( '____________________' ). out->write( flights ). out->write( ` ` ). * Sort with no additions - sort by primary table key carrier_id connection_id flight_date SORT flights. out->write( 'Effect of SORT with no additions - sort by primary table key' ). out->write( '____________________________________________________________' ). out->write( flights ). out->write( ` ` ). * Sort with field list - default sort direction is ascending SORT flights BY currency_code plane_type_id. out->write( 'Effect of SORT with field list - ascending is default direction' ). out->write( '________________________________________________________________' ). out->write( flights ). out->write( ` ` ). * Sort with field list and sort directions. SORT flights BY carrier_Id ASCENDING flight_Date DESCENDING. out->write( 'Effect of SORT with field list and sort direction' ). out->write( '_________________________________________________' ). out->write( flights ). out->write( ` ` ).
  3. Select CTRL + F3 to activate the class and F9 to execute it as a console app.
  4. Play around with the source code to get familiar with the concepts.

Deletion of Duplicate Records

Sometimes you will need to delete duplicate entries from an internal table. For instance, in this example, each combination of carrier_id and connection_id should only appear once in the table.

You can use the statement DELETE ADJACENT DUPLICATES to remove duplicate entries from an internal table. However, the statement only works if the entries are adjacent, that is, next to each other.

Sort the Internal Table First

Before using the DELETE ADJACENT DUPLICATES statement, you must sort the internal table according to the columns that you want to screen for duplicate values – in this case, xz carrier_id and connection_id.

However, even though the duplicate flight numbers are now next to each other, the DELETE ADJACENT FIELDS statement in this form does not delete anything. This is because the statement in its basic form considers all key fields of the table when identifying duplicate entries, and since the two LH 0400 connections have different values of flight_date, they do not count as duplicate entries.

In order to delete entries that have the same combination of carrier_id and connection_id, even if they have different values of flight_date, you must use the COMPARING addition of the DELETE ADJACENT DUPLICATES statement. By using COMPARING carrier_id connection_id in the statement, you tell the system only to consider these fields when identifying duplicate entries.

You may specify any combination of key and non-key fields in the COMPARING addition, but you must first have sorted the table according to the same fields.

There is also an addition COMPARING ALL FIELDS. If you use this, the entire table line of two entries must be identical in order for the system to identify a duplicate entry.

Try It Out: Deletion of Duplicate Records

  1. Create a new class that implements the interface IF_OO_ADT_CLASSRUN.
  2. Copy the following code snippet to the implementation part of method if_oo_adt_classrun~main( ):
    Code Snippet
    Copy code
    Switch to dark mode
    123456789101112131415161718192021222324252627282930313233343536373839
    TYPES t_flights TYPE STANDARD TABLE OF /dmo/flight WITH NON-UNIQUE KEY carrier_id connection_id flight_date. DATA: flights TYPE t_flights. flights = VALUE #( ( client = sy-mandt carrier_id = 'LH' connection_id = '0400' flight_date = '20230201' plane_type_id = '747-400' price = '600' currency_code = 'EUR' ) ( client = sy-mandt carrier_id = 'QF' connection_id = '0006' flight_date = '20230112' plane_type_id = 'A380' price = '1600' currency_code = 'AUD' ) ( client = sy-mandt carrier_id = 'AA' connection_id = '0017' flight_date = '20230110' plane_type_id = '747-400' price = '600' currency_code = 'USD' ) ( client = sy-mandt carrier_id = 'LH' connection_id = '0400' flight_date = '20230301' plane_type_id = '747-400' price = '600' currency_code = 'EUR' ) ( client = sy-mandt carrier_id = 'UA' connection_id = '0900' flight_date = '20230201' plane_type_id = '777-200' price = '600' currency_code = 'USD' ) ( client = sy-mandt carrier_id = 'QF' connection_id = '0006' flight_date = '20230210' plane_type_id = 'A380' price = '1600' currency_code = 'AUD' ) ). out->write( 'Contents Before DELETE ADJACENT DUPLICATES' ). out->write( '____________________' ). out->write( flights ). out->write( ` ` ). DELETE ADJACENT DUPLICATES FROM flights. out->write( 'Contents after DELETE ADJACENT DUPLICATES' ). out->write( 'Nothing deleted - key values are not adjacent' ). out->write( 'Sort the table before DELETE ADJACENT DUPLICATES'). out->write( flights ). out->write( ` ` ). SORT flights BY carrier_id connection_id flight_date. DELETE ADJACENT DUPLICATES FROM flights. out->write( 'Contents after DELETE ADJACENT DUPLICATES' ). out->write( 'Nothing deleted - ABAP compares all key values including flight_date, which is different for every entry' ). out->write( flights ). out->write( ` ` ). DELETE ADJACENT DUPLICATES FROM flights COMPARING carrier_id connection_id. out->write( 'Contents after DELETE ADJACENT DUPLICATES with COMPARING and field list' ). out->write( 'Entries with identical values of carrier_id and connection_id have been deleted' ). out->write( flights ).
  3. Select CTRL + F3 to activate the class and F9 to execute it as a console app.
  4. Play around with the source code to get familiar with the concepts.

Table Comprehensions

You will often need to fill an internal table with values that you have read from another internal table. Sometimes it is a simple matter of copying data from one table into another. Other times, you will need to read data from more than one source table to fill your target table and maybe use expressions along the way to calculate values.

ABAP provides means for performing these tasks. In the case of simply copying data from one table to another, you can use the CORRESPONDING #( ) expression. For more complex operations, you can use a table comprehension.

To copy data between identically-named fields of two internal tables, use the CORRESPONDING operator. This works similarly to CORRESPONDING for structures – for each row of the source internal table, the system creates a new row in the target internal table and copies data between identically-named fields. Source fields for which there is no identically named field in the target are not copied. Target fields for which there is no identically named field in the source are filled with type-specific initial values.

In the example, the source internal table carriers contains three rows. Therefore, after the value assignment, the target internal table connections also contains three rows.

Fields carrier_id and carrier_name exist in both internal tables. They are copied from source to target. Field currency_code only exists in the source. It is not copied. Fields connection_id, airport_from_id, and airport_to_id exist only in the target. They are filled with initial values.

Note:

If the target internal table contains data before the assignment, the system deletes it.

Let us now consider a more complicated example. In a program, you have an internal table containing flight connections and another containing the names and locations of airports. You need to fill a third internal table with the carrier_id, connection_id and airport_from_id of the connections table and the airport name from the airports table. You can do this using a table comprehension.

To fill one internal table from another, you can use the VALUEoperator with a FOR expression. FOR implements a loop over the source table and places each row sequentially into the variable line. In the parentheses, you then assign values to the components of the new row using the syntax component = VALUE. This is exactly the same syntax as you use when you fill a structure using VALUE #( ).

Here is the code from our example. FOR line IN connections iterates over the connections table and places each line sequentially into the variable line. In the parentheses, we construct each new line of the table result_table. On the left-hand side of the expressions are the columns of result_table, on the right-hand side, the values that we want to assign to them. The columns carrier_id, connection_id and departure_airport are filled using the components carrier_id, connection_id, and airport_from_id of line respectively. To fill the column departure_airport_name, we use a table expression to look up the name of the airport in the internal table airports using the component airport_from_id of line as the airport key.

You can restrict the iteration over the source table either by using WHERE and specifying a logical condition (for example, only the connections for a particular airline) or by specifying a range of lines using the additions FROM and TO.

Try It Out: Table Comprehensions

  1. Create a new class that implements the interface IF_OO_ADT_CLASSRUN.
  2. Copy the following code snippet to the implementation part of method if_oo_adt_classrun~main( )
    Code Snippet
    Copy code
    Switch to dark mode
    1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
    TYPES: BEGIN OF t_connection, carrier_id TYPE /dmo/carrier_id, connection_id TYPE /dmo/connection_id, departure_airport TYPE /dmo/airport_from_id, departure_airport_Name TYPE /dmo/airport_Name, END OF t_connection. TYPES t_connections TYPE STANDARD TABLE OF t_connection WITH NON-UNIQUE KEY carrier_id connection_id. DATA connections TYPE TABLE OF /dmo/connection. DATA airports TYPE TABLE OF /dmo/airport. DATA result_table TYPE t_connections. * Aim of the method: * Read a list of connections from the database and use them to fill an internal table result_table. * This contains some data from the table connections and adds the name of the departure airport. SELECT FROM /dmo/airport FIELDS * INTO TABLE @airports. SELECT FROM /dmo/connection FIELDS * INTO TABLE @connections. out->write( 'Connection Table' ). out->write( '________________' ). out->write( connections ). out->write( ` ` ). * The VALUE expression iterates over the table connections. In each iteration, the variable line * accesses the current line. Inside the parentheses, we build the next line of result_table by * copying the values of line-carrier_Id, line-connection_Id and line-airport_from_id, then * loooking up the airport name in the internal table airports using a table expression result_table = VALUE #( FOR line IN connections ( carrier_Id = line-carrier_id connection_id = line-connection_id departure_airport = line-airport_from_id departure_airport_name = airports[ airport_id = line-airport_from_id ]-name ) ). out->write( 'Results' ). out->write( '_______' ). out->write( result_table ).
  3. Select CTRL + F3 to activate the class and F9 to execute it as a console app.
  4. Play around with the source code to get familiar with the concepts.

Table Reductions

Sometimes when you iterate over an internal table, you need to summarize information – to produce a sum or row count, for example.

The simplest form of reduction iterates over an internal table and returns an elementary value. The INIT part of the reduction defines a variable total, whose type must be compatible with the type of sum. FOR line IN flights is an iteration over the internal table flights in which each line is placed sequentially into the work area line. In the NEXT expression, the contents of seats_occupied from each line are added to the variable total. At the end of the iteration, the contents of total are assigned to the variable sum.

Note

The assignment total += line-seats_occupied is a calculation assignment. It is a short form for total = total + line-seats_occupied. Similar operators exist for the other basic arithmetic operations (-=, *=, /= ).

The return value of a reduction can also have a structured type. In this example, the type t_results is a structure containing the components occupied and maximum. In the INIT section of the reduction, we declare a variable totals with the type t_results. In the NEXT expression, we address the components occupied and maximum, compiling the sum of each across the entire internal table.

The type of the target variable and the type of the result of the REDUCE expression must be compatible.

As well as having a return value, a REDUCE expression can also use local variables. In this example, the INIT section of the expression contains two data declarations – totals_avg, which will be the return value, and count. As count is declared with the integer value 1, the system assigns it the type I. We then use count in the NEXT section of the expression. In each iteration, we use it to calculate the average occupation of all flights so far, adding 1 to the value at the end of each iteration.

Try It Out: Table Reductions

  1. Create a new class that implements the interface IF_OO_ADT_CLASSRUN.
  2. Copy the following code snippet to the implementation part of method if_oo_adt_classrun~main( )
    Code Snippet
    Copy code
    Switch to dark mode
    1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
    TYPES: BEGIN OF t_results, occupied TYPE /dmo/plane_seats_occupied, maximum TYPE /dmo/plane_seats_max, END OF t_results. TYPES: BEGIN OF t_results_with_Avg, occupied TYPE /dmo/plane_seats_occupied, maximum TYPE /dmo/plane_seats_max, average TYPE p LENGTH 16 DECIMALS 2, END OF t_results_with_avg. DATA flights TYPE TABLE OF /dmo/flight. SELECT FROM /dmo/flight FIELDS * INTO TABLE @flights. * Result is a scalar data type DATA(sum) = REDUCE i( INIT total = 0 FOR line IN flights NEXT total += line-seats_occupied ). out->write( 'Result is a scalar data type' ). out->write( '_____________ ______________' ). out->write( sum ). out->write( ` ` ). * Result is a structured data type DATA(results) = REDUCE t_results( INIT totals TYPE t_results FOR line IN flights NEXT totals-occupied += line-seats_occupied totals-maximum += line-seats_max ). out->write( 'Result is a structure' ). out->write( '_____________________' ). out->write( results ). out->write( ` ` ). * Result is a structured data type * Reduction uses a local helper variable * Result of the reduction is always the *first* variable declared after init out->write( 'Result is a structure. The average is calculated using a local helper variable' ). out->write( '______________________________________________________________________________' ). DATA(result_with_Average) = REDUCE t_results_with_avg( INIT totals_avg TYPE t_results_with_avg count = 1 FOR line IN flights NEXT totals_Avg-occupied += line-seats_occupied totals_avg-maximum += line-seats_max totals_avg-average = totals_avg-occupied / count count += 1 ). out->write( result_with_average ).
  3. Select CTRL + F3 to activate the class and F9 to execute it as a console app.
  4. Play around with the source code to get familiar with the concepts.

Process the Contents of Internal Tables

You realize that in method GET_FLIGHTS_BY_CARRIER, you refresh the buffer whenever the method is called for a new carrier ID. In this exercise, you adjust the method implementation to keep already buffered flight data.

You also replace the aggregation on the database with a table reduction.

Template:

  • /LRN/CL_S4D401_DBS_AGGREGATE (Global Class)

Solution:

  • /LRN/CL_S4D401_ITS_PROCESSING (Global Class)

Task 1: Copy Template (Optional)

Copy the template class. If you finished the previous exercise, you can skip this task and continue editing your class ZCL_##_SOLUTION.

Steps

  1. Copy class /LRN/CL_S4D401_DBS_AGGREGATE to a class in your own package (suggested name: ZCL_##_SOLUTION, where ## stands for your group number).

    1. In the Project Explorer view, right-click class /LRN/CL_S4D401_DBS_AGGREGATE to open the context menu.

    2. From the context menu, choose Duplicate ....

    3. Enter the name of your package in the Package field. In the Name field, enter the name ZCL_##_SOLUTION, where ## stands for your group number.

    4. Adjust the description and choose Next.

    5. Confirm the transport request and choose Finish.

  2. Activate the copy.

    1. Press Ctrl + F3 to activate the class.

Task 2: Append the Flights Buffer

Change the implementation of method GET_FLIGHTS_BY_CARRIER in local class LCL_PASSENGER_FLIGHT. Make sure, that when you read flight data from the database, you add the data to the buffer (static attribute flight_buffer), rather than replacing the current content. In a first approach, add the new entries in any case, then remove the duplicates. In a second approach, skip the SELECT statement, if the buffer already contains flights of the requested airline.

Steps

  1. In local class LCL_PASSENGER_FLIGHT, navigate to the implementation of method GET_FLIGHTS_BY_CARRIER.

    1. Perform this step as in previous exercises.

  2. Adjust the loop over static attribute flight_buffer. Add a filter that only reads the flights from the airline specified in importing parameter i_carrier_id.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      123
      LOOP AT flights_buffer INTO DATA(flight).
  3. Adjust the SELECT statement to make sure the result is added to the content of internal table flights_buffer instead of replacing the current content.

    Hint

    Replace the INTO TABLE clause with an APPENDING TABLE clause.
    1. In the SELECT statement, replace INTO with APPENDING.

    2. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678910111213141516
      SELECT FROM /lrn/passflight FIELDS carrier_id, connection_id, flight_date, plane_type_id, seats_max, seats_occupied, seats_max - seats_occupied AS seats_free, currency_conversion( amount = price, source_currency = currency_code, target_currency = @currency, exchange_rate_date = flight_date, on_error = @sql_currency_conversion=>c_on_error-set_to_null ) AS price, @currency AS currency_code WHERE carrier_id = @i_carrier_id ORDER BY flight_date ASCENDING
  4. After the SELECT statement, add a statement that removes duplicate entries from internal table flights_buffer.

    1. After the SELECT statement, add the following code:

      Code snippet
      Expand
  5. Now that you sort the contents of flights_buffer after every SELECT APPENDING, you can omit the redundant sorting on the database.

    1. In the SELECT statement, remove or comment the ORDER BY clause.

  6. Activate and test your global class as console app.

    1. Press Ctrl + F3.

    2. Press F9.

  7. Now adjust the logic. Instead of removing the duplicates, skip the SELECT statement if flights_buffer already contains flights for the requested airline.

    1. Comment the DELETE statement you just added.

    2. Surround the SELECT statement and the SORT statement with IF … ENDIF.

    3. In the IF statement, use predicate function line_exists to ensure that flights_buffer does not contain any rows with carrier_id = i_carrier_id.

    4. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567891011121314151617181920
      SELECT FROM /lrn/passflight FIELDS carrier_id, connection_id, flight_date, plane_type_id, seats_max, seats_occupied, seats_max - seats_occupied AS seats_free, currency_conversion( amount = price, source_currency = currency_code, target_currency = @currency, exchange_rate_date = flight_date, on_error = @sql_currency_conversion=>c_on_error-set_to_null ) AS price, @currency AS currency_code WHERE carrier_id = @i_carrier_id ORDER BY flight_date ASCENDING * INTO TABLE @flights_buffer. APPENDING TABLE @flights_buffer. SORT flights_buffer BY carrier_id connection_id flight_date.
  8. Activate and test your global class as console app.

    1. Press Ctrl + F3.

    2. Press F9.

Task 3: Use a Table Comprehension

Change the implementation of method GET_FLIGHTS_BY_CARRIER in local class LCL_PASSENGER_FLIGHT. Replace the loop over static attribute flights_buffer with a table comprehension, that is, directly fill the result parameter r_result with a VALUE expression.

Steps

  1. In local class LCL_PASSENGER_FLIGHT, navigate to the implementation of method GET_FLIGHTS_BY_CARRIER.

    1. Perform this step as before.

  2. Comment the LOOP … ENDLOOP block at the end of the method implementation.

    1. Select the code rows with LOOP and ENDLOOP and all code rows between them and press Ctrl + < to insert a comment sign at the beginning of each selected row.

    2. Your code should look like this:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678910
      * LOOP AT flights_buffer INTO DATA(flight) * WHERE carrier_id = i_carrier_id. * * APPEND NEW lcl_passenger_flight( i_carrier_id = flight-carrier_id * i_connection_id = flight-connection_id * i_flight_date = flight-flight_date ) * TO r_result. * ENDLOOP.
  3. Implement a VALUE expression and assign the result to returning parameter r_result..

    1. Add the following code:

      Code Snippet
      Copy code
      Switch to dark mode
      12345
      r_result = VALUE #( ).
  4. Inside the expression, add a loop over internal table flights_buffer and add a new row to the result (suggested name for the work area: flight).

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234
      r_result = VALUE #( ).
  5. Add a filter condition to ensure that only flights of the requested airline are processed.

    Note

    Remember to surround the condition with an extra pair of brackets.
    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567
      r_result = VALUE #( FOR flight IN flights_buffer ( ) ).
  6. Fill each row of the result with a new instance of class LCL_PASSENGER_FLIGHT. Supply the constructor with information from the current row of flights_buffer, that is, from the work area flight).

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678
      r_result = VALUE #( FOR flight IN flights_buffer WHERE ( carrier_id = i_carrier_id ) ( ) ).
  7. Activate and test your global class as console app.

    1. Press Ctrl + F3.

    2. Press F9.

Task 4: Use a Table Reduction

In local class LCL_CARRIER, adjust the implementation of method GET_AVERAGE_FREE_SEATS. Instead of calculating the result in a SELECT statement, use a table reduction to extract the result from the instances that are stored in internal table passenger_flights.

Steps

  1. In local class LCL_CARRIER, navigate to the implementation of method GET_AVERAGE_FREE_SEATS.

    1. Perform this step as before.

  2. Delete or comment the source code of the method implementation.

    1. Mark the code between statements METHOD get_average_free_seats. and ENDMETHOD. and press Ctrl + <.

  3. Implement a REDUCE expression and assign the result to returning parameter r_result..

    1. Add the following code:

      Code Snippet
      Copy code
      Switch to dark mode
      12345
      r_result = REDUCE #( ).
  4. Inside the expression, declare and initialize a temporary variable with value 0 (suggested name: i).

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234
      r_result = REDUCE #( ).
  5. Before the closing bracket of the expression, add a loop over internal table passenger_flights (suggested name for the work area: flight).

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345
      r_result = REDUCE #( INIT i = 0 ).
  6. In each iteration, add the number of free seats of the current LCL_PASSENGER_FLIGHT instance to the temporary variable.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      123456
      r_result = REDUCE #( INIT i = 0 FOR flight IN passenger_flights ).

      Note

      Alternatively, you can use an assignment expression like this:
      Code snippet
      Expand
  7. Finally, calculate the average. Divide the total number of free seats (that is, the result of the table reduction) by the number of entries in passenger_flights .

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567
      r_result = REDUCE #( INIT i = 0 FOR flight IN passenger_flights NEXT i = i + flight->get_free_seats( ) ).
  8. Activate and test your global class as console app.

    1. Press Ctrl + F3.

    2. Press F9.

Log in to track your progress & complete quizzes