处理内部表的内容

学习目标

  • 处理内部表的内容。

SORT 语句

在标准内部表中,内容未排序。使用 APPEND 语句添加到表中的新条目将添加到表的末尾。

排序标准内部表

在本视频中,您将了解如何按键值、任意字段以及升序和降序对表进行排序。

视频:本节含 SAP 官方视频(视频 ID 1_042gv1s8),需在 learning.sap.com 在线观看。

试试:SORT 语句

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

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

TYPES t_flights TYPE STANDARD TABLE OF /dmo/flight
WITH NON-UNIQUE KEY carrier_id connection_id flight_date.
DATA  flights TYPE t_flights.
flights = VALUE #( ( client        = sy-mandt
carrier_id    = 'LH'
connection_id = '0400'
flight_date = '20230201'
plane_type_id = '747-400'
price = '600'
currency_code = 'EUR' )
( client = sy-mandt
carrier_id = 'LH'
connection_id = '0400'
flight_date = '20230115'
plane_type_id = '747-400'
price = '600' currency_code = 'EUR' )
( client = sy-mandt
carrier_id = 'QF'
connection_id = '0006'
flight_date = '20230112'
plane_type_id = 'A380'
price = '1600' currency_code = 'AUD' )
( client = sy-mandt
carrier_id = 'AA'
connection_id = '0017'
flight_date = '20230110'
plane_type_id = '747-400'
price = '600'
currency_code = 'USD' )
( client = sy-mandt
carrier_id = 'UA'
connection_id = '0900'
flight_date = '20230201'
plane_type_id = '777-200'
price = '600'
currency_code = 'USD' )
).
out->write( 'Contents Before Sort' ).
out->write( '____________________' ).
out->write( flights ).
out->write( ` ` ).
* Sort with no additions - sort by primary table key carrier_id connection_id flight_date
SORT flights.
out->write( 'Effect of SORT with no additions - sort by primary table key' ).
out->write( '____________________________________________________________' ).
out->write( flights ).
out->write( ` ` ).
* Sort with field list - default sort direction is ascending
SORT flights BY currency_code plane_type_id.
out->write( 'Effect of SORT with field list - ascending is default direction' ).
out->write( '________________________________________________________________' ).
out->write( flights ).
out->write( ` ` ).
* Sort with field list and sort directions.
SORT flights BY carrier_Id ASCENDING flight_Date DESCENDING.
out->write( 'Effect of SORT with field list and sort direction' ).
out->write( '_________________________________________________' ).
out->write( flights ).
out->write( ` ` ).
  1. 选择 CTRL + F3 激活类,然后选择 F9 将其作为控制台应用执行。

  2. 浏览源代码以熟悉概念。

删除重复记录

有时,您需要从内部表中删除重复条目。例如,在此示例中,carrier_idconnection_id 的每个组合在表中只能出现一次。

可以使用语句 DELETE ADJACENT DUPLICATES 从内部表中移除重复条目。但是,仅当条目相邻(即彼此相邻)时, 语句才有效。

首先对内部表进行排序

在使用 DELETE ADJACENT DUPLICATES 语句之前,必须根据要筛选出重复值的列对内部表进行排序 - 本例中为 carrier_idconnection_id

但是,即使重复的航班号现在彼此相邻,此表单中的 DELETE ADJACENT DUPLICATES 语句也不会删除任何内容。这是因为在识别重复条目时,语句的基本形式会考虑表的所有关键字段,并且由于两个 LH 0400 连接具有不同的值 flight_date,因此它们不算作重复条目。

要删除具有相同 carrier_idconnection_id 组合的条目,即使它们具有不同的值 flight_date,也必须使用 COMPARING 语句的 DELETE ADJACENT DUPLICATES 附加项。通过在 语句中使用 COMPARING carrier_id connection_id,可告知系统在识别重复条目时只考虑这些字段。

您可以在 COMPARING 附加中指定键值和非键值字段的任意组合,但必须首先根据相同字段对表进行排序。

此外还有 COMPARING ALL FIELDS。如果使用此选项,两个条目的整个表行必须相同,以便系统识别重复条目。

试用:删除重复记录

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

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

TYPES t_flights TYPE STANDARD TABLE OF /dmo/flight WITH NON-UNIQUE KEY carrier_id connection_id flight_date.
DATA: flights TYPE t_flights.
flights = VALUE #( ( client = sy-mandt carrier_id = 'LH' connection_id = '0400' flight_date = '20230201' plane_type_id = '747-400' price = '600' currency_code = 'EUR' )
( client = sy-mandt carrier_id = 'QF' connection_id = '0006' flight_date = '20230112' plane_type_id = 'A380' price = '1600' currency_code = 'AUD' )
( client = sy-mandt carrier_id = 'AA' connection_id = '0017' flight_date = '20230110' plane_type_id = '747-400' price = '600' currency_code = 'USD' )
( client = sy-mandt carrier_id = 'LH' connection_id = '0400' flight_date = '20230301' plane_type_id = '747-400' price = '600' currency_code = 'EUR' )
( client = sy-mandt carrier_id = 'UA' connection_id = '0900' flight_date = '20230201' plane_type_id = '777-200' price = '600' currency_code = 'USD' )
( client = sy-mandt carrier_id = 'QF' connection_id = '0006' flight_date = '20230210' plane_type_id = 'A380' price = '1600' currency_code = 'AUD' ) ).
out->write( 'Contents Before DELETE ADJACENT DUPLICATES' ).
out->write( '____________________' ).
out->write( flights ).
out->write( ` ` ).
DELETE ADJACENT DUPLICATES FROM flights.
out->write( 'Contents after DELETE ADJACENT DUPLICATES' ).
out->write( 'Nothing deleted - key values are not adjacent' ).
out->write( 'Sort the table before DELETE ADJACENT DUPLICATES').
out->write( flights ).
out->write( ` ` ).
SORT flights BY carrier_id connection_id flight_date.
DELETE ADJACENT DUPLICATES FROM flights.
out->write( 'Contents after DELETE ADJACENT DUPLICATES' ).
out->write( 'Nothing deleted - ABAP compares all key values including flight_date, which is different for every entry' ).
out->write( flights ).
out->write( ` ` ).
DELETE ADJACENT DUPLICATES FROM flights COMPARING carrier_id connection_id.
out->write( 'Contents after DELETE ADJACENT DUPLICATES with COMPARING and field list' ).
out->write( 'Entries with identical values of carrier_id and connection_id have been deleted' ).
out->write( flights ).
  1. 选择 CTRL + F3 激活类,然后选择 F9 将其作为控制台应用执行。

  2. 浏览源代码以熟悉概念。

表理解

通常需要使用其他内部表中的值填充内部表。有时,将数据从一个表复制到另一个表是一个简单的问题。有时,你需要从多个源表中读取数据来填充目标表,并且可以在计算值的过程中使用表达式。

ABAP 提供了执行这些任务的方法。如果仅将数据从一个表复制到另一个表,则可以使用 CORRESPONDING #( ) 表达式。对于更复杂的操作,您可以使用表理解。

若要在两个内部表的同名字段之间复制数据,请使用 CORRESPONDING 运算符。这与结构的 CORRESPONDING 类似 - 对于源内部表的每一行,系统在目标内部表中创建一个新行,并在名称相同的字段之间复制数据。不会复制目标中没有相同命名字段的源字段。源中没有相同命名字段的目标字段使用类型特定的初始值填充。

在示例中,源内部表运输公司包含三行。因此,在值分配后,目标内部表连接还包含三行。

字段 carrier_idcarrier_name 存在于两个内部表中。它们从源复制到目标。字段 currency_code 仅存在于源中。不会复制。字段 connection_idairport_from_idairport_to_id 仅存在于目标中。它们使用初始值填充。

注意:

如果目标内部表包含分配前的数据,系统会将其删除。

现在,让我们考虑一个更复杂的示例。在程序中,您有一个包含航班连接的内部表以及另一个包含机场名称和位置的内部表。您需要使用机场表中连接表和机场 airport_from_idcarrier_idconnection_idname 填充第三个内部表。您可以使用表理解执行此操作。

要从另一个内部表填充一个内部表,可以使用 VALUE 运算符和 FOR 表达式。FOR 在源表上实现循环,并将每行按顺序放入变量行中。然后,在括号中使用语法 component = VALUE 将值指派给新行的组成部分。这与使用 VALUE #( ) 填充结构时使用的语法完全相同。

以下是我们示例的代码。FOR line IN connections 遍历连接表,并将每行按顺序放入变量行中。在括号中,我们构造表 result_table 的每个新行。表达式的左侧是 result_table 列,右侧是我们要分配给它们的值。分别使用行 的组件 carrier_idconnection_iddeparture_airport 填充 carrier_idconnection_idairport_from_id 列。要填充列 departure_airport_name,我们使用表表达式查找内部表"机场"中的机场名称,使用行 的组件 airport_from_id 作为机场键值。

您可以通过使用 WHERE 并指定逻辑条件(例如,仅特定航空公司的转机)或使用附加项 FROMTO 指定行范围来限制源表的迭代。

试用:表理解

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

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

TYPES: BEGIN OF t_connection,
carrier_id TYPE /dmo/carrier_id,
connection_id TYPE /dmo/connection_id,
departure_airport TYPE /dmo/airport_from_id,
departure_airport_Name TYPE /dmo/airport_Name,
END OF t_connection.
TYPES t_connections TYPE STANDARD TABLE OF t_connection WITH NON-UNIQUE KEY carrier_id connection_id.
DATA connections TYPE TABLE OF /dmo/connection.
DATA airports TYPE TABLE OF /dmo/airport.
DATA result_table TYPE t_connections.
* Aim of the method:
* Read a list of connections from the database and use them to fill an internal table result_table.
* This contains some data from the table connections and adds the name of the departure airport.
SELECT FROM /dmo/airport FIELDS * INTO TABLE @airports.
SELECT FROM /dmo/connection FIELDS * INTO TABLE @connections.
out->write( 'Connection Table' ).
out->write( '________________' ).
out->write( connections ).
out->write( ` ` ).
* The VALUE expression iterates over the table connections. In each iteration, the variable line
* accesses the current line. Inside the parentheses, we build the next line of result_table by
* copying the values of line-carrier_Id, line-connection_Id and line-airport_from_id, then
* loooking up the airport name in the internal table airports using a table expression
result_table = VALUE #( FOR line IN connections ( carrier_Id = line-carrier_id
connection_id = line-connection_id
departure_airport = line-airport_from_id
departure_airport_name = airports[ airport_id = line-airport_from_id ]-name ) ).
out->write( 'Results' ).
out->write( '_______' ).
out->write( result_table ).
  1. 选择 CTRL + F3 激活类,然后选择 F9 将其作为控制台应用执行。

  2. 浏览源代码以熟悉概念。

表减少

有时,在迭代内部表时,需要汇总信息,例如,生成总和或行计数。

最简单的缩减形式在内部表上迭代,并返回基本值。扣减的 INIT 部分定义变量总计,其类型必须与总和类型兼容。FOR line IN flights 是内部表航班的迭代,其中每行按顺序放入工作区线。在 NEXT 表达式中,每行中的 seats_occupied 内容将添加到变量总计。迭代结束时,将总计的内容分配给变量总和。

注意

分配 total += line-seats_occupied 是计算分配。它是 total = total + line-seats_occupied 的简写形式。其他基本算术运算存在类似运算符 (-=, *=, /=)。

缩减的返回值也可以具有结构化类型。在此示例中,类型 t_results 是包含组件占用和最大值的结构。在缩减的 INIT 部分,我们声明类型为 t_results 的变量总计。在 NEXT 表达式中,我们处理组件占用和最大值,编译整个内部表中每个组件的总和。

目标变量的类型与 REDUCE 表达式的结果类型必须兼容。

除具有返回值外,REDUCE 表达式还可以使用其它局部变量。在此示例中,表达式的 INIT 部分包含两个数据声明 - totals_avg,其中包含返回值和计数。由于 count 是使用整数值 1 声明的,因此系统会为其分配类型 I。然后在表达式的 count 部分使用 NEXT。在每个迭代中,我们使用它来计算到目前为止所有航班的平均占用次数,并在每次迭代结束时的值加 1。

试用:表减少

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

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

TYPES: BEGIN OF t_results,
occupied TYPE /dmo/plane_seats_occupied,
maximum TYPE /dmo/plane_seats_max,
END OF t_results.
TYPES: BEGIN OF t_results_with_Avg,
occupied TYPE /dmo/plane_seats_occupied,
maximum TYPE /dmo/plane_seats_max,
average TYPE p LENGTH 16 DECIMALS 2,
END OF t_results_with_avg.
DATA flights TYPE TABLE OF /dmo/flight.
SELECT FROM /dmo/flight FIELDS * INTO TABLE @flights.
* Result is a scalar data type
DATA(sum) = REDUCE i( INIT total = 0 FOR line IN flights NEXT total += line-seats_occupied ).
out->write( 'Result is a scalar data type' ).
out->write( '_____________ ______________' ).
out->write( sum ).
out->write( ` ` ).
* Result is a structured data type
DATA(results) = REDUCE t_results( INIT totals TYPE t_results
FOR line IN flights NEXT totals-occupied += line-seats_occupied
totals-maximum += line-seats_max ).
out->write( 'Result is a structure' ).
out->write( '_____________________' ).
out->write( results ).
out->write( ` ` ).
* Result is a structured data type
* Reduction uses a local helper variable
* Result of the reduction is always the *first* variable declared after init
out->write( 'Result is a structure. The average is calculated using a local helper variable' ).
out->write( '______________________________________________________________________________' ).
DATA(result_with_Average) = REDUCE t_results_with_avg( INIT totals_avg TYPE t_results_with_avg count = 1
FOR line IN flights NEXT totals_Avg-occupied += line-seats_occupied
totals_avg-maximum += line-seats_max
totals_avg-average = totals_avg-occupied / count
count += 1 ).
out->write( result_with_average ).
  1. 选择 CTRL + F3 激活类,然后选择 F9 将其作为控制台应用执行。

  2. 浏览源代码以熟悉概念。

处理内部表的内容

您注意到在本地类 LCL_PASSENGER_FLIGHTGET_FLIGHTS_BY_CARRIER 方法中,每次方法调用都会刷新缓冲区。在本练习中,您将调整方法实施以保留已缓冲的航班数据。

在本地类 LCL_CARRIERGET_AVERAGE_FREE_SEATS 方法中,还将用表减少替换数据库中的聚合。

模板:

  • /LRN/CL_S4D401_DBS_AGGREGATE(全局类)

解决方案:

  • /LRN/CL_S4D401_ITS_PROCESSING(全局类)

任务 1: 复制模板(可选)

复制模板类 /LRN/CL_S4D401_DBS_AGGREGATE。如果已完成上一练习,则可以跳过此任务并继续编辑类 ZCL_##_SOLUTION

步骤

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

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

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

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

  5. 选择下一步

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

  7. 激活复制。

  8. Ctrl + F3 激活类。

任务 2: 调整航班缓冲区

更改本地类 LCL_PASSENGER_FLIGHT 中方法 GET_FLIGHTS_BY_CARRIER 的实施。确保从数据库读取航班数据时,将数据添加到缓冲区(静态属性 flights_buffer),而不是替换当前内容。在第一种方法中,在任何情况下都添加新条目,然后移除重复项。在第二种方法中,如果缓冲区已包含请求航空公司的航班,则跳过 SELECT 语句。

步骤

  1. 在局部类 LCL_PASSENGER_FLIGHT 中,导航至方法 GET_FLIGHTS_BY_CARRIER 的实施。

  2. 如之前练习所述执行该步骤。

  3. 调整内部表 flights_buffer 的循环。添加仅从导入参数 i_carrier_id 中指定的航空公司读取航班的筛选器。

  4. 如下调整代码:

LOOP AT flights_buffer INTO DATA(flight)
WHERE carrier_id = i_carrier_id.
  1. 调整 SELECT 语句以确保将结果添加到内部表 flights_buffer 的内容中,而不是替换当前内容。

提示

将 INTO TABLE 子句替换为 APPENDING TABLE 子句。

  1. 在 SELECT 语句中,将 INTO 替换为 APPENDING。

  2. 如下调整代码:

SELECT
FROM /lrn/passflight
FIELDS carrier_id, connection_id, flight_date,
plane_type_id, seats_max, seats_occupied,
seats_max - seats_occupied AS seats_free,
currency_conversion(
amount             = price,
source_currency    = currency_code,
target_currency    = @currency,
exchange_rate_date = flight_date,
on_error           =
@sql_currency_conversion=>c_on_error-set_to_null
) AS price,
@currency AS currency_code
WHERE carrier_id = @i_carrier_id
ORDER BY flight_date ASCENDING
*    INTO TABLE @flights_buffer.
APPENDING TABLE @flights_buffer.
  1. 在 SELECT 语句之后,添加一个从内部表 flights_buffer 中删除重复条目的语句。

  2. 在 SELECT 语句之后,添加以下代码:

SORT flights_buffer BY carrier_id connection_id flight_date.
DELETE ADJACENT DUPLICATES FROM flights_buffer
COMPARING carrier_id connection_id flight_date.
  1. 现在,在每次 SELECT APPENDING 之后对 flights_buffer 的内容进行排序,可以省略数据库中的冗余排序。

  2. 在 SELECT 语句中,删除或注释 ORDER BY 子句。

  3. 以控制台应用形式激活并测试全局类。

  4. Ctrl + F3

  5. F9

  6. 现在调整逻辑。如果 flights_buffer 已包含请求航空公司的航班,则跳过 SELECT 语句,而不是移除重复项。

  7. 注释您刚刚添加的 DELETE 语句。

  8. 将 SELECT 语句和 SORT 语句与 IF 括起来… ENDIF.

  9. 在 IF 语句中,使用谓词函数 line_exists 确保 flights_buffer 不包含任何带有 carrier_id = i_carrier_id 的行。

  10. 如下调整代码:

IF NOT line_exists( flights_buffer[ carrier_id = i_carrier_id ] ).
SELECT
FROM /lrn/passflight
FIELDS carrier_id, connection_id, flight_date,
plane_type_id, seats_max, seats_occupied,
seats_max - seats_occupied AS seats_free,
currency_conversion(
amount             = price,
source_currency    = currency_code,
target_currency    = @currency,
exchange_rate_date = flight_date,
on_error           =
@sql_currency_conversion=>c_on_error-set_to_null
) AS price,
@currency AS currency_code
WHERE carrier_id = @i_carrier_id
*      ORDER BY flight_date ASCENDING
*      INTO TABLE @flights_buffer.
APPENDING TABLE @flights_buffer.
SORT flights_buffer BY carrier_id connection_id flight_date.
*    DELETE ADJACENT DUPLICATES FROM flights_buffer
*      COMPARING carrier_id connection_id flight_date.
ENDIF.
  1. 以控制台应用形式激活并测试全局类。

  2. Ctrl + F3

  3. F9

任务 3: 使用表理解

更改本地类 LCL_PASSENGER_FLIGHT 中方法 GET_FLIGHTS_BY_CARRIER 的实施。用表理解替换内部表 flights_buffer 的循环,即直接使用 VALUE 表达式填充结果参数 r_result

步骤

  1. 在局部类 LCL_PASSENGER_FLIGHT 中,导航至方法 GET_FLIGHTS_BY_CARRIER 的实施。

  2. 照常执行该步骤。

  3. 在方法实施结束时注释 LOOP…ENDLOOP 块。

  4. 选择具有 LOOP 和 ENDLOOP 的代码行以及它们之间的所有代码行,然后按 Ctrl + < 在每个选定行的开头插入注释符号。

  5. 您的代码应如下所示:

*  LOOP AT flights_buffer INTO DATA(flight)
*    WHERE carrier_id = i_carrier_id.
*
*    APPEND NEW lcl_passenger_flight( i_carrier_id    = flight-carrier_id
*                                     i_connection_id = flight-connection_id
*                                     i_flight_date   = flight-flight_date )
*      TO r_result.
*  ENDLOOP.
  1. 实施 VALUE 表达式并将结果指派给返回参数 r_result

  2. 添加以下代码:

r_result = VALUE #(
).
  1. 在 VALUE 表达式中,在内部表 flights_buffer 上添加循环,并在结果中添加一个新行(工作区的建议名称:航班)。

  2. 如下调整代码:

r_result = VALUE #(
FOR flight IN flights_buffer ( )
).
  1. 添加过滤条件以确保仅处理申请航空公司的航班。

注意

请记住用额外的一对括号将条件括起来。

  1. 如下调整代码:

r_result = VALUE #(
FOR flight IN flights_buffer
WHERE ( carrier_id = i_carrier_id ) (
)
).
  1. 使用类 LCL_PASSENGER_FLIGHT 的新实例填充结果的每一行。为构造函数提供当前行 flights_buffer 中的数据,即来自工作区**航班**的数据。

  2. 如下调整代码:

r_result = VALUE #(
FOR flight IN flights_buffer
WHERE ( carrier_id = i_carrier_id ) (
NEW lcl_passenger_flight(
i_carrier_id    = flight-carrier_id
i_connection_id = flight-connection_id
i_flight_date   = flight-flight_date )
)
).
  1. 以控制台应用形式激活并测试全局类。

  2. Ctrl + F3

  3. F9

任务 4: 使用表减少

在局部类 LCL_CARRIER 中,调整方法 GET_AVERAGE_FREE_SEATS 的实施。使用减少表功能从存储在内部表 passenger_flight 中的实例中提取结果,而不是在 SELECT 语句中计算结果。

步骤

  1. 在局部类 LCL_CARRIER 中,导航到方法 GET_AVERAGE_FREE_SEATS 的实施。

  2. 照常执行该步骤。

  3. 删除或注释方法实施的源代码。

  4. 标记语句 METHOD get_average_free_seats.ENDMETHOD. 之间的代码,然后按 Ctrl + <

  5. 实施 REDUCE 表达式并将结果指派给返回参数 r_result

  6. 添加以下代码:

r_result = REDUCE #(
).
  1. 在 REDUCE 表达式中,声明一个临时变量并使用值 0 对其进行初始化(建议名称:i)。

  2. 如下调整代码:

r_result = REDUCE #(
INIT i = 0
).
  1. 在 REDUCE 表达式的右括号之前,在内部表 passenger_flights(工作区的建议名称:航班)上添加一个循环。

  2. 如下调整代码:

r_result = REDUCE #(
INIT i = 0
FOR flight IN passenger_flights
).
  1. 在每次迭代中,将当前 LCL_PASSENGER_FLIGHT 实例的空闲座位数添加到临时变量中。

  2. 如下调整代码:

r_result = REDUCE #(
INIT i = 0
FOR flight IN passenger_flights
NEXT i = i + flight->get_free_seats( )
).

注意

或者,您可以使用如下分配表达式:

abap NEXT i += flight->get_free_seats( )

  1. 最后,计算平均值。可用座位总数(即表减少的结果)除以 passenger_flights 中的条目数。

  2. 如下调整代码:

r_result = REDUCE #(
INIT i = 0
FOR flight IN passenger_flights
NEXT i = i + flight->get_free_seats( )
)
/ lines( passenger_flights ).
  1. 以控制台应用形式激活并测试全局类。

  2. Ctrl + F3

  3. F9