ABAP 动态编程:通过字段符号和数据引用输出任意数据库表内容

介绍

在 SAP 中,与"静态"(static)编程相比,ABAP 程序可以包括动态(dynamic)和静态部分。

动态编程是一个非常大的话题。它可以简单地定义为一种其行为/影响仅在执行时才明显的技术。使用 ABAP 进行动态编程有许多方面。其中包括通用编程 ,如动态源代码生成。此外,使用字符串变量动态指定开放式 SQL 语句(子句)的部分内容,以及根据运行时才知道的类型创建数据对象,都属于动态编程的范畴。使用动态编程时,我们有时可能还需要在运行时确定数据对象的数据类型。这就是所谓的运行时类型标识(Runtime Type Identification,RTTI)。

这一点有点类似编译型编程语言(C、Java)和解释型编程语言(Python)。前者通过定义数据类型和代码中的静态属性来提供所有属性,而后者的属性、名称、类型不是在编译时确定的,而是在运行时确定的。

ABAP 语言提供了许多新功能/特点来用于使程序动态化或避免对数值进行硬编码。我们将从字段符号(Field Symbols)和对引用的了解开始。

例如,我们将创建一个程序,输入表格名称并打印其内容。在了解动态编程之前,我们可以回这样写:

ABAP 复制代码
REPORT zprint_test.

PARAMETERS: p_table(20) OBLIGATORY .  "表名

SELECT *
  FROM (p_table)
  INTO TABLE @DATA(some_itab).

PERFORM frm_show_data USING some_itab.

字段符号和数据引用相关知识介绍

字段符号(Field symbols)和数据引用(data references)是动态编程的重要组合。数据引用是存储在引用变量中的数据对象的地址。字段符号是占位符,或这些数据对象的符号表示。

字段符号

字段符号的概念

  • 是几乎任何数据对象或现有数据对象的一部分的符号名称。
  • 可以在程序运行时分配实际内存区域(使用 ASSIGN)。请注意,如果字段符号之前确实已分配,则只能使用字段符号。
  • 可用作操作数位置上数据对象的占位符:假设程序中有一个数据对象。此外,还可以使用字段符号来分配此数据对象的内存区域。访问字段符号就像访问命名数据对象或对象本身的一部分一样。
  • 不要在程序的数据区域中保留物理空间,例如数据对象。相反,它们充当特定数据对象或对象的一部分所在的内存区域的动态标识符。
  • 可以使用泛型数据类型或完整数据类型进行类型化。
  • 使用语句 FIELD-SYMBOLS 或声明运算符 FIELD-SYMBOL 进行声明。他们的名字必须包含在尖括号(<>)之间。

语法:

  • 用于声明完整的类型
ABAP 复制代码
FIELD-SYMBOLS: <fs_i>        TYPE i,
               <fs_fli>      TYPE zdemo_abap_fli,
               <fs_tab_type> TYPE LINE OF some_table_type,
               <fs_like>     LIKE some_data_object.
  • 用于声明通用类型
ABAP 复制代码
FIELD-SYMBOLS <fs_c>         TYPE c.           "Text field with a generic length
FIELD-SYMBOLS <fs_cseq>      TYPE csequence.   "Text-like (c, string)
FIELD-SYMBOLS <fs_data>      TYPE data.        "Any data type
FIELD-SYMBOLS <fs_any_table> TYPE any table.   "Internal table with any table type
  • 分配数据对象

ASSIGN 语句将数据对象的内存区域分配给字段符号。分配内存区域后,即可处理内容。

xml 复制代码
FIELD-SYMBOLS: <fs_banks> TYPE banks,
                 <fs_swift> TYPE swift,
                 <fs_banka> TYPE banka,
                 <fs_excel> LIKE gs_excel,
                 <fs_icon>  TYPE char10,
                 <fs_type>  TYPE char01,
                 <fs_msg>   TYPE char128,
                 <fs_bankl> TYPE bankk,
                 <fs_regio> TYPE regio,
                 <fs_bnklz> TYPE bankk.

DATA: lv_count TYPE i.
LOOP AT <fs_out_tab>[] ASSIGNING <fs_out>.

    UNASSIGN: <fs_excel>, <fs_icon>, <fs_type>, <fs_msg>, <fs_bankl>, <fs_regio>, <fs_bnklz>.

    ASSIGN COMPONENT 'BANKS' OF STRUCTURE <fs_out> TO <fs_banks>.
    ASSIGN COMPONENT 'SWIFT' OF STRUCTURE <fs_out> TO <fs_swift>.
    ASSIGN COMPONENT 'BANKA' OF STRUCTURE <fs_out> TO <fs_banka>.
    ASSIGN COMPONENT 'ICON' OF STRUCTURE <fs_out> TO <fs_icon>.
    ASSIGN COMPONENT 'TYPE' OF STRUCTURE <fs_out> TO <fs_type>.
    ASSIGN COMPONENT 'MSG' OF STRUCTURE <fs_out> TO <fs_msg>.
    ASSIGN COMPONENT 'BANKL' OF STRUCTURE <fs_out> TO <fs_bankl>.
    ASSIGN COMPONENT 'PROVZ' OF STRUCTURE <fs_out> TO <fs_regio>.
    ASSIGN COMPONENT 'BNKLZ' OF STRUCTURE <fs_out> TO <fs_bnklz>.
    
ENDLOOP.

如果使用未赋值的字段符号,则会出现异常。在使用前,可以用下面的逻辑表达式检查赋值情况。如果字段符号已赋值,则语句为真。

vbnet 复制代码
IF <fs> IS ASSIGNED.
  ...
ENDIF.

DATA(check) = COND #( WHEN <fs> IS ASSIGNED THEN `assigned` ELSE `not assigned` ).
  • 取消分配 UNASSIGN

使用语句 UNASSIGN,可以显式删除字段符号的赋值。CLEAR 语句仅初始化值。

ABAP 复制代码
UNASSIGN <fs>.

数据引用

数据引用概念:

  • 是包含引用的数据对象。
  • 是"不透明的",即无法直接访问所包含的引用。若要访问内容,必须先取消引用这些变量。
  • 是深层数据对象,如字符串和内部表。
  • 键入附加的语句 REF TO,后跟静态类型。请注意此上下文中的动态类型:此类变量的动态类型是它实际指向的数据类型。这个概念在分配的上下文中特别重要。
  • 可以使用完整类型或泛型类型进行类型化。但是,只有 data 可以用作泛型类型。

声明数据引用变量

  • 显示声明:使用静态类型的数据引用变量声明示例。静态类型可以是完整的,也可以是通用的(但只能使用数据)请注意,它们尚未指向数据对象。在此阶段初始引用变量包含空引用。
ABAP 复制代码
DATA: ref_a TYPE REF TO i,                 "Complete data type
      ref_b TYPE REF TO some_dbtab,        "Complete data type
      ref_c LIKE REF TO some_data_object,
      ref_d TYPE REF TO data,              "Generic data type
      ref_e LIKE ref_a.                    "Referring to an existing data reference variable  
  • 内联声明 :使用引用运算符 REF 来进行内联声明
ABAP 复制代码
DATA: dy_table TYPE REF TO data,
      dy_line  TYPE REF TO data.

DATA ref1    TYPE REF TO i.

使用字段符合和数据应用打印动态数据库表内容

声明用于输入要访问其数据的表格名称。此外,我们还要输入要显示的行数和列数(字段)。 1.

ABAP 复制代码
PARAMETERS: p_tab(30) DEFAULT 'MARA',  
            row            TYPE i,  
            columns        TYPE i.
  1. 为内部表、表行和表字段声明字段符号。此外,还定义了内部表和结构的数据引用变量:
ABAP 复制代码
DATA: tab_ref    TYPE REF TO data,
      struct_ref TYPE REF TO data.

FIELD-SYMBOLS: <my_struct> TYPE any,
               <my_field>  TYPE any,
               <my_itab>   TYPE ANY TABLE.
  1. 我们放置了一个小的检查 check 语句,以确保只有当用户输入的列值等于一个或多个时,程序才会运行。
css 复制代码
CHECK columns GE 1.
  1. 接下来,我们使用创建数据语句创建数据对象。然后,我们取消引用并将它们分别赋值给占位符(字段符号)。如果用户输入的表名有误,就会产生 cx_sy_create_data_error 异常,因此我们需要在编码中使用 catch 语句捕获该异常。
ABAP 复制代码
TRY.
    CREATE DATA tab_ref TYPE SORTED TABLE OF (p_tab)
          WITH NON-UNIQUE DEFAULT KEY.

    ASSIGN tab_ref->* TO <my_itab>.

    CREATE DATA struct_ref TYPE (p_tab).
    ASSIGN struct_ref->* TO <my_struct>.

  CATCH cx_sy_create_data_error.
    WRITE :/ 'The Table Name does not exist!'.

ENDTRY.
  1. 然后,编写 SELECT 语句以获取数据。参数中的名称作为表名,并读入字段符号 <my_itab> 所指向的内部表中。行数也根据用户输入指定。
SQL 复制代码
SELECT * FROM (p_tab) INTO TABLE <my_itab> UP TO row ROWS.
  1. 然后使用 describe_by_data 语句读取已创建的内部表行结构(由 my_struc 指向)的描述。我们声明了类 cl_abap_structdescr 的对象引用 descr。然后,调用 cl_abap_typedescr 类的静态方法 describe_by_data,并返回给 descr 变量。操作符 (?=) 用于向下传递描述对象 cl_abap_typedescr 的引用。
ini 复制代码
DATA: descr TYPE REF TO cl_abap_structdescr.

descr ?= cl_abap_typedescr=>describe_by_data( <my_struct> ).
  1. 由于数据库表中的每一列(字段)在 descr 对象的组件中都表示为一行,因此要对其运行一个循环。使用 from 1 to columns 只读取用户输入的列数。
  2. 还将创建一个位置 positions 内部表,用于保存屏幕上显示的每一列的位置(起始位置)。每个字段的长度用于查找下一个字段的位置。还包括打印表列标题的代码。
ABAP 复制代码
DATA: BEGIN OF positions OCCURS 0,
        position TYPE i,
      END OF positions.

DATA: end_position TYPE i.
DATA: wa_key TYPE LINE OF abap_compdescr_tab.

positions-position = 1.

LOOP AT descr->components INTO wa_key FROM 1 TO columns.
  WRITE AT positions-position '|'.
  IF wa_key-length LT 10.
    wa_key-length = 10.
  ENDIF.

  WRITE: wa_key-name.
  APPEND positions.
  positions-position = wa_key-length + positions-position.
ENDLOOP.

end_position = positions-position + 1.
WRITE AT end_position '|'.
  1. 然后,写入读取表格内容的主要部分。然后在字段符号 my_itab 指向的数据内部表上运行循环。每一行还运行一个 do 循环,对每个字段值使用赋值语句。读取表的位置值,以获得相应字段列的正确位置。
ABAP 复制代码
LOOP AT <my_itab> INTO <my_struct>.
  NEW-LINE.

  DO columns TIMES.
    READ TABLE positions INDEX sy-index.
    ASSIGN COMPONENT sy-index OF STRUCTURE <my_struct> TO <my_field>.
    IF sy-subrc = 0.
      WRITE AT positions-position '|'.
      WRITE: <my_field>.
    ELSE.
      EXIT.
    ENDIF.

    WRITE: AT end_position '|'.
  ENDDO.
ENDLOOP.
NEW-LINE.
ULINE AT 1(end_position).

测试运行

在数据浏览器程序中,语句读取用户输入的任何表格名称以及输入的行数和列数,然后显示表格中的相关数据。让我们看看程序代码是如何工作的。

参数语句向用户显示选择屏幕,并输入表名、表行和表列。

cl_abap_typedescr 类的静态方法 describe_by_data 提供了与传递的结构相关的描述对象。

拓宽 cast 方法用于将返回的对象存储到 cl_abap_structdescr 类型的 descr 变量中(因为 cl_abap_typedescr 类是 cl_abap_structdescr 的超类,而 cl_abap_typedescr 是一个抽象类)。

descr 对象包含一个组件内部表组件。然后在组件表上执行循环,以打印作为列标题的表字段名称。字段的长度也在考虑之列。例如,如果输入表格 MARA,则各种表格字段的长度和名称将存在于 DESCR->COMPONENTS 中。位置表也会在此循环中填入,以存储每个字段的适当位置。这将在以后使用,以便在相应的列标题(字段名)下打印正确的数据。

最后,运行主循环。循环在内部表上执行,该表包含相关数据库表的所有行。在 do 循环中,处理表行的组件(字段)并将其分配给字段符号 <my_field>do 循环的运行次数等于用户要求的表字段数。然后输出字段的值。处理完所有要求的字段后,退出 do 循环。在 do 循环中,将读取之前填写的位置表,以获得特定单元格的正确位置。

通过位置表和 uline 语句,您可以为输出设置方框形状。

参考链接:

相关推荐
冷眼Σ(-᷅_-᷄๑)40 分钟前
如何将Asp.net Core站点部署到CentOS
后端·centos·asp.net
St_Ludwig40 分钟前
C语言小撰特殊篇-assert断言函数
c语言·c++·后端·算法
techdashen1 小时前
Go与黑客(第四部分)
开发语言·后端·golang
河北小田1 小时前
基于 Java 注解实现 WebSocket 服务器端
后端·websocket·程序员
as_jopo2 小时前
-Dspring.profiles.active=dev与--spring.profiles.active=dev的区别
java·后端·spring
hummhumm2 小时前
第 32 章 - Go语言 部署与运维
java·运维·开发语言·后端·python·sql·golang
techdashen2 小时前
Go与黑客(第二部分)
开发语言·后端·golang
fa_lsyk2 小时前
Spring:AOP面向切面案例讲解AOP核心概念
java·后端·spring
尘浮生2 小时前
Java项目实战II基于SpringBoot的客户关系管理系统(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·小程序
2401_857610032 小时前
企业OA系统:Spring Boot技术实现与管理
java·spring boot·后端