CDS Association详解
1、 Association 的核心概念
1.1、 什么是 Association?
- 定义: Association 是 CDS 中一种声明性的语句,用于定义两个 CDS 实体(视图或表)之间的关系。
- 本质: 它本身不是一条 JOIN 语句,而是一个元数据定义,描述了“如何连接”的关系。只有在被显式使用时(例如通过路径表达式),它才会在生成的 SQL 中触发实际的 JOIN 操作。
1.2、 为什么需要 Association?
- 抽象性: 将关系逻辑从查询中分离出来,封装在模型内部,提高复用性和可维护性。
- 语义丰富: 为数据模型赋予业务含义(如“销售订单 属于 一个客户”)。
- 便捷性: 通过简洁的路径表达式访问关联数据,无需编写复杂的 JOIN。
- 框架集成: 是 SAP Fiori Elements 和 OData 服务自动提供导航功能的基础。
1.3、 基本语法
association [min..max] to TargetEntity as _Alias on $projection.SourceField = _Alias.TargetField
[min..max]: 基数,定义关联的维度。[0..1]: 目标实体最多有一条记录与之对应。[1..1]: 目标实体必须有一条记录与之对应。[0..*]或[*]: 目标实体有零条或多条记录与之对应。[1..*]: 目标实体有一条或多条记录与之对应。
TargetEntity: 要关联的目标 CDS 视图或数据库表。_Alias: 关联的别名,通常以下划线_开头以区分于普通字段。on ...: 关联条件,类似于 JOIN 的 ON 子句。$projection代表当前视图的字段。
2、Association 的用法与场景
为了进行对比,我们将使用以下简单的数据模型:
YBINV_CUSTOMER: 客户主数据视图YBINV_ORDER: 销售订单抬头视图
2.1、Association定义与路径表达式
目标: 在订单抬头视图中,关联到客户主数据,以获取客户名称。
2.1.1、定义 Association
// 客户主数据视图
define view YBINV_CUSTOMER
as select
key kunnr,
land1,
name1
from kna1
// 销售订单头视图
define view YBINV_ORDER
as select
vbeln,
auart,
kunnr,
_customer
from vbak
// 定义到客户的关联
association[1..1] to YBINV_CUSTOMER as _customer on $projection.kunnr = _customer.kunnr
关键点:
- 此处使用Association关联客户,并定义了一个别名
_customer。 - 基数
[1..1]表示每个订单必须对应一个客户。 - 关联条件使用
$projection引用vbak的字段。
2.1.2、使用路径表达式消费 Association
仅定义 Association 不会在结果中看到任何信息。必须在 select 列表中显式消费它。
// 消费 Association 的视图
define view YBINV_ORDER_CUSTOMER
as select
vbeln,
auart,
kunnr,
// 使用路径表达式将关联实体的字段“带”过来
_customer.name1, // 获取客户名称
_customer.land1 // 获取客户国家
from YBINV_ORDER
生成的 SQL 等价于:
SELECT
so.vbeln,
so.auart,
so.kunnr,
cust.name1,
cust.land1
FROM VBAK AS so
LEFT OUTER JOIN KNA1 AS cust
ON so.kunnr = cust.kunnr
执行结果
2.2、内外连接
基数只定义了表的关系,但不影响最终生成的SQL连接。
比如定义了
association[1..1] to vbap as _item on $projection.vbeln = _item.vbeln
但是系统执行查询时,仍然生成的是左连接


因为系统默认采用left outer join 进行关联。
如果要使用内连接(INNER JOIN),需要特殊处理。比如在查询时,加入限制
define view YBINV_ORDER_CUSTOMER
as select
vbeln,
auart,
kunnr,
_item[inner].posnr, //强制使用inner连接
_item.kwmeng
from YBINV_ORDER

2.3、过滤 Association
在消费时,对 Association 指向的目标实体进行过滤。
比如,我们只想获取德国的客户信息。
define view YBINV_ORDER_CUSTOMER
as select
vbeln,
auart,
kunnr,
// 使用路径表达式将关联实体的字段“带”过来
_customer[land1 = 'DE'].name1, // 获取德国客户名称
_customer.land1 // 这个仍然是原始的客户国家
from YBINV_ORDER
结果对比:
| 订单ID | 客户名称 | 客户国家 | 客户名称 |
|---|---|---|---|
| SO001 | ABC Company | DE | ABC Company |
| SO002 | XYZ Corp | US | NULL |
| SO003 | German GmbH | DE | German GmbH |
过滤只影响通过该路径表达式获取的值,不会影响主结果集的行数,也不会影响其他使用同一 Association 的路径。
2.4、暴露 Association 用于导航
在 OData 服务中,我们经常希望将 Association 本身暴露为一个导航链接(Navigation Link),而不是直接展开其字段。
在 OData 元数据中定义 /YBINV_UI_CUSTOMER_CDS/YBINV_UI_CUSTOMER('SO001')/toCustomer 这样的导航属性。
@OData.publish: true
define view YBINV_UI_CUSTOMER as select
key vbeln,
auart,
kunnr,
// 直接暴露 Association,而不是其字段
// 这将在 OData 元数据中生成一个名为 `toCustomer` 的导航属性 其中'to'是OData自动添加的导航前缀
_customer as Customer
from YBINV_ORDER
结果与对比:
- 不暴露 Association: OData 服务只有平铺的字段(如
name1,land1)。 - 暴露 Association: OData 服务包含一个导航链接。客户端可以通过以下方式访问关联数据:
GET /YBINV_UI_CUSTOMER('SO001')?$expand=toCustomerGET /YBINV_UI_CUSTOMER('SO001')/toCustomer
- 404错误:对于导航连接,有时系统会自动添加前缀,比如to,所以如果访问报404错误,需要看
/$metadata文件中<NavigationProperty Name="toCustomer">对应的name值。
这种方式提供了更大的灵活性,允许客户端决定是否需要以及何时需要加载关联数据。
2.5、使用 $projection 进行自关联
当关联条件依赖于当前视图中的计算字段或筛选后的结果时,需要使用 $projection。
比如:当客户为空时,默认一个客户,需要使用case when语法得出计算后的字段ZKUNNR,然后通过ZKUNNR查询对应的客户主数据:
define view YBINV_ORDER
as select
key vbeln,
auart,
case
when kunnr is null
then '0000300022' //如果没有客户就默认客户编码
else kunnr
end as zkunnr,
_customer,
_item
from vbak
// 定义到客户的关联 此处使用处理过的字段关联
association [0..1] to YBINV_CUSTOMER as _customer on $projection.zkunnr = _customer.kunnr
$projection 确保了关联条件是在当前视图的上下文(包括可能的计算和过滤)中进行的。
3、JOIN和Association的对比
通过以下几点,对两种方法进行深度对比
3.1、灵活性与过度获取数据
JOIN 封装的问题:
// 基础视图已经固定返回所有客户字段
define view I_SalesOrderHeaderWithCustomer as select from snwd_so
inner join snwd_bpa {...}
{
salesorder_id,
gross_amount,
customer_name, // 总是返回
customer_country, // 总是返回
customer_city, // 总是返回
customer_phone, // 总是返回
customer_email // 总是返回
};
使用 Association 的灵活性:
define view I_SalesOrderHeader as select from snwd_so {
key salesorder_id,
gross_amount,
currency_code,
_customer
from vbak
// 定义到客户的关联
association[1..1] to I_Customer as _customer on $projection.customer_guid = _Customer.customer_id
// 消费视图1:只需要客户名称
define view ZC_SimpleOrder as select from I_SalesOrderHeader {
salesorder_id,
gross_amount,
_Customer.customer_name // 只获取需要的字段
};
// 消费视图2:不需要客户信息
define view ZC_FinancialReport as select from I_SalesOrderHeader {
salesorder_id,
gross_amount,
currency_code
// 不消费 _Customer,不会产生 JOIN
//_Customer.customer_name
};
对比结果:
| 场景 | JOIN 封装 | Association |
|---|---|---|
| 只需要1个客户字段 | 仍然获取所有客户字段 | 只获取需要的字段 |
| 不需要客户数据 | 仍然执行 JOIN | 不执行 JOIN |
| 需要不同客户字段组合 | 需要创建多个基础视图 | 单一基础视图满足所有需求 |
3.2、维护成本
当客户模型变化时:
JOIN 封装方式:
// 客户表新增了重要字段
alter table snwd_bpa add company_size nvarchar(20);
// 需要修改所有封装了JOIN的基础视图
define view I_SalesOrderHeaderWithCustomer as select from ...
{
...,
_customer.company_size // 必须手动添加
};
// 所有基于这个视图的消费视图都能看到新字段(可能不需要)
Association 方式:
// 只需要在 YBINV_CUSTOMER视图中暴露新字段
define view I_Customer as select from snwd_bpa {
...,
company_size // 在源头添加
};
// 消费视图按需决定是否使用新字段
// 不需要修改任何现有的消费视图
3.3、多重关系处理
复杂场景:订单有创建者、修改者、销售员等多个人员关联
JOIN 封装的困境:
define view I_SalesOrderHeaderWithAllJoins as select from snwd_so
inner join snwd_bpa as _customer ...
left join snwd_emp as _creator ...
left join snwd_emp as _salesperson ...
left join snwd_bpa as _bill_to_party ...
{
salesorder_id,
_customer.customer_name,
_creator.employee_name as creator_name,
_salesperson.employee_name as salesperson_name,
_bill_to_party.formatted_name as bill_to_name
// 字段爆炸,命名冲突等风险
};
Association 的清晰方案:
define view I_SalesOrderHeader as select from snwd_so {
key salesorder_id,
association [1..1] to I_Customer as _Customer ...,
association [0..1] to I_Employee as _CreatedBy ...,
association [0..1] to I_Employee as _SalesPerson ...,
association [0..1] to I_Customer as _BillToParty ...
};
// 消费时 按需选择
define view ZC_OrderForSales as select from I_SalesOrderHeader {
salesorder_id,
_Customer.customer_name,
_SalesPerson.employee_name // 只关心销售相关
};
define view ZC_OrderForBilling as select from I_SalesOrderHeader {
salesorder_id,
_BillToParty.customer_name // 只关心账单相关
};
3.4、框架集成与语义化
Association 的独特优势:
3.4.1、OData 导航
@OData.publish: true
define view ZC_SalesOrderForOData as select from I_SalesOrderHeader {
key salesorder_id,
gross_amount,
_Customer as ToCustomer // 自动生成导航属性
};
客户端可以调用:/SalesOrders('123')/ToCustomer
3.4.2、文本关联
define view YBINV_ORDER
as select from vbak
// 定义到客户的关联
association [1..1] to YBINV_CUSTOMER as _customer on $projection.kunnr = _customer.kunnr
{
key vbeln,
auart,
@ObjectModel.text.element: ['name'] //绑定name和kunnr
kunnr,
@Semantics.text: true
_customer.name, //将name作为描述
_customer
}
在消费视图中定义
@OData.publish: true
define view YBINV_UI_CUSTOMER
as select
key vbeln,
auart,
@ObjectModel.text.association: '_customer' //在客户中展示描述
kunnr,
_customer.name,
_customer as Customer
from YBINV_ORDER
UI 自动显示客户名称

3.4.3、搜索帮助
Value Help注解@Consumption.valueHelpDefinition
define view ZC_SalesOrder as select from I_SalesOrderHeader {
@Consumption.valueHelpDefinition: [{ entity: { name: 'I_Customer', element: 'customer_guid' } }]
customer_guid,
_Customer.customer_name
};
3.5、性能对比
实际执行计划分析
JOIN 封装视图:
消费时,该视图已经执行了完整的JOIN。
Association 视图:
消费时,优化器可能将路径表达式重写为高效的JOIN,只获取需要的字段。
4、 总结与最佳实践
| 特性 | 传统 SQL JOIN | CDS Association |
|---|---|---|
| 本质 | 命令式,是查询的一部分 | 声明式,是模型元数据的一部分 |
| 复用性 | 差,JOIN 逻辑在每个查询中重复 | 极佳,定义一次,随处消费 |
| 可读性 | 复杂查询可读性差 | 路径表达式使查询意图更清晰 |
| 灵活性 | 直接控制 JOIN 类型和条件 | 通过基数和过滤间接控制,更抽象 |
| 框架集成 | 无特殊支持 | 深度集成,是 Fiori Elements 和 OData 导航的基石 |
| 耦合 | 查询与关系逻辑紧耦合 | 查询与关系逻辑解耦,模型更清晰 |
最佳实践:
- 始终使用 Association: 在新的 CDS 开发中,优先使用 Association 而不是直接 JOIN。
- 明确的别名: 使用下划线
_开头为 Association 命名,以示区分。 - 用于文本关联: 结合
@ObjectModel.text.association注解,自动为代码字段提供描述文本。 - OData 导航: 通过暴露 Association 来构建丰富的、可导航的 OData 服务。
以上就是关于Association的介绍,希望对你有所帮助
定期更文,欢迎关注



浙公网安备 33010602011771号