实现联接
学习目标
实施连接。
区分内连接和外连接。
实现嵌套连接。
连接的基础知识和语法
连接定义
有时,需要将两个 SQL 数据源合并为一个结果集,该结果集由两个数据源的列组成。这称为连接。

例如,读取航班转机时,可能还需要读取航班承运人的详细信息。
结果集的行由连接类型和数据源列之间的连接条件确定。连接条件以关键字 ON 开头,并定义数据源各列之间的关系。
连接类型由相应的关键字定义。在 ABAP SQL 中,支持的连接类型为 INNER JOIN、LEFT OUTER JOIN 和 RIGHT OUTER JOIN。
我们将很快讨论这些联接类型的不同结果。但是,我们先来看看联接的 ABAP SQL 语法。

该示例将承运商数据(承运商标识和承运商名称)和连接数据(连接标识、出发机场和目的地机场)合并到一个结果集中。左数据源是数据库表 /DMO/CARRIER,而数据库表 /DMO/CONNECTION 用作正确的数据源。两个数据源名称之间的关键字 INNER JOIN 指定连接类型。
ON 后的连接条件请求数据库表 /DMO/CARRIER 的字段 CARRIER_ID 的内容等于数据库表 /DMO/CONNECTION 的字段 CARRIER_ID 的内容。
在连接条件以及字段列表、WHERE 子句等中,数据源的列由数据源名称和列名称完全标识,以波浪号 ( ~) 分隔。
注意
从其他 SQL 方言中,您可能被用于句点符号或句号 (.)。这必须由 ABAP SQL 中的其他符号替换,因为句点符号已具有结束语句的含义。

可以在没有数据源名称的情况下寻址列名称。前提条件是列名称仅存在于其中一个数据源中。
在此示例中,只有数据库表 /DMO/CARRIER 包含列 NAME 和 CURRENCY_CODE,而 CONNECTION_ID、AIRPORT_FROM_ID 和 AIRPORT_TO_ID 列仅存在于数据库表 /DMO/CONNECTION 中。因此,这些列名称是唯一的,没有前面的/DMO/CARRIER~ 或 /DMO/CONNECTION~。
另一方面,两个表都包含列 CARRIER_ID。因此,对于此字段 /DMO/CARRIER~ 或 /DMO/CONNECTION~ 为必填字段。

最好对数据源名称使用别名,以缩短语句并提高可读性。在数据源名称后使用附加项 AS 完成此操作。在此示例中,别名 A 用于数据库表 /DMO/CARRIER,别名 C 用于数据库表 /DMO/CONNECTION。
如果数据源具有别名,则必须使用别名而不是原始数据源名称。
自连接中所需的别名
在某些情况下,数据源的别名不仅仅是语法替代方法。在多次包含相同数据源的语句中,它们变得不可或缺。

让我们看一个示例。让我们考虑一个包含转机 (/DMO/CONNECTION) 的表。我们对从法兰克福国际机场(FRA)开始可以到达的目的地感兴趣。但是,我们不是直接航班,而是查找一次转机的航班。
解决方案是表 /DMO/CONNECTION 与自身的连接。第一个连接条件要求第二个连接(第 2 段)从第一个连接(第 1 段)结束的机场开始。第二个连接条件(使用 AND 运算符链接到第一个连接条件)排除第二个连接是原始出发机场的直接返程航班的组合。
很明显,如果没有别名 a(用于 /DMO/CONNECTION 的左侧使用)和 b(用于 /DMO/CONNECTION 的正确使用),这种自连接将无法工作。如果没有不同的别名,则无法区分第一个连接和第二个连接 - 既不在连接条件中,也不在字段列表或 WHERE 子句中。
注意
在此类自联接中,每个列名称前面都需要别名,因为每个列名称都存在于两个数据源中。
试用:连接语法
创建实施接口 IF_OO_ADT_CLASSRUN 的新全局类。
将以下代码片段复制到方法 if_oo_adt_classrun~main() 的实施部分:
SELECT FROM /dmo/carrier INNER JOIN /dmo/connection
* SELECT FROM /dmo/carrier AS a INNER JOIN /dmo/connection AS c
ON /dmo/carrier~carrier_id = /dmo/connection~carrier_id
FIELDS /dmo/carrier~carrier_id,
/dmo/carrier~name AS carrier_name,
/dmo/connection~connection_id,
/dmo/connection~airport_from_id,
/dmo/connection~airport_to_id
WHERE /dmo/carrier~currency_code = 'EUR'
INTO TABLE @DATA(result).
out->write(
EXPORTING
data = result
name = 'RESULT'
).
选择 CTRL + F3 激活类,然后选择 F9 将其作为控制台应用执行。
分析控制台输出。
将光标置于第一行(关键字为 SELECT 的行),然后选择 CTRL + < 在该行的第一列添加星号 (*)。
从下一行的开头移除星号 (*)。分析语法错误,告知您表名 /DMO/CARRIER 已被别名隐藏。
在 ON 条件中,将表名 /DMO/CARRIER 替换为其别名 A,将表名 /DMO/CONNECTION 替换为其别名 C。
在 FIELDS 列表和 WHERE 子句中,移除每个字段名称前面的别名和波浪符 (~)。您应该发现仅在字段 carrier_id 前面为必填字段。
激活并再次执行。
连接类型
观看以下视频,了解有关内连接、右外连接和左外连接的详细信息。
视频:本节含 SAP 官方视频(视频 ID
1_rrgb207z),需在 learning.sap.com 在线观看。
视频:本节含 SAP 官方视频(视频 ID
1_7wdd13yz),需在 learning.sap.com 在线观看。
视频:本节含 SAP 官方视频(视频 ID
1_hcjbz3kl),需在 learning.sap.com 在线观看。

此示例说明了左外连接的结果。结果集中突出显示的两行在内部连接中将不存在。它们对应于数据库表 /DMO/CONNECTION 中没有转机的运输公司。
这些行仅包含有关承运商的信息,即来自左侧数据源的表 /DMO/CARRIER 的信息。通常包含来自正确数据源的信息的列(即 CONNECTION_ID、AIRPORT_FROM_ID 和 AIRPORT_TO_ID 列)用初始值填充。这些值由 ABAP 运行时输入,而不是数据库返回的 Null 值。
注意
CONNECTION_ID 列的类型基于 ABAP 内置类型 N。此类型的初始值不是一系列空格,而是一系列零。
试用:内连接和外连接
创建实施接口 IF_OO_ADT_CLASSRUN 的新全局类。
将以下代码片段复制到方法 if_oo_adt_classrun~main() 的实施部分:
SELECT FROM /dmo/Agency AS a
INNER JOIN /dmo/customer AS c
* LEFT OUTER JOIN /dmo/customer AS c
* RIGHT OUTER JOIN /dmo/customer AS c
ON a~city = c~city
FIELDS agency_id,
name AS Agency_name,
a~city AS agency_city,
c~city AS customer_city,
customer_id,
last_name AS customer_name
WHERE ( c~customer_id < '000010' OR c~customer_id IS NULL )
AND ( a~agency_id < '070010' OR a~agency_id IS NULL )
INTO TABLE @DATA(result_Join).
out->write(
EXPORTING
data = result_join
name = 'RESULT_JOIN'
).
选择 CTRL + F3 激活类,然后选择 F9 将其作为控制台应用执行。
分析控制台输出。
注意
代码示例检索地址位于同一城市的客户和旅行社对。它使用 WHERE 子句使结果集保持较小。因此,使用 INNER JOIN 只能找到客户 '000010' 和旅行社 '070010'。WHERE 子句的写入形式也适用于外连接。需要条件<field_name> IS NULL 以便不从数据库中排除具有 NULL 值的结果。
在 FROM 语句的 SELECT 子句中,将 INNER JOIN 替换为 LEFT OUTER JOIN 并再次激活。或者,可以使用 INNER JOIN 注释行,并使用 LEFT OUTER JOIN 取消注释。
再次执行 ,并将控制台输出与第一个输出进行比较。
在 FROM 语句的 SELECT 子句中,将 LEFT OUTER JOIN 替换为 RIGHT OUTER JOIN 并再次激活。或者,可以使用 LEFT OUTER JOIN 注释行,并使用 RIGHT OUTER JOIN 取消注释。
再次执行 并将控制台输出与前两个输出进行比较。
嵌套连接
多个数据源的连接
在 ABAP SQL 中,您可以同时连接两个以上的数据源。我们先从两个数据源连接开始,即我们之前讨论的加入航班承运人和转机。

现在,我们考虑添加数据库表 /DMO/AIRPORT 以读取启运机场 (AIRPORT_FROM_ID) 的名称和城市。
要联接这三个数据源,我们首先用一对括号将现有联接括起来。现在,第一个联接用作下一个联接的左数据源。我们添加了连接关键字(INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN)、右侧数据源 (/DMO/AIRPORT)、别名 (AS apf) 和附加连接的连接条件 (ON c~airport_from_id = apf~airport_id)。
现在,我们考虑再次添加数据库表 /DMO/AIRPORT 以同时读取目的地机场 (AIRPORT_TO_ID) 的名称和城市。
要实现这四个数据源的联接,我们重复三个数据源的连接操作:我们将三个数据源的现有联接与另一对括号括起来,并添加联接关键字、右数据源和联接条件。
注意
可重复此操作,直到达到可以相互链接的最大数据源数。目前,一个 SELECT 语句中最多允许 50 个数据源(49 个连接)。
连接顺序
在上一示例中,下一个数据源始终添加到下一个联接的右侧。左数据源是数据库表或联接。所有三个联接的正确数据源都是数据库表。

借助括号,还可以使用联接作为另一个联接的正确数据源。在下方示例中,我们从连接 /DMO/CONNECTION 和 /DMO/AIRPORT (AS apf) 开始。我们用括号将其括起来,并将其用作与 /DMO/AIRPORT (AS apt) 连接的左数据源。要添加 /DMO/CARRIER,请将现有联接与另一对括号括起来。然后,在左括号前添加左数据源 (/DMO/CARRIER) 和连接关键字,在右括号后添加 ON 条件。
注意
在我们的示例中,两个结果相同。但是,在某些情况下,结果可能取决于数据库评估连接的顺序。
括号的重要性
从语法角度来看,不需要括号。但为了便于阅读,强烈建议不要在嵌套连接中省略括号。这应该通过以下示例进行说明。

SELECT 语句包含四个数据库表的连接,但不使用任何括号。一目了然,可以认为首先评估 /DMO/CARRIER 和 /DMO/CONNECTION 的连接,因为这些表是第一个出现在 FROM 子句中的表。
但这个结论是错误的!
如果没有括号,则评估顺序由 ON 条件的顺序确定,而不是由数据源的顺序确定。仔细检查时,我们会看到,将表 /DMO/CARRIER(别名 a)和 /DMO/CONNECTION(别名 c)相互链接的 ON 条件放置在 FROM 子句的最末尾。因此,此连接是最后一个要评估的连接。
提示
在嵌套连接中始终使用括号,以避免这种混淆。
实施嵌套连接
在本练习中,实施嵌套连接以在同一 SELECT 语句中读取转机和机场详细信息。
模板:
/LRN/CL_S4D401_TCS_STRING_PROC(全局类)
解决方案:
/LRN/CL_S4D401_DBS_NESTED_JOIN(全局类)
任务 1: 复制模板(可选)
复制模板类 /LRN/CL_S4D401_TCS_STRING_PROC。如果已完成上一练习,则可以跳过此任务并继续编辑类 ZCL_##_SOLUTION。
步骤
将类
/LRN/CL_S4D401_TCS_STRING_PROC复制到您自己的包中的类(建议名称:ZCL_##_SOLUTION,其中 ## 代表组编号)。在项目浏览器中,右键单击类
/LRN/CL_S4D401_TCS_STRING_PROC以打开上下文菜单。从上下文菜单中,选择复制....
在包字段中输入包的名称。在名称字段中,输入
ZCL_##_SOLUTION,其中 ## 代表组编号。选择下一步。
确认传输请求并选择完成。
激活复制。
按
Ctrl + F3激活类。
任务 2: 实施连接
调整本地类 LCL_PASSENGER_FLIGHT 的方法 CLASS_CONSTRUCTOR 的实施。扩展从数据库表 /LRN/CONNECTION 中读取的 SELECT 语句。添加两个连接以读取出发机场和目标机场的时区。
步骤
导航到本地类
LCL_PASSENGER_FLIGHT中方法CLASS_CONSTRUCTOR的实施。例如,可以在编辑器中打开局部类型标签,然后搜索
METHOD class_constructor。或者,您可以在左侧的概要视图中展开 ZCL_##_SOLUTION→LCL_PASSENGER_FLIGHT,然后选择 CLASS_CONSTRUCTOR。
查找从数据库表
/LRN/CONNECTION中读取的 SELECT 语句,并为数据源添加别名(建议别名:c)。如下调整代码:
SELECT
FROM /lrn/connection AS c
FIELDS carrier_id, connection_id,
airport_from_id, airport_to_id, departure_time, arrival_time
INTO TABLE @connections_buffer.
添加具有相应 JOIN 类型的连接,该连接从数据库表 /LRN/AIRPORT 中读取出发机场的详细信息。为第二个数据源添加别名(建议别名:
f)。如下调整代码:
SELECT
FROM lrn/connection AS c
LEFT OUTER JOIN /lrn/airport AS f
FIELDS carrier_id, connection_id,
airport_from_id, airport_to_id, departure_time, arrival_time
INTO TABLE @connections_buffer.
添加连接条件。
如下调整代码:
SELECT
FROM /lrn/connection AS c
LEFT OUTER JOIN /lrn/airport AS f
ON c~airport_from_id = f~airport_id
FIELDS carrier_id, connection_id,
airport_from_id, airport_to_id, departure_time, arrival_time
INTO TABLE @connections_buffer.
添加读取目标机场详细信息的类似联接,并为第三个数据源添加不同的别名(建议的别名:
t)。如下调整代码:
SELECT
FROM /lrn/connection AS c
LEFT OUTER JOIN /lrn/airport AS f
ON c~airport_from_id = f~airport_id
LEFT OUTER JOIN /lrn/airport AS t
ON c~airport_to_id = t~airport_id
FIELDS carrier_id, connection_id,
airport_from_id, airport_to_id, departure_time, arrival_time
INTO TABLE @connections_buffer.
将启运机场的时区和目的地机场的时区添加到字段列表。提供合适的别名(建议的别名:
timzone_from和timzone_to)。如下调整代码:
SELECT
FROM /lrn/connection AS c
LEFT OUTER JOIN /lrn/airport AS f
ON c~airport_from_id = f~airport_id
LEFT OUTER JOIN /lrn/airport AS t
ON c~airport_to_id = t~airport_id
FIELDS carrier_id, connection_id,
airport_from_id, airport_to_id, departure_time, arrival_time,
f~timzone AS timzone_from,
t~timzone AS timzone_to
INTO TABLE @connections_buffer.
导航到内部表
connections_buffer的行类型,并为时区添加两个组件。使用在 SELECT 语句的字段列表中用作别名的相同名称。
警告
确保在组件持续时间之前添加新组件。这很重要,因为未在 SELECT 语句中填充持续时间组件。或者,在 SELECT 语句的 INTO 子句中使用 CORRESPONDING FIELDS OF 附加项。
查找结构类型
st_connections_buffer的定义。如下调整代码:
TYPES:
BEGIN OF st_connections_buffer,
carrier_id TYPE /dmo/carrier_id,
connection_id TYPE /dmo/connection_id,
airport_from_id TYPE /dmo/airport_from_id,
airport_to_id TYPE /dmo/airport_to_id,
departure_time TYPE /dmo/flight_departure_time,
arrival_time TYPE /dmo/flight_departure_time,
timzone_from TYPE timezone,
timzone_to TYPE timezone,
duration TYPE i,
END OF st_connections_buffer.
任务 3: 移除冗余 SELECT
在本地类 LCL_PASSENGER_FLIGHT 的方法 CLASS_CONSTRUCTOR 的实施中,在计算航班持续时间时使用内部表 connections_buffer 中的时区信息。然后移除对数据库表 /LRN/AIRPORT 的冗余读取访问。
步骤
返回到本地类
LCL_PASSENGER_FLIGHT中方法CLASS_CONSTRUCTOR的实施。照常执行该步骤。
在第一个 CONVERT 语句的 TIME ZONE 附加中,将对本地内部表**
机场的读取访问替换为对结构连接** 的组件timzone_from的访问。按如下所示调整第一个 CONVERT 语句:
CONVERT DATE today
TIME connection-departure_time
* TIME ZONE airports[ airport_id = connection-airport_from_id ]-timzone
TIME ZONE connection-timzone_from
INTO UTCLONG DATA(departure_utclong).
使用组件
timzone_to以类似方式调整第二个 CONVERT 语句。按如下所示调整第二个 CONVERT 语句:
CONVERT DATE today
TIME connection-arrival_time
* TIME ZONE airports[ airport_id = connection-airport_to_id ]-timzone
TIME ZONE connection-timzone_to
INTO UTCLONG DATA(arrival_utclong).
向上滚动并在从数据库表
/LRN/AIRPORT读取的 SELECT 语句的所有代码行前添加注释符号。向上滚动到方法
CLASS_CONSTRUCTOR实施的开头。选择从数据库表
/LRN/AIRPORT中读取的完整 SELECT 语句,然后按Ctrl + <。然后,代码应如下所示:
* SELECT
* FROM /lrn/airport
* FIELDS airport_id, timzone
* INTO TABLE @DATA(airports).
以控制台应用形式激活并测试全局类。
按
Ctrl + F3。按
F9。