Mark的技术博客

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

吴让宇(Mark)

吴让宇(Mark)

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

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

SAP Fiori ABAP CDS OData Dify Docker AI LLM

最新文章

ABAP Web Dynpro

🎯 Web Dynpro概述

用于ABAP的==Web Dynpro==(WD)是SAP AG开发的SAP标准用户界面技术。它可用于在SAP ABAP环境中开发基于Web的应用程序。它提供了一个前端Web用户界面,用于直接连接到后端SAP R/3系统。

Web Dynpro由==运行时环境==和集成在ABAP工作台(事务:SE80)中的==图形开发环境==组成。



graph TD
    A[Web Dynpro] --> B[运行时环境]
    A --> C[图形开发环境<br>集成在SE80]
    A --> D[MVC编程模型]
    D --> E[模型 Model]
    D --> F[视图 View]
    D --> G[控制器 Controller]

🏗️ MVC架构

所有Web Dynpro应用程序都按照==模型视图控制器(MVC)==编程模型进行构造:



graph LR
    A[用户] -->|操作| B[控制器 Controller]
    B -->|更新| C[模型 Model]
    C -->|通知| B
    B -->|格式化数据| D[视图 View]
    D -->|显示| A

    style C fill:#e1f5fe
    style D fill:#f3e5f5
    style B fill:#e8f5e9

模型(Model)

  • 定义了到主系统的==接口==
  • Web Dynpro应用程序可以访问系统数据
  • 负责业务逻辑和数据管理

视图(View)

  • 负责在Web浏览器中==显示数据==
  • 提供用户界面
  • 展示模型数据

控制器(Controller)

  • 位于视图和模型之间
  • ==格式化==要在视图中显示的模型数据
  • ==处理用户==创建的用户条目
  • 将用户交互==返回到模型==

📋 Web Dynpro的优势

优势 说明
🛠️ 图形工具 使用图形工具显著减少了实施工作
🔁 组件重用 通过组件重用和更好的可维护性
🎨 灵活布局 可以轻松更改布局和导航
♿ 辅助功能 支持用户界面辅助功能
🔗 完全集成 完全集成在ABAP开发环境中

💡 Web Dynpro组件和窗口

组件(Component)

  • 组件是Web Dynpro应用程序项目的==全局单位==
  • 创建Web Dynpro组件是开发新应用程序的==初始步骤==
  • 可以在组件中创建任意数量的组件视图

窗口(Window)

  • 每个Web Dynpro组件中至少包含一个Web Dynpro==窗口==
  • Web Dynpro窗口嵌入在前端Web应用程序中显示的==所有视图==
  • 该窗口在ABAP工作台的窗口编辑器中处理


graph TD
    A[Web Dynpro组件] --> B[组件视图1]
    A --> C[组件视图2]
    A --> D[组件视图N]
    A --> E[窗口1]
    A --> F[窗口2]
    E --> B
    E --> C
    F --> D

🔍 开发环境

Web Dynpro应用程序的开发完全集成在ABAP工作台中:

工具 用途
ABAP工作台(SE80) 集成的开发工具
图形开发环境 提供可视化的开发体验
窗口编辑器 处理Web Dynpro窗口
组件视图编辑器 创建和管理视图

组件视图和应用程序

  • 组件视图:显示应用程序的所有管理详细信息(描述、创建者、创建日期、开发包)
  • Web Dynpro应用程序:是ABAP工作台对象列表中的独立对象
  • 窗口和应用程序的交互:由给定窗口的接口视图创建

🏗️ 开发流程



graph LR
    A[1. 创建组件] --> B[2. 创建视图]
    B --> C[3. 组装窗口]
    C --> D[4. 定义模型]
    D --> E[5. 实现控制器]
    E --> F[6. 测试部署]

  1. 创建Web Dynpro组件:开发新应用的初始步骤
  2. 创建组件视图:设计用户界面
  3. 组装到窗口:将视图嵌入到窗口中
  4. 定义模型接口:连接到后端系统
  5. 实现控制器逻辑:处理用户交互
  6. 测试和部署:验证应用程序功能

⚠️ 注意事项

  1. Web Dynpro需要 ==SAP NetWeaver== 支持
  2. 开发需要 ==ABAP工作台环境==(SE80)
  3. 建议使用==图形工具==进行开发
  4. 注意组件的==重用性==和==维护性==
  5. 考虑用户体验和界面设计
💡新项目建议
对于新的SAP UI开发,建议考虑使用 ==SAPUI5/Fiori== 替代Web Dynpro,这是SAP推荐的前端技术方向。

ABAP oData Service

🎯 概述

本文介绍SAP后端系统基于数据结构创建==OData服务==的过程。OData(Open Data Protocol)是一种标准化的Web服务协议,用于对数据进行CRUD(创建、读取、更新、删除)操作。



graph TD
    A[创建OData服务流程] --> B["1. 创建数据字典<br>结构"]
    B --> C["2. 创建Gateway<br>服务项目(SEGW)"]
    C --> D["3. 创建Data Model<br>(Entity/EntitySet)"]
    D --> E["4. Generate<br>Runtime Objects"]
    E --> F["5. 服务注册"]
    F --> G["6. 实现CRUD方法"]
    G --> H["7. 测试服务"]

📋 创建Gateway Service Project

事务码:SEGW

Gateway Service Project分为四个部分:

组成部分 说明
==Data Model== 数据模型,包括Entity type、Entity set和Association
==Service Implementation== Entity set的CRUD实现
==Runtime Artifacts== 基于Entity set的代码框架
==Service maintenance== 注册服务、测试服务

🏗️ 创建Data Model

创建Entity

  1. 选中Data Model,右键 → Import → DDIC Structure
  2. 输入Entity Name以及数据表结构(如 ZRICO_USR
  3. 勾选 ==Create Default entity set== 自动创建Entity set
  4. 选中所有字段,点击 NEXT
  5. 选择主键字段(如 MANDT、USRID),Name值首字符需大写
  6. 点击 Finish

调整Entity Sets

设置Entity集合的CRUD操作权限。

💡 Generate Runtime Objects

  1. 选中项目名,右键 → Generate Runtime
  2. 保持所有值默认,点击 Continue
  3. 系统自动生成Runtime objects

🔍 服务注册

  1. 在 Service Maintenance 下可以看到已创建的Gateway Service别名
  2. 此时 Register Status ==没有状态==,点击 ==Register== 按钮
  3. 选择系统别名 ==LOCAL==
  4. 确认后完成服务注册,状态已改变

📋 测试OData Service

浏览器测试

点击 Maintain 进入激活并维护服务界面,调用浏览器测试。

⚠️浏览器测试可能的问题
问题1:主机名或端口未配置
使用事务码 SMICM → Goto → Services,检查主机和端口是否配置好。

问题2:无法通过Internet访问
需要设置本机 hosts 文件:C:\Windows\System32\drivers\etc

Gateway客户端测试

使用事务代码 /N/IWFND/GW_CLIENT 进行测试。

💡事务码注意
输入事务码时需要加 /O 前缀,如 /O/IWFND/MAINT_SERVICE/O/IWFND/GW_CLIENT

🏗️ 服务实现(CRUD)

服务实现是提供CRUD的具体实现。SAP把读取Entity叫做 ==Read==,读取EntitySet叫做 ==Query==。



graph LR
    A[CRUD操作] --> B["Query<br>GetEntitySet"]
    A --> C["Read<br>GetEntity"]
    A --> D["Create"]
    A --> E["Update"]
    A --> F["Delete"]

Query重定义(GetEntitySet)

选中 GetEntitySet(Query),右键 → Go to ABAP Workbench:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
METHOD zusrset_get_entityset.
DATA: lt_usr TYPE TABLE OF zrico_usr.

" 查询数据
SELECT * FROM zrico_usr INTO TABLE lt_usr.

" 转换数据格式
et_entityset = VALUE #(
FOR ls_usr IN lt_usr (
mandt = ls_usr-mandt
usrid = ls_usr-usrid
name = ls_usr-name
email = ls_usr-email
created_at = ls_usr-created_at
)
).
ENDMETHOD.
ℹ️ET_ENTITYSET
ET_ENTITYSET 是类方法预设的参数,基于ZRICO_USR内表结构。

Read重定义(GetEntity)

选中 GetEntity(Read),进入ABAP工作台:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
METHOD zusrset_get_entity.
DATA: ls_usr TYPE zrico_usr.
DATA: ls_entity TYPE zcl_zrico_stru_usr_mpc=>ts_zuser.

" 根据主键查询数据
SELECT SINGLE * FROM zrico_usr
WHERE mandt = is_entity_key-mandt
AND usrid = is_entity_key-usrid
INTO @ls_usr.

IF sy-subrc = 0.
ls_entity-mandt = ls_usr-mandt.
ls_entity-usrid = ls_usr-usrid.
ls_entity-name = ls_usr-name.
ls_entity-email = ls_usr-email.
ls_entity-created_at = ls_usr-created_at.

es_entity = ls_entity.
ENDIF.
ENDMETHOD.

Postman测试地址:

1
http://XXX.XXXXXXXXX.com:8000/sap/opu/odata/sap/ZRICO_STRU_USR_SRV/ZUSERSet(Mandt='300',Usrid='1')?$format=json

Create重定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
METHOD zusrset_create_entity.
DATA: ls_usr TYPE zrico_usr.
DATA: ls_entity LIKE er_entity.

" 转换实体数据到内部表结构
ls_usr-mandt = is_entity-mandt.
ls_usr-usrid = is_entity-usrid.
ls_usr-name = is_entity-name.
ls_usr-email = is_entity-email.
ls_usr-created_at = sy-datum.
ls_usr-created_by = sy-uname.

" 插入数据
INSERT zrico_usr FROM ls_usr.
IF sy-subrc = 0.
es_entity = is_entity. " 返回创建的实体
ENDIF.
ENDMETHOD.
CSRF Token
Postman测试Create服务前,必须先通过GET请求获取CSRF Token:
1
Headers: KEY = X-CSRF-Token, VALUE = Fetch

获取Token后在后续POST请求中使用。

Update重定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
METHOD zusrset_update_entity.
DATA: ls_usr TYPE zrico_usr.
DATA: ls_entity LIKE is_entity.

" 转换实体数据
ls_entity = is_entity.
ls_usr-mandt = ls_entity-mandt.
ls_usr-usrid = ls_entity-usrid.
ls_usr-name = ls_entity-name.
ls_usr-email = ls_entity-email.
ls_usr-changed_at = sy-datum.
ls_usr-changed_by = sy-uname.

" 更新数据
UPDATE zrico_usr FROM ls_usr.
ENDMETHOD.

使用 PUT 或 MERGE 方式发送更新请求。

Delete重定义

1
2
3
4
5
6
METHOD zusrset_delete_entity.
" 根据主键删除数据
DELETE FROM zrico_usr
WHERE mandt = is_entity_key-mandt
AND usrid = is_entity_key-usrid.
ENDMETHOD.

使用 DELETE 方式发送删除请求。

💡 Function Import函数导入

如果操作不适合CRUD方案,可以通过==函数导入==来执行自定义操作。

创建Function Import

  1. 选中 DataModel,右键 → Create → Function Import
  2. 输入导入名称(如 UserByName
  3. 设置参数:
参数
Return type Entity type: ZUSER
Return Cardinality 0…n(返回实体集合)
HTTP GET

创建导入参数

双击 Function Import Parameters,创建参数:UsrName,类型 Edm.String

重定义Function Import方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
METHOD zusrset_userbyname.
DATA: lt_usr TYPE TABLE OF zrico_usr.
DATA: ls_entity TYPE zcl_zrico_stru_usr_mpc=>ts_zuser.

" 根据用户名查询
SELECT * FROM zrico_usr
WHERE name = iv_usname
INTO TABLE @lt_usr.

" 转换数据格式
et_entityset = VALUE #(
FOR ls_usr IN lt_usr (
mandt = ls_usr-mandt
usrid = ls_usr-usrid
name = ls_usr-name
email = ls_usr-email
created_at = ls_usr-created_at
)
).
ENDMETHOD.

Postman测试:

1
http://XXX.XXXXX.com:8000/sap/opu/odata/sap/ZRICO_STRU_USR_SRV/UserByName?UsrName='12'

🔍 常见问题解决

问题 解决方案
登录错误 删除Cookies重新登录,检查用户权限
CSRF Token失败 Headers中添加 X-CSRF-Token: Fetch
数据格式问题 使用 ?$format=json 获取JSON格式
服务未激活 确保所有方法已激活,检查语法错误

⚠️ 注意事项

  1. 创建服务前确保==数据字典结构==已定义
  2. Postman测试需要先获取==CSRF Token==
  3. 方法实现后必须==激活==才能生效
  4. URI中的参数需要==区分大小写==
  5. 建议使用 ?$format=json 获取JSON格式数据

ABAP 创建内表

📋 内部表创建概述

DATA语句声明

DATA语句用于声明一个内部表。程序必须告知表的开始和结束位置。

OCCURS子句

使用OCCURS子句告诉SAP正在创建一个内部表:

  • OCCURS 0:最初不包含任何记录,将随着填充数据而扩展
  • OCCURS n:预分配n行内存

🛠️ 传统创建方法

基本语法

1
2
3
4
DATA: BEGIN OF <internal_tab> Occurs 0,
<field1> LIKE <table_field>,
<field2> LIKE <table_field>,
END OF <internal_tab>.

示例

1
2
3
4
DATA: BEGIN OF itab01 Occurs 0,
name LIKE ZCUSTOMERS1-name,
dob LIKE ZCUSTOMERS1-dob,
END OF itab01.

说明

  • 'itab01'是创建临时表时常用的缩写
  • OCCURS子句用于定义内部表的主体
  • OCCUR 0使用的默认内存大小为8 KB

带标题行的内表

1
2
3
4
5
6
7
8
* 创建带标题行的内表
DATA: BEGIN OF itab_header OCCURS 0,
name LIKE ZCUSTOMERS1-name,
dob LIKE ZCUSTOMERS1-dob,
END OF itab_header.

* 或者使用WITH HEADER LINE
DATA itab_with_header LIKE SCUSTOM OCCURS 0 WITH HEADER LINE.

不带标题行的内表

1
2
* 创建不带标题行的内表
DATA itab_no_header LIKE TABLE OF SCUSTOM.

🔧 TYPES语句创建方法

定义本地数据类型

TYPES语句创建内部表作为本地数据类型(仅在当前程序的上下文中使用的数据类型)。

语法

1
2
TYPES <internal_tab> TYPE|LIKE <internal_tab_type> OF
<line_type_itab> WITH <key> INITIAL SIZE <size_number>.

参数说明

参数 说明 示例
内部表名称 mytable
表类型 STANDARD TABLE, SORTED TABLE, HASHED TABLE
行类型 CustomerLine, SCUSTOM
键定义 WITH UNIQUE KEY, WITH NON-UNIQUE KEY
INITIAL SIZE 初始大小 INITIAL SIZE 10

完整示例

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

* 定义行结构
TYPES: BEGIN OF CustomerLine,
Cust_ID TYPE c,
Cust_Name(20) TYPE c,
END OF CustomerLine.

* 创建排序表,定义唯一键
TYPES mytable TYPE SORTED TABLE OF CustomerLine
WITH UNIQUE KEY Cust_ID.

* 输出信息
WRITE: / 'The mytable is an Internal Table'.

💡 不同类型的内表创建

1. 标准表

1
2
3
4
5
6
* 方法1:使用DATA语句
DATA: lt_standard LIKE STANDARD TABLE OF SCUSTOM.

* 方法2:使用TYPES
TYPES: lt_standard_type TYPE STANDARD TABLE OF SCUSTOM.
DATA lt_standard TYPE lt_standard_type.

2. 排序表

1
2
3
4
5
6
7
8
* 方法1:使用DATA语句
DATA: lt_sorted LIKE SORTED TABLE OF SCUSTOM
WITH UNIQUE KEY id.

* 方法2:使用TYPES
TYPES: lt_sorted_type TYPE SORTED TABLE OF SCUSTOM
WITH UNIQUE KEY id.
DATA lt_sorted TYPE lt_sorted_type.

3. 哈希表

1
2
3
4
5
6
7
8
* 方法1:使用DATA语句
DATA: lt_hashed LIKE HASHED TABLE OF SCUSTOM
WITH UNIQUE KEY id.

* 方法2:使用TYPES
TYPES: lt_hashed_type TYPE HASHED TABLE OF SCUSTOM
WITH UNIQUE KEY id.
DATA lt_hashed TYPE lt_hashed_type.

🔑 键定义

键类型

键类型 语法 特点
唯一键 WITH UNIQUE KEY 不允许重复值
非唯一键 WITH NON-UNIQUE KEY 允许重复值
默认键 无关键字 系统自动确定

键定义示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* 单个唯一键
TYPES lt_single_key TYPE HASHED TABLE OF SCUSTOM
WITH UNIQUE KEY id.

* 多个字段唯一键
TYPES lt_multi_key TYPE SORTED TABLE OF SCUSTOM
WITH UNIQUE KEY carrid connid.

* 非唯一键
TYPES lt_non_unique TYPE STANDARD TABLE OF SCUSTOM
WITH NON-UNIQUE KEY name.

* 多字段非唯一键
TYPES lt_multi_non_unique TYPE STANDARD TABLE OF SCUSTOM
WITH NON-UNIQUE KEY carrid connid.

📊 INITIAL SIZE选项

作用

INITIAL SIZE子句通过向其分配初始量的内存来创建内部表对象。

示例

1
2
3
4
5
6
7
* 预分配10行的内存
TYPES: lt_with_init SIZE TYPE HASHED TABLE OF SCUSTOM
WITH UNIQUE KEY id
INITIAL SIZE 10.

* 创建内表对象
DATA lt_data TYPE lt_with_init SIZE.

注意事项

  • 首次填充内部表时消耗的内存大大减少
  • 表的大小不属于表的数据类型,而是属于对象

💡 完整创建流程

步骤1:创建程序

  1. 执行SE38交易代码
  2. 输入程序名称(如ZINTERNAL_DEMO)
  3. 选择”可执行程序”类型
  4. 保存程序

步骤2:定义结构

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

* 定义行类型
TYPES: BEGIN OF ty_scustomer,
id TYPE scustom-id,
name TYPE scustom-name,
city TYPE scustom-city,
END OF ty_scustomer.

* 定义表类型
TYPES: tt_scustomer TYPE HASHED TABLE OF ty_scustomer
WITH UNIQUE KEY id.

步骤3:创建内表

1
2
3
* 声明内表
DATA: lt_customers TYPE tt_scustomer,
wa_customer TYPE ty_scustomer.

步骤4:使用内表

1
2
3
4
5
6
7
8
9
* 填充数据
wa_customer-id = '100001'.
wa_customer-name = '张三'.
wa_customer-city = '北京'.
INSERT wa_customer INTO TABLE lt_customers.

* 读取数据
READ TABLE lt_customers INTO wa_customer
WITH TABLE KEY id = '100001'.

⚠️ 注意事项

1. 内存管理

  • 合理设置INITIAL SIZE
  • 避免过度预分配
  • 及时清理不再需要的数据

2. 性能考虑

  • 根据访问模式选择表类型
  • 为频繁查找操作使用哈希表
  • 为有序操作使用排序表

3. 键设计

  • 选择合适的键字段
  • 考虑键的唯一性需求
  • 避免过大的键

ABAP 内表

📊 内部表概述

定义

内部表实际上是一个临时表,它包含正在执行的ABAP程序的记录。内部表仅在SAP程序的运行期间存在。

内部表的特点



graph TD
    A[内部表] --> B[临时存储]
    A --> C[运行时存在]
    A --> D[行结构]
    A --> E[灵活访问]
    
    B --> B1[程序终止时丢弃]
    C --> C1[不占用永久内存]
    D --> D1[相同结构和键]
    E --> E1[索引或键访问]

数据存储结构

内部表中的数据存储在行和列中:

  • 每一行称为行(Row)
  • 每一列称为字段(Field)

所有记录具有相同的结构和键,使用索引或键访问内部表的各个记录。

💾 内部表的用途

主要用途

  1. 计算结果存储

    • 保存以后可在程序中使用的计算结果
    • 临时存储中间计算数据
  2. 数据缓冲

    • 保存记录和数据,以便快速访问
    • 避免重复访问数据库表
  3. 临时缓冲区

    • 可以根据需要修改数据
    • 只在运行时占用内存,不在声明时占用
  4. 数据结构定义

    • 可以使用任何数量的其他定义的结构来定义
    • 非常多才多艺

使用建议

保持内部表尽可能小,以避免系统运行缓慢。

🔧 内部表的种类

1. 标准表 (STANDARD TABLE)

特性 说明 访问方式 性能
表类型 STANDARD TABLE 索引或关键字 中等
索引 系统为每一行生成逻辑索引 - -
数据存储 可以附加在现有行后或插入指定位置 - -
操作特点 插入、删除时,数据行位置不变,系统重新排列索引值 - -

2. 排序表 (SORTED TABLE)

特性 说明 访问方式 性能
表类型 SORTED TABLE 索引或关键字 高(查找时)
索引 具有逻辑索引 - -
数据存储 总是按关键字升序排列 - -
特点 维护表时会自动保持排序 - 插入较慢

3. 哈希表 (HASHED TABLE)

特性 说明 访问方式 性能
表类型 HASHED TABLE 仅关键字 极高(查找时)
索引 无索引 - -
数据管理 使用哈希算法管理数据 - -
性能特点 寻址时间与表大小无关 - 插入/删除较慢

🔑 表关键字

关键字定义规则

  1. 结构体作为行

    • 可以指定结构体中的某几个字段作为内表关键字
    • 该字段不能是内表或引用类型,但可以是嵌套体
  2. 基本类型作为行

    • 如果内表的整行都是由基本类型字段组成,可以把整行指定为关键字
  3. 默认关键字

    • 如果不指定任何关键字,使用默认的标准关键字
    • 扁平结构内表的默认关键字是非数字和非内表的组件字段

默认关键字示例

字段类型 示例 默认关键字
姓名(C类型) C 类型 姓名
年龄(N类型) N 类型 年龄
工资(F类型) F 类型 姓名、年龄
单个基本类型 I 类型 整行

💡 内部表的定义和使用

基本定义

1
2
3
4
5
6
7
8
9
10
11
* 使用STANDARD TABLE(默认)
DATA: lt_customers TYPE TABLE OF scustom,
wa_customer LIKE LINE OF lt_customers.

* 使用SORTED TABLE
DATA: lt_sorted TYPE SORTED TABLE OF spfli
WITH UNIQUE KEY carrid connid.

* 使用HASHED TABLE
DATA: lt_hashed TYPE HASHED TABLE OF scustom
WITH UNIQUE KEY id.

操作示例

1. 数据插入

1
2
3
4
5
* APPEND - 追加到末尾
APPEND wa_customer TO lt_customers.

* INSERT - 插入到指定位置
INSERT wa_customer INTO TABLE lt_customers INDEX 1.

2. 数据读取

1
2
3
4
5
6
7
8
9
10
11
12
13
* 使用索引读取
READ TABLE lt_customers INTO wa_customer INDEX 3.

* 使用关键字读取
READ TABLE lt_customers INTO wa_customer
WITH TABLE KEY id = '100001'.

* 检查是否成功
IF sy-subrc = 0.
WRITE: / '读取成功'.
ELSE.
WRITE: / '读取失败'.
ENDIF.

3. 数据修改

1
2
3
4
5
6
7
* 修改指定行
MODIFY lt_customers FROM wa_customer
TRANSPORTING name title
WHERE id = '100001'.

* 使用索引修改
MODIFY lt_customers FROM wa_customer INDEX 3.

4. 数据删除

1
2
3
4
5
6
7
8
9
* 删除指定行
DELETE lt_customers WHERE id = '100001'.

* 使用索引删除
DELETE lt_customers INDEX 3.

* 删除所有行
CLEAR lt_customers. " 清空表
REFRESH lt_customers. " 删除所有行

📊 内部表类型对比

类型 访问方式 插入性能 查找性能 删除性能 适用场景
标准表 索引或键 慢(线性) 中等 通用,需要索引访问
排序表 索引或键 慢(需要保持排序) 快(二分查找) 中等 需要有序数据,频繁查找
哈希表 仅键 极快(O(1)) 仅按键查找,性能要求高

⚠️ 注意事项

1. 内存管理

  • 保持内部表尽可能小
  • 避免不必要的数据存储
  • 及时清理不再需要的数据

2. 性能考虑

1
2
3
* 高效的批量操作
* 使用批量语句而不是循环
SELECT * FROM scustom INTO TABLE lt_customers.

3. 数据完整性

  • 注意关键字唯一性
  • 正确处理系统返回代码
  • 避免数据不一致

ABAP While循环

ℹ️概述
只要给定条件为真,WHILE循环语句就会重复执行目标语句。WHILE循环在执行循环体之前检查条件。

📐 语法格式

WHILE命令的一般格式如下:

1
2
3
WHILE <logical expression>.
<statement block>.
ENDWHILE.

参数说明

  • <logical expression> - 逻辑表达式,决定循环是否继续
  • <statement block> - 要重复执行的语句块,可以是单个语句或语句块
📌循环执行顺序
WHILE循环执行由WHILE和ENDWHILE命令包围的语句,直到逻辑表达式变为false。

🔁 流程图



graph TD
    A[开始WHILE循环] --> B{评估条件}
    B -->|条件为真| C[执行语句块]
    B -->|条件为假| D[结束循环]
    C --> E{继续循环?}
    E -->|是| B
    E -->|否| D
    D --> F[继续执行]


📝 基本示例

简单计数循环

1
2
3
4
5
6
7
REPORT ys_sep_15.
DATA: a TYPE i.
a = 0.
WHILE a <> 8.
WRITE: / 'This is the line:', a.
a = a + 1.
ENDWHILE.

输出

1
2
3
4
5
6
7
8
This is the line: 0
This is the line: 1
This is the line: 2
This is the line: 3
This is the line: 4
This is the line: 5
This is the line: 6
This is the line: 7

条件控制循环

1
2
3
4
5
6
7
8
9
REPORT ys_sep_15.
DATA:
lv_counter TYPE I VALUE 1,
lv_max TYPE I VALUE 10.

WHILE lv_counter <= lv_max.
WRITE: / 'Counter:', lv_counter.
lv_counter = lv_counter + 1.
ENDWHILE.

⚡ 性能考虑

WHILE循环在考虑程序性能时是优选的,原因如下:

优势

  1. 条件检查前置:在执行循环体之前就检查条件,避免不必要的执行
  2. 可控的执行次数:基于条件控制,不会无限执行(除非逻辑错误)
  3. 灵活的条件:可以使用复杂的条件表达式

适用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
" 适合:基于条件的数据处理
DATA: lv_finished TYPE ABAP_BOOL.

WHILE lv_finished = abap_false.
" 处理数据
PERFORM process_data CHANGING lv_finished.
ENDWHILE.

" 适合:用户交互循环
WHILE sy-ucomm <> 'EXIT'.
" 显示界面
CALL SCREEN 100.
ENDWHILE.

🔧 高级用法

1. 复杂条件

1
2
3
4
5
6
7
8
9
10
11
12
13
DATA: 
lv_count TYPE I VALUE 1,
lv_max TYPE I VALUE 10,
lv_threshold TYPE I VALUE 5.

WHILE lv_count <= lv_max AND lv_count > 0.
IF lv_count > lv_threshold.
WRITE: / 'Above threshold:', lv_count.
ELSE.
WRITE: / 'Below threshold:', lv_count.
ENDIF.
lv_count = lv_count - 1.
ENDWHILE.

2. 嵌套WHILE循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
WHILE sy-subrc = 0.
" 处理数据直到失败
SELECT SINGLE * FROM mara INTO @DATA(lwa_mara)
WHERE matnr = 'TEST'.

IF sy-subrc <> 0.
EXIT.
ENDIF.

WRITE: / 'Material:', lwa_mara-matnr.
ENDWHILE.

⚠️ 注意事项

1. 避免无限循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
" ❌ 危险:无限循环示例
DATA: lv_count TYPE I VALUE 1.

WHILE lv_count > 0.
WRITE: / 'This will loop forever!'.
" 忘记修改循环条件
ENDWHILE.

" ✅ 正确:确保循环能结束
DATA: lv_count TYPE I VALUE 1.

WHILE lv_count <= 10.
WRITE: / 'Iteration:', lv_count.
lv_count = lv_count + 1.
ENDWHILE.

2. 条件更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
" ❌ 错误:条件没有更新
DATA: lv_found TYPE ABAP_BOOL.

WHILE lv_found = abap_false.
WRITE: / 'Searching...'.
" 忘记检查或设置lv_found
ENDWHILE.

" ✅ 正确:在循环中更新条件
WHILE lv_found = abap_false.
PERFORM search_data CHANGING lv_found.
IF lv_found = abap_false.
WRITE: / 'Not found, retrying...'.
" 添加延迟避免过快循环
WAIT UP TO 1 SECONDS.
ENDIF.
ENDWHILE.

3. 资源管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
" ✅ 正确:注意资源释放
WHILE lv_active = abap_true.
" 打开资源
OPEN DATASET lv_file FOR INPUT IN TEXT MODE.

WHILE sy-subrc = 0.
READ DATASET lv_file INTO lv_data.
IF sy-subrc = 0.
" 处理数据
ENDIF.
ENDWHILE.

" 关闭资源
CLOSE DATASET lv_file.

lv_active = abap_false.
ENDWHILE.

💡 使用场景

1. 数据处理

1
2
3
4
5
6
7
8
9
10
11
" 读取数据直到结束
WHILE sy-subrc = 0.
SELECT * FROM vbak INTO TABLE @DATA(lt_vbak)
WHERE vkorg = '1000'
UP TO 100 ROWS.

IF sy-subrc = 0.
" 处理数据
PERFORM process_orders USING lt_vbak.
ENDIF.
ENDWHILE.

2. 用户界面

1
2
3
4
" 保持界面直到用户退出
WHILE sy-ucomm <> 'BACK' AND sy-ucomm <> 'EXIT'.
CALL SCREEN 0100.
ENDWHILE.

3. 等待条件

1
2
3
4
5
6
7
" 等待某个条件满足
WHILE lv_ready = abap_false.
PERFORM check_system_readiness CHANGING lv_ready.
IF lv_ready = abap_false.
WAIT UP TO 5 SECONDS.
ENDIF.
ENDWHILE.

🔄 While循环与其他循环对比

While循环 vs Do循环

特性 While循环 Do循环
条件检查 循环体之前执行 循环体之后执行(Do TIMES除外)
执行次数 基于条件,可能0次 至少执行1次(Do TIMES)
适用场景 条件控制重复 固定次数重复
风险 可能不执行 可能无限循环

While循环 vs Loop循环

特性 While循环 Loop循环
数据源 不指定数据源 需要数据集合
用途 通用循环 遍历数据行
控制 基于条件 基于数据行
性能 条件检查开销小 数据遍历开销小

💡 最佳实践

1. 循环设计

  • 确保条件最终会变为false
  • 在循环体内修改条件变量
  • 添加超时或最大次数保护

2. 代码质量

  • 使用有意义的变量名
  • 保持条件表达式简洁
  • 添加注释说明循环目的

3. 性能优化

  • 避免在循环中进行复杂计算
  • 考虑循环体的执行效率
  • 合理使用系统变量

4. 错误处理

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

ABAP 删除内表

🗑️ DELETE语句概述

基本功能

DELETE语句用于从内部表中删除一个或多个记录。可以通过指定表键、条件或查找重复条目来删除内部表的记录。

删除特点

  • 如果内部表具有非唯一键并且包含重复条目,则将删除表中的第一个条目
  • 删除操作会影响内表的结构和大小
  • 删除后的内表会重新索引

📝 DELETE语法详解

1. 基本删除语法

1
DELETE TABLE <internal_table> FROM <work_area_itab>.

参数说明

参数 描述 示例
internal_table 目标内表 mytable
work_area_itab 与内表类型兼容的工作区 wa_line

工作原理

  • 基于可以从工作区组件获取的默认键来执行删除操作
  • 工作区必须与内表的行类型兼容

2. 显式指定表键

1
2
DELETE TABLE <internal_table> 
WITH TABLE KEY <K1> = <F1> ... <Kn> = <Fn>.

参数说明

参数 描述 示例
K1, K2, … Kn 内部表的关键字段 ColP
F1, F2, … Fn 字段的值 1

类型转换

如果字段的数据类型与关键字段不兼容,SAP系统会自动将它们转换为兼容格式。

💡 实例解析

完整示例

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

DATA:
BEGIN OF Line1,
ColP TYPE i,
ColQ TYPE i,
END OF Line1.
DATA mytable LIKE HASHED TABLE OF Line1 WITH UNIQUE KEY ColP.

DO 8 TIMES.
Line1-ColP = sy-index.
Line1-ColQ = sy-index + 4.
INSERT Line1 INTO TABLE mytable.
ENDDO.

Line1-ColP = 1.
DELETE TABLE mytable:
FROM Line1,
WITH TABLE KEY ColP = 3.

LOOP AT mytable INTO Line1.
WRITE: / Line1-ColP, Line1-ColQ.
ENDLOOP.

输出示例

1
2
3
4
5
6
2         6
4 8
5 9
6 10
7 11
8 12

代码解析

  1. 内表创建

    • mytable是哈希表类型的内部表
    • 唯一键是ColP
    • Line1作为工作区
  2. 数据填充

    • 循环8次,填充数据
    • ColP包含值1,2,3,4,5,6,7,8
    • ColQ包含值5,6,7,8,9,10,11,12
  3. 删除操作

    • 删除ColP值为1的行
    • 删除ColP值为3的行
    • 使用WITH TABLE KEY显式指定键
  4. 结果验证

    • 剩余ColP值:2,4,5,6,7,8
    • 剩余ColQ值:6,8,9,10,11,12

🔧 DELETE TABLE的多种用法

1. 使用工作区删除

1
2
3
4
5
6
* 准备工作区
DATA: wa_line TYPE ty_line.
wa_line-key = '1001'.

* 删除
DELETE TABLE lt_table FROM wa_line.

2. 使用键值删除

1
2
3
4
* 直接使用键值
DELETE TABLE lt_table
WITH TABLE KEY id = '1001'
name = 'Test'.

3. 使用条件删除

1
2
3
4
5
* 使用条件删除
LOOP AT lt_table INTO wa_line
WHERE status = 'inactive'.
DELETE TABLE lt_table FROM wa_line.
ENDLOOP.

4. 删除特定索引的行

1
2
* 删除指定索引的行
DELETE TABLE lt_table INDEX 5.

5. 批量删除

1
2
3
4
5
6
7
8
* 使用内表批量删除
DATA: lt_keys TYPE TABLE OF ty_key.

APPEND '1001' TO lt_keys.
APPEND '1002' TO lt_keys.
APPEND '1003' TO lt_keys.

DELETE lt_table FROM TABLE lt_keys.

⚠️ 注意事项

1. 键值匹配

1
2
3
4
5
6
7
* 正确的键值匹配
DELETE TABLE lt_table
WITH TABLE KEY id = '1001'.

* 错误:字段名不匹配
DELETE TABLE lt_table
WITH TABLE KEY key = '1001'. " 如果键字段不是key

2. 数据类型兼容性

1
2
3
4
5
* 自动类型转换
DATA: lv_char(10) TYPE c VALUE '1001'.

DELETE TABLE lt_table
WITH TABLE KEY id = lv_char. " 自动转换为适当类型

3. 性能考虑



graph TD
    A[删除方式] --> B[哈希表]
    A --> C[标准表]
    A --> D[排序表]
    
    B --> E[O(1) - 快速]
    C --> F[O(n) - 较慢]
    D --> G[O(log n) - 中等]

💡 最佳实践

1. 错误处理

1
2
3
4
5
6
7
8
9
* 标准错误处理
DELETE TABLE lt_table
WITH TABLE KEY id = '1001'.

IF sy-subrc = 0.
WRITE: / '记录删除成功'.
ELSE.
WRITE: / '记录不存在'.
ENDIF.

2. 先查找后删除

1
2
3
4
5
6
7
8
9
10
* 先检查是否存在再删除
READ TABLE lt_table INTO wa_line
WITH TABLE KEY id = '1001'.

IF sy-subrc = 0.
DELETE TABLE lt_table FROM wa_line.
WRITE: / '记录已删除'.
ELSE.
WRITE: / '记录不存在'.
ENDIF.

3. 使用TRANSPORTING选项

1
2
3
4
* 只删除特定字段(如果适用)
DELETE TABLE lt_table
WITH TABLE KEY id = '1001'
TRANSPORTING field1 field2.

4. 批量删除优化

1
2
3
4
5
6
7
8
9
10
11
* 批量删除而非循环单条删除
DATA: lt_to_delete TYPE TABLE OF ty_key.

" 收集要删除的键
SELECT id FROM ztable WHERE status = 'inactive'
INTO TABLE lt_to_delete.

" 一次性删除
IF lt_to_delete IS NOT INITIAL.
DELETE ztable FROM TABLE lt_to_delete.
ENDIF.

🎯 删除操作的变种

1. DELETE(不带TABLE关键字)

1
2
3
4
5
6
* 删除当前行
LOOP AT lt_table INTO wa_line.
IF wa_line-status = 'delete'.
DELETE lt_table.
ENDIF.
ENDLOOP.

2. 删除重复项

1
2
3
* 删除重复项(保留第一个)
SORT lt_table BY key.
DELETE ADJACENT DUPLICATES FROM lt_table.

3. 删除范围

1
2
* 删除索引范围
DELETE lt_table FROM 3 TO 5. " 删除第3到第5行

ABAP 功能模块

⚙️ 功能模块概述

定义

功能模块是包含一组具有导入和导出参数的可重用语句的子程序。

功能模块的特点



graph TD
    A[功能模块] --> B[可重用代码]
    A --> C[独立执行]
    A --> D[参数化接口]
    A --> E[全局访问]
    
    B --> B1[代码重用]
    C --> C1[独立运行]
    D --> D1[导入导出参数]
    E --> E1[跨程序调用]

与包含程序的区别

特性 功能模块 包含程序
执行方式 独立执行 必须包含在程序中
作用域 全局 程序内
参数传递 导入/导出/表 无参数传递
维护性 集中管理 分散维护

🔍 函数构建器

事务代码

  • SE37:函数构建器
  • SE80:对象导航器

功能模块搜索

在函数构建器中,可以使用通配符搜索功能模块:

  • 输入 * amount * 搜索包含”amount”的功能模块
  • 按F4显示搜索选项

搜索结果

  • 功能模块:显示在蓝色背景的行中
  • 功能组:显示在粉红色的行中

功能组概念

功能组用作逻辑上属于一起的多个功能模块的容器。例如,人力资源工资系统的功能模块会被放在一起成为一个功能组。

🛠️ 创建功能模块程序

步骤1:创建新程序

使用事务 SE38 创建一个名为 Z_SPELLAMOUNT 的新程序。

步骤2:设置参数

1
2
3
4
SELECTION-SCREEN BEGIN OF LINE.
SELECTION-SCREEN COMMENT 1(15) TEXT-001.
PARAMETER num_1 TYPE i.
SELECTION-SCREEN END OF LINE.

步骤3:调用功能模块

使用 Ctrl + F6 快速插入 CALL FUNCTION 语句。

1
2
3
4
5
CALL FUNCTION 'SPELL_AMOUNT'
EXPORTING
amount = num_1
IMPORTING
in_words = result.

步骤4:处理返回值

1
2
3
4
5
IF sy-subrc <> 0.
WRITE: 'Value returned is:', sy-subrc.
ELSE.
WRITE: 'Amount in words is:', result-word.
ENDIF.

💡 完整示例

主程序代码

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
REPORT Z_SPELLAMOUNT.

* 定义结果变量
DATA result LIKE spell.

* 选择屏幕
SELECTION-SCREEN BEGIN OF LINE.
SELECTION-SCREEN COMMENT 1(15) TEXT-001. " 文本元素: 输入值
PARAMETER num_1 TYPE i.
SELECTION-SCREEN END OF LINE.

* 调用功能模块
CALL FUNCTION 'SPELL_AMOUNT'
EXPORTING
amount = num_1
IMPORTING
in_words = result
EXCEPTIONS
input_invalid = 1
OTHERS = 2.

* 处理返回结果
IF sy-subrc <> 0.
WRITE: 'Error occurred. Return code:', sy-subrc.
ELSE.
WRITE: 'Amount in words is:', result-word.
ENDIF.

输出示例

1
2
3
Spelling the Amount
Amount in words is:
FIVE THOUSAND SIX HUNDRED EIGHTY

🔧 功能模块参数类型

参数方向

参数类型 关键字 用途 示例
导入参数 IMPORTING 传入数据 amount = 1000
导出参数 EXPORTING 传出数据 result = result
** changing** CHANGING 双向传递 counter = counter
表参数 TABLES 传入传出表 lt_data = lt_data
异常 EXCEPTIONS 错误处理 input_invalid = 1

常用功能模块示例

1. 数字转文字

1
2
3
4
5
CALL FUNCTION 'SPELL_AMOUNT'
EXPORTING
amount = lv_amount
IMPORTING
in_words = lv_words.

2. 日期转换

1
2
3
4
5
CALL FUNCTION 'CONVERT_DATE_TO_EXTERNAL'
EXPORTING
date_internal = sy-datum
IMPORTING
date_external = lv_date_ext.

3. 货币转换

1
2
3
4
5
6
7
8
CALL FUNCTION 'CURRENCY_CONVERT'
EXPORTING
amount = lv_amount
currency_from = 'USD'
currency_to = 'CNY'
date = sy-datum
IMPORTING
amount_to = lv_amount_cny.

🎯 功能模块的优势

1. 代码重用

1
2
3
4
5
6
* 多个程序调用同一功能模块
REPORT Z_PROGRAM_1.
CALL FUNCTION 'Z_CALCULATE_DISCOUNT'.

REPORT Z_PROGRAM_2.
CALL FUNCTION 'Z_CALCULATE_DISCOUNT'.

2. 集中管理



graph LR
    A[功能组 Z_FINANCE] --> B[计算利息]
    A --> C[计算折扣]
    A --> D[格式化金额]
    
    B --> E[多个程序使用]
    C --> E
    D --> E

3. 版本控制

  • 功能模块有独立的版本管理
  • 更新功能模块不影响调用程序
  • 支持向后兼容

⚠️ 注意事项

1. 参数设计

  • 明确参数的输入输出方向
  • 使用合适的数据类型
  • 提供默认值(如果需要)

2. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CALL FUNCTION 'Z_MY_FUNCTION'
EXPORTING
param1 = lv_param1
IMPORTING
result = lv_result
EXCEPTIONS
error1 = 1
error2 = 2
OTHERS = 3.

IF sy-subrc <> 0.
" 处理错误
CASE sy-subrc.
WHEN 1.
MESSAGE '错误1发生' TYPE 'E'.
WHEN 2.
MESSAGE '错误2发生' TYPE 'E'.
WHEN OTHERS.
MESSAGE '未知错误' TYPE 'E'.
ENDCASE.
ENDIF.

3. 性能考虑

  • 避免在循环中调用功能模块
  • 合理使用缓存
  • 注意内存使用

ABAP 判断

ℹ️概述
决策结构具有要由程序评估或测试的一个或多个条件,以及如果条件被确定为真则要执行的一个或多个语句,以及可选地,如果条件被确定为假。

📐 判断结构流程



graph TD
    A[开始判断] --> B{条件评估}
    B -->|条件为真| C[执行真分支]
    B -->|条件为假| D[执行假分支]
    C --> E[结束判断]
    D --> E
    E --> F[继续执行]


🎯 ABAP判断语句类型

ABAP编程语言提供以下类型的决策语句:

序号 语句类型 描述
1 IF语句 IF语句由逻辑表达式后跟一个或多个语句组成。
2 IF..ELSE语句 IF语句后面可以是一个可选的ELSE语句,当表达式为false时执行。
3 嵌套IF语句 你可以在另一个IF或ELSEIF语句中使用一个IF或ELSEIF语句。
4 CASE控制语句 当我们需要比较两个或多个字段或变量时,使用CASE语句。

🔧 IF语句

基本语法

1
2
3
IF <condition>.
" 条件为真时执行的代码
ENDIF.

示例

1
2
3
4
5
DATA: lv_age TYPE I VALUE 25.

IF lv_age >= 18.
WRITE: / '成年人'.
ENDIF.

🔀 IF..ELSE语句

基本语法

1
2
3
4
5
IF <condition>.
" 条件为真时执行的代码
ELSE.
" 条件为假时执行的代码
ENDIF.

示例

1
2
3
4
5
6
7
DATA: lv_score TYPE I VALUE 75.

IF lv_score >= 60.
WRITE: / '及格'.
ELSE.
WRITE: / '不及格'.
ENDIF.

🏗️ 嵌套IF语句

基本语法

1
2
3
4
5
6
7
8
9
10
IF <condition1>.
" 条件1为真时执行的代码
IF <condition2>.
" 条件1和条件2都为真时执行的代码
ENDIF.
ELSEIF <condition3>.
" 条件1为假且条件3为真时执行的代码
ELSE.
" 所有条件都为假时执行的代码
ENDIF.

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
DATA: 
lv_age TYPE I VALUE 25,
lv_status TYPE C.

IF lv_age < 18.
lv_status = '未成年'.
ELSEIF lv_age >= 18 AND lv_age < 65.
lv_status = '成年'.
ELSE.
lv_status = '老年'.
ENDIF.

WRITE: / '状态:', lv_status.

🎭 CASE语句

基本语法

1
2
3
4
5
6
7
8
CASE <variable>.
WHEN <value1>.
" 变量等于value1时执行的代码
WHEN <value2>.
" 变量等于value2时执行的代码
WHEN OTHERS.
" 变量不等于任何指定值时执行的代码
ENDCASE.

示例

1
2
3
4
5
6
7
8
9
10
11
12
DATA: lv_grade TYPE CHAR1 VALUE 'B'.

CASE lv_grade.
WHEN 'A'.
WRITE: / '优秀'.
WHEN 'B'.
WRITE: / '良好'.
WHEN 'C'.
WRITE: / '及格'.
WHEN OTHERS.
WRITE: / '不及格'.
ENDCASE.

🔍 条件表达式

比较运算符

运算符 描述 替代形式
= 等于 EQ
<> 不等于 NE
> 大于 GT
< 小于 LT
>= 大于等于 GE
<= 小于等于 LE

逻辑运算符

运算符 描述 示例
AND 逻辑与 condition1 AND condition2
OR 逻辑或 condition1 OR condition2
NOT 逻辑非 NOT condition

示例

1
2
3
4
5
6
7
8
9
10
11
DATA: 
lv_age TYPE I VALUE 25,
lv_score TYPE I VALUE 80.

IF lv_age >= 18 AND lv_score >= 60.
WRITE: / '条件满足'.
ELSEIF lv_age < 18 OR lv_score < 60.
WRITE: / '条件不满足'.
ELSE.
WRITE: / '其他情况'.
ENDIF.

💡 判断语句选择指南

何时使用IF语句



graph LR
    A[选择判断语句] --> B{简单条件判断}
    B -->|是| C[使用IF语句]
    B -->|否| D{多值比较?}
    D -->|是| E[使用CASE语句]
    D -->|否| F{复杂条件组合?}
    F -->|是| G[使用嵌套IF]
    F -->|否| H[使用简单IF]

使用场景对比

语句类型 适用场景 优点 缺点
IF 简单条件判断 灵活 条件多时复杂
IF-ELSE 二选一判断 清晰 只能两个分支
嵌套IF 多重条件判断 功能强大 可读性差
CASE 多值比较 简洁直观 只能单值比较

⚠️ 注意事项

1. 条件顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
" ❌ 错误:顺序导致逻辑错误
IF lv_score >= 90.
WRITE: / '优秀'.
ELSEIF lv_score >= 80.
WRITE: / '良好'.
ELSEIF lv_score >= 70.
WRITE: / '一般'.
ELSE.
WRITE: / '不及格'.
ENDIF.

" ✅ 正确:顺序正确
IF lv_score >= 90.
WRITE: / '优秀'.
ELSEIF lv_score >= 80.
WRITE: / '良好'.
ELSEIF lv_score >= 70.
WRITE: / '一般'.
ELSEIF lv_score >= 60.
WRITE: / '及格'.
ELSE.
WRITE: / '不及格'.
ENDIF.

2. 条件覆盖

确保所有可能的情况都被覆盖:

1
2
3
4
5
6
7
8
9
10
11
12
13
" ✅ 完整的条件覆盖
DATA: lv_status TYPE C VALUE 'X'.

CASE lv_status.
WHEN 'A'.
WRITE: / '状态A'.
WHEN 'B'.
WRITE: / '状态B'.
WHEN 'C'.
WRITE: / '状态C'.
WHEN OTHERS.
WRITE: / '未知状态'.
ENDCASE.

3. 可读性

  • 使用有意义的变量名
  • 添加注释说明复杂逻辑
  • 避免过深的嵌套

💡 最佳实践

1. 代码设计原则

  • 使用最简单的判断结构满足需求
  • 保持条件逻辑清晰易懂
  • 避免过深的嵌套层次

2. 条件编写

  • 使用有意义的变量名
  • 避免复杂的条件表达式
  • 考虑条件的可读性

3. 错误处理

  • 使用OTHERS子句处理未知情况
  • 添加适当的错误处理逻辑
  • 考虑边界条件

4. 性能考虑

  • 将最可能的条件放在前面
  • 避免重复计算
  • 注意条件判断的执行顺序

ABAP 域

🏗️ 基本概念

域的作用

域用于表字段的技术定义,包括:

  • 字段类型
  • 字段长度
  • 小数位数(如果有)

数据元素的关系



graph TD
    A[域] --> B[技术定义]
    B --> C[字段类型]
    B --> D[字段长度]
    B --> E[小数位数]
    
    A --> F[数据元素]
    F --> G[语义定义]
    G --> H[业务含义]
    G --> I[字段标签]
    G --> J[字段帮助]
    
    F --> K[表字段/结构字段]

分配关系

域被分配给数据元素,数据元素又被分配给表字段或结构字段:

1
MATNR域(CHAR材料号) → MATNR_N, MATNN, MATNR_D数据元素 → 多个表域和结构域

🛠️ 创建域

步骤1:准备阶段

在创建新域之前,需要检查任何现有域是否具有表字段中所需的相同技术规范。如果有,应该使用现有的域。

步骤2:启动事务

使用事务 SE11 进入ABAP字典。

步骤3:选择域类型

在ABAP字典的初始屏幕中选择域的单选按钮:



graph LR
    A[SE11事务] --> B[选择域单选按钮]
    B --> C[输入域名称]
    C --> D[点击CREATE按钮]

命名规则

  • 对象名称始终以 “Z”“Y” 开头
  • 确保在客户名称空间下创建

步骤4:输入基本信息

短文本字段

  • 输入域的说明(如”客户域”)
  • 注意:必须先输入短文本,才能输入其他属性

步骤5:定义技术规格

定义选项卡

在”定义”选项卡中的”格式”框中输入:

属性 说明 示例
数据类型 字段的数据类型 CHAR, NUMC, DEC
字符数 字段的长度 10, 20, 40
小数位数 小数点后的位数 2, 3

输出长度

  • 系统会建议并显示输出长度
  • 如果覆盖建议的输出长度,激活时可能会看到警告

可选属性

  • Convers例程:转换例程
  • 符号:符号定义
  • 小写字段:大小写设置

步骤6:值范围设置

选择值范围标签,可以设置:

设置类型 用途 示例
固定值 限制只能有特定值 ‘A’, ‘B’, ‘C’
间隔 定义数值范围 1-100, 1000-9999
值表 外键检查的参考表 SCUSTOM表

步骤7:保存对象

  1. 保存变更

    • 将显示”创建对象目录条目”弹出窗口
    • 输入包名称或使用本地对象
  2. 激活域

    • 点击激活图标(火柴图标)
    • 或按 Ctrl + F3

步骤8:激活处理

激活过程

  1. 激活名为”DOMA”的顶部条目(如ZSEP_18)
  2. 点击绿色勾号按钮
  3. 状态栏显示”对象已激活”

激活日志

如果出现错误或警告:

  • 系统自动显示激活日志
  • 可以通过实用程序(M)→激活日志查看

📝 域的属性详解

技术属性

属性 描述 示值
数据类型 字段的基础类型 CHAR, NUMC, DEC, DATS, TIMS
长度 字段的字符数 10, 20, 40
小数位 小数点后的位数 0, 2, 3

业务属性

属性 描述 作用
短文本 域的描述 便于理解和识别
字段标签 在屏幕上显示的文本 用户界面显示
字段帮助 输入时的帮助信息 用户指导

🎯 域的使用场景

1. 表字段定义

1
2
3
* 使用域定义表字段
TABLES: scustom.
DATA: customer_name TYPE scustom-name.

2. 数据类型声明

1
2
3
* 引用域定义数据类型
DATA: material_number TYPE matnr,
quantity TYPE mnglg.

3. 输入验证

1
2
3
* 域的固定值限制
PARAMETERS: status TYPE char1 DEFAULT 'A'.
* 系统自动限制只能输入A/B/C

⚠️ 注意事项

1. 重用原则

  • 优先使用现有域,避免重复创建
  • 确保技术规格的一致性

2. 命名规范

  • 使用有意义的名称
  • 遵循Z或Y前缀规则

3. 激活要求

  • 必须先保存短文本
  • 激活前检查所有属性
  • 激活失败时查看日志

ABAP 包含程序

📦 包含程序概述

定义

包含程序是用于模块化源代码的全局存储库对象,允许您在不同的程序中使用相同的源代码。

包含程序的特点



graph TD
    A[包含程序] --> B[代码重用]
    A --> C[模块化管理]
    A --> D[有序组织]
    A --> E[全局访问]
    
    B --> B1[跨程序共享]
    C --> C1[功能分离]
    D --> D1[代码组织]
    E --> E1[库对象]

主要作用

  1. 代码重用:在不同的程序中使用相同的源代码
  2. 有序管理:以有序的方式管理复杂的程序
  3. 功能分离:将不同功能的代码分离到不同的包含程序中

🔧 包含程序语法

基本语法

1
INCLUDE <program_name>.

包含机制

INCLUDE 语句具有与复制包含程序 <program_name> 的源代码并进入另一个程序相同的效果。

嵌套包含

包含程序也可以嵌套使用:

1
2
3
4
5
6
7
REPORT Z_MAIN_PROGRAM.

* 第一层包含
INCLUDE Z_COMMON_FUNCTIONS.

* 第二层包含(在Z_COMMON_FUNCTIONS中)
INCLUDE Z_UTILITIES.

⚠️ 包含程序限制

1. 自调用限制

  • 包含程序不能调用自己
  • 避免循环包含

2. 语句完整性

  • 包含程序必须包含完整的语句
  • 不能包含不完整的代码片段

3. 执行独立性

  • 包含程序不能独立运行
  • 必须嵌入到其他程序中

🛠️ 创建和使用包含程序

步骤1:创建包含程序

使用事务 SE38 创建程序 Z_TOBEINCLUDED

设置程序类型

将程序的类型设置为 INCLUDE 程序

步骤2:编写包含程序代码

1
2
3
4
5
PROGRAM Z_TOBEINCLUDED.

WRITE: / 'This program is started by:', SY-UNAME,
/ 'The Date is:', SY-DATUM,
/ 'Time is', SY-UZEIT.

步骤3:保存程序

点击”保存”按钮,将程序保存在包中(如 ZINCL_PCKG)。

步骤4:创建主程序

创建另一个程序 Z_INCLUDINGTEST,类型指定为可执行程序

步骤5:引用包含程序

1
2
3
4
REPORT Z_INCLUDINGTEST.

* 包含包含程序
INCLUDE Z_TOBEINCLUDED.

步骤6:执行程序

保存、激活并执行程序。

输出示例

1
2
3
This program is started by: SAPUSER
The Date is: 06.10.2015
Time is 13:25:11

💡 包含程序的使用场景

1. 通用功能库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* 通用功能包含程序
* Z_GLOBAL_UTILS.abap
REPORT Z_GLOBAL_UTILS.

* 常用宏定义
DEFINE format_header.
ULINE.
WRITE: / &1 COLOR COL_HEADING.
ULINE.
END-OF-DEFINITION.

* 常用函数
FORM get_sy_username.
WRITE: / 'User:', SY-UNAME.
ENDFORM.
1
2
3
4
5
6
7
8
9
* 主程序
REPORT Z_MAIN.

* 包含通用功能
INCLUDE Z_GLOBAL_UTILS.

START-OF-SELECTION.
format_header '系统信息'.
PERFORM get_sy_username.

2. 数据定义

1
2
3
4
5
6
7
8
* 常量定义
* Z_CONSTANTS.abap
REPORT Z_CONSTANTS.

CONSTANTS: c_true TYPE abap_bool VALUE abap_true,
c_false TYPE abap_bool VALUE abap_false.

DATA: gv_max_lines TYPE i VALUE 1000.
1
2
3
4
5
6
7
8
9
10
11
* 主程序
REPORT Z_DATA_PROGRAM.

* 包含常量定义
INCLUDE Z_CONSTANTS.

START-OF-SELECTION.
WRITE: / '最大行数:', gv_max_lines.
IF c_true = abap_true.
WRITE: / '常量定义成功'.
ENDIF.

3. 类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
* 类定义
* Z_CLASSES.abap
REPORT Z_CLASSES.

* 全局类定义
CLASS lcl_utility DEFINITION.
PUBLIC SECTION.
METHODS: display_info
IMPORTING
iv_text TYPE string.
ENDCLASS.

CLASS lcl_utility IMPLEMENTATION.
METHOD display_info.
WRITE: / iv_text.
ENDMETHOD.
ENDCLASS.
1
2
3
4
5
6
7
8
9
* 使用类的主程序
REPORT Z_CLASS_USER.

* 包含类定义
INCLUDE Z_CLASSES.

START-OF-SELECTION.
DATA(lo_util) = NEW lcl_utility( ).
lo_util->display_info( 'Hello from Class' ).

🎯 包含程序的最佳实践

1. 命名规范

程序类型 前缀 示例 说明
通用功能 Z_ Z_GLOBAL_UTILS 全局通用功能
特定功能 Z_ Z_SALES_UTILS 销售相关功能
数据定义 Z_ Z_FINANCE_CONST 财务常量
类定义 Z_ Z_ABAP_CLASSES ABAP类定义

2. 组织结构

1
2
3
4
5
6
7
* 推荐的包含程序组织
Z_BASE # 基础功能和常量
Z_UTILS # 通用工具函数
Z_CONSTANTS # 系统常量
Z_CLASSES # 类定义
Z_FORMS # 子程序定义
Z_MACROS # 宏定义

3. 依赖管理

  • 避免循环包含
  • 明确包含顺序
  • 减少不必要的依赖

🔗 包含程序 vs 其他模块化方式

方式 作用域 重用性 维护性 适用场景
包含程序 程序间 集中 通用代码
子程序 程序内 程序内 简单功能
功能模块 全局 很高 集中 复杂功能
全局 很高 面向对象 OOP编程

⚠️ 使用注意事项

1. 性能考虑

  • 包含程序在编译时展开
  • 过多的包含可能影响编译速度
  • 避免重复包含

2. 代码管理

  • 保持包含程序的简洁
  • 单一职责原则
  • 版本控制

3. 错误调试

  • 包含程序的错误可能难以定位
  • 注意包含顺序的影响
  • 使用清晰的注释
0%