Mark的技术博客

SAP顾问 | 咨询专家 | IT与AI技术探索者

吴让宇(Mark)

吴让宇(Mark)

SAP顾问 | 咨询专家 | IT与AI技术探索者

吴让宇(Mark)的个人技术博客,专注于SAP系统实施、行业方案咨询、IT技术与AI技术的研究与分享。

SAP Fiori ABAP CDS OData Dify Docker AI LLM

最新文章

CDS注释

🎯 CDS注解概述

==注解(Annotation)==用于向CDS实体添加元数据。它们由使用CDS实体的框架进行评估,指定实体的属性和语义及其在运行时访问时的行为。

当CDS源代码中定义的对象被激活时,注解定义的元数据保存在ABAP Dictionary的==内部数据库表==中。



graph TD
    A[CDS注解] --> B[ABAP目录注解]
    A --> C[访问控制注解]
    A --> D[文本注解]
    A --> E[语义注解]
    A --> F[对象模型注解]
    A --> G[聚合注解]
    A --> H[消费注解]
    A --> I[VDM/OData注解]
    A --> J[分析注解]
    A --> K[系统字段注解]

📋 系统常量

CDS视图中可以使用的==会话变量==:

常量 描述
$session.client SAP客户端
$session.system_date 系统时间(SY-DATUM)
$session.system_language SAP语言
$session.user 登录SAP的用户

🏗️ ABAP目录注解

@AbapCatalog.sqlViewName

1
@AbapCatalog.sqlViewName: '<SQL_VIEW_NAME>'
  • 描述:ABAP词典中CDS数据库视图的名称
  • 可能的值:最多==16个字符==的字符串
  • 注意:CDS视图的每个定义都==必须包含==此注解

@AbapCatalog.compiler.compareFilter

1
@AbapCatalog.compiler.compareFilter: '<VALUE>'
  • 描述:定义CDS视图路径表达式中过滤条件的评估
  • 可能的值truefalse

🔍 访问控制注解

@AccessControl.authorizationCheck

1
@AccessControl.authorizationCheck: '<VALUE>'
  • 描述:定义使用Open SQL访问CDS视图时的==隐式访问控制==
  • 可能的值
说明
#CHECK 有角色时执行访问控制,无角色时语法检查警告
#NOT_REQUIRED 类似#CHECK,但无警告
#NOT_ALLOWED 不执行访问控制
ℹ️CDS角色概念
CDS角色隐式映射到每个用户(映射角色)。访问条件包括:
  • 将CDS实体元素与==文字值==比较的文字条件
  • 将CDS实体元素与SAP==授权==关联的PFCG条件

📝 最终用户文本注解

@EndUserText.label

1
@EndUserText.label: '<String>'
  • 描述:CDS视图的==可翻译短文本==
  • 可能的值:最多40个字符的字符串
  • 源代码中指定的值应为CDS源代码原始语言的文本

💡 语义注解

语义注解用于通知客户端有关==数据类型==的信息,如电话号码、邮件地址、城市等。

文本和URL

注解 描述
@Semantics.text 人类可读文本 true / false
@Semantics.url URL字段 URL

货币代码和金额

注解 描述
@Semantics.currencyCode 标记包含货币代码的字段 true / false
@Semantics.amount.currencyCode 货币金额,引用货币代码字段 [ELEMENT, PARAMETER]

度量单位

注解 描述
@Semantics.unitOfMeasure 标记包含度量单位 true / false
@Semantics.quantity.unitOfMeasure 与测量数量相关的单位 [ELEMENT, PARAMETER]

🔍 对象模型注解

代表键和文本元素

1
@ObjectModel.representativeKey: '<名称键字段>'

代表键对应的外键字段代表实体。

1
@ObjectModel.text.element: ['<元素>']

建立字段与描述性文本的联系。

外键关联

1
@ObjectModel.foreignKey.association: '<名称键字段>'

定义与视图的关联,该视图表示带注释字段的值列表/检查表。

其他ObjectModel注解

注解 描述
@ObjectModel.readOnly 该CDS只读
@ObjectModel.association.type: [#TO_COMPOSITION_ROOT] 跳转到根页面
@ObjectModel.association.type: [#TO_COMPOSITION_CHILD] 跳转到子页面

📊 聚合注解

@DefaultAggregation

1
@DefaultAggregation: '<值>'
说明
SUM 求和
MAX / MIN 最大值 / 最小值
AVG 平均值
COUNT / COUNT_DISTINCT 计数
FORMULA 公式,不聚合
NONE 非度量,用于过滤器和GROUP BY

🏗️ 消费注解

@Consumption.filter

1
@Consumption.filter: '<值>'

可以过滤底层视图的元素。过滤器值由用户在UI上手动输入。

💡 VDM和OData注解

@VDM.viewType

1
@VDM.viewType: '<值>'
说明
BASIC 核心数据基础,无冗余
COMPOSITE 从BASIC视图派生和组合
CONSUMPTION 特定应用目的

@OData.publish

1
@OData.publish: true

当设置且CDS视图被激活时,==自动生成OData服务==。随后需要在事务 /IWFND/MAINT_SERVICE 中手动激活。

🔍 分析注解

@Analytics.dataCategory

1
@Analytics.dataCategory: '<值>'
说明
#DIMENSION 主数据视图
#FACT 事务数据(星型模式中心)
#CUBE 事实数据(允许冗余)
#AGGREGATIONLEVEL 投影(支持回写)

@Analytics.query

1
@Analytics.query: true

指定将向分析管理器暴露的视图。

@Analytics.dataExtraction.enabled

1
@Analytics.dataExtraction.enabled: true

标记适合数据复制的视图。

📋 系统字段注解

@Environment.systemField

1
2
3
4
5
6
7
8
@Environment.systemField: #CLIENT
p_mandt : syst_mandt,

@Environment.systemField: #SYSTEM_DATE
p_datum : syst_datum,

@Environment.systemField: #USER
p_uname : syst_uname

支持的系统字段:#CLIENT#SYSTEM_DATE#SYSTEM_TIME#SYSTEM_LANGUAGE#USER

💡隐式参数传递
在Open SQL中使用带注释的参数访问CDS视图时,可以==放弃显式参数传递==。Open SQL会隐式传递相应系统字段的内容。

示例:系统字段使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@AbapCatalog.sqlViewName: 'DEMO_CDS_SYST'
@AccessControl.authorizationCheck: #NOT_REQUIRED
define view demo_cds_system_fields
with parameters
@Environment.systemField: #CLIENT
p_mandt : syst_mandt,
@Environment.systemField: #SYSTEM_DATE
p_datum : syst_datum,
@Environment.systemField: #SYSTEM_LANGUAGE
p_langu : syst_langu,
@Environment.systemField: #USER
p_uname : syst_uname
as select from demo_expressions
{
:p_mandt as client,
:p_datum as datum,
:p_langu as langu,
:p_uname as uname
}
where id = '1'
1
2
3
4
5
6
7
8
9
10
11
12
" 使用OPEN SQL访问,参数是隐式传递的
SELECT *
FROM demo_cds_system_fields( )
INTO TABLE @DATA(result).

" 也可以显式传递参数(p_mandt除外)
SELECT *
FROM demo_cds_system_fields(
p_datum = @sy-datum,
p_langu = @sy-langu,
p_uname = @sy-uname )
INTO TABLE @DATA(result2).
⚠️注意
  • p_mandt 的值==不能再显式传递==
  • 隐式参数传递仅在Open SQL中可用
  • 在CDS实体中使用带参数的CDS实体时,必须==再次显式传递参数==

ABAP CDS开发

🎯 CDS概念

==Core Data Services==(CDS)是SAP的战略性技术解决方案之一。其目的是在数据的物理存在和用户需求中间构建一个==虚拟层==,满足用户对访问SAP系统数据的需求。



graph TD
    A[CDS] --> B[HANA CDS]
    A --> C[ABAP CDS]
    B --> B1["仅支持HANA数据库<br>功能丰富"]
    C --> C1["支持多种数据库<br>通过ADT开发"]

CDS类型对比

类型 支持平台 特点
HANA CDS 仅支持HANA数据库 功能丰富,直接在数据库内开发
ABAP CDS 支持多种数据库平台 通过ADT工具开发,功能相对较少

CDS设计目标

  1. 易于定义和创建
  2. 能够满足==复杂业务逻辑==编写的需求
  3. 能够充分发挥数据库,尤其是SAP HANA的==性能==
  4. 具有==充足的语意信息==
  5. 具有良好的==复用性==和适应性
  6. 易于管理和传输
  7. 与现有系统的安全机制集成
  8. 具有一定的==自解释==功能
  9. 同时满足OLTP(ABAP,Fiori)和OLAP(BW、BO)的调用需求

📋 CDS特点

主要特性

  1. 将==数据计算逻辑下推==到数据库层
  2. 可以使用 UNIONUNION ALL 叠加查询结果
  3. 支持 CASE 条件语句
  4. 支持 Arithmetic、String 和 CAST 等语句
  5. 支持 INNER JOINOUTER JOIN
  6. 可以使用 ==Annotation==(注解)
  7. 支持 Aggregations、Grouping 功能
  8. 支持==90%以上的SQL功能==

开发环境

ABAP CDS==不能在SAP GUI==上进行开发,只能在 ==Eclipse== 上通过安装ABAP Development Tools(ADT)的方式进行开发。

💡ADT安装步骤
  1. 下载安装 Eclipse
  2. 打开 Eclipse → Help → Install New Software
  3. 输入网址:https://tools.hana.ondemand.com/xxxx(xxxx为Eclipse版本号)
  4. 选择 ABAP Development Tools for SAP NetWeaver

🏗️ ABAP CDS部分语法

基本语法

1
2
3
4
5
6
7
8
9
10
11
@AbapCatalog.sqlViewName: 'ZMY_CDS_VIEW'
@AccessControl.authorizationCheck: #NOT_REQUIRED
define view ZMY_CDS
as select from mara
{
key matnr,
mtart,
maktx,
@Semantics.unitOfMeasure: 'EA'
meins
}

使用CASE条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@AbapCatalog.sqlViewName: 'ZMATERIAL_STATUS'
@AccessControl.authorizationCheck: #NOT_REQUIRED
define view ZMATERIAL_STATUS
as select from mara
{
key matnr,
mtart,
maktx,
case
when mtart = 'FERT' then 'Finished Goods'
when mtart = 'HALB' then 'Semi-finished'
when mtart = 'ROH' then 'Raw Material'
else 'Other'
end as MaterialStatus
}

使用WHERE条件

1
2
3
4
5
6
7
8
9
10
11
@AbapCatalog.sqlViewName: 'ZMATERIAL_ACTIVE'
@AccessControl.authorizationCheck: #NOT_REQUIRED
define view ZMATERIAL_ACTIVE
as select from mara
{
key matnr,
mtart,
maktx,
meins
}
where matnr in ('1000', '1001', '1002')

使用CAST语句

1
2
3
4
5
6
7
8
9
10
11
@AbapCatalog.sqlViewName: 'ZMATERIAL_PRICE'
@AccessControl.authorizationCheck: #NOT_REQUIRED
define view ZMATERIAL_PRICE
as select from mara as Material
join mbew as Price on Material.matnr = Price.matnr
{
key Material.matnr,
Material.maktx,
cast( Price.price as abap.dec(15,2) ) as MaterialPrice,
cast( Price.currency as abap.cuky ) as Currency
}

使用Association

1
2
3
4
5
6
7
8
9
10
11
12
@AbapCatalog.sqlViewName: 'ZMATERIAL_SUPPLIER'
@AccessControl.authorizationCheck: #NOT_REQUIRED
define view ZMATERIAL_SUPPLIER
as select from mara as Material
association [1] to EKPO as PurchaseOrder
on $projection.matnr = PurchaseOrder.matnr
{
key Material.matnr,
Material.maktx,
PurchaseOrder.ebeln,
PurchaseOrder.ebelp
}

定义选择参数

1
2
3
4
5
6
7
8
9
10
11
@AbapCatalog.sqlViewName: 'ZMATERIAL_SEL'
@AccessControl.authorizationCheck: #NOT_REQUIRED
define view ZMATERIAL_SEL
as select from mara
{
key matnr,
mtart,
maktx,
meins
}
parameters p_matnr : matnr
⚠️参数限制
目前CDS视图的选择屏幕只支持==单值==(parameters),不支持多值(selection-options)。

🔍 S/4 数据表查看对应CDS视图方法

mseg 表为例:在使用CDS View查询MSEG时发现查不出数据,这是因为S/4部分表需要使用对应的CDS视图查询才会生效。

查找步骤

  1. 在 ==SE16== 中输入需要查询的表名
  2. 在视图框中输入 NSDM_E_MSEG,查出结果:NSDM_V_MSEG
  3. 使用 NSDM_V_MSEG 作为数据源创建CDS视图

💡 使用CDS创建oData Service

步骤1:创建CDS view

创建带有oData注解的CDS视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
@AbapCatalog.sqlViewName: 'ZWX_CDS3'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@OData.publish: true
define view ZWX_CDS3
as select from EKPO
{
key ebeln,
key ebelp,
matnr,
menge,
netpr,
waers
}
📌关键注意
  1. 加上 @OData.publish: true 可以将CDS发布为oData
  2. ==一定要定义主键==(在字段前加上 key 关键字),否则会报错

步骤2:激活并维护服务

  1. 事务代码:/N/IWFND/MAINT_SERVICE
  2. 点击==添加服务==
  3. 输入筛选条件,点击获取服务
  4. 查出对应服务后点击==添加所选服务==
  5. ==激活==服务

步骤3:测试oData服务

使用事务代码 /N/IWFND/GW_CLIENT 测试:

1
request uri: /sap/opu/odata/sap/zwx_cds3_cds/ZWX_CDS3?$filter=posnr eq '000010'
⚠️大小写敏感
URI需要==区分大小写==。

🏗️ 开发流程总结



graph LR
    A["1. 安装Eclipse<br>+ ADT"] --> B["2. 创建ABAP<br>项目"]
    B --> C["3. 创建CDS<br>Data Definition"]
    C --> D["4. 定义CDS<br>视图"]
    D --> E["5. 检查并<br>激活"]
    E --> F["6. 添加oData<br>注解"]
    F --> G["7. 激活服务<br>+ 测试"]

⚠️ 注意事项

  1. CDS视图激活后==不能修改==,只能删除重新创建
  2. sqlViewNamedefine view 后面的名称==不能相同==
  3. 选择屏幕目前只支持==单值==(parameters)
  4. S/4系统的某些表需要使用对应的==CDS视图==才能查询到数据
  5. URI需要==区分大小写==
  6. 在ABAP程序中调用时使用的是 sqlViewName

ABAP程序调用CDS视图

1
2
3
4
5
6
7
8
9
10
11
12
13
REPORT z_cds_call_example.

SELECT *
FROM ZMATERIAL_COMPLETE
INTO TABLE @DATA(lt_material)
WHERE matnr = '1000'.

cl_salv_table=>factory(
IMPORTING r_salv_table = DATA(lo_table)
CHANGING t_table = lt_material
).

lo_table->display( ).

ABAP Do循环

ℹ️概述
无条件循环重复执行多个语句,而不指定任何条件。DO语句通过无条件地多次执行一组语句块来实现无条件循环。

📐 语法格式

DO语句的一般格式如下:

1
2
3
DO [n TIMES].
<statement block>.
ENDDO.

参数说明

  • n - 循环次数的数值表达式
  • TIMES - 可选关键字,指定循环次数
  • <statement block> - 要重复执行的语句块
⚠️循环次数限制
“n”的值不应为负或零。如果它为零或为负,则不执行循环中的语句。

🔄 流程图



graph TD
    A[开始DO循环] --> B{次数限制?}
    B -->|有n次| C{计数器 <= n?}
    B -->|无限制| D{循环继续}
    C -->|是| E[执行语句块]
    C -->|否| F[结束循环]
    D --> G[执行语句块]
    E --> H{是否继续?}
    G --> H
    H -->|是| D
    H -->|否| F
    F --> I[继续执行]


📝 基本示例

固定次数循环

1
2
3
4
REPORT YH_SEP_15.
DO 15 TIMES.
WRITE: / 'Hello'.
ENDDO.

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello

无限循环(注意:需要终止条件)

1
2
3
4
5
6
7
8
9
10
11
DATA: lv_count TYPE I VALUE 1.

DO.
WRITE: / 'Iteration:', lv_count.
lv_count = lv_count + 1.

" 添加终止条件
IF lv_count > 10.
EXIT.
ENDIF.
ENDDO.

🔧 高级用法

1. 使用VARYING子句

1
2
3
4
5
6
7
REPORT YH_SEP_15.

DO 5 TIMES VARYING lv_index FROM 1 THEN 3.
WRITE: / 'Index:', lv_index.
" 每次循环增加3
" 1, 4, 7, 10, 13
ENDDO.

输出

1
2
3
4
5
Index: 1
Index: 4
Index: 7
Index: 10
Index: 13

2. 嵌套DO循环

1
2
3
4
5
6
7
8
REPORT YH_SEP_15.

DO 3 TIMES.
WRITE: / 'Outer loop:', sy-index.
DO 2 TIMES.
WRITE: ' Inner loop:', sy-index.
ENDDO.
ENDDO.

输出

1
2
3
4
5
6
7
8
9
Outer loop: 1
Inner loop: 1
Inner loop: 2
Outer loop: 2
Inner loop: 1
Inner loop: 2
Outer loop: 3
Inner loop: 1
Inner loop: 2

⚠️ 注意事项

1. 循环次数验证

1
2
3
4
5
6
7
8
9
10
11
12
13
DATA: lv_times TYPE I VALUE -5.

" ❌ 错误:负数次数
DO lv_times TIMES.
WRITE: / 'This will not execute'.
ENDDO.

" ✅ 正确:检查循环次数
IF lv_times > 0.
DO lv_times TIMES.
WRITE: / 'This will execute', sy-index.
ENDDO.
ENDIF.

2. 无限循环风险

1
2
3
4
5
" ⚠️ 危险:无限循环示例
DO.
WRITE: / 'This will loop forever!'.
" 忘记添加EXIT或类似终止条件
ENDDO.

3. 内存使用

1
2
3
4
5
6
7
8
9
10
11
12
" ❌ 错误:大数据量循环可能导致内存问题
DO 1000000 TIMES.
DATA: lv_large_string TYPE STRING.
lv_large_string = 'Some data'.
" 处理数据...
ENDDO.

" ✅ 正确:考虑内存使用
DO 1000000 TIMES.
" 直接处理数据,避免创建大型对象
" 或者分批处理
ENDDO.

💡 使用场景

1. 重复执行固定次数的任务

1
2
3
4
5
" 处理固定数量的记录
DO 100 TIMES.
" 处理每个记录
PERFORM process_record USING sy-index.
ENDDO.

2. 生成序列数据

1
2
3
4
5
6
7
" 生成编号序列
DATA: lv_number TYPE I VALUE 1.

DO 10 TIMES.
lv_number = lv_number * 2.
WRITE: / 'Power of 2:', sy-index, lv_number.
ENDDO.

3. 测试数据生成

1
2
3
4
5
6
7
8
9
" 生成测试数据
DO 1000 TIMES.
" 创建测试记录
INSERT test_table FROM VALUE #(
id = sy-index
name = 'Test User ' && sy-index
created_at = sy-datum
).
ENDDO.

🔧 与其他循环的比较

Do循环 vs While循环

特性 Do循环 While循环
条件检查 循环体之后执行 循环体之前执行
次数控制 可以指定次数 基于条件判断
适用场景 固定次数重复 条件控制重复
风险 可能无限循环 可能一次都不执行

Do循环 vs Loop循环

特性 Do循环 Loop循环
数据源 不指定数据源 需要数据表/内表
用途 通用循环 遍历数据集合
性能 适合简单循环 适合数据处理
灵活性 更灵活 面向数据

💡 最佳实践

1. 循环设计原则

  • 明确循环的目的和次数
  • 确保循环有明确的终止条件
  • 避免不必要的嵌套

2. 代码质量

  • 使用有意义的循环变量名
  • 在循环内处理逻辑,避免重复计算
  • 添加适当的注释说明循环用途

3. 性能优化

  • 避免在循环中进行复杂的操作
  • 考虑循环次数对性能的影响
  • 使用索引或缓存优化数据访问

4. 错误处理

  • 添加循环异常处理机制
  • 考虑循环超时或资源限制
  • 确保资源正确释放

ABAP Native SQL概述

🔍 Native SQL基础

定义

术语”Native SQL”是指可以静态传输到数据库接口的本机SQL接口的所有语句。

Native SQL的特点



graph TD
    A[Native SQL] --> B[数据库特定]
    A --> C[直接传输]
    A --> D[完整语言]
    
    B --> B1[特定数据库语法]
    C --> C1[不变传输]
    D --> D1[完整SQL支持]

与ABAP语法的关系

Native SQL语句不属于ABAP的语言范围,不遵循ABAP语法。ABAP仅包含用于隔离其中可以列出Native SQL语句的程序段的语句。

🗄️ Native SQL机制

工作原理

  1. 传输过程:Native SQL语句从本地SQL接口不变地传输到数据库系统并执行
  2. 语法规则:使用相关数据库的完整SQL语言范围
  3. 表访问:所寻址的数据库表不必在ABAP字典中声明

SAP特定的本地SQL语句

有一小组SAP特定的本地SQL语句,由本地SQL接口以特定方式处理。

📝 Native SQL语法

基本语法

1
2
3
EXEC SQL PERFORMING <form>.
<Native SQL statement>
ENDEXEC.

语句区域定义

EXECENDEXEC 语句定义ABAP程序中可以列出一个或多个Native SQL语句的区域。

语句处理规则

  1. 数据库特定语句:对于所寻址的数据库系统的程序接口有效的所有SQL语句都可以在EXEC和ENDEXEC之间列出,特别是DDL(数据定义语言)语句。

  2. 不变传输:这些SQL语句从Native SQL接口传递到数据库系统基本不变。语法规则由数据库系统指定,特别是数据库对象的区分大小写规则。

  3. 语句分隔:如果语法允许单个语句之间的分隔符,则可以在EXEC和ENDEXEC之间包括许多本地SQL语句。

  4. SAP特定元素:可以在EXEC和ENDEXEC之间指定SAP特定的本地SQL语言元素。这些语句不会直接从Native SQL接口传递到数据库,但会适当地进行转换。

💡 Native SQL示例

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
REPORT zdemonative_sql.

* 定义工作区
DATA: BEGIN OF wa,
connid TYPE spfli-connid,
cityfrom TYPE spfli-cityfrom,
cityto TYPE spfli-cityto,
END OF wa.

* 定义变量
DATA c1 TYPE spfli-carrid VALUE 'LH'.

* 执行Native SQL
EXEC SQL PERFORMING loop_output.
SELECT connid, cityfrom, cityto
INTO :wa
FROM SPFLI
WHERE carrid = :c1
ENDEXEC.

* 处理子程序
FORM loop_output.
WRITE: / wa-connid, wa-cityfrom, wa-cityto.
ENDFORM.

输出示例

1
2
3
0400  FRANKFURT  NEW YORK
2402 FRANKFURT BERLIN
0402 FRANKFURT NEW YORK

🔍 Native SQL的使用场景

1. 数据定义语言(DDL)

1
2
3
4
5
6
7
EXEC SQL.
CREATE TABLE TEMP_TABLE (
ID NUMBER(10),
NAME VARCHAR2(100),
CREATED_DATE DATE
)
ENDEXEC.

2. 复杂查询

1
2
3
4
5
6
7
8
EXEC SQL.
SELECT a.*, b.name
INTO :wa_a, :wa_b
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE a.status = :status
ORDER BY a.created_date DESC
ENDEXEC.

3. 批量操作

1
2
3
4
5
6
7
8
9
10
11
12
* 使用PERFORMING处理多行结果
EXEC SQL PERFORMING process_rows.
SELECT id, name, email
INTO :wa_id, :wa_name, :wa_email
FROM customers
WHERE active = 1
ENDEXEC.

FORM process_rows.
" 处理每一行数据
WRITE: / wa_id, wa_name, wa_email.
ENDFORM.

🎯 Native SQL vs Open SQL

特性 Native SQL Open SQL
语法 数据库特定 ABAP标准
表访问 任何数据库表 ABAP字典表
DDL支持 完全支持 不支持
转换 无转换 自动转换
性能 高(直接) 中等(通过接口)
维护性 低(依赖数据库) 高(独立)

⚠️ Native SQL注意事项

1. 数据库依赖性

  • 语法因数据库而异
  • 需要了解特定数据库的SQL方言
  • 移植性差

2. 性能考虑

  • 直接访问数据库,性能更高
  • 但失去了ABAP的优化层
  • 需要手动管理事务

3. 安全风险

  • 可能存在SQL注入风险
  • 需要谨慎处理用户输入
  • 建议使用参数化查询

🔧 最佳实践

1. 参数化查询

1
2
3
4
5
6
7
8
9
10
11
12
* 安全的参数化查询
DATA: lv_carrid TYPE spfli-carrid,
wa_spfli TYPE spfli.

lv_carrid = 'LH'.

EXEC SQL.
SELECT *
INTO :wa_spfli
FROM spfli
WHERE carrid = :lv_carrid
ENDEXEC.

2. 错误处理

1
2
3
4
5
6
7
8
9
10
11
* 添加错误处理
DATA: lv_rc TYPE i.

EXEC SQL.
INSERT INTO temp_table (id, name) VALUES (1, 'Test')
INTO :wa_spfli
ENDEXEC.

IF sy-subrc <> 0.
WRITE: / 'SQL执行错误:', sy-subrc.
ENDIF.

3. 事务管理

1
2
3
4
5
6
7
8
9
10
11
12
13
* 显式事务控制
EXEC SQL.
BEGIN WORK
ENDEXEC.

" 执行操作
EXEC SQL.
INSERT INTO table1 VALUES (1, 'Test')
ENDEXEC.

EXEC SQL.
COMMIT WORK
ENDEXEC.

📊 使用建议

适合使用Native SQL的场景

  1. 需要使用特定数据库功能
  2. 执行复杂DDL操作
  3. 性能要求极高且了解目标数据库时
  4. 临时表操作

不适合使用Native SQL的场景

  1. 需要跨数据库兼容
  2. ABAP字典表操作
  3. 标准SAP功能已满足需求时
  4. 维护性要求高

ABAP Exit语句

🎯 Exit语句概述

EXIT语句用于==无条件==和==立即==终止整个循环。执行EXIT语句后,循环终止,并处理循环之后的语句。



graph TD
    A[循环开始] --> B[执行代码]
    B --> C{遇到EXIT?}
    C -->|是| D[立即退出循环]
    C -->|否| E[继续执行]
    E --> F{循环结束?}
    F -->|否| B
    F -->|是| G[结束]
    D --> H[执行循环后代码]

Exit语句的特点

  • 无条件退出:==立即==终止循环执行
  • 完全终止:退出==整个循环==,继续执行循环后的代码
  • 嵌套支持:在嵌套循环中只退出==当前层循环==
  • 简单直接:不需要条件判断

📋 Exit语句语法

1
EXIT.
⚠️嵌套循环中的行为
如果在嵌套循环中使用EXIT语句,则处理EXIT语句后==仅退出最内层循环==,外层循环继续执行。

💡 示例

示例1:基本用法

1
2
3
4
5
6
7
Report YH_SEP_15.
DO 5 TIMES.
IF SY-INDEX = 3.
EXIT.
ENDIF.
Write / SY-INDEX.
ENDDO.

输出结果

1
2
1
2
💥代码解析
  • 循环执行到 sy-index = 3 时遇到 EXIT
  • 立即终止循环,不再执行后续迭代
  • 所以只输出 1 和 2

示例2:在内表处理中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
REPORT z_exit_example.
DATA: lt_items TYPE TABLE OF sflight,
ls_item LIKE LINE OF lt_items,
lv_count TYPE i VALUE 0.

" 获取数据
SELECT * FROM sflight INTO TABLE lt_items
UP TO 10 ROWS.

LOOP AT lt_items INTO ls_item.
lv_count = lv_count + 1.

" 处理前5条记录后退出
IF lv_count > 5.
EXIT.
ENDIF.

WRITE: / 'Processing flight:', ls_item-connid.
ENDLOOP.

示例3:嵌套循环中的Exit

1
2
3
4
5
6
7
8
9
10
11
12
13
REPORT z_nested_exit_example.
DO 3 TIMES.
WRITE: / 'Outer loop:', sy-index.

DO 3 TIMES.
IF sy-index = 2.
EXIT. " 只退出内层循环
ENDIF.
WRITE: / ' Inner loop:', sy-index.
ENDDO.

ULINE.
ENDDO.

输出结果

1
2
3
4
5
6
Outer loop: 1
Inner loop: 1
Outer loop: 2
Inner loop: 1
Outer loop: 3
Inner loop: 1
💡嵌套循环说明
内层循环在 sy-index = 2 时退出,但外层循环继续执行。每次外层循环中内层循环只执行第一次迭代。

🔍 Exit与其他循环控制语句的比较

语句 作用 条件 适用场景
EXIT 立即退出整个循环 无条件 需要完全终止循环
CHECK 跳过当前迭代 有条件 基于条件跳过
CONTINUE 跳到下一次迭代 无条件 跳过当前迭代


graph TD
    A[循环控制语句] --> B["EXIT<br>退出整个循环"]
    A --> C["CHECK<br>条件跳过迭代"]
    A --> D["CONTINUE<br>跳到下次迭代"]

    B --> B1["循环 i = 1,2,3<br>EXIT at i=2<br>输出: 1"]
    C --> C1["循环 i = 1,2,3<br>CHECK i<>2<br>输出: 1, 3"]
    D --> D1["循环 i = 1,2,3<br>CONTINUE at i=2<br>输出: 1, 3"]

🏗️ 最佳实践

1. 明确的退出条件

1
2
3
4
5
6
7
8
" ✅ 好的做法 - 条件明确
LOOP AT lt_items INTO ls_item.
IF ls_item-status = 'COMPLETED' AND lv_all_processed = abap_true.
EXIT.
ENDIF.
" 处理逻辑
PERFORM process_item USING ls_item.
ENDLOOP.

2. 达到限制后退出

1
2
3
4
5
6
7
8
9
LOOP AT lt_items INTO ls_item.
" 达到最大处理数量后退出
IF lv_processed_count >= gv_max_items.
EXIT.
ENDIF.

" 处理逻辑
ADD 1 TO lv_processed_count.
ENDLOOP.

3. 避免过度使用

1
2
3
4
" ❌ 避免 - 简单循环不需要EXIT
DO 10 TIMES.
WRITE: / 'Processing item', sy-index.
ENDDO.

💡 常见应用场景

场景 说明
批量处理限制 处理达到一定数量后退出
错误处理 遇到严重错误时退出循环
用户取消 响应用户取消操作
数据遍历 找到目标数据后退出
性能优化 避免不必要的循环

⚠️ 注意事项

  1. 嵌套循环:在嵌套循环中使用时只退出==当前层循环==
  2. 性能影响:频繁使用EXIT可能影响代码可读性
  3. 替代方案:对于简单条件,考虑使用 CHECKIF
  4. 调试困难:过多EXIT语句可能增加调试难度
  5. GET/START-OF-SELECTON事件:EXIT在这些事件中的行为可能不同

ABAP JSON解析

概述

在 ABAP 中,可以使用标准类 /ui2/cl_json 进行 JSON 数据的序列化和反序列化操作,实现 ABAP 数据结构与 JSON 字符串之间的相互转换。

ℹ️类说明
/ui2/cl_json 是 SAP 提供的标准工具类,支持:
  • ABAP 结构/内表 → JSON 字符串(序列化)
  • JSON 字符串 → ABAP 结构/内表(反序列化)

序列化 serialize

使用 /ui2/cl_json=>serialize 方法将 ABAP 结构或内表转换为 JSON 字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DATA: lv_json TYPE string,
ls_data TYPE my_structure.

" 填充数据
ls_data-field1 = 'Value1'.
ls_data-field2 = 'Value2'.

" 序列化为 JSON
lv_json = /ui2/cl_json=>serialize(
data = ls_data
pretty_name = /ui2/cl_json=>pretty_mode-none
).

" 输出结果
WRITE: / lv_json.
" 结果: {"FIELD1":"Value1","FIELD2":"Value2"}

参数说明

参数 类型 说明
data 任意 要序列化的 ABAP 数据对象(结构或内表)
pretty_name ENUM JSON 字段命名格式模式
compress CHAR 是否压缩输出(X=压缩, =格式化)
assoc_arrays CHAR 是否保持数组顺序

pretty_name 字段名格式

模式 说明 示例
pretty_mode-none 保持原始字段名 FIELD_NAMEFIELD_NAME
pretty_mode-low_case 字段名转为小写 FIELD_NAMEfield_name
pretty_mode-camel_case 字段名转为驼峰命名 FIELD_NAMEfieldName

格式对比示例

1
2
3
4
5
6
7
8
9
10
" 原始 ABAP 结构字段: DOC_TYPE, DOC_ID, CREATED_AT

" pretty_mode-none
{"DOC_TYPE":"INV","DOC_ID":"12345","CREATED_AT":"2026-04-23"}

" pretty_mode-low_case
{"doc_type":"INV","doc_id":"12345","created_at":"2026-04-23"}

" pretty_mode-camel_case
{"DocType":"INV","DocId":"12345","CreatedAt":"2026-04-23"}

反序列化 deserialize

使用 /ui2/cl_json=>deserialize 方法将 JSON 字符串转换回 ABAP 数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DATA: lv_json TYPE string,
ls_data TYPE my_structure.

" JSON 字符串
lv_json = `{"FIELD1":"Value1","FIELD2":"Value2"}`.

" 反序列化为结构
/ui2/cl_json=>deserialize(
EXPORTING
json = lv_json
pretty_name = /ui2/cl_json=>pretty_mode-none
CHANGING
data = ls_data
).

" 使用数据
WRITE: / ls_data-field1, ls_data-field2.

参数说明

参数 类型 说明
json STRING 要反序列化的 JSON 字符串
pretty_name ENUM JSON 字段命名格式(需与序列化时一致)
data 任意 接收数据的 ABAP 数据对象(CHANGING 参数)
⚠️类型匹配
确保目标结构/内表的字段类型与 JSON 数据匹配,否则可能导致:
  • 转换失败
  • 数据丢失
  • 类型不兼容错误

完整示例

示例1: 基础序列化/反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
TYPES: BEGIN OF ts_person,
name TYPE string,
age TYPE i,
email TYPE string,
END OF ts_person.

DATA: ls_person TYPE ts_person,
lt_person TYPE STANDARD TABLE OF ts_person,
lv_json TYPE string.

" 序列化:结构 → JSON
ls_person-name = '张三'.
ls_person-age = 30.
ls_person-email = 'zhangsan@example.com'.

lv_json = /ui2/cl_json=>serialize(
data = ls_person
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
).
" 结果: {"Name":"张三","Age":30,"Email":"zhangsan@example.com"}

" 反序列化:JSON → 结构
CLEAR ls_person.
/ui2/cl_json=>deserialize(
EXPORTING
json = lv_json
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
CHANGING
data = ls_person
).

示例2: 内表序列化/反序列化

1
2
3
4
5
6
7
8
9
10
" 准备内表数据
APPEND VALUE #( name = '张三' age = 30 email = 'zhangsan@example.com' ) TO lt_person.
APPEND VALUE #( name = '李四' age = 28 email = 'lisi@example.com' ) TO lt_person.

" 序列化内表
lv_json = /ui2/cl_json=>serialize(
data = lt_person
pretty_name = /ui2/cl_json=>pretty_mode-low_case
).
" 结果: [{"name":"张三","age":30,"email":"zhangsan@example.com"},...]

示例3: 与外部系统交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
TYPES: BEGIN OF ts_import_data,
doc_type TYPE string,
doc_id TYPE string,
amount TYPE string,
currency TYPE string,
END OF ts_import_data.

TYPES: BEGIN OF ts_export_data,
status TYPE string,
message TYPE string,
timestamp TYPE string,
END OF ts_export_data.

DATA: lv_import_json TYPE string,
ls_import_data TYPE ts_import_data,
lv_export_json TYPE string,
ls_export_data TYPE ts_export_data.

" 导入:处理外部系统的 JSON
lv_import_json = `{
"docType": "INV",
"docId": "12345",
"amount": "1000.00",
"currency": "USD"
}`.

/ui2/cl_json=>deserialize(
EXPORTING
json = lv_import_json
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
CHANGING
data = ls_import_data
).

" 导出:生成返回 JSON
ls_export_data-status = 'SUCCESS'.
ls_export_data-message = 'Data processed successfully'.
ls_export_data-timestamp = |{ sy-datum DATE = ISO }T{ sy-uzeit TIME = ISO }|.

lv_export_json = /ui2/cl_json=>serialize(
data = ls_export_data
pretty_name = /ui2/cl_json=>pretty_mode-none
).

最佳实践

💡字段名映射
与外部系统(JavaScript/Web服务)交互时,建议使用 pretty_mode-camel_case 保持与 JSON 的命名约定一致。
💡数据校验
反序列化后务必对关键字段进行校验:
1
2
3
IF ls_import_data-doc_id IS INITIAL.
" 处理错误情况
ENDIF.
💡性能优化
  • 大数据量使用 compress = abap_true 减少传输大小
  • 避免嵌套过深的结构
  • 考虑使用 ST 传输二进制数据
💡错误处理
1
2
3
4
5
6
7
8
9
DATA: lx_json TYPE REF TO cx_sy_move_cast_error.

TRY.
/ui2/cl_json=>deserialize(
EXPORTING json = lv_json
CHANGING data = ls_data ).
CATCH cx_sy_move_cast_error INTO lx_json.
WRITE: / 'JSON解析错误:', lx_json->get_text( ).
ENDTRY.

常见问题

Q: 如何处理嵌套 JSON?

定义嵌套的结构体类型即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
TYPES: BEGIN OF ts_address,
city TYPE string,
street TYPE string,
zip TYPE string,
END OF ts_address.

TYPES: BEGIN OF ts_person,
name TYPE string,
age TYPE i,
address TYPE ts_address,
END OF ts_person.

DATA: ls_person TYPE ts_person.

Q: 如何处理数组?

使用内表类型:

1
2
3
4
5
6
7
8
9
10
TYPES: BEGIN OF ts_item,
item_id TYPE string,
quantity TYPE i,
unit_price TYPE f,
END OF ts_item.

TYPES: BEGIN OF ts_order,
order_id TYPE string,
items TYPE STANDARD TABLE OF ts_item WITH EMPTY KEY,
END OF ts_order.

Q: 如何处理日期时间格式?

转换为标准字符串格式:

1
2
3
" 使用 ISO 8601 格式
DATA(lv_timestamp) = |{ sy-datum DATE = ISO }T{ sy-uzeit TIME = ISO }|.
" 结果: 2026-04-23T14:30:00

ABAP Nested控制

ℹ️概述
DO和WHILE语句可以被测试以及与其他循环形式组合。每个嵌套循环将具有由系统创建和监视的自己的SY-INDEX。嵌套循环允许我们在一个循环内部包含另一个或多个循环。

🔧 语法格式

嵌套DO循环语法

1
2
3
4
5
6
DO [n TIMES].
<statement block n>.
DO [m TIMES].
<statement block m>.
ENDDO.
ENDDO.

嵌套WHILE循环语法

1
2
3
4
5
6
WHILE <condition1>.
<statement block 1>.
WHILE <condition2>.
<statement block 2>.
ENDWHILE.
ENDWHILE.

混合嵌套语法

1
2
3
4
5
6
DO [n TIMES].
<statement block 1>.
WHILE <condition>.
<statement block 2>.
ENDWHILE.
ENDDO.

📊 嵌套循环执行流程



graph TD
    A[开始外层循环] --> B{外层条件}
    B -->|满足| C[执行外层语句块]
    B -->|不满足| G[结束外层循环]
    C --> D{进入内层循环}
    D --> E{内层条件}
    E -->|满足| F[执行内层语句块]
    E -->|不满足| H[退出内层循环]
    F --> I{继续内层循环?}
    I -->|是| E
    I -->|否| C
    H --> C
    G --> J[继续执行]


📝 示例代码

1. 简单嵌套DO循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
REPORT ys_sep_15.
DATA:
a1 TYPE i,
b1 TYPE i.
a1 = 0.
b1 = 0.

DO 2 TIMES.
a1 = a1 + 1.
WRITE: / 'Outer', a1.
DO 10 TIMES.
b1 = b1 + 1.
WRITE: ' Inner', b1.
ENDDO.
ENDDO.

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Outer 1
Inner 1
Inner 2
Inner 3
Inner 4
Inner 5
Inner 6
Inner 7
Inner 8
Inner 9
Inner 10
Outer 2
Inner 11
Inner 12
Inner 13
Inner 14
Inner 15
Inner 16
Inner 17
Inner 18
Inner 19
Inner 20

2. 嵌套WHILE循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
REPORT ys_sep_15.
DATA:
lv_outer TYPE I VALUE 1,
lv_inner TYPE I VALUE 1.

WHILE lv_outer <= 3.
WRITE: / 'Outer loop:', lv_outer.

WHILE lv_inner <= 2.
WRITE: ' Inner loop:', lv_inner.
lv_inner = lv_inner + 1.
ENDWHILE.

lv_outer = lv_outer + 1.
lv_inner = 1. " 重置内层计数器
ENDWHILE.

3. 混合嵌套循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
REPORT ys_sep_15.
DATA:
lv_counter TYPE I VALUE 1.

DO 3 TIMES.
WRITE: / 'Outer DO loop:', sy-index.

WHILE lv_counter <= 3.
WRITE: ' Inner WHILE loop:', lv_counter.
lv_counter = lv_counter + 1.
ENDWHILE.

lv_counter = 1. " 重置计数器
ENDDO.

🔍 嵌套循环的执行机制

SY-INDEX的行为

每个嵌套循环都有自己的SY-INDEX:

1
2
3
4
5
6
DO 2 TIMES.
WRITE: / 'Outer loop - SY-INDEX:', sy-index.
DO 3 TIMES.
WRITE: ' Inner loop - SY-INDEX:', sy-index.
ENDDO.
ENDDO.

输出

1
2
3
4
5
6
7
8
Outer loop - SY-INDEX: 1
Inner loop - SY-INDEX: 1
Inner loop - SY-INDEX: 2
Inner loop - SY-INDEX: 3
Outer loop - SY-INDEX: 2
Inner loop - SY-INDEX: 1
Inner loop - SY-INDEX: 2
Inner loop - SY-INDEX: 3

执行次数计算

对于嵌套循环,总执行次数 = 外层循环次数 × 内层循环次数

外层次数 内层次数 总次数
2 3 6
5 10 50
n m n × m

⚠️ 注意事项

1. 性能考虑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
" ❌ 性能差:不必要的三层嵌套
DO 1000 TIMES.
DO 1000 TIMES.
DO 1000 TIMES.
" 复杂操作
ENDDO.
ENDDO.
ENDDO.

" ✅ 性能优化:减少嵌套层次或使用数据表
DATA: lt_large TYPE TABLE OF i.

DO 1000 TIMES.
APPEND sy-index TO lt_large.
ENDDO.

LOOP AT lt_large INTO DATA(lv_value).
DO 1000 TIMES.
" 处理数据
ENDDO.
ENDLOOP.

2. 无限循环风险

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
" ❌ 危险:内层循环可能无限
DO.
DO.
" 忘记添加退出条件
ENDDO.
ENDDO.

" ✅ 安全:添加明确的退出条件
DO 10 TIMES.
DO 5 TIMES.
" 处理逻辑
IF some_condition.
EXIT.
ENDIF.
ENDDO.
ENDDO.

3. 变量作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
" ✅ 正确:注意变量作用域
DATA: lv_outer_var TYPE I.

DO 3 TIMES.
DATA: lv_inner_var TYPE I VALUE 1.
lv_outer_var = sy-index.

WRITE: / 'Outer:', lv_outer_var.
WRITE: ' Inner initial:', lv_inner_var.

DO 2 TIMES.
lv_inner_var = lv_inner_var + 1.
WRITE: ' Inner now:', lv_inner_var.
ENDDO.
ENDDO.

💡 使用场景

1. 矩阵处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
" 处理二维数据
DATA:
lt_matrix TYPE TABLE OF TABLE OF i,
lv_row TYPE I,
lv_col TYPE I.

" 填充矩阵
DO 5 TIMES.
DATA: lt_row TYPE TABLE OF i.
DO 5 TIMES.
APPEND sy-index TO lt_row.
ENDDO.
APPEND lt_row TO lt_matrix.
ENDDO.

" 读取矩阵
LOOP AT lt_matrix INTO DATA(lt_row).
DO 5 TIMES.
lv_col = sy-index.
WRITE: / lt_row[ lv_col ].
ENDDO.
ENDLOOP.

2. 多层条件检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
" 检查多层数据
SELECT * FROM vbak INTO TABLE @DATA(lt_vbak)
WHERE vkorg = '1000'.

LOOP AT lt_vbak INTO DATA(lwa_vbak).
DO 3 TIMES. " 检查3个订单项
SELECT * FROM vbap INTO TABLE @DATA(lt_vap)
WHERE vbeln = lwa_vbak-vbeln
AND posnr = sy-index.

IF sy-subrc = 0.
LOOP AT lt_vap INTO DATA(lwa_vap).
WRITE: / 'Order:', lwa_vbak-vbeln,
'Item:', lwa_vap-posnr,
'Material:', lwa_vap-matnr.
ENDLOOP.
ENDIF.
ENDDO.
ENDLOOP.

3. 分组处理

1
2
3
4
5
6
7
8
9
" 按组处理数据
DO 3 TIMES. " 处理3个不同的组
WRITE: / 'Processing Group:', sy-index.

DO 10 TIMES. " 处理每个组的10个项目
WRITE: ' Item:', sy-index.
" 具体处理逻辑
ENDDO.
ENDDO.

🔧 优化技巧

1. 减少嵌套层数



graph TD
    A[原始嵌套] --> B[优化思路]
    B --> C[使用数据结构]
    B --> D[使用辅助方法]
    B --> E[使用循环控制]
    
    A --> A1[DO 10]
    A1 --> A2[DO 5]
    A2 --> A3[DO 3]
    A3 --> A4[处理数据]
    
    C --> C1[使用内表]
    D --> D1[提取方法]
    E --> E1[使用EXIT]

2. 使用EXIT提前退出

1
2
3
4
5
6
7
8
9
10
11
12
13
" 使用EXIT避免不必要的循环
DO 100 TIMES.
DO 100 TIMES.
IF some_condition.
EXIT. " 退出内层循环
ENDIF.
" 处理数据
ENDDO.

IF another_condition.
EXIT. " 退出外层循环
ENDIF.
ENDDO.

3. 使用CONTINUE跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
" 使用CONTINUE跳过特定迭代
DO 10 TIMES.
IF some_skip_condition.
CONTINUE. " 跳过当前迭代
ENDIF.

DO 5 TIMES.
IF another_skip_condition.
CONTINUE. " 跳过内层迭代
ENDIF.

" 处理数据
ENDDO.
ENDDO.

💡 最佳实践

1. 嵌套设计原则

  • 保持嵌套层次简单(建议不超过3层)
  • 确保每个循环都有明确的退出条件
  • 考虑使用辅助函数减少嵌套

2. 性能优化

  • 避免在深层嵌套中进行复杂操作
  • 考虑使用索引或缓存优化数据访问
  • 适当使用EXIT和CONTINUE控制流程

3. 代码可读性

  • 使用有意义的变量名
  • 添加必要的注释说明嵌套逻辑
  • 保持代码缩进清晰

4. 错误处理

  • 为嵌套循环添加适当的错误处理
  • 考虑资源释放和内存管理
  • 避免无限循环的风险

ABAP BADI

🎯 BADI概述

BADI(==Business Add-In==)是一种增强技术,允许在SAP系统中实现预定义的特殊功能来增强各种应用。类似于Microsoft Excel的加载项,SAP通过提供称为BADI的==业务加载项==来实现一些预定义的功能。

BADI必须首先被==定义==,然后==实现==以增强SAP应用程序。在定义BADI时,将创建一个接口。BADI由该接口实现,接口又由一个或多个适配器类实现。



graph TD
    A[BADI定义] --> B[创建接口]
    B --> C[适配器类实现接口]
    C --> D[客户自定义实现]
    D --> E[增强后的SAP应用]

🔍 BADI与其他增强技术的区别

BADI技术与其他增强技术在两个方面不同:

  1. ==增强技术只能实现一次==
  2. ==可以被许多客户同时使用==
ℹ️过滤器BADI
您还可以创建==过滤器BADI==,这意味着BADI是基于过滤的数据定义的,这是传统增强技术不可能实现的。

📋 SAP版本中的BADI

在SAP版本7.0中重新定义了BADI的概念,具有以下目标:

  • 通过在ABAP语言中添加 GET BADICALL BADI 来增强标准应用程序
  • 提供更多灵活性功能,如==上下文==和==过滤器==


graph LR
    A[BADI优势] --> B[灵活性]
    A --> C[上下文支持]
    A --> D[过滤器功能]
    A --> E[多客户支持]
    A --> F[可重复使用]

    B --> B1["GET BADI / CALL BADI"]
    C --> C1["上下文相关的增强"]
    D --> D1["基于过滤数据的增强"]
    E --> E1["多客户同时使用"]
    F --> F1["可以多次实现"]

🏗️ BADI的组成

创建BADI时,它包含以下组件:

组件 说明
==接口== 定义BADI的基本结构
==功能代码== 用于菜单增强和屏幕增强的附加组件

增强过程

BADI创建允许客户在标准SAP应用程序中包含自己的增强功能。增强、接口和生成的类位于适当的应用程序开发命名空间中。

📌历史背景
SAP在版本4.6A中引入了BADI增强技术,该技术在版本7.0中重新实现,增加了更多功能。

💡 BADI实现示例

基本BADI定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
REPORT z_badi_example.

* 定义接口
INTERFACE zif_badi_example.
METHODS: process_data
IMPORTING iv_input TYPE string
EXPORTING ev_output TYPE string.
ENDINTERFACE.

* 实现BADI
CLASS zcl_badi_example DEFINITION.
PUBLIC SECTION.
INTERFACES zif_badi_example.
ENDCLASS.

CLASS zcl_badi_example IMPLEMENTATION.
METHOD zif_badi_example~process_data.
" 处理输入数据
ev_output = to_upper( iv_input ).
CONCATENATE 'Processed: ' ev_output INTO ev_output.
ENDMETHOD.
ENDCLASS.

使用BADI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
START-OF-SELECTION.
DATA: lv_input TYPE string VALUE 'Hello BADI',
lv_output TYPE string,
lo_badi TYPE REF TO zif_badi_example.

" 创建BADI实例
GET BADI lo_badi.

" 调用BADI方法
IF lo_badi IS BOUND.
CALL BADI lo_badi->process_data
EXPORTING
iv_input = lv_input
IMPORTING
ev_output = lv_output.

WRITE: / lv_output.
ENDIF.
💡过滤器BADI示例
1
GET BADI lo_badi FILTERS country = 'CN'.

使用过滤器可以基于特定条件选择不同的BADI实现。

BADI相关事务码

事务码 用途
SE18 查看BADI定义
SE19 实现BADI
SPRO IMG中查找BADI

⚠️ 注意事项

  1. BADI必须在==定义后==才能实现
  2. 可以创建==多个实现==,但需要使用过滤器
  3. 修改BADI需要==适当的开发权限==
  4. 建议在修改前进行==充分测试==
💡BADI选择建议
  • 优先使用BADI而不是用户出口,因为BADI更灵活
  • 考虑使用过滤器BADI来实现不同客户的特定需求
  • 避免在BADI中进行大量数据处理,以免影响性能

ABAP Open SQL概述

🗄️ Open SQL基础

定义

Open SQL是指允许直接访问当前AS ABAP的中央数据库中数据的ABAP语句的子集。

Open SQL的特点



graph TD
    A[Open SQL] --> B[数据库无关]
    A --> C[自动转换]
    A --> D[简化操作]
    
    B --> B1[统一接口]
    C --> C1[转换为特定SQL]
    D --> D1[ABAP语法]

主要功能

Open SQL语句映射所有数据库系统支持的ABAP中的SQL的数据操作语言功能。

工作原理

  1. 转换过程:Open SQL语句在数据库接口的Open SQL接口中转换为特定于数据库的SQL
  2. 执行流程:将转换后的SQL传送到数据库系统并执行
  3. 表访问:可用于访问在ABAP字典中声明的数据库表

🔍 数据库访问

默认访问

  • 默认访问AS ABAP的中央数据库
  • 可以通过辅助数据库连接访问其他数据库

系统字段检查

每当在ABAP程序中使用Open SQL语句时,检查执行动作是否成功非常重要:

1
2
3
4
5
6
7
8
* 使用SY-SUBRC检查执行结果
IF SY-SUBRC = 0.
" 语句执行成功
ELSE.
" 语句执行失败
" 显示错误代码
WRITE: 'The return code is ', SY-SUBRC.
ENDIF.

💾 工作区声明

DATA语句

DATA语句用于声明一个工作区(work area)。工作区只能存储一条记录。

声明方式

1
2
* 使用LIKE语句声明工作区
DATA wa_customers1 LIKE ZCUSTOMERS1.

工作区特点

  • 具有与表相同的结构
  • 但不是表本身
  • 只能存储一条记录
  • 用于临时数据存储

📝 Open SQL操作

1. INSERT语句

功能

将工作区和它保存的记录插入到表中。

语法

1
INSERT <table_name> FROM <work_area>.

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DATA wa_customers1 LIKE ZCUSTOMERS1.

* 填充工作区数据
wa_customers1-customer = '100006'.
wa_customers1-name = 'DAVE'.
wa_customers1-title = 'MR'.
wa_customers1-dob = '19931017'.

* 插入记录
INSERT ZCUSTOMERS1 FROM wa_customers1.

* 检查是否成功
IF SY-SUBRC = 0.
WRITE 'Record Inserted Successfully'.
ELSE.
WRITE: 'The return code is ', SY-SUBRC.
ENDIF.

2. CLEAR语句

功能

清除字段或变量,以便在其位置插入新数据,从而允许重新使用。

语法

1
CLEAR <work_area>.

使用场景

在插入新记录前,使用CLEAR语句清除工作区:

1
2
* 清除工作区以准备新数据
CLEAR wa_customers1.

3. UPDATE语句

功能

同时更新表中的一个或多个现有记录。

语法

1
UPDATE <table_name> FROM <work_area>.

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DATA wa_customers1 LIKE ZCUSTOMERS1.

* 更新现有记录
wa_customers1-customer = '100006'.
wa_customers1-name = 'RICHARD'. * 更改姓名
wa_customers1-title = 'MR'.
wa_customers1-dob = '19931017'.

UPDATE ZCUSTOMERS1 FROM wa_customers1.

* 检查结果
IF SY-SUBRC = 0.
WRITE 'Record Updated Successfully'.
ENDIF.

4. MODIFY语句

功能

MODIFY语句可以被视为INSERT和UPDATE语句的组合。它可用于插入新记录或修改现有记录。

语法

1
MODIFY <table_name> FROM <work_area>.

执行逻辑

  • 如果具有键字段值的记录已存在,则更新该记录
  • 如果没有,则创建新记录

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* 清除工作区
CLEAR wa_customers1.

* 创建新记录
wa_customers1-customer = '100007'.
wa_customers1-name = 'RALPH'.
wa_customers1-title = 'MR'.
wa_customers1-dob = '19910921'.

* 修改(插入或更新)
MODIFY ZCUSTOMERS1 FROM wa_customers1.

* 检查结果
IF SY-SUBRC = 0.
WRITE 'Record Modified Successfully'.
ENDIF.

📊 操作对比

操作 功能 特点 适用场景
INSERT 插入新记录 只能插入,不能更新 添加全新数据
UPDATE 更新现有记录 只能更新,不能插入 修改已存在数据
MODIFY 插入或更新 自动判断,更灵活 不确定记录是否存在时
CLEAR 清除数据 准备重复使用 重复使用工作区

🔧 最佳实践

1. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
* 标准错误处理模式
DATA wa_customers LIKE scustom.

wa_customers-id = '100001'.
wa_customers-name = 'Test Customer'.

INSERT scustom FROM wa_customers.

IF sy-subrc = 0.
WRITE: / '成功插入记录'.
ELSE.
WRITE: / '插入失败,代码:', sy-subrc.
" 可以添加具体的错误处理
CASE sy-subrc.
WHEN 1.
WRITE: / '重复键错误'.
WHEN 2.
WRITE: / '违反数据完整性'.
WHEN OTHERS.
WRITE: / '其他错误'.
ENDCASE.
ENDIF.

2. 工作区管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
* 良好的工作区使用习惯
DATA: lt_customers TYPE TABLE OF scustom,
wa_customer LIKE LINE OF lt_customers.

* 清除工作区
CLEAR wa_customer.

* 填充数据
wa_customer-id = '100001'.
wa_customer-name = 'Customer 1'.

* 操作
INSERT scustom FROM wa_customer.

* 重复使用前清除
CLEAR wa_customer.
wa_customer-id = '100002'.
wa_customer-name = 'Customer 2'.

3. 事务处理

1
2
3
4
5
6
7
8
9
10
11
12
13
* 使用事务保证数据一致性
DATA: lt_data TYPE TABLE OF zcustomers,
wa_data LIKE LINE OF lt_data.

* 准备数据
wa_data-customer = '100001'.
wa_data-name = 'Test'.
APPEND wa_data TO lt_data.

* 事务开始
CALL FUNCTION 'DB_COMMIT'
EXPORTING
wait = 'X'.

⚠️ 注意事项

1. 性能考虑

  • 避免在循环中使用单个INSERT
  • 使用批量操作提高性能
  • 注意锁的使用

2. 数据完整性

  • 确保数据符合约束
  • 正确处理系统返回代码
  • 考虑事务的原子性

3. 安全性

  • 防止SQL注入
  • 验证输入数据
  • 使用参数化查询

ABAP Smart Forms

📖 简介

Smart Forms 是SAP提供的强大的表单设计工具,可用于创建、维护和打印各类文档。该工具提供了友好的图形界面,允许用户无需编程知识即可设计和配置表单。

🔄 Smart Forms的核心特性

  • 图形化设计:使用可视化工具而非编程语言设计表单
  • 多格式输出:支持打印、PDF、电子邮件等多种输出格式
  • 数据绑定:与SAP业务数据无缝集成
  • 模块化结构:便于维护和扩展

💡 优势 - SAP Smart Forms工具提供了一个接口来构建和维护表单的布局和逻辑。该工具允许您使用简单的图形工具而不是使用任何编程工具来修改表单。这意味着没有编程知识的用户可以毫不费力地为这些表单配置业务流程的数据。

🎯 Smart Forms应用场景

业务流程支持

业务领域 典型应用 相关表单示例
CRM 客户关系管理文档 客户报表、服务单据
SD 销售和分销 发票、订单确认、交货单
FI 财务会计 账单、对账单、财务报表
HR 人力资源 工资单、员工证书

输出格式支持

  • 打印输出:传统打印格式
  • PDF文档:电子文档格式
  • 电子邮件:直接发送邮件
  • Internet文档:Web友好格式

📝 标准Smart Forms示例

SAP系统中可用的标准Smart Forms示例:

表单名称 描述 特点
SF_EXAMPLE_01 客户航班预订的发票输出 基本发票格式
SF_EXAMPLE_02 带小计的发票 包含计算字段
SF_EXAMPLE_03 多客户发票选择 支持批量处理

🏗️ 创建Smart Forms

步骤详解

第1步 - 启动Smart Forms

  1. 在命令字段中输入事务代码:SMARTFORMS
  2. 打开SAP Smart Forms的初始屏幕
  3. 在表单字段中输入表单名称:SF_EXAMPLE_01

步骤2 - 复制表单

  1. 选择 Forms → Copy 或单击复制图标
  2. 打开复制表单或文本对话框

步骤3 - 设置新表单名称

  1. 在”目标对象”字段中,输入新表单的名称
  2. 重要:名称必须以Y或Z字母开头(如ZSMM1)

步骤4 - 创建副本

  1. 单击继续图标或按Enter键
  2. 创建ZSMM1表单作为预定义表单SF_EXAMPLE_01的副本

步骤5 - 保存表单

  1. 点击保存图标
  2. 表单名称显示在SAP Smart Forms初始屏幕的表单字段中

步骤6 - 打开表单

  1. 点击SAP Smart Forms初始屏幕的创建按钮
  2. ZSMM1表单显示在表单构建器中

步骤7 - 创建主窗口

  1. 使用MAIN窗口创建第一个草稿页
  2. 新表单的所有组件都基于SF_EXAMPLE_01预定义形式
  3. 可以单击导航菜单中的节点查看其内容

📊 Smart Form Builder界面



graph TB
    A[Form Builder] --> B[节点树]
    A --> C[属性面板]
    A --> D[文本编辑器]
    A --> E[工具栏]
    
    B --> F[全局设置]
    B --> G[页面节点]
    B --> H[窗口节点]
    B --> I[文本节点]
    B --> J[表格节点]

🔧 在表单中创建文本节点

详细步骤

步骤1 - 打开表单并创建文本

  1. 在SAP表单构建器屏幕的更改模式下打开表单
  2. 右键单击第一页节点中的主窗口选项
  3. 从上下文菜单选择 Create → Text

步骤2 - 配置文本属性

  1. 将文本字段中的文本修改为:My_Text
  2. 将”含义”字段中的文本修改为:Text_Demo
  3. 在表单构造器中心框架的文本编辑框中输入文本:
    1
    Hello TutorialPoint .....

步骤3 - 保存节点

  1. 点击保存按钮保存节点

步骤4 - 激活和测试

  1. 通过分别单击激活和测试图标激活和测试节点
  2. 显示Function Builder的初始屏幕

步骤5 - 激活和测试功能模块

  1. 通过单击激活和执行图标激活和测试功能模块
  2. 功能模块的参数显示在Function Builder的初始屏幕中

步骤6 - 执行表单

  1. 通过单击执行图标执行功能模块
  2. 将出现打印对话框

步骤7 - 预览输出

  1. 将输出设备指定为:LP01
  2. 单击打印预览按钮

📝 预览输出示例

1
2
3
4
My Text
Text_Demo

Hello TutorialPoint .....

🎨 Smart Forms核心组件

1. 节点类型

节点类型 描述 用途
全局设置 表单的整体设置 常规属性、输出格式
页面节点 页面定义 页面大小、方向
窗口节点 输出区域 文本、表格的位置
文本节点 静态文本 标题、说明文字
表格节点 数据表格 动态数据展示

2. **数据流控制



graph LR
    A[SAP数据] --> B[表单接口]
    B --> C[节点处理]
    C --> D[格式化]
    D --> E[输出设备]

3. 触发事件

  • Form Initialization:表单初始化
  • Start of Form:开始输出
  • Start of Page:开始新页
  • Start of Window:开始新窗口
  • End of Form:表单结束

💡 Smart Forms高级特性

1. 动态数据绑定

1
2
3
4
" 在节点中绑定数据
&KNA1-NAME& " 客户名称
&VBAP-POSNR& " 行项目
&MARA-MATNR& " 物料编号

2. 条件输出



graph TB
    A[条件判断] -->|条件1| B[输出内容1]
    A -->|条件2| C[输出内容2]
    A -->|其他| D[默认输出]

3. 图形和背景

  • 静态和动态表中检索数据
  • 可包含图形作为表单的一部分或背景
  • 可在打印输出时抑制背景图形

📋 开发最佳实践

1. 模块化设计

  • 将复杂的表单分解为多个小节点
  • 每个节点负责特定功能
  • 便于维护和重用

2. 性能优化

  • 合理使用循环结构
  • 避免嵌套过深
  • 优化数据查询

3. 错误处理

  • 设置合理的错误提示
  • 提供备用输出方案
  • 确保关键数据的完整性

🚀 实际应用技巧

1. 调试技巧

  • 使用测试工具预览输出
  • 检查节点数据绑定
  • 验证条件逻辑

2. 样式管理

  • 使用样式表统一格式
  • 设置统一的字体和颜色
  • 保持文档一致性

3. 版本控制

  • 定期备份重要表单
  • 使用版本命名规范
  • 记录变更历史
0%