Working with Structured Data Objects

Objectives

After completing this lesson, you will be able to:

  • Work with structured data objects
  • Use structured data objects in ABAP SQL

Structured Data Objects

To access a component of a structure, you have to place a minus-sign (-) between the structure name and the component name.

Note

No blanks are allowed before or after the component selector.

Accessing a structure component that way, you can use it in any operand position in which you can use a variable of the same type. Component airport_from_id of structure connection in the example above is of type /DMO/AIRPORT_FROM_ID. In consequence, you can use this component in any operand position in which you could use a simple variable of type /DMO/AIRPORT_FROM_ID; not only on the left-hand side of a value assignment as in the example, but also on the right-hand side, in the parameter passing of a method call, in the INTO clause or WHERE clause of a SELECT statement, and so on.

If the component of a structure is itself a structure you access the sub-components by using the component selector again after the name of the main component. The first value assignment in the example accesses component MSGTY of MESSAGE, which itself is a component of nested structure CONNECTION_NESTED.

Hint

You can use code-completion to implement access to structure components. Place the cursor immediately after the structure component selector and press CTRL + SPACE to see a list of all available structure components.

The VALUE #( ) expression is an elegant way to assign values to a structured data object.

If you want to fill a whole structure, you can address each component individually as you saw in the previous example.

However, you can also use a VALUE #( ) expression to fill the structure. The expression constructs a structure, fills it with value and assigns the filled structure to a variable, in this case connection. The pound sign (#) tells the ABAP runtime environment to construct a structure with the same type as the target variable connection. In the brackets, you list the components of the structure that you want to fill (it does not have to be all of them) and assign a value to them. The value can be either a literal or the contents of a variable.

When you fill a structure in this way, the runtime system deletes all existing values from the structure before refilling it with the values from your expression.

Note

An assignment in the form connection = VALUE #( ). with just a blank between the brackets, fills all components of the structure with the type-specific initial value. This has the same effect as statement CLEAR connection.

In ABAP, you may only copy the contents of one structure directly into another structure using the notation <target structure> = <source structure> if the two structure types are compatible. This is generally only the case if both structures have the same type. If the structures have different types, two things can happen:

  • If one of the structures has a non-char-like component at a position where the other structure has a char-like component, direct assignment leads to a syntax error.
  • If both structures are char-like, or, in other words, both structures consist of char-like components, only, direct assignment is technically possible. But usually, the result will be wrong.

In the example, source structure and target structure are char-like. Therefore, direct assignment is technically possible. But because they are not compatible the result is wrong: The content of component carrier_name is copied to component message in the target structure.

Because there is no syntax error, you have to be extra careful when working with non-compatible char-like structures.

When you copy data between structures, you usually want to copy information from one field into the corresponding field of the target structure - airport_from_id to airport_from_id, airport_to_id to airport_to_id, and so on. To achieve this in ABAP, use the CORRESPONDING expression. This assigns values from <source_structure> to the corresponding, that is, identically-named components of . <target_structure>. You must remember the following points:

  • The fields must have identical names.
  • The components do not have to be in the same position or sequence in the two structures.
  • If the fields have different types, ABAP attempts a type conversion according to the predefined set of rules.

Note

The target structure is initialized before being re-filled with the result of the expression.

Try It Out: Access to Structured Data Objects

  1. Like in the first exercise of this course, create a new global class that implements interface IF_OO_ADT_CLASSRUN.
  2. Copy the following code snippet to the implementation part of method if_oo_adt_classrun~main( ):
    Code Snippet
    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
    TYPES: BEGIN OF st_connection, airport_from_id TYPE /dmo/airport_from_id, airport_to_id TYPE /dmo/airport_to_id, carrier_name TYPE /dmo/carrier_name, END OF st_connection. TYPES: BEGIN OF st_connection_nested, airport_from_id TYPE /dmo/airport_from_id, airport_to_id TYPE /dmo/airport_to_id, message TYPE symsg, carrier_name TYPE /dmo/carrier_name, END OF st_connection_nested. DATA connection TYPE st_connection. DATA connection_nested TYPE st_Connection_nested. * Example 1: Access to structure components ********************************************************************** connection-airport_from_id = 'ABC'. connection-airport_to_id = 'XYZ'. connection-carrier_name = 'My Airline'. "Access to sub-components of nested structure connection_nested-message-msgty = 'E'. connection_nested-message-msgid = 'ABC'. connection_nested-message-msgno = '123'. * Example 2: Filling a structure with VALUE #( ). ********************************************************************** CLEAR connection. connection = VALUE #( airport_from_id = 'ABC' airport_to_id = 'XYZ' carrier_name = 'My Airline' ). " Nested VALUE to fill nested structure connection_nested = VALUE #( airport_from_id = 'ABC' airport_to_id = 'XYZ' message = VALUE #( msgty = 'E' msgid = 'ABC' msgno = '123' ) carrier_name = 'My Airline' ). * Example 3: Wrong result after direct assignment ********************************************************************** connection_nested = connection. out->write( `-------------------------------------------------------------` ). out->write( `Example 3: Wrong Result after direct assignment` ). out->write( data = connection name = `Source Structure:`). out->write( |Component connection_nested-message-msgid: { connection_nested-message-msgid }| ). out->write( |Component connection_nested-carrier_name : { connection_nested-carrier_name }| ). * Example 4: Assigning Structures using CORRESPONDING #( ) ********************************************************************** CLEAR connection_nested. connection_nested = CORRESPONDING #( connection ). " out->write( `-------------------------------------------------------------` ). out->write( `Example 4: Correct Result after assignment with CORRESPONDING` ). out->write( data = connection name = `Source Structure:`). out->write( |Component connection_nested-message-msgid: { connection_nested-message-msgid }| ). out->write( |Component connection_nested-carrier_name : { connection_nested-carrier_name }| ).
  3. Press CTRL + F3 to activate the class and F9 to execute the console app.
  4. Analyze the console output. Debug the program, play around with the source code to get familiar with the concepts.

Structured Data Objects in ABAP SQL

The INTO clause of the SELECT statement will only work correctly if the number and types of the components of the structure correspond to the number and types of the columns specified in the FIELDS clause. In the above example, the statement can only work if the target structure connection has three components with the same type and length as the columns DepartureAirport, DestinationAirport, and \_Airline-Name listed in the FIELDS clause. Note that, in this case, the names do not have to be identical - the system fills the target structure from left to right.

If the field list in the FIELDS clause does not match the structure or table line type in the INTO clause, a runtime error will occur.

The example shows an easy technique to ensure that the target structure matches the field selection:

  • The target structure is typed with CDS view /DMO/I_Connection, which is the data source in the FROM clause.
  • The asterisk sign (*) after keyword FIELDS is a short notation to makes sure, that all fields of the view are part of the field selection. Exposed associations are ignored.

Note

This technique is also available when you read directly from a database table. Just like CDS view definitions, database table definitions can also serve as global structure types in ABAP.

The main advantage of this technique is, that the SELECT statement stays syntactically intact even if you or someone else makes changes to CDS view or database table. The most important drawback is, that you always read all fields from the database, whether you actually need them or not.

Note

Only use this technique for views and tables with a small number of fields and if you actually need all the fields. Unnecessary reading of data from the database is a major cause for performance problems.

Another way to avoid syntax errors is variant INTO CORRESPONDING FIELDS. This variant has the same effect as the CORRESPONDING #( ) operator that you learned about earlier. It ensures that data is copied between identically-named components. By defining the structure type according to your needs you can ensure that only the required data is read.

Once again, only the names must be identical. But to avoid problems you should make sure that identically-named components have compatible types. Otherwise the system attempts to convert the contents of the source field into the type of the target field. This can lead to data loss or (catchable) runtime errors.

If the field names in the data source and the component names in the target structure do not match, the combination of FIELDS * and INTO CORRESPONDING FIELDS OF does not work.

If you want to keep variant INTO CORRESPONDING FIELDS OF, you can define alias names for the selected fields in the field list. For this, add addition AS after the field name, followed by the alias name. In the example, the alias name for view field DepartureAirport is airport_from_id and the alias name for path expression \_Airline-Name is carrier_name. Based on this alias names, INTO CORRESPONDING FIELDS OF correctly identifies the structure component in which to store the retrieved data.

The simplest technique to avoid conflicts between the field selection and the target structure is an inline declaration in the INTO clause. The sequence, type and name of the inline declared structure is derived from the FIELD clause. Therefore the target structure always fits the field selection.

Note

Inline declarations are only supported after INTO. You cannot use inline declarations after INTO CORRESPONDING FIELDS OF.

If you use an inline declaration in the INTO clause, you have to provide a name for each element in the FIELDS clause. For fields of the data source, this can be the field name itself or, optionally an alias name. For expressions, the alias name becomes mandatory.

In the example, there is no alias for field DepartureAirport. The name of the field is used as component name in structure connection_inline. Field DestinationAirport has an optional alias ArrivalAirport. In this case the alias is used as component name. The alias for path expression \_Airline-Name is mandatory.

When working with a relational database you often face the problem that you have to read related data from different database tables. We already learned that associations in CDS views are an elegant way to perform this task.

If no CDS View with suitable associations exist you can implement SQL joins, instead. The example above illustrates the principle of joins:

We are interested in flight connections and the airports they connect with each other. We find the 3-letter IDs of the airports in database table /DMO/CONNECTION. The full names of the airports are stored in database table /DMO/AIRPORT.

To retrieve a connection with the departure airport name, in one SELECT statement, we read connection data from DB table /DMO/CONNECTION and join it with DB table /DMO/AIRPORT.

A join consists of the following building blocks:

Data Sources
The Database tables and views to join with each other. A single join always combines a left-hand data source with a right-hand data source. In the example above, table /DMO/CONNECTION is the left-hand data source and table /DMO/AIRPORT the right-hand data source. ABAP SQL also supports joins of joins (nested joins)
Join Condition
The join condition specifies which records of the right-hand data source belong to a record from the left-hand data source. In the example above, the related departure airport is identified by the value in columns CLIENT and AIRPORT_ID. The join condition reads:
/DMO/CONNECTION~CLIENT = /DMO/AIRPORT~CLIENT
AND
/DMO/CONNECTION~AIRPORT_FROM_ID = /DMO/AIRPORT~AIRPORT_ID
Join Type
The join type has an influence on the result if one of the data sources does not contain a matching records. ABAP SQL currently supports INNER JOIN, LEFT OUTER JOIN, and RIGHT OUTER JOIN. The most common join type is a LEFT OUTER JOIN.

The figure shows the ABAP SQL syntax for a join. In the FROM clause, the join type is specified by keywords LEFT OUTER JOIN between the left-hand data source /dmo/connection and the right-hand data source /dmo/airport. The syntax introduces alias names c and f for the data sources. Alias names for data sources are optional, unless a data source appears more than once in the join.

The join condition follows keyword ON. The separator between the data source or its alias and the field is the tilde sign (~).

Note

In ABAP SQL, it is not necessary to mention the client fields. They are added to the join condition by the database interface before the statement is sent to the database. If the FROM clause defines a join, you can use fields from both data sources in the FIELDS and WHERE clauses.

In this example, the SELECT statement not only read the departure airport name but also the destination airport name. To do so, the FROM clause defines a nested join:

The first join is a left outer join of tables /dmo/connection and /dmo/airport, introducing alias "f" (like "from") for the right-hand data source. This first join is then used as left-hand data source for a second left outer join that has table /dmo/airport as right-hand data source. Note that in this case alias "t" (for "to") is crucial to distinguish this appearance of table /dmo/airport from the previous one.

The FIELDS clause, lists the airport names from both data sources, introducing aliases airport_from_name and airport_to_name to distinguish them from each other.

Hint

The brackets around the first join are optional. If they are omitted, the joins in the from clause are evaluated from left to right.

Try It Out: Structured Data Objects in ABAP SQL

  1. Like in the first exercise of this course, create a new global class that implements interface IF_OO_ADT_CLASSRUN.
  2. Copy the following code snippet to the implementation part of method if_oo_adt_classrun~main( ):
    Code Snippet
    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
    TYPES: BEGIN OF st_connection, airport_from_id TYPE /dmo/airport_from_id, airport_to_id TYPE /dmo/airport_to_id, carrier_name TYPE /dmo/carrier_name, END OF st_connection. TYPES: BEGIN OF st_connection_short, DepartureAirport TYPE /dmo/airport_from_id, DestinationAirport TYPE /dmo/airport_to_id, END OF st_connection_short. DATA connection TYPE st_connection. DATA connection_short TYPE st_connection_short. DATA connection_full TYPE /DMO/I_Connection. * Example 1: Correspondence between FIELDS and INTO ********************************************************************** SELECT SINGLE FROM /DMO/I_Connection FIELDS DepartureAirport, DestinationAirport, \_Airline-Name WHERE AirlineID = 'LH' AND ConnectionID = '0400' INTO @connection. out->write( `------------------------------` ). out->write( `Example 1: Field List and INTO` ). out->write( connection ). * Example 2: FIELDS * ********************************************************************** SELECT SINGLE FROM /DMO/I_Connection FIELDS * WHERE AirlineID = 'LH' AND ConnectionID = '0400' INTO @connection_full. out->write( `----------------------------` ). out->write( `Example 2: FIELDS * and INTO` ). out->write( connection_full ). * Example 3: INTO CORRESPONDING FIELDS ********************************************************************** SELECT SINGLE FROM /DMO/I_Connection FIELDS * WHERE AirlineID = 'LH' AND ConnectionID = '0400' INTO CORRESPONDING FIELDS OF @connection_short. out->write( `----------------------------------------------------` ). out->write( `Example 3: FIELDS * and INTO CORRESPONDING FIELDS OF` ). out->write( connection_short ). * Example 4: Alias Names for Fields ********************************************************************** CLEAR connection. SELECT SINGLE FROM /DMO/I_Connection FIELDS DepartureAirport AS airport_from_id, \_Airline-Name AS carrier_name WHERE AirlineID = 'LH' AND ConnectionID = '0400' INTO CORRESPONDING FIELDS OF @connection. out->write( `---------------------------------------------------` ). out->write( `Example 4: Aliases and INTO CORRESPONDING FIELDS OF` ). out->write( connection ). * Example 5: Inline Declaration ********************************************************************** SELECT SINGLE FROM /DMO/I_Connection FIELDS DepartureAirport, DestinationAirport AS ArrivalAirport, \_Airline-Name AS carrier_name WHERE AirlineID = 'LH' AND ConnectionID = '0400' INTO @DATA(connection_inline). out->write( `-----------------------------------------` ). out->write( `Example 5: Aliases and Inline Declaration` ). out->write( connection_inline ). * Example 6: Joins ********************************************************************** SELECT SINGLE FROM ( /dmo/connection AS c LEFT OUTER JOIN /dmo/airport AS f ON c~airport_from_id = f~airport_id ) LEFT OUTER JOIN /dmo/airport AS t ON c~airport_to_id = t~airport_id FIELDS c~airport_from_id, c~airport_to_id, f~name AS airport_from_name, t~name AS airport_to_name WHERE c~carrier_id = 'LH' AND c~connection_id = '0400' INTO @DATA(connection_join). out->write( `------------------------------------------` ). out->write( `Example 6: Join of Connection and Airports` ). out->write( connection_join ).
  3. Press CTRL + F3 to activate the class and F9 to execute the console app.
  4. Analyze the console output. Debug the program, play around with the source code to get familiar with the concepts.

Use a Structured Data Object

In this exercise, you declare a structured attribute, fill it using a SELECT statement and access the structure components.

Template:

  • /LRN/CL_S4D400_DBS_CDS (global Class)

Solution:

  • /LRN/CL_S4D400_STS_STRUCTURE (global Class)

Task 1: Copy Template

Copy the template class. Alternatively, copy your solution of the previous exercise.

Steps

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

    1. Open the source code of the global class /LRN/CL_S4D400_DBS_CDS.

    2. Link the Project Explorer view with the editor.

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

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

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

    6. Adjust the description and choose Next.

    7. Confirm the transport request and choose Finish.

Task 2: Declare a Structured Data Object

In the local class, declare a structured attribute details to replace the scalar attributes airport_from_id, airport_to_id, and carrier_name. Begin by defining a private structure type st_details inside the local class.

Steps

  1. Switch to the local class lcl_connection.

    1. In the global class, choose Local Types.

  2. Define a private structure type st_details with the following components:

    Components of structure type st_details:

    Component NameData Type
    DepartureAirport/dmo/airport_from_id
    DestinationAirport/dmo/airport_to_id
    AirlineName/dmo/carrier_name
    1. After line PRIVATE SECTION., add the following code:

      Code Snippet
      12345678
      TYPES: BEGIN OF st_details, DepartureAirport TYPE /dmo/airport_from_id, DestinationAirport TYPE /dmo/airport_to_id, AirlineName TYPE /dmo/carrier_name, END OF st_details.
  3. Comment or remove the declaration of attributes airport_from_id, airport_to_id, and carrier_name.

    1. Select the lines with the three DATA statements.

    2. Press Ctrl + < to add a star sign (*) in front of each selected line.

  4. Declare a new private instance attribute details and type it with structure type st_details..

    1. Adjust the code as follows:

      Code Snippet
      123456789
      * DATA airport_from_id TYPE /dmo/airport_from_id. * DATA airport_to_id TYPE /dmo/airport_to_id. * * DATA carrier_name TYPE /dmo/carrier_name. DATA details TYPE st_details.

Task 3: Access Structure Components

Use the components of the structured attribute details in the method get_output.

Steps

  1. Adjust the implementation of the method get_output. Replace any access to the attributes airport_from_id, airport_to_id, and carrier_name with the corresponding component of attribute details.

    Hint

    Do not type in the component names manually! After typing the structure component selector (-), press Ctrl + Space to get a list of all components.
    1. Navigate to the implementation of the method get_output.

    2. Adjust the APPEND statements as follows:

      Code Snippet
      12345678910111213
      * APPEND |--------------------------------| TO r_output. * APPEND |Carrier: { carrier_id } { carrier_name }| TO r_output. * APPEND |Connection: { connection_id }| TO r_output. * APPEND |Departure: { airport_from_id }| TO r_output. * APPEND |Destination: { airport_to_id }| TO r_output. APPEND |--------------------------------| TO r_output. APPEND |Carrier: { carrier_id } { details-airlinename }| TO r_output. APPEND |Connection: { connection_id }| TO r_output. APPEND |Departure: { details-departureairport }| TO r_output. APPEND |Destination: { details-destinationairport }| TO r_output.

Task 4: Fill the Structured Attribute in the SELECT Statement

Use the structured attribute as the target of the SELECT statement in the constructor method.

Steps

  1. Adjust the SELECT statement in the implementation of the constructor method. Replace the list of data objects in the INTO clause with the structured attribute details.

    1. Navigate to the implementation of method constructor.

    2. Adjust the SELECT statement as follows:

      Code Snippet
      123456789
      SELECT SINGLE FROM /DMO/I_Connection FIELDS DepartureAirport, DestinationAirport, \_Airline-Name WHERE AirlineID = @i_carrier_id AND ConnectionID = @i_connection_id * INTO ( @airport_from_id, @airport_to_id, @carrier_name ). INTO @details.
  2. Optional: Use syntax variant INTO CORRESPONDING FIELDS OF @details..

    1. In the SELECT statement, replace INTO @details.with the following code:

      Code Snippet
      123
        INTO CORRESPONDING FIELDS OF @details.
    2. Add alias name AirlineName for the path expression.

    3. The SELECT statement should now look like this:

      Code Snippet
      12345678
      SELECT SINGLE FROM /DMO/I_Connection FIELDS DepartureAirport, DestinationAirport, \_Airline-Name as AirlineName WHERE AirlineID = @i_carrier_id AND ConnectionID = @i_connection_id INTO CORRESPONDING FIELDS OF @details.
  3. Activate the class. Execute it and analyze the console output. Check that the output displays data for all attributes.

    1. Press Ctrl + F3 to activate the class.

    2. Press F9 to run the class.

Log in to track your progress & complete quizzes