This is a test message to test the length of the message box.
Login
ABAP Common Table Expression
Created by Software-Heroes

ABAP - Common Table Expression (CTE)

In this article we want to take a look at the general table expression WITH and how you can use it in everyday life.

The general table expression was introduced with ABAP 7.52, with which local views can be defined, via which access can then take place. The amounts of data are read efficiently on a HANA database and the data is evaluated. As an example, we use our data model from Core Data Services.

 

WITH

How are such statements put together? The compound expressions are introduced with a WITH, followed by data access, which forms a result set in the form of a table. This expression is then combined with an alias and another expression can follow a comma in order to generate a large number of data pots. At the end of the statement comes the main query, which then usually refers to the delimited quantities. Let's take a look at an example:

WITH
  +eupartners AS (
    SELECT FROM zbs_dmo_partner FIELDS partner WHERE country = 'DE'
    UNION DISTINCT
    SELECT FROM zbs_dmo_partner FIELDS partner WHERE country = 'CH'
  ),
  +partner_per_month AS (
    SELECT FROM zbs_dmo_invoice
      FIELDS DISTINCT partner, document, substring( doc_date, 1, 6 ) AS buy_month
      WHERE partner IN ( SELECT partner FROM +eupartners )
  )
  SELECT FROM +partner_per_month
    FIELDS DISTINCT buy_month, COUNT( document ) AS doc_number
    GROUP BY buy_month
    ORDER BY doc_number DESCENDING, buy_month DESCENDING
    INTO TABLE @DATA(lt_result).

 

What exactly is happening in the example?

  • In the first step, we create a number of partners from Germany and Switzerland with UNION and assign them to the alias "+eupartners".
  • In the second step, we select all documents for these determined partners and prepare the output fields so that, in addition to partner and document, there is also year/month to determine the document volume per month. We assign the result to the "+partner_per_month" data set.
  • In the last step, the actual selection of the result takes place, we determine the number of documents per month as a result for Germany and Switzerland.

 

The result of the query now looks like this for us, although depending on the amount of data, it may look different for you:

 

Usage

In our example you have seen the different possibilities of use, in addition to various set operations such as UNION, EXCEPT or INTERSECT, table functions such as CONCAT or SUBSTRING are also possible to create the result set. Such queries are mainly used when you need temporary views in the logic and do not want to create them on the database.

There are no limits when using the queries, you can also merge the results of the queries yourself into complex statements and query or merge the data volumes generated as you wish. Only if it makes sense for the query, of course.

 

Performance

Let's take a look at the performance of the statement and compare it to a manual operation within the class and a core data service. For comparison, we speak of a small data volume of a total of 122 documents.

 

Manual query

Here is the manual query about the class, we read in all the data and work with the dataset locally. We use the possibility of a SELECT on @itab for the last query:

SELECT FROM zbs_dmo_partner
  FIELDS 'I' AS sign, 'EQ' AS option, partner AS low, partner AS high
  WHERE country IN ('CH','DE')
  INTO TABLE @DATA(lt_r_partner).

SELECT FROM zbs_dmo_invoice
  FIELDS DISTINCT partner, document, substring( doc_date, 1, 6 ) AS buy_month
  WHERE partner IN @lt_r_partner
  INTO TABLE @DATA(lt_invoice).

SELECT FROM @lt_invoice AS invoice
  FIELDS DISTINCT buy_month, COUNT( document ) AS doc_number
  GROUP BY buy_month
  ORDER BY doc_number DESCENDING, buy_month DESCENDING
  INTO TABLE @DATA(lt_result).

 

Core Data Service

Let's now map the queries as CDS views in the system. To do this, we have to create two views to summarize the basic data. The first CDS view forms the basic set of partners, also via a UNION:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Partner from EU'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZBS_I_DmoCTEPartnerEU
  as select from zbs_dmo_partner
{
  key partner
}
where
  country = 'DE'
union select from zbs_dmo_partner
{
  key partner
}
where
  country = 'CH'

 

The second view provides the documents per month for the previously delimited documents:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sum per Month'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZBS_I_DmoCTESumPerMonth
  as select from ZBS_I_DmoCTEPartnerEU as Partner
    join         zbs_dmo_invoice       as Invoice on Invoice.partner = Partner.partner
{
  key Invoice.partner,
  key Invoice.document,
      substring( Invoice.doc_date, 1, 6 ) as buy_month
}

 

In the last step we still need the data query in our test class, which refers to the Core Data Service:

SELECT FROM ZBS_I_DmoCTESumPerMonth
  FIELDS DISTINCT buy_month, COUNT( document ) AS doc_number
  GROUP BY buy_month
  ORDER BY doc_number DESCENDING, buy_month DESCENDING
  INTO TABLE @DATA(lt_result).

 

Measurement

The data for the measurement is very manageable, so we loop the whole thing and run each case study 1000 times.

 

As you can see from the documentation, the CTE bypasses the table buffer, so we ran it at the very end so there is no dependency on buffered queries. As a result, one can roughly state that CTE does not have to hide behind the CDS views. Because the data does not have to be copied into the class, it is also a lot faster than running it manually.

Hint: The accuracy of the measurement plays no role for us in this example, the rough measurement variables alone should suffice for comparison.

 

Example

Here is the complete example for the performance comparison and the class to run again:

CLASS zcl_bs_demo_cte DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

  PROTECTED SECTION.
  PRIVATE SECTION.
    CONSTANTS:
      c_times TYPE i VALUE 1000.

    METHODS:
      select_in_code
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,

      select_with_cte
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,

      select_from_cds
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.



CLASS zcl_bs_demo_cte IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    DATA:
      ld_start TYPE timestampl,
      ld_end   TYPE timestampl.

    GET TIME STAMP FIELD ld_start.
    select_from_cds( out ).
    GET TIME STAMP FIELD ld_end.
    out->write( |With CDS: { ld_end - ld_start }| ).

    GET TIME STAMP FIELD ld_start.
    select_in_code( out ).
    GET TIME STAMP FIELD ld_end.
    out->write( |With Code: { ld_end - ld_start }| ).

    GET TIME STAMP FIELD ld_start.
    select_with_cte( out ).
    GET TIME STAMP FIELD ld_end.
    out->write( |With CTE: { ld_end - ld_start }| ).
  ENDMETHOD.


  METHOD select_with_cte.
    DO c_times TIMES.
      WITH
        +eupartners AS (
          SELECT FROM zbs_dmo_partner FIELDS partner WHERE country = 'DE'
          UNION DISTINCT
          SELECT FROM zbs_dmo_partner FIELDS partner WHERE country = 'CH'
        ),
        +partner_per_month AS (
          SELECT FROM zbs_dmo_invoice
            FIELDS DISTINCT partner, document, substring( doc_date, 1, 6 ) AS buy_month
            WHERE partner IN ( SELECT partner FROM +eupartners )
        )
        SELECT FROM +partner_per_month
          FIELDS DISTINCT buy_month, COUNT( document ) AS doc_number
          GROUP BY buy_month
          ORDER BY doc_number DESCENDING, buy_month DESCENDING
          INTO TABLE @DATA(lt_result).
    ENDDO.
  ENDMETHOD.



  METHOD select_in_code.
    DO c_times TIMES.
      SELECT FROM zbs_dmo_partner
        FIELDS 'I' AS sign, 'EQ' AS option, partner AS low, partner AS high
        WHERE country IN ('CH','DE')
        INTO TABLE @DATA(lt_r_partner).

      SELECT FROM zbs_dmo_invoice
        FIELDS DISTINCT partner, document, substring( doc_date, 1, 6 ) AS buy_month
        WHERE partner IN @lt_r_partner
        INTO TABLE @DATA(lt_invoice).

      SELECT FROM @lt_invoice AS invoice
        FIELDS DISTINCT buy_month, COUNT( document ) AS doc_number
        GROUP BY buy_month
        ORDER BY doc_number DESCENDING, buy_month DESCENDING
        INTO TABLE @DATA(lt_result).
    ENDDO.
  ENDMETHOD.


  METHOD select_from_cds.
    DO c_times TIMES.
      SELECT FROM ZBS_I_DmoCTESumPerMonth
        FIELDS DISTINCT buy_month, COUNT( document ) AS doc_number
        GROUP BY buy_month
        ORDER BY doc_number DESCENDING, buy_month DESCENDING
        INTO TABLE @DATA(lt_result).
    ENDDO.
  ENDMETHOD.
ENDCLASS.

 

Conclusion

CTE or Common Table Expression is another way of doing complex operations from ABAP on the database. The temporary queries save the creation of Core Data Services or other views and break down complex statements in the program for better readability.

 

Source:
SAP Documentation - WITH


Included topics:
New ABAPWITHCTE
Comments (0)

ABAP Deep Dive - Table access (internal)

Category - ABAP

In this article, let's take a look at table access to internal tables and how they replace READ TABLE.

02/03/2023

ABAP Developer still relevant

Category - ABAP

In this article we look at whether ChatGPT can already replace an ABAP developer or whether it can be used as a help in everyday life.

01/06/2023

ABAP - FINAL

Category - ABAP

In this article, we'll take a look at the new FINAL language construct, how it works, and what you can do with it.

12/23/2022

ABAP Deep Dive - VALUE

Category - ABAP

In this article we want to look at the value statement again in all its forms and how you can use it in your daily work.

11/11/2022

ABAP Deep Dive - CORRESPONDING

Category - ABAP

In this article, a little more about the new Corresponding Statement and how to use it in detail. Let's take a look at the additional features.

09/16/2022