使用结构化数据对象

学习目标

  • 使用结构化数据对象。

  • 在 ABAP SQL 中使用结构化数据对象。

结构化数据对象

要访问结构组件,必须在结构名称和组件名称之间放置一个减号 (-)。

注意

在部件选择器之前或之后不允许使用空格。

通过这种方式访问结构组件,您可以在任何可使用相同类型变量的操作数位置使用结构组件。上例中结构连接的组件 airport_from_id 的类型为 /DMO/AIRPORT_FROM_ID。因此,您可以在任何操作数位置使用此组件,在该位置可以使用 /DMO/AIRPORT_FROM_ID 类型的简单变量;不仅像示例中一样在值分配的左侧,还可以在方法调用的参数传递、SELECT 语句的 INTO 子句或 WHERE 子句等位置中使用该组件。

如果结构的组件本身是一个结构,则可以通过在主组件名称后再次使用组件选择器访问子组件。示例中的第一个值分配访问 MESSAGE 的组件 MSGTY,该组件本身是嵌套结构 CONNECTION_NESTED 的组件。

提示

您可以使用代码完成实施对结构组件的访问。将光标放在结构组件选择器之后,然后按 CTRL + SPACE 查看所有可用结构组件的清单。

VALUE #() 表达式是为结构化数据对象分配值的一种优雅方法。

如果要填充整个结构,可以像上一示例中所看到的那样,分别寻址每个组件。

但是,您还可以使用 VALUE #() 表达式来填充结构。表达式构造结构,用值填充结构,并将填充的结构赋给变量,在本例中为 connection。井号 (#) 指示 ABAP 运行时环境构造与目标变量 connection 类型相同的结构。在括号中,列出要填充的结构组件(不必全部填充)并为其分配值。

以这种方式填充结构时,运行时系统会先从结构中删除所有现有值,然后再使用表达式中的值对其进行充值。

注意

connection= VALUE #() 形式的分配。括号之间只有一个空格,使用类型特定的初始值填充结构的所有组件。这与语句 CLEAR connection 具有相同的效果。

在 ABAP 中,如果两个结构类型兼容,则只能使用注释 <target structure> = <source structure> 将一个结构的内容直接复制到另一个结构中。通常,仅当两个结构具有相同类型时,才会出现这种情况。如果结构具有不同的类型,则可能发生两种情况:

  • 如果其中一个结构在另一个结构具有字符型组件的位置具有非字符型组件,则直接分配会导致语法错误。

  • 如果两个结构都是类似字符的,或者换句话说,两个结构仅由类似字符的组件组成,则技术上可以进行直接分配。但通常,结果会是错误的。

在此示例中,源结构和目标结构类似 char-like。因此,技术上可以进行直接分配。但由于它们不兼容,因此结果错误:将组件 carrier_name 的内容复制到目标结构中的组件 message

由于没有语法错误,因此在使用不兼容的类似字符的结构时必须格外小心。

在结构之间复制数据时,通常希望将信息从一个字段复制到目标结构的相应字段 - airport_from_idairport_from_idairport_to_idairport_to_id 等。要在 ABAP 中实现此目的,请使用 CORRESPONDING 表达式。这会将值从 <source_structure> 分配到相应的 ,即 <target_structure> 的同名组件。您必须记住以下几点:

  • 字段必须具有相同的名称。

  • 在两个结构中,组件不必处于相同的位置或顺序。

  • 如果字段具有不同的类型,ABAP 会根据预定义的规则集尝试类型转换。

示例中的 CORRESPONDING 表达式创建类型为 st_connection_nested 的结构。然后,将来自 connection 结构的同名组件的值分配到此结构。所有其他组件保持初始值。从而构建的结构随后将分配到 connection_nested 结构。这将为 message 组件分配初始值 。

试用:访问结构化数据对象

  1. 与本课程的第一个练习一样,创建一个实施接口 IF_OO_ADT_CLASSRUN 的新全局类。

  2. 将以下代码片段复制到方法 if_oo_adt_classrun~main() 的实施部分:

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  }| ).
  1. 按 CTRL + F3 激活类,按 F9 执行控制台应用。

  2. 分析控制台输出。调试程序,使用源代码熟悉概念。

ABAP SQL 中的结构化数据对象

仅当结构组件的数量和类型与 FIELDS 子句中指定的列的数量和类型相对应时,SELECT 语句的 INTO 子句才会正常工作。在上述示例中,仅当目标结构连接有三个与 FIELDS 子句中列出的 DepartureAirport、DestinationAirport 和 _AirlineName 列具有相同类型和长度的组件时, 语句才能工作。请注意,在这种情况下,名称不必相同 - 系统会从左到右填充目标结构。

如果 FIELDS 子句中的字段列表与 INTO 子句中的结构或表行类型不匹配,则会发生运行时错误。

该示例显示了确保目标结构与字段选择匹配的简单技术:

  • 目标结构使用 CDS 视图 /DMO/I_Connection 键入,该视图是 FROM 子句中的数据源。

  • 关键字 FIELDS 后的星号 (*) 是简短表示法,用于确保视图的所有字段都是字段选择的一部分。忽略公开的关联。

注意

当您直接从数据库表中读取时,此技术也可用。与 CDS 视图定义一样,数据库表定义也可以用作 ABAP 中的全局结构类型。

此技术的主要优势是,即使您或其他人对 CDS 视图或数据库表进行更改,SELECT 语句在语法上仍保持不变。最重要的缺点是,您始终从数据库中读取所有字段,无论实际是否需要这些字段。

注意

仅当实际需要所有字段时,才将此技术用于包含少量字段的视图和表。从数据库中读取不必要的数据是导致性能问题的主要原因。

避免语法错误的另一种方法是变式 INTO CORRESPONDING FIELDS。此变式与您之前了解的 CORRESPONDING #() 运算符具有相同的效果。它确保在名称相同的组件之间复制数据。通过根据需要定义结构类型,您可以确保仅读取所需数据。

同样,只有名称必须相同。但为避免出现问题,应确保名称相同的组件具有兼容类型。否则,系统会尝试将源字段的内容转换为目标字段的类型。这可能会导致数据丢失或(可捕获)运行时错误。

如果数据源中的字段名称和目标结构中的组件名称不匹配,则 FIELDS * 和 INTO CORRESPONDING FIELDS OF 的组合不起作用。

如果要保留变式 INTO CORRESPONDING FIELDS OF,可以在字段列表中为所选字段定义别名。为此,在字段名称后添加附加项 AS,后跟别名。在此示例中,视图字段 DepartureAirport 的别名是 airport_from_id,路径表达式 _AirlineName 的别名是 carrier_name。根据此别名,INTO CORRESPONDING FIELDS OF 正确标识存储所检索数据的结构组件。

避免字段选择与目标结构之间冲突的最简单技术是 INTO 子句中的内联声明。内联声明结构的序列、类型和名称派生自 FIELDS 子句。因此,目标结构始终适合字段选择。

注意

仅在 INTO 之后支持内嵌声明。INTO CORRESPONDING FIELDS OF 之后无法使用内联声明。

在此示例中,字段 DepartureAirport 没有别名。字段名称用作结构 connection_inline 中的组件名称。字段 DestinationAirport以及路径表达式 \_Airline-Name 具有可选别名(ArrivalAirportAirlineName)。此处的别名用作组件名称。

使用关系数据库时,您经常会遇到必须从不同数据库表中读取相关数据的问题。我们已经了解到,CDS 视图中的关联是执行此任务的一种优雅方式。

如果不存在具有合适关联的 CDS 视图,则可以实施 SQL 连接。上述示例说明了连接原则:

我们感兴趣的是航班连接及其相互连接的机场。我们在数据库表 /DMO/CONNECTION 中找到机场的 3 个字母标识。机场的全名存储在数据库表 /DMO/AIRPORT 中。

要使用出发机场名称检索连接,我们会在一个 SELECT 语句中读取数据库表 /DMO/CONNECTION 中的连接数据并将其与数据库表 /DMO/AIRPORT 连接。

连接由以下构件块组成:

数据源

要相互连接的数据库表和视图。单个联接始终将左侧数据源与右侧数据源相结合。在上述示例中,表 /DMO/CONNECTION 是左侧数据源,表 /DMO/AIRPORT 是右侧数据源。ABAP SQL 还支持连接连接(嵌套连接)

连接条件

连接条件指定右侧数据源的哪些记录属于左侧数据源中的记录。在上述示例中,相关出发机场由 CLIENT 和 AIRPORT_ID 列中的值标识。连接条件为:

/DMO/CONNECTION~CLIENT = /DMO/AIRPORT~CLIENT

AND

/DMO/CONNECTION~AIRPORT_FROM_ID = /DMO/AIRPORT~AIRPORT_ID

连接类型

如果其中一个数据源不包含匹配的记录,则联接类型会对结果产生影响。ABAP SQL 当前支持 INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 和 CROSS JOIN。最常见的连接类型为 LEFT OUTER JOIN。

该图显示了用于连接的 ABAP SQL 语法。在 FROM 子句中,连接类型由左侧数据源 /dmo/connection 和右侧数据源 /dmo/airport 之间的关键字 LEFT OUTER JOIN 指定。语法为数据源引入了别名 c 和 f。数据源的别名是可选的,除非数据源在联接中出现多次。

连接条件在关键字 ON 之后。数据源或其别名与 字段之间的分隔符为波浪号 (~)。

注意

在 ABAP SQL 中,无需提及客户端字段。在将语句发送到数据库之前,数据库接口会将它们添加到连接条件中。如果 FROM 子句定义连接,则可以在 FIELDS 和 WHERE 子句中使用来自两个数据源的字段。

在此示例中,SELECT 语句不仅读取出发机场名称,还读取目的地机场名称。为此,FROM 子句定义一个嵌套连接:

第一个联接是表 /dmo/connection 和 /dmo/airport 的左外联接,为右侧数据源引入别名"f"(如"from")。然后,第一个联接用作以表 /dmo/airport 作为右侧数据源的第二个左外联接的左侧数据源。请注意,在这种情况下,别名 "t"(表示"至")对于区分表 /dmo/airport 与前一个表的外观至关重要。

FIELDS 子句列出了两个数据源中的机场名称,引入了别名 airport_from_name 和 airport_to_name 以将其相互区分。

提示

第一个连接周围的括号是可选的。如果省略它们,则自左到右计算 from 子句中的连接。

试用:ABAP SQL 中的结构化数据对象

  1. 与本课程的第一个练习一样,创建一个实施接口 IF_OO_ADT_CLASSRUN 的新全局类。

  2. 将以下代码片段复制到方法 if_oo_adt_classrun~main() 的实施部分:

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 ).
  1. 按 CTRL + F3 激活类,按 F9 执行控制台应用。

  2. 分析控制台输出。调试程序,使用源代码熟悉概念。

使用结构化数据对象

在本练习中,声明结构化属性,使用 SELECT 语句填充该属性并访问结构组件。

模板:

  • /LRN/CL_S4D400_DBS_CDS(全局类)

解决方案:

  • /LRN/CL_S4D400_STS_STRUCTURE(全局类)

任务 1: 复制模板

复制模板类。或者,复制上一练习的解决方案。

步骤

  1. 将类 /LRN/CL_S4D400_DBS_CDS 复制到您自己的包中的类(建议名称:ZCL_##_STRUCTURE,其中 ## 代表组编号)。

  2. 打开全局类 /LRN/CL_S4D400_DBS_CDS 的源代码。

  3. 将项目浏览器视图与编辑器链接。

  4. 在项目浏览器视图中,右键单击类 /LRN/CL_S4D400_DBS_CDS 以打开上下文菜单。

  5. 从上下文菜单中,选择复制 ....

  6. 在包字段中输入的名称。在名称字段中,输入名称 ZCL_##_STRUCTURE,其中 ## 代表组编号。

  7. 调整描述,然后选择下一步

  8. 确认传输请求并选择完成

任务 2: 声明结构化数据对象

在局部类中,声明结构化属性**详细信息以替换标量属性 airport_from_idairport_to_id** 和 carrier_name。首先在本地类中定义私有结构类型 st_details

步骤

  1. 切换到局部类 lcl_connection

  2. 在全局类中,选择局部类型

  3. 使用以下组件定义专用结构类型 st_details

结构类型为 st_details 的组件:

组件名称

数据类型

出发机场

/dmo/airport_from_id

DestinationAirport

/dmo/airport_to_id

航空公司名称

/dmo/carrier_name

  1. PRIVATE SECTION. 行后,添加以下代码:

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.
  1. 注释或移除属性 airport_from_idairport_to_idcarrier_name 的声明。

  2. 选择包含三个 DATA 语句的行。

  3. Ctrl + < 在每个选定行前添加星号 (*)。

  4. 声明新的私有实例属性**详细信息**,并使用结构类型 st_details 键入。

  5. 如下调整代码:

*    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.

任务 3: 访问结构组件

在方法 get_output 中使用结构化属性**详细信息**的组件。

步骤

  1. 调整方法 get_output 的实施。使用属性**详细信息的相应组件替换对属性 airport_from_idairport_to_id** 和 carrier_name 的任何访问。

提示

不要手动输入组件名称!键入结构组件选择器 (-) 后,按 Ctrl + 空格获取所有组件的列表。

  1. 导航到方法 get_output 的实施。

  2. 按如下所示调整 APPEND 语句:

*    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.

任务 4: 在 SELECT 语句中填充结构化属性

在**构造函数**方法中,使用结构化属性作为 SELECT 语句的目标。

步骤

  1. 在**构造方法实施中调整 SELECT 语句。将 INTO 子句中的数据对象列表替换为结构化属性详细信息**。

  2. 导航到方法**构造**函数的实施。

  3. 按如下所示调整 SELECT 语句:

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.
  1. 可选:使用语法变式 INTO CORRESPONDING FIELDS OF @details.

  2. 在 SELECT 语句中,将 INTO @details.替换为以下代码:

INTO CORRESPONDING FIELDS OF @details.
  1. 为路径表达式添加别名 AirlineName

  2. SELECT 语句现在应如下所示:

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.
  1. 激活此类。执行并分析控制台输出。检查输出是否显示所有属性的数据。

  2. Ctrl + F3 激活类。

  3. F9 键运行类。