ABAP New open SQL:Code Pushdown って何のこと?

Code Pushdown ってなんのことだろう?

  • 先日、SAP HANAの勉強会に参加したときのこと。
  • 多少時代遅れ感はありますが、「ABAP CDS View vs HANA Infomation View」のスライドがありまして、ざっくり体感的には、HANA Infomation Viewが3倍から30倍くらい高速に動作します。
  • 一時期、営業的なのかどうか不明ですが、SAPJ様が「ABAP CDS Viewのほうが速い」と発信していたらしいですが、実測してみると、HANA Infomation Viewの圧勝で、SAP HANAの仕組みを少し考えると、当然ということに気づきます。
  • ただし、1千万~10億レコードといったボリュームの大きなデータを扱うときの話で、プロジェクトでアドオンプログラム(10万レコード程度)のパフォーマンスが出なくて困っていることとリンクしていないことが、なかなか伝わらないようでした。
  • アドオンプログラムの場合、Netweaverのメモリ容量制限から、扱えるのは500万レコード位でしょうか?桁が違う話なのです。
  • パフォーマンスが正義みたいな考え方はあまり好きでは無いのですが、R/3時代のルールで作成した(ITAB多用)プログラムも含めて乱暴な比較をすると以下のような感じになると思います。
    • 1:ITAB多用(BINARY SEARCH等をガチガチに組んだ場合)
    • 100:ABAP CDS View(パフォーマンス上は、Classic Open SQL、New Open SQLも同じ)
    • 1000:HANA Infomation View

(数値が大きい方が高速動作)

昔々SAP社様が、「東京→大阪を数秒で行ける」といった宣伝をしていました。
「ITAB多用で、2時間30分→9,000秒、,1,000倍高速なHANA Infomation Viewで置き換えに成功したとすると、9秒」
条件付きではありますが、嘘は言っていなかったと思います。
でも、これが90秒でもおそらく万々歳だと思うのです。そして、New Open SQLのみで実現できるとしたら...嬉しいですよね。

一方で営業的には、
「リアルタイム経営の為には、ビックデータの高速分析が必要です。Code Pushdownという設計手法があり、CDS Viewを採用することで実現します。」
CDS→Pushdown→リアルタイム経営」みたいな。

多少逆説的ですが、ABAPコードの中の「LOOP文を、廃止または削減」すると、Code Pushdownが実現できると思います。そして処理速度100倍も夢ではないと思います。っということでプログラムを作ってみました。
外見上の振る舞いは変えずに5種類の実装方法を作りました。
処理としては、トランザクションを格納したテーブルから、会社コードのバリエーション一覧を取得します。

A-1
 従来方式です。
 ACDOCAのデータを取得して
 SORT命令で並び替えて(Non-Pushdown)
 DELETE命令で重複を削除する(Non-Pushdown)

A-2
 従来方式の変形です。
 ACDOCAのデータをORDER BY句で並び替えて取得して(Pushdown)
 DELETE命令で重複を削除する(Non-Pushdown)

B-1
 ACDOCAのデータをDISTINCT句で重複削除して(Pushdown)
 ORDER BY句で並び替えて取得する(Pushdown)

B-2
 ACDOCAのデータをGROUP BY句で重複削除して(Pushdown)
 ORDER BY句で並び替えて取得する(Pushdown)

C-1
 番外編
 ACDOCAのデータを取得して
 LOOP + BINARY SEARCHで、重複削除および並び替えする(Non-Pushdown)

私の環境では以下の通りになりました。
ACDOCA:300万レコード→実行結果:20レコード
A-1:2.9sec
A-2:1.0sec
B-1:0.018sec
B-2:0.018sec
C-1:3.0sec
純粋なITAB(Non-Pushdown)のA-1とC-1と比較して、PushdownしているB-1、B-2は100倍以上の処理速度となりました。
そして、HANA Infomation Viewはこれより更に10倍高速動作する可能性があります。

ABAP CDS Viewは、ABAP層で動作しているので、Open SQL(New, Classic含む)とほぼ同じ速度です。だから、使い慣れたOpen SQLにちょい足しをして、もっとカジュアルにCode Pushdownすると良いのではと思います。

REPORT SQL_Console.
*-----------------------------------------------------------------------
* GLOBAL VARIABLE
*-----------------------------------------------------------------------
DATA GDF_SUBRC TYPE SY-SUBRC.

*-----------------------------------------------------------------------
* CONDITION VIEW
*-----------------------------------------------------------------------
DATA GDF_CHAR1 TYPE CHAR10.
TYPES GTR_CHAR1 LIKE RANGE OF GDF_CHAR1.
SELECT-OPTIONS S_CHAR1 FOR GDF_CHAR1.

DATA GDF_CHAR2 TYPE CHAR10.
TYPES GTR_CHAR2 LIKE RANGE OF GDF_CHAR2.
SELECT-OPTIONS S_CHAR2 FOR GDF_CHAR2.

DATA GDF_NUMC1 TYPE NUMC08.
TYPES GTR_NUMC1 LIKE RANGE OF GDF_NUMC1.
SELECT-OPTIONS S_NUMC1 FOR GDF_NUMC1.

DATA GDF_NUMC2 TYPE NUMC08.
TYPES GTR_NUMC2 LIKE RANGE OF GDF_NUMC2.
SELECT-OPTIONS S_NUMC2 FOR GDF_NUMC2.

DATA GDF_DATS1 TYPE DATS.
TYPES GTR_DATS1 LIKE RANGE OF GDF_DATS1.
SELECT-OPTIONS S_DATS1 FOR GDF_DATS1.

DATA GDF_DATS2 TYPE DATS.
TYPES GTR_DATS2 LIKE RANGE OF GDF_DATS2.
SELECT-OPTIONS S_DATS2 FOR GDF_DATS2.

DATA GDF_TIMS1 TYPE TIMS.
TYPES GTR_TIMS1 LIKE RANGE OF GDF_TIMS1.
SELECT-OPTIONS S_TIMS1 FOR GDF_TIMS1.

DATA GDF_TIMS2 TYPE TIMS.
TYPES GTR_TIMS2 LIKE RANGE OF GDF_TIMS2.
SELECT-OPTIONS S_TIMS2 FOR GDF_TIMS2.

*-----------------------------------------------------------------------
* START-OF-SELECTION
*-----------------------------------------------------------------------
START-OF-SELECTION.
  PERFORM START_OF_SELECTION
    USING
      S_CHAR1[]
      S_CHAR2[]
      S_NUMC1[]
      S_NUMC2[]
      S_DATS1[]
      S_DATS2[]
      S_TIMS1[]
      S_TIMS2[]
    CHANGING
      GDF_SUBRC.

*-----------------------------------------------------------------------
* SUBROUTINE
*-----------------------------------------------------------------------
FORM START_OF_SELECTION
    USING
      PIR_CHAR1 TYPE GTR_CHAR1
      PIR_CHAR2 TYPE GTR_CHAR2
      PIR_NUMC1 TYPE GTR_NUMC1
      PIR_NUMC2 TYPE GTR_NUMC2
      PIR_DATS1 TYPE GTR_DATS1
      PIR_DATS2 TYPE GTR_DATS2
      PIR_TIMS1 TYPE GTR_TIMS1
      PIR_TIMS2 TYPE GTR_TIMS2
    CHANGING
      POF_SUBRC TYPE SY-SUBRC.
*-----------------------------------------------------------------------
  DATA LDF_TIMESTAMP_START TYPE TIMESTAMPL.       "タイムスタンプデータ取得開始
  GET TIME STAMP FIELD LDF_TIMESTAMP_START.       "タイムスタンプデータ取得開始
*-----------------------------------------------------------------------
* Please write the SQL statement here ☆
* Set the internal table to be stored to LDT_DATA ☆
* Inline definition of LDT_DATA is fun ☆

  "ACDOCAに登録済みの会社コードを並び替えて一覧出力する

  "A-1
  "ACDOCAのデータを取得して
  "SORT命令で並び替えて
  "DELETE命令で重複を削除する
  SELECT
      ACDOCA~RBUKRS
    FROM ACDOCA
    INTO TABLE @DATA(LDT_DATA).
  SORT LDT_DATA ASCENDING BY
    RBUKRS.
  DELETE ADJACENT DUPLICATES FROM LDT_DATA COMPARING
    RBUKRS.
*
*  "A-2
*  "ACDOCAのデータをORDER BY句で並び替えて取得して
*  "DELETE命令で重複を削除する
*  SELECT
*      ACDOCA~RBUKRS
*    FROM ACDOCA
*    ORDER BY
*      ACDOCA~RBUKRS
*    INTO TABLE @DATA(LDT_DATA).
*  DELETE ADJACENT DUPLICATES FROM LDT_DATA COMPARING
*    RBUKRS.
*
*  "B-1
*  "ACDOCAのデータをDISTINCT句で重複削除して
*  "ORDER BY句で並び替えて取得する
*  SELECT DISTINCT
*      ACDOCA~RBUKRS
*    FROM ACDOCA
*    ORDER BY
*      ACDOCA~RBUKRS
*    INTO TABLE @DATA(LDT_DATA).
*
*  "B-2
*  "ACDOCAのデータをGROUP BY句で重複削除して
*  "ORDER BY句で並び替えて取得する
*  SELECT
*      ACDOCA~RBUKRS
*    FROM ACDOCA
*    GROUP BY
*      ACDOCA~RBUKRS
*    ORDER BY
*      ACDOCA~RBUKRS
*    INTO TABLE @DATA(LDT_DATA).
*
*  "C-1
*  "番外編
*  "ACDOCAのデータを取得して
*  "LOOP + BINARY SEARCHで、重複削除および並び替えする
*  SELECT
*      ACDOCA~RBUKRS
*    FROM ACDOCA
*    INTO TABLE @DATA(LDT_DATA_1).
*  DATA LDT_DATA LIKE LDT_DATA_1.
*  LOOP AT LDT_DATA_1 ASSIGNING FIELD-SYMBOL(<LFS_DATA_1>).
*    READ TABLE LDT_DATA
*        TRANSPORTING NO FIELDS
*        WITH KEY RBUKRS = <LFS_DATA_1>-RBUKRS
*        BINARY SEARCH.
*    IF SY-SUBRC <> 0.
*      INSERT <LFS_DATA_1>
*          INTO LDT_DATA
*          INDEX SY-TABIX.
*      CONTINUE.
*    ENDIF.
*  ENDLOOP.
  LDO_H_FLOW->CREATE_TEXT( TEXT = 'タイムスタンプ(UTC)終了と開始の差' ).
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 4  COLUMN = 2 ).
  DATA LDF_TIMESTAMP_SA TYPE TIMESTAMPL.
  LDF_TIMESTAMP_SA = LDF_TIMESTAMP_END - LDF_TIMESTAMP_START.
  DATA LDF_SA TYPE CHAR30.
  WRITE LDF_TIMESTAMP_SA TIME ZONE 'JAPAN' TO LDF_SA.
  LDO_H_FLOW->CREATE_TEXT( TEXT = LDF_TIMESTAMP_SA ).

  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 5  COLUMN = 1 ).
  LDO_H_FLOW->CREATE_TEXT( TEXT = '入力件数:ACDOCA' ).
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 5  COLUMN = 2 ).
  SELECT COUNT(*) FROM ACDOCA INTO @DATA(LDF_INPUT_COUNT).
  LDO_H_FLOW->CREATE_TEXT( TEXT = LDF_INPUT_COUNT ).

  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 6  COLUMN = 1 ).
  LDO_H_FLOW->CREATE_TEXT( TEXT = '出力件数' ).
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 6  COLUMN = 2 ).


  LDO_H_FLOW->CREATE_TEXT( TEXT = LINES( LDT_DATA ) ).

  LDO_ALV->SET_TOP_OF_LIST( LDO_HEADER ).
  LDO_ALV->SET_TOP_OF_LIST_PRINT( LDO_HEADER ).

  LDO_ALV->DISPLAY( ).

  POF_SUBRC = 0.
ENDFORM.

参考までに、Non-SAP HANA(Oracle 10.2)で動作するSAP ERP 6.0で同様の検証をしてみました。
以下ソースコードは、ABAP 7.31で動作しています。そのため、Classic Open SQLで記述しています。
ACDOCAは無いので、BKPFで実装しました。

BKPF:32万レコード→実行結果:150レコード
A-1:0.21sec
A-2:0.15sec
B-1:0.10sec
B-2:0.11sec
C-1:0.22sec

データ量が少なかったのでドラスティックな差は出ませんでしたが、Non-PushdownのA-1、C-1が大体0.2sec、PushdownのB-1、B-2が大体0.1sec、半分ずつのA-2が結果も中間の0.15secとなりました。さすがにOracleも10.2くらい新しいと、Pushdownした方が良好な結果になりました。実際は、ITABを高速に扱うのは高度なスキルが必要と思います。過不足なくBINARY SEARCHを完璧に使えているプログラムはほとんどないと思います。だから実際はもっと差がつくと思います。

REPORT SQL_Console.
*-----------------------------------------------------------------------
* GLOBAL VARIABLE
*-----------------------------------------------------------------------
DATA GDF_SUBRC TYPE SY-SUBRC.

*-----------------------------------------------------------------------
* CONDITION VIEW
*-----------------------------------------------------------------------
DATA GDF_CHAR1 TYPE CHAR10.
TYPES GTR_CHAR1 LIKE RANGE OF GDF_CHAR1.
SELECT-OPTIONS S_CHAR1 FOR GDF_CHAR1.

DATA GDF_CHAR2 TYPE CHAR10.
TYPES GTR_CHAR2 LIKE RANGE OF GDF_CHAR2.
SELECT-OPTIONS S_CHAR2 FOR GDF_CHAR2.

DATA GDF_NUMC1 TYPE NUMC08.
TYPES GTR_NUMC1 LIKE RANGE OF GDF_NUMC1.
SELECT-OPTIONS S_NUMC1 FOR GDF_NUMC1.

DATA GDF_NUMC2 TYPE NUMC08.
TYPES GTR_NUMC2 LIKE RANGE OF GDF_NUMC2.
SELECT-OPTIONS S_NUMC2 FOR GDF_NUMC2.

DATA GDF_DATS1 TYPE DATS.
TYPES GTR_DATS1 LIKE RANGE OF GDF_DATS1.
SELECT-OPTIONS S_DATS1 FOR GDF_DATS1.

DATA GDF_DATS2 TYPE DATS.
TYPES GTR_DATS2 LIKE RANGE OF GDF_DATS2.
SELECT-OPTIONS S_DATS2 FOR GDF_DATS2.

DATA GDF_TIMS1 TYPE TIMS.
TYPES GTR_TIMS1 LIKE RANGE OF GDF_TIMS1.
SELECT-OPTIONS S_TIMS1 FOR GDF_TIMS1.

DATA GDF_TIMS2 TYPE TIMS.
TYPES GTR_TIMS2 LIKE RANGE OF GDF_TIMS2.
SELECT-OPTIONS S_TIMS2 FOR GDF_TIMS2.

*-----------------------------------------------------------------------
* START-OF-SELECTION
*-----------------------------------------------------------------------
START-OF-SELECTION.
  PERFORM START_OF_SELECTION
    USING
      S_CHAR1[]
      S_CHAR2[]
      S_NUMC1[]
      S_NUMC2[]
      S_DATS1[]
      S_DATS2[]
      S_TIMS1[]
      S_TIMS2[]
    CHANGING
      GDF_SUBRC.

*-----------------------------------------------------------------------
* SUBROUTINE
*-----------------------------------------------------------------------
FORM START_OF_SELECTION
    USING
      PIR_CHAR1 TYPE GTR_CHAR1
      PIR_CHAR2 TYPE GTR_CHAR2
      PIR_NUMC1 TYPE GTR_NUMC1
      PIR_NUMC2 TYPE GTR_NUMC2
      PIR_DATS1 TYPE GTR_DATS1
      PIR_DATS2 TYPE GTR_DATS2
      PIR_TIMS1 TYPE GTR_TIMS1
      PIR_TIMS2 TYPE GTR_TIMS2
    CHANGING
      POF_SUBRC TYPE SY-SUBRC.
*-----------------------------------------------------------------------
  DATA LDF_TIMESTAMP_START TYPE TIMESTAMPL.       "タイムスタンプデータ取得開始
  GET TIME STAMP FIELD LDF_TIMESTAMP_START.       "タイムスタンプデータ取得開始
*-----------------------------------------------------------------------
* Please write the SQL statement here ☆
* Set the internal table to be stored to LDT_DATA ☆
* Inline definition of LDT_DATA is fun ☆

  "BKPFに登録済みの会社コードを並び替えて一覧出力する

  TYPES:
    BEGIN OF LTS_DATA,
      BUKRS TYPE BSEG-BUKRS,
    END OF LTS_DATA,
    LTT_DATA TYPE STANDARD TABLE OF LTS_DATA.
  DATA:
    LDT_DATA TYPE LTT_DATA.

  "A-1
  "BKPFのデータを取得して
  "SORT命令で並び替えて
  "DELETE命令で重複を削除する
  SELECT
      BKPF~BUKRS
    INTO TABLE LDT_DATA
    FROM BKPF.
  SORT LDT_DATA ASCENDING BY
    BUKRS.
  DELETE ADJACENT DUPLICATES FROM LDT_DATA COMPARING
    BUKRS.
*
*  "A-2
*  "BKPFのデータをORDER BY句で並び替えて取得して
*  "DELETE命令で重複を削除する
*  SELECT
*      BKPF~BUKRS
*    INTO TABLE LDT_DATA
*    FROM BKPF
*    ORDER BY
*      BKPF~BUKRS.
*  DELETE ADJACENT DUPLICATES FROM LDT_DATA COMPARING
*    BUKRS.
*
*  "B-1
*  "BKPFのデータをDISTINCT句で重複削除してORDER BY句で並び替えて取得する
*  SELECT DISTINCT
*      BKPF~BUKRS
*    INTO TABLE LDT_DATA
*    FROM BKPF
*    ORDER BY
*      BKPF~BUKRS.
*
*  "B-2
*  "BKPFのデータをGROUP BY句で重複削除してORDER BY句で並び替えて取得する
*  SELECT
*      BKPF~BUKRS
*    INTO TABLE LDT_DATA
*    FROM BKPF
*    GROUP BY
*      BKPF~BUKRS
*    ORDER BY
*      BKPF~BUKRS.
*
*  "C-1
*  "番外編
*  "BKPFのデータを取得して
*  "LOOP + BINARY SEARCHで、重複削除および並び替えする
*  DATA LDT_DATA_1 LIKE LDT_DATA.
*  FIELD-SYMBOLS <LFS_DATA_1> LIKE LINE OF LDT_DATA_1.
*  SELECT
*      BKPF~BUKRS
*    INTO TABLE LDT_DATA_1
*    FROM BKPF.
*  LOOP AT LDT_DATA_1 ASSIGNING <LFS_DATA_1>.
*    READ TABLE LDT_DATA
*        TRANSPORTING NO FIELDS
*        WITH KEY BUKRS = <LFS_DATA_1>-BUKRS
*        BINARY SEARCH.
*    IF SY-SUBRC <> 0.
*      INSERT <LFS_DATA_1>
*          INTO LDT_DATA
*          INDEX SY-TABIX.
*      CONTINUE.
*    ENDIF.
*  ENDLOOP.

*-----------------------------------------------------------------------
  DATA LDF_TIMESTAMP_END TYPE TIMESTAMPL.         "タイムスタンプデータ取得終了
  GET TIME STAMP FIELD LDF_TIMESTAMP_END.         "タイムスタンプデータ取得終了
*-----------------------------------------------------------------------

  DATA:
    LDO_ALV TYPE REF TO CL_SALV_TABLE,
    LDX_SALV_MSG TYPE REF TO CX_SALV_MSG.
  TRY.
      CL_SALV_TABLE=>FACTORY(
        IMPORTING
          R_SALV_TABLE = LDO_ALV
        CHANGING
          T_TABLE  = LDT_DATA ).
    CATCH CX_SALV_MSG INTO LDX_SALV_MSG.
      POF_SUBRC = 8.
      RETURN.
  ENDTRY.
  DATA LDO_FUNCTIONS TYPE REF TO CL_SALV_FUNCTIONS_LIST.
  LDO_FUNCTIONS = LDO_ALV->GET_FUNCTIONS( ).
  LDO_FUNCTIONS->SET_ALL( ).
  DATA LDO_SELECTIONS TYPE REF TO CL_SALV_SELECTIONS.
  LDO_SELECTIONS = LDO_ALV->GET_SELECTIONS( ).
  LDO_SELECTIONS->SET_SELECTION_MODE(
  EXPORTING
  VALUE = IF_SALV_C_SELECTION_MODE=>MULTIPLE ).

  "ヘッダ表示
  DATA LDO_HEADER TYPE REF TO CL_SALV_FORM_LAYOUT_GRID.
  CREATE OBJECT LDO_HEADER TYPE CL_SALV_FORM_LAYOUT_GRID.

  DATA LDO_H_LABEL TYPE REF TO CL_SALV_FORM_LABEL.
  LDO_H_LABEL = LDO_HEADER->CREATE_LABEL( ROW = 1 COLUMN = 1 ).
  LDO_H_LABEL->SET_TEXT( 'パフォーマンスデータ' ).

  DATA LDO_H_FLOW TYPE REF TO CL_SALV_FORM_LAYOUT_FLOW.
  CONSTANTS LCF_TIMEZONE TYPE TIMEZONE VALUE 'JAPAN'.

  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 2  COLUMN = 1 ).
  LDO_H_FLOW->CREATE_TEXT( TEXT = 'タイムスタンプ(UTC)データ取得開始' ).
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 2  COLUMN = 2 ).
  DATA LDF_UTC_START TYPE CHAR30.
  WRITE LDF_TIMESTAMP_START TIME ZONE LCF_TIMEZONE TO LDF_UTC_START.
  LDO_H_FLOW->CREATE_TEXT( TEXT = LDF_UTC_START ).

  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 3  COLUMN = 1 ).
  LDO_H_FLOW->CREATE_TEXT( TEXT = 'タイムスタンプ(UTC)データ取得終了' ).
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 3  COLUMN = 2 ).
  DATA LDF_UTC_END TYPE CHAR30.
  WRITE LDF_TIMESTAMP_END TIME ZONE LCF_TIMEZONE TO LDF_UTC_END.
  LDO_H_FLOW->CREATE_TEXT( TEXT = LDF_UTC_END ).

  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 4  COLUMN = 1 ).
  LDO_H_FLOW->CREATE_TEXT( TEXT = 'タイムスタンプ(UTC)終了と開始の差' ).
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 4  COLUMN = 2 ).
  DATA LDF_TIMESTAMP_SA TYPE TIMESTAMPL.
  LDF_TIMESTAMP_SA = LDF_TIMESTAMP_END - LDF_TIMESTAMP_START.
  DATA LDF_SA TYPE CHAR30.
  WRITE LDF_TIMESTAMP_SA TIME ZONE LCF_TIMEZONE TO LDF_SA.
  LDO_H_FLOW->CREATE_TEXT( TEXT = LDF_TIMESTAMP_SA ).

  DATA LDF_COUNT TYPE INT4.
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 5  COLUMN = 1 ).
  LDO_H_FLOW->CREATE_TEXT( TEXT = '入力件数' ).
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 5  COLUMN = 2 ).
  SELECT COUNT(*) FROM BKPF INTO LDF_COUNT.
  LDO_H_FLOW->CREATE_TEXT( TEXT = LDF_COUNT ).

  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 6  COLUMN = 1 ).
  LDO_H_FLOW->CREATE_TEXT( TEXT = '出力件数' ).
  LDO_H_FLOW = LDO_HEADER->CREATE_FLOW( ROW = 6  COLUMN = 2 ).
  DESCRIBE TABLE LDT_DATA LINES LDF_COUNT.
  LDO_H_FLOW->CREATE_TEXT( TEXT = LDF_COUNT ).

  LDO_ALV->SET_TOP_OF_LIST( LDO_HEADER ).
  LDO_ALV->SET_TOP_OF_LIST_PRINT( LDO_HEADER ).

  LDO_ALV->DISPLAY( ).

  POF_SUBRC = 0.
ENDFORM.                    "START_OF_SELECTION

当初は、DISTINCTとGROUP BYのどちらが速いか?をテーマにしようと思っていたのですが、急遽変更しました。

5年くらい前はGROUP BYの圧勝だったんだけれど、今回なんかねぇ~あまり差がつかなかったんだよね~。

以上

検索キーワード
ABAP
ABAP 7.31
ABAP 7.4
ABAP 7.50
ABAP 7.51
ABAP 7.52
SQL
Open SQL
New Open SQL
Classic Open SQL
ABAP SQL
SAP HANA
Code to Data
Code Pushdown
SAP ERP 6.0
SAP S/4HANA

最近のヘビロテです。
www.youtube.com