定义数据库表之间的关系

学习目标

  • 了解数据模型中的关系。

  • 在字典表中定义外键相关性。

数据模型中的关系

通常,数据模型的实体彼此相关。定义数据模型包括定义这些关系。

我们来看一下公司的数据模型,其中包括实体员工和**部门。这两个实体相关,因为员工分配到部门。

但这两个实体也可以共享其他关系。例如,每个部门由一名员工领导。

关系的重要特征是其基数或多样性。它们会告诉您一个实体的实例有多少属于另一个实体的实例。

基数有不同的表示法系统。例如,统一建模语言 (UML) 使用最大值和最小值,其中 * 表示无限制的最大值。

让我们看一下我们的示例:

每个员工必须仅分配到一个部门。因此,此方向的基数为 1..1。在另一个方向上,一个部门可以分配任意数量的员工,甚至允许为零。因此,基数为 0..*。

对于另 一个关系,让我们考虑到,每个部门都只由一名员工领导,而不是更多,不少。这意味着我们忽略空缺主要职位(零是允许值)或双或多个领导(允许大于 1 的值)等特殊情况。另外 一个方向,我们可以轻易地说,不是每个员工都是领导,这意味着最小值为零。我们决定将最大值设置为 1,我们表示一名员工不能同时领导两个部门。

注意

在 ABAP 字典和 ABAP CDS 中,基数和多样性用作同义词。严格来说,基数是实例的实际数量,而多样性指定允许值的范围。

ABAP CDS 和 ABAP 字典使用不同的概念来表示关系:

关联

ABAP CDS 使用关联定义 CDS 视图实体之间的关系。通常,一个关系由两个关联反映,指向相反方向。

外键

ABAP 字典定义表字段的相关性以建立与另一个数据库表的关系。其中一个外键依赖对应于一个关系。

让我们通过深入了解 ABAP 字典中的外键相关性来探索定义关系的可能性。

字典表中的外键定义

在 ABAP 字典中,可使用外键相关性定义表之间的关系。

在现代 ABAP 编程中,这些外键相关性的主要目的是文档。但在传统 ABAP 开发中,他们对用户界面有直接影响。它们产生了隐式输入检查和生成的值帮助。

注意

在现代 ABAP 开发中,CDS 视图实体之间的关联已部分接管此任务。有关详细信息,请参阅下一课。

需要注意的是,ABAP 字典中的外键定义仍保留在 ABAP 层中。它们不会传递到数据库。因此,它们不会导致对数据库的隐式一致性检查。

外键相关性定义两个字典数据库表之间的关系,其中一个称为外键表,另一个称为检查表

完整的外键依赖将检查表的每个主键字段链接到外键表中的相应字段。在此示例中,检查表的主键由三个字段组成:C1、C2 和 C3。外键关系将其链接到外键表中的字段 F1、F2 和 F6。F1、F2 和 F6 字段共同构成外键

注意

外键可以包含字段、关键字段和非关键字段的任意组合。检查表中的相关字段始终为关键字段。

外键相关性限制外键字段中的值:仅允许检查表中存在的组合。此限制对于最后一个外键字段(即链接到检查表的最后一个主键字段的字段)尤为重要。此字段称为检查字段。在我们的示例中,检查字段为字段 F6。

示例:外键关系

播放视频查看外键关系的示例。

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

外键的语义属性

定义外键关系时,可以添加语义信息以进一步描述关系并影响框架的使用方式。

大多数语义信息使用外键注释指定。在 DEFINE TABLE 语句中仅直接指定基数。

如果字段具有外键关系,则可以添加以下外键注释:

简短描述

短文本用作外键关系的说明文本。

外键字段类型

注释 @AbapCatalog.foreignKey.keyType 描述外键表中外键字段的含义。如果所有外键字段都是外键表的键字段,请选择值 #KEY。如果至少有一个外键字段不是外键表的键值字段,则选择 #NON_KEY。在此示例中,#NON_KEY 正确,因为字段 DEPARTMENT_ID 不是表 EMPLOYEE 的关键字段。如果外键表为文本表(即包含检查表的可翻译文本),则第三个值 #TEXT_KEY 相关。

传统 UI 的值检查

注释 @ABAPCatalog.foreignKey.screenCheck 控制传统用户对话是否基于此外键关系执行值检查。如果设置为 true,则可以使用更多注释指定专用错误消息。否则,将显示通用错误消息。

注意

屏幕和消息注释在现代用户对话中不相关。除非您计划开发传统 UI,否则将 @ABAPCatalog.foreignKey.screenCheck 的值设置为 false

基数根据可能的相关记录(外键表的记录)或参考记录(检查表的记录)的数量描述外键关系。

在 ABAP 字典中,基数在关键字 KEY 后面立即使用 [ n , m ] 指定。

  • 左侧 (n) 指定每个检查表记录的外键表记录数,并且可以具有值 1、0..1、1..* 和 0..*。

  • 右侧 (m) 指定每个外键表记录的检查表记录数,并且可以具有值 1 或 0..1。

要选择正确的基数,请从右侧开始。

如果检查字段为必填字段,则选择 m = 1。在这种情况下,外键表中的每条记录都需要检查字段中的值和检查表中的匹配记录。

如果检查字段为可选字段,则选择 m = 0..1。然后,在检查字段中没有值的记录不需要检查表中的匹配记录。

我们再来看一下员工和部门的示例:

如果必须将员工分配到部门,则外键的基数应为 m = 1。如果也可能未将员工分配到部门,则基数应为 m = 0..1

现在,我们来看一下基数的左侧。不同值的含义如下:

n = 1

检查表的每条记录只有一个相关记录(外键表的记录)。

n = 0..1

检查表的每条记录最多有一条相关记录。但是,检查表的记录在外键表中没有相关记录。

n = 1..*

检查表的每条记录至少有一条相关记录。

n = 0..*

检查表的每条记录可以有任意数量的相关记录:无、一个或多个相关记录。

在我们的示例中,您必须问自己:可以为同一部门分配多少员工?只有一个?最多一个?至少一个?有编号吗?你觉得怎么样?

如何定义外键关系

播放视频,了解如何定义外键关系。

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

定义外键关系

为部门定义第二个数据库表,并定义部门和员工之间的外键关系。

模板:

  • /LRN/EMPLOY_DEP(数据库表定义)

解决方案:

  • /LRN/DEPMENT_REL(数据库表定义)

  • /LRN/EMPLOY_REL(数据库表定义)

  • /LRN/DEPMENT_HEAD(数据元素)

  • /LRN/DEPMENT_ASSISTANT(数据元素)

前提条件

在本练习中,您需要先前练习中创建并填写的员工数据的数据库表(建议名称为 Z##EMPLOY,其中 ## 是您的组编号)。如果尚未完成该练习,请创建数据库表 /LRN/EMPLOY_DEP 的副本。

任务 1: 定义部门数据的数据库表

创建数据库表定义以存储部门数据(建议名称:Z##DEPMENT,其中 ## 是您的组编号)。

除客户端字段外,添加以下字段:

字段名称

数据元素

id

Z##_DEPARTMENT_ID(在之前的练习中创建)

description

/LRN/DEPMENT_DESCRIPTION

将**标识**字段添加到表键值,并将管理数据的 /LRN/S_ADMIN 结构包括在字段列表中。

步骤

  1. 创建数据库表类型的新开发对象(建议名称:Z##DEPMENT)。

  2. 在项目浏览器中,展开包并打开子节点 字典 上的上下文菜单。

  3. 选择新→数据库表

  4. 确认包,输入名称 Z##DEPMENT 和描述**部门**,然后选择下一步

  5. 将新对象分配到传输请求,然后选择完成

  6. 数据库表定义的代码应如下所示:

@EndUserText.label : 'Department'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table z##depment {
key client : abap.clnt not null;
}
  1. 在客户端字段之后,添加第二个关键字段标识,并使用数据元素 Z##_DEPARTMENT_ID 键入。

注意

如果尚未完成相应练习,请改用数据元素 /LRN/DEPARTMENT_ID。

  1. 如下调整代码:

define table z##depment {
key client      : abap.clnt not null;
key id          : z##_department_id;
}
  1. 基于 /LRN/DEPMENT_DESCRIPTION 数据元素添加字段**描述**。

  2. 如下调整代码:

define table z##depment {
key client              : abap.clnt not null;
key id                  : z##_department_id;
description         : /lrn/depment_description;
}
  1. 包含结构 /LRN/S_ADMIN 及管理字段。

  2. 如下调整代码:

define table z##depment {
key client              : abap.clnt not null;
key id                  : z##_department_id;
description         : /lrn/depment_description;
include             : /lrn/s_admin;
}
  1. 维护技术表设置。使用值帮助为数据类和大小类别选择适当的值。

注意

我们希望表包含少于 100 个条目。

  1. 右键单击编辑器中的任意位置,然后选择打开其他技术设置

  2. 选择 APPL0 主数据,透明表

  3. 选择 0 预期记录 0 - 2.600

  4. 激活技术表设置和数据库表定义。

  5. 仍在技术表设置对话框中,按 Ctrl + F3 激活技术表设置。

  6. 使用数据库表定义的源代码返回到编辑器,然后按 Ctrl + F3 激活数据库表定义。

任务 2: 定义外键关系

定义员工数据和部门数据数据库表之间的外键关系。关系应记录只能将员工分配到现有部门。换句话说,在 Z##EMPLOY 表的 department_id 字段中,仅允许具有表 Z##DEMENT 中存在条目的值。选择合适的基数以记录可以为任意数量的员工分配单个部门。

步骤

  1. 识别外键表并导航到检查字段

  2. 打开员工数据数据库表的定义(建议名称为 Z##EMPLOY)。

  3. 向下滚动至 department_id 字段。

  4. 将外键添加到检查字段。

提示

尽可能使用代码完整化。

  1. department_id 字段的定义中,将光标放在结束分号 (;) 之前,然后按 Enter 插入新代码行。

  2. 将光标置于新行的开头,然后按 **Ctrl + 空格**键调用代码完成。

  3. 从建议清单中,选择 with foreign key - keyword

  4. 输入空格并按 **Ctrl + 空格**再次调用代码完整化。

  5. 从建议清单中,选择部门数据的数据库表,即条目 z##depment - 表

  6. 输入空格并使用代码完整化输入 WHERE 关键字。

  7. 继续使用代码完整化添加关键字段(左侧)和外键字段(右侧)的分配。

  8. 保存数据库表定义以调用自动格式化。

注意

保存定义时,编辑器还会添加注释@AbapCatalog.foreignKey.screenCheck。

  1. 按 **Ctrl + 空格**键保存开发对象。

  2. 代码现在应如下所示:

entry_date      : z##_entry_date;
@AbapCatalog.foreignKey.screenCheck : false
department_id   : z##_department_id
with foreign key z##depment
where client = z##employ.client
and id     = z##employ.department_id;
  1. 添加基数以记录单个部门编号可用于任意数量的员工数据集,另一方面,应将每个员工分配到现有部门。

  2. 如下调整代码:

entry_date      : z##_entry_date;
@AbapCatalog.foreignKey.screenCheck : false
department_id   : z##_department_id
with foreign key [0..*,1] z##depment
where client = z##employ.client
and id     = z##employ.department_id;
  1. 激活数据库表定义。

  2. Ctrl + F3 激活开发对象。

任务 3: 定义部门主管和助理

使用部门主管和部门助理字段(建议名称:head_idassistant_id)扩展部门数据的数据库表。为每个字段定义新数据元素,但请确保使用与员工表的 employee_id 字段中相同的域。然后为每个字段定义外键关系,以记录仅允许现有员工号。

步骤

  1. 编辑部门数据数据库表的字段列表。在 INCLUDE 语句之前,添加以下字段并使用快速修复创建新的数据元素: | 字段名称 | 数据元素 | | --- | --- | | department_head | Z##_DEPMENT_HEAD | | department_assistant | Z##_DEPMENT_ASSISTANT | 对于这两个数据元素,使用域 **Z##_EMPLOYEE_ID作为**数据类型。

注意

仅当您未创建 Z##_EMPLOYEE_ID 域时,才可以使用 /LRN/EMPLOYEE_ID 域。确保员工数据数据库表的关键字段 ID(建议名称为 Z##EMPLOY)基于相同的域。

  1. 在元素清单的末尾输入 department_head : z##_depment_head;(其中 ## 是您的组编号)。

  2. 打开 问题 视图。应看到错误字段 DEPARTMENT_HEAD:使用的组件类型或域未激活或不存在

  3. 将光标置于z##_depment_head 上,然后按 Ctrl + 1 打开快速修复菜单。

  4. 在快速修复菜单中,双击创建数据元素 ....

  5. 确认包和名称,输入描述,例如**部门负责人**,然后选择下一步

  6. 将新对象分配到传输请求,然后选择完成

  7. 类型名称下,输入 Z##_EMPLOYEE_ID 并输入以下字段标签: | 字段 | 值 | | --- | --- | | 短 | 折旧标题 | | 中 | 部门负责人 | | 长整型 | 部门负责人 | | 标题 | 部门负责人 |

  8. 激活新数据元素并返回到数据库表 Z##DEPMENT

  9. 在新代码行中,输入 assistant_id : Z##_depment_assistant; 并再次使用快速修复通过相同域创建数据元素 Z##_DEPMENT_ASSISTANT,但具有以下字段标签: | 字段 | 值 | | --- | --- | | 短 | 助手 | | 中 | 折旧助手 | | 长整型 | 部门助理 | | 标题 | 部门助理 |

  10. 对于这两个新字段,将表 Z##EMPLOY 的外键关系添加为检查表。然后保存数据库表定义以调用自动格式化。

提示

尽可能使用代码完整化。

  1. 应用自动格式化后,代码应如下所示:

define table z##depment {
key client    : abap.clnt not null;
key id        : z##_department_id not null;
description   : /lrn/depment_description;
@AbapCatalog.foreignKey.screenCheck : false
head_id      : z##_depment_head
with foreign key z##employ
where client      = z##depment.client
and employee_id = z##depment.head_id;
@AbapCatalog.foreignKey.screenCheck : false
assistant_id : z##_depment_assistant
with foreign key z##employ
where client      = z##depment.client
and employee_id = z##depment.assistant_id;
include /lrn/s_admin;
}
  1. 向两个外键关系添加基数。基于以下信息选择基数:

  • 并非每个员工都可以是部门主管或部门助理。

  • 同一员工不能是多个部门的负责人。

  • 同一员工不能是多个部门的助理。

  • 每个部门只需要一名部门助理。

  • 没有部门负责人,部门可以做得很好。

  1. 如下调整代码:

@AbapCatalog.foreignKey.screenCheck : false
head_id      : z##_depment_head
with foreign key [0..1,0..1] z##employ
where client      = z##depment.client
and employee_id = z##depment.head_id;
@AbapCatalog.foreignKey.screenCheck : false
assistant_id : z##_depment_assistant
with foreign key [0..1,1] z##employ
where client      = z##depment.client
and employee_id = z##depment.assistant_id;
  1. 激活数据库表定义。

  2. Ctrl + F3 激活开发对象。

任务 4: 填充表

用某些部门数据填充新的数据库表,并更新员工表。为此,创建 ABAP 类 /LRN/CL_S4D430_CHECK_AND_FILL 的副本,并调整常量 c_versionc_employ_tablec_depment_table 的值。然后激活类并将其作为控制台应用执行。

步骤

  1. 创建 ABAP 类 /LRN/CL_S4D430_CHECK_AND_FILL 的副本,将其命名为 Z##_S4D430_CHECK_AND_FILL 并将其放置在包 ZS4D430_## 中。

注意

如果您在之前的练习中创建了 /LRN/CL_S4D430_CHECK_AND_FILL 的副本,请跳过此步骤。

  1. 在项目浏览器视图中,找到包 /LRN/S4D430_EXERCISE 中的 ABAP 类,然后右键单击以打开上下文菜单。

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

  3. 输入包名称、副本名称和描述。然后选择下一步

  4. 将新对象分配到传输请求,然后选择完成

  5. 将常量 c_version 的值从 lcl_generator=>employee_table_only 更改为 lcl_generator=>with_relations

提示

您可以在当前定义前添加注释符号,并从第二个定义备选项中移除注释符号。

  1. 如下调整代码:

* CONSTANTS c_version TYPE t_version VALUE lcl_generator=>employee_table_only.
CONSTANTS c_version TYPE t_version VALUE lcl_generator=>with_relationships.
* CONSTANTS c_version TYPE t_version VALUE lcl_generator=>with_extensions.
  1. 将常量 c_employ_tablec_depment_table 的值更改为数据库表的名称。

  2. 向下滚动到以 CONSTANTS c_employ_table 开头的代码行。

  3. 如果 VALUE 后的文字仍包含 ##,请将其替换为您的组编号。

  4. 向下滚动到以 CONSTANTS c_depment_table 开头的代码行。

  5. 在 VALUE 后的文字中,将 ## 替换为您的组编号。

  6. 激活 ABAP 类并将其作为控制台应用执行。如果控制台输出包含错误,请调整数据库表的定义,直到它们与数据库表定义 /LRN/EMPLOY_RELLRN/DEPMENT_REL 兼容。

  7. Ctrl + F3 激活开发对象。

  8. F9 将 ABAP 类作为控制台应用执行。

  9. 确保控制台输出包含两个数据库表的文本 已填充表 ...