Mark的技术博客

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

吴让宇(Mark)

吴让宇(Mark)

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

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

SAP Fiori ABAP CDS OData Dify Docker AI LLM

最新文章

BC402 快速参考

#dashboard

程序类型 → 程序类型与模块化

程序类型 引导语句 处理块
可执行程序 REPORT name ... [MESSAGE-ID id] 事件块、对话模块、过程
函数组 FUNCTION-POOL name ... 仅 LOAD-OF-PROGRAM、函数模块、事件块
模块池 PROGRAM name ... 事件块、对话模块、过程
类池 CLASS-POOL 仅方法
接口池 INTERFACE-POOL 无(纯声明)
类型池 TYPE-POOL name 无(纯声明)

数据类型 → 数据类型与数据对象

分类 类型 长度 说明
完整数值型 i, f, decfloat16, decfloat34 固定 无需 LENGTH
不完整数值型 p 可变 需要 LENGTH + DECIMALS
完整字符型 d, t, string 固定/可变 日期=8,时间=6
不完整字符型 c, n 可变 需要 LENGTH
完整字节型 xstring 可变
不完整字节型 x 可变 需要 LENGTH

泛型类型anyany tablecdatadecfloatindex tablennumericobjectsimplesorted tablestandard tabletablexcsequenceclikexsequence

算术优先级 → 算术表达式

条件 使用的算术类型
decfloat16/decfloat34 操作数或结果 十进制浮点
f 操作数或结果(无 decfloat) 二进制浮点
p 操作数或结果(无 f、无 decfloat) 定点(压缩)
全部为 i 操作数且结果为 i 整数

注意** 运算符即使所有操作数都是 i 也会触发浮点运算。

程序调用 → 程序调用

语句 行为 内存效果
SUBMIT prog AND RETURN 插入(暂停调用者) 新内部会话入栈
SUBMIT prog(无 AND RETURN) 替换栈顶会话 栈顶会话被替换
CALL TRANSACTION 'TCODE' 插入 新内部会话入栈
LEAVE TO TRANSACTION 'TCODE' 重新启动 整个栈被销毁

内存区域 → 运行时与内存管理

区域 范围 生命周期
滚动区 每个内部会话 会话期间
PXA 每个应用服务器 服务器生命周期(共享)
ABAP 内存 每个外部会话 直到会话关闭
SAP 内存 每个用户会话 终端会话期间
共享对象 每个应用服务器 直到显式删除

内表类型 → 内表类型

类型 键访问 索引访问 适用场景
STANDARD(标准表) 线性 O(n) 顺序处理、排序
SORTED(排序表) 二分 O(log n) 部分顺序读取
HASHED(哈希表) 哈希 O(1) 按键单条查找

字符串函数 → 字符串处理与正则表达式

描述型strlen()xstrlen()numofchar()find()count()contains()matches()
处理型substring()to_upper()to_lower()concat_lines_of()reverse()repeat()shift_left()shift_right()
模板|文本 {表达式 选项} 文本| — 使用 && 连接

动态编程 → 动态编程技术

技术 语句/类
动态令牌(字面量) 数据对象直接用于语句中
动态令牌(标识符) 数据对象放在括号中 (field)
动态 SQL SELECT ... FROM (tab) WHERE (cond)
动态函数调用 CALL FUNCTION fm_name
动态方法调用 CALL METHOD (meth) ... PARAMETER-TABLE
程序生成 INSERT REPORT name FROM itab
运行时创建数据 CREATE DATA ref TYPE (typename)
运行时创建对象 CREATE OBJECT ref TYPE (classname)

RTTI 类层次 → 运行时类型识别

1
2
3
4
5
6
7
8
9
10
CL_ABAP_TYPEDESCR(根类)
├── CL_ABAP_DATADESCR
│ ├── CL_ABAP_ELEMDESCR
│ ├── CL_ABAP_REFDESCR
│ ├── CL_ABAP_STRUCTDESCR
│ ├── CL_ABAP_TABLEDESCR
│ └── CL_ABAP_ENUMDESCR
└── CL_ABAP_OBJECTDESCR
├── CL_ABAP_CLASSDESCR
└── CL_ABAP_INTFDESCR

关键方法describe_by_name()describe_by_data()get_ddic_field_list()get_components()

SQL 聚合 → SQL 聚合与处理

函数 含义
COUNT(*) 计算所有行数
COUNT(DISTINCT col) 计算不同值的数量
SUM(col)AVG(col) 求和 / 平均
MAX(col)MIN(col) 最大值 / 最小值
GROUP BY col 分组结果
HAVING cond 过滤分组
ORDER BY col 排序结果

JOIN 类型 → SQL 多表访问

类型 语法 行为
INNER JOIN FROM a INNER JOIN b ON a~k = b~k 仅匹配的行
LEFT OUTER JOIN FROM a LEFT OUTER JOIN b ON a~k = b~k 左表所有行 + 匹配
子查询 WHERE col IN (SELECT ...) 嵌套 SELECT
FOR ALL ENTRIES FOR ALL ENTRIES IN itab WHERE ... 与内表连接

FOR ALL ENTRIES 陷阱:务必先检查 itab IS NOT INITIAL

检查点 → 检查点与断言

类型 语句 激活时效果
断点 BREAK-POINT [ID grp] 启动调试器
断言 ASSERT [ID grp] cond 条件为假时产生转储
日志点 LOG-POINT ID grp 记录到检查点组日志

检查点组状态:非活动 / 中断 / 日志 / 终止(仅断言)

性能快速规则 → ABAP 性能数据库性能

  • 单条记录访问使用排序表/哈希表(而非标准表+线性搜索)
  • LOOP 中使用 ASSIGNING <fs>REFERENCE INTO dref 避免数据复制
  • 避免使用 SELECT * — 明确指定所需列
  • 大数据量使用 PACKAGE SIZEINTO TABLE
  • 每次数据库操作后检查 sy-subrc
  • 仅在驱动表小且非空时使用 FOR ALL ENTRIES
  • 避免在循环中使用 SELECT — 使用 JOIN 或预缓冲替代

BC402 高级ABAP 学习地图

#dashboard #abap-basics

概述

  • 课程:BC402 高级ABAP(SAP NetWeaver 7.0 EhP2,Col92)
  • 时长:5天讲师引导式课程
  • 前置课程:SAPTEC、BC400、BC401
  • 目标:开发复杂、高性能的ABAP应用程序,掌握动态编程技术

主题地图

章节 来源(页码) 笔记 状态
第1单元:ABAP 基础 pp. 1-33
程序类型与模块化 pp. 2-11 程序类型与模块化 [ ]
数据类型与数据对象 pp. 12-23 数据类型与数据对象 [ ]
Open SQL 基础 pp. 24-33 Open SQL 基础 [ ]
第2单元:程序调用与内存 pp. 35-123
程序调用(SUBMIT、CALL TRANSACTION) pp. 37-53 程序调用 [ ]
ABAP 运行时与内存管理 pp. 54-93 运行时与内存管理 [ ]
共享对象 pp. 94-123 共享对象 [ ]
第3单元:内部数据处理 pp. 125-252
算术表达式 pp. 127-145 算术表达式 [ ]
字符串处理(字节/字符、正则) pp. 146-188 字符串处理与正则表达式 [ ]
内表类型与使用 pp. 189-212 内表类型 [ ]
内表特殊技术与辅助键 pp. 213-234 特殊表技术 [ ]
数据引用与字段符号 pp. 235-252 数据引用与字段符号 [ ]
第4单元:动态编程 pp. 253-358
动态调用与程序生成 pp. 258-274 动态编程技术 [ ]
泛型数据类型与动态访问 pp. 275-303 泛型类型与动态访问 [ ]
运行时类型识别(RTTI) pp. 304-328 运行时类型识别 [ ]
运行时类型创建(RTTC) pp. 329-358 运行时类型创建 [ ]
第5单元:Open SQL 进阶 pp. 361-457
数据库接口与表缓冲 pp. 363-379 数据库接口与缓冲 [ ]
WHERE 条件与目标区域 pp. 380-389 SQL WHERE 与 INTO 子句 [ ]
SELECT 处理与聚合 pp. 390-408 SQL 聚合与处理 [ ]
连接、子查询、FOR ALL ENTRIES pp. 409-447 SQL 多表访问 [ ]
流与定位器 pp. 448-457 LOB 流与定位器 [ ]
第6单元:分析与测试 pp. 459-477
断点、断言、日志点 pp. 460-477 检查点与断言 [ ]
第7单元:技巧与提示 pp. 479-507
常见错误 pp. 480-485 常见错误与规范 [ ]
ABAP 性能 pp. 491-499 ABAP 性能 [ ]
数据库访问性能 pp. 500-507 数据库性能 [ ]
附录:工具 pp. 509-538
ABAP 调试器 pp. 509-518 ABAP 调试器 [ ]
运行时分析 pp. 519-524 运行时分析 [ ]
代码检查器 pp. 525-530 代码检查器 [ ]
SQL 跟踪 pp. 531-538 SQL 跟踪 [ ]

练习题

单元 题目数 链接
第1单元:ABAP 基础 10 ABAP 基础练习
第2单元:程序调用与内存 10 程序调用与内存练习
第3单元:内部数据处理 12 内部数据练习
第4单元:动态编程 10 动态编程练习
第5单元:Open SQL 进阶 10 Open SQL 进阶练习
第6单元:分析与测试 8 检查点练习
第7单元:技巧与提示 10 性能与技巧练习
工具(附录) 8 ABAP 工具练习

学习工具

工具 说明 链接
考试陷阱 常见陷阱和考试错误集锦 [[_posts/SAP_ABAP/BC402_ABAP高阶技巧/00-仪表盘/考试陷阱]]
快速参考 完整速查表 [[_posts/SAP_ABAP/BC402_ABAP高阶技巧/00-仪表盘/快速参考]]

标签索引

标签 主题 规则
#abap-basics ABAP语言基础 顶级
#program-structure 程序类型、模块化、包含程序 领域
#data-types 类型系统、泛型、数据对象 领域
#open-sql Open SQL 数据库访问 顶级
#memory-mgmt ABAP 运行时内存、会话 顶级
#shared-objects 共享内存、共享对象 领域
#internal-tables 表类型、访问模式 顶级
#string-processing 字符串操作、正则表达式 顶级
#arithmetic 数值类型、表达式 领域
#dynamic-prog 动态编程、令牌 顶级
#rtti 运行时类型识别 领域
#rttc 运行时类型创建 领域
#database 数据库接口、缓冲、索引 顶级
#sql-joins JOIN、子查询、FOR ALL ENTRIES 领域
#performance 性能优化技术 顶级
#testing 检查点、断言、日志点 顶级
#debugging 调试器工具 领域
#analysis-tools 运行时分析、代码检查器、SQL跟踪 领域
#best-practices 编程规范、常见错误 领域
#lobs 大对象、流、定位器 领域
#data-reference 数据引用、字段符号 领域
#table-buffer SAP 表缓冲 领域
#dashboard MOC、概览笔记 笔记类型
#practice 练习题 笔记类型
#exam-traps 常见陷阱 笔记类型

标签规则:顶级标签标记广泛领域。领域标签附加到详细概念笔记上,需同时附加父级标签。笔记类型标签(#dashboard#practice#exam-traps)专用于各自类型的文件。

薄弱环节

  • 混合算术表达式类型判定 → 算术表达式 → [[_posts/SAP_ABAP/BC402_ABAP高阶技巧/00-仪表盘/考试陷阱]]
  • 泛型数据类型与字段符号 → 泛型类型与动态访问 → [[_posts/SAP_ABAP/BC402_ABAP高阶技巧/00-仪表盘/考试陷阱]]
  • 内表辅助键 → 特殊表技术 → [[_posts/SAP_ABAP/BC402_ABAP高阶技巧/00-仪表盘/考试陷阱]]
  • 共享对象实现 → 共享对象 → [[_posts/SAP_ABAP/BC402_ABAP高阶技巧/00-仪表盘/考试陷阱]]
  • FOR ALL ENTRIES 陷阱 → SQL 多表访问 → [[_posts/SAP_ABAP/BC402_ABAP高阶技巧/00-仪表盘/考试陷阱]]

非核心主题策略

来源 内容 处理方式
练习解答 分步练习指南 排除 — 动手实验内容,非考试材料
BAdI 实现细节 附录 BAdI 练习 排除 — 课程特定练习

Open SQL 基础 (★★)

#abap-basics #open-sql

Open SQL vs. Native SQL

属性 Open SQL Native SQL
集成方式 完全集成在 ABAP 中 嵌入在 EXEC SQL...ENDEXEC 之间
可移植性 与数据库无关 与数据库相关
转换 数据库接口自动转换 直接传递给数据库
缓冲 使用 SAP 表缓冲 绕过 SAP 表缓冲
语法 标准 SQL 子集 特定数据库厂商的 SQL 方言
📌始终优先使用 Open SQL
Open SQL 保证无论安装了什么数据库系统,都能统一访问。数据库接口会动态将 Open SQL 转换为特定数据库的 SQL。

SELECT 语句结构

1
2
3
4
SELECT  <result_columns>        <-- SELECT 子句:单行/多行,哪些列
FROM <database_table> <-- FROM 子句:源表或视图
INTO <target_area> <-- INTO 子句:数据放置位置
WHERE <conditions>. <-- WHERE 子句:筛选行

三种 SELECT 变体

变体 语法 使用场景 需要循环
单条记录 SELECT SINGLE ... 读取恰好一行
顺序处理 SELECT ... ENDSELECT 逐行处理
数组读取 SELECT ... INTO TABLE ... 一次性将所有行复制到内表
📌互斥语法
SINGLEENDSELECTINTO TABLE互斥的。不能在同一个 SELECT 中组合使用。
例外:PACKAGE SIZE(后续介绍)允许将 INTO TABLEENDSELECT 组合使用。

单条记录访问:SELECT SINGLE

1
2
3
4
5
6
SELECT SINGLE *
FROM sflight
INTO ls_flight
WHERE carrid = 'AA'
AND connid = '0017'
AND fldate = '20260510'.
方面 说明
用途 从数据库中读取恰好一条记录
最佳实践 WHERE 中填写所有关键字段以确保唯一访问
* vs. 列列表 * 读取所有列;指定具体列名更高效
sy-subrc 0 = 找到记录;4 = 未找到记录
CORRESPONDING FIELDS OF 按名称逐组件填充目标区域
不使用 CORRESPONDING 按左对齐填充目标,忽略结构
💡SELECT SINGLE 始终要指定 WHERE 子句
没有 WHERE 时,数据库返回任意的单行 – 几乎永远不是你想要的。

顺序处理:SELECT … ENDSELECT

1
2
3
4
5
6
7
SELECT carrid connid fldate price
FROM sflight
INTO (lv_carrid, lv_connid, lv_fldate, lv_price)
WHERE carrid = 'AA'.
" 逐行处理每条记录
WRITE: / lv_carrid, lv_connid, lv_fldate, lv_price.
ENDSELECT.
方面 说明
用途 在循环中逐行处理
数据库交付 数据以包的形式从数据库接口到达
逐行复制 ABAP 运行时按顺序将每条记录复制到目标区域
ENDSELECT 后的 sy-subrc 0 = 至少读取一条记录;4 = 无记录
ENDSELECT 后的 sy-dbcnt 读取的总行数
WHERE 条件 只允许使用数据库表字段名;用 AND/OR 组合
⚠️性能考虑
SELECT ... ENDSELECT 在数据库上打开游标。对于大型结果集,数组读取(INTO TABLE)通常更高效。

数组读取:SELECT … INTO TABLE

1
2
3
4
5
SELECT *
FROM sflight
INTO TABLE lt_flights
WHERE carrid = 'AA'
AND connid IN so_conn.
方面 说明
用途 将整个结果集直接复制到内表
机制 ABAP 运行时直接复制数据 – 无需循环
ENDSELECT 不得编写(数组读取不是循环)
sy-subrc 0 = 至少读取一条记录;4 = 无记录
sy-dbcnt 读取的行数
💡大多数场景优先使用数组读取
INTO TABLE 通常是读取数据最有效的方式,因为它最小化了数据库接口和 ABAP 运行时之间的往返次数。

SELECT 变体比较

1
2
3
4
5
6
7
8
9
                    SELECT SINGLE          SELECT...ENDSELECT        SELECT INTO TABLE
============== ================ =================
目标区域: 结构体 结构体/内表 内表
读取记录: 单行 多行 多行
处理方式: 立即处理 循环体中处理 读取后处理
ENDSELECT: 不需要 需要 不需要
sy-subrc 检查: 立即检查 ENDSELECT 之后 立即检查
sy-dbcnt: 不适用 总行数 总行数
典型用途: 单条查找 逐行处理 批量数据检索

WHERE 子句基础

1
2
3
4
5
6
WHERE carrid = 'AA'                     " 等于
AND connid >= 100 " 比较运算符
AND fldate BETWEEN '20260101' " BETWEEN 范围
AND '20261231'
AND planetype LIKE '74%' " 模式匹配
AND currency IN ('USD', 'EUR'). " IN 列表
运算符 含义
= 等于
<>, != 不等于
>, <, >=, <= 比较
BETWEEN ... AND ... 范围(包含边界)
LIKE 模式匹配(% = 任意字符,_ = 单个字符)
IN (...) 值列表
IS NULL / IS NOT NULL 空值检查

选择屏幕参数

语句 用途 示例
PARAMETERS 单个输入字段 PARAMETERS pa_car TYPE sflight-carrid.
SELECT-OPTIONS 范围/复杂选择 SELECT-OPTIONS so_con FOR gs_flight-connid.

SELECT-OPTIONS 内表结构

使用 SELECT-OPTIONS 声明时,会创建一个包含四个字段的内表:

字段 类型 含义
SIGN c(1) I = 包含,E = 排除
OPTION c(2) 运算符:EQ, NE, BT, CP, GT, GE, LT, LE, NB, NP
LOW (字段类型) 下限值 / 单值
HIGH (字段类型) 上限值(用于 BT/NB

在 WHERE 子句中的使用

1
2
3
4
5
6
PARAMETERS: pa_car TYPE sflight-carrid.
SELECT-OPTIONS: so_con FOR gs_flight-connid.

SELECT * FROM sflight INTO TABLE gt_flight
WHERE carrid = pa_car " 单值参数
AND connid IN so_con. " SELECT-OPTIONS(IN 运算符)
📌WHERE 中使用 IN 处理 SELECT-OPTIONS
SELECT-OPTIONSWHERE 子句中必须使用 IN 运算符。运行时系统会自动将选择表展开为相应的 SQL 条件。

sy-subrc 检查模式

1
2
3
4
5
6
7
SELECT * FROM sflight INTO TABLE gt_flight
WHERE carrid = pa_car
AND connid IN so_con.

IF sy-subrc <> 0.
MESSAGE e038. " 错误:未找到数据
ENDIF.
sy-subrc SELECT SINGLE SELECT … ENDSELECT SELECT INTO TABLE
0 找到记录 至少一条记录 至少一条记录
4 未找到记录 未找到记录 未找到记录
8 (不适用) (不适用) (不适用)

考试/测试模式

关键词 / 问题 答案
“SELECT SINGLE 的 WHERE 需要什么?” 所有关键字段以确保唯一访问
“ENDSELECT 可以与 INTO TABLE 一起使用吗?” 不可以 – 它们互斥
“哪种变体直接将数据复制到内表?” 数组读取(INTO TABLE
“SELECT 后 sy-subrc = 4 表示什么?” 未找到匹配 WHERE 条件的记录
“什么运算符将 SELECT-OPTIONS 连接到 WHERE?” IN(如 WHERE connid IN so_con
“可以不带 WHERE 使用 SELECT 吗?” 可以,但会读取整个表 – 大表很危险
“CORRESPONDING FIELDS OF 的作用是什么?” 按组件名称匹配填充目标,而非左对齐
“SELECT 后 sy-dbcnt 是什么?” 读取的行数(ENDSELECT 或 INTO TABLE 后可用)
“PARAMETERS 和 SELECT-OPTIONS 的区别?” PARAMETERS = 单值;SELECT-OPTIONS = 范围表(SIGN, OPTION, LOW, HIGH)

相关笔记

数据类型与数据对象 (★★★)

#abap-basics #data-types

数据类型 vs. 数据对象

概念 描述 占用内存?
数据类型 仅是描述;不与内存地址关联
数据对象 数据类型的实例;运行时占用内存

数据对象使用 DATA 语句定义。类型静态绑定,运行时不可更改。

1
2
3
4
DATA: lv_name  TYPE string,           "完整预定义类型
lv_count TYPE i, "完整预定义类型
lv_price TYPE p LENGTH 8 DECIMALS 2, "不完整类型 + 长度
lv_code TYPE c LENGTH 3. "不完整类型 + 长度

ABAP 预定义类型

完整类型(无需指定长度)

类型 类别 长度 初始值 说明
i 数值型 4 字节 0 整数
f 数值型 8 字节 0 二进制浮点数
decfloat16 数值型 8 字节 0 十进制浮点数(16位)
decfloat34 数值型 16 字节 0 十进制浮点数(34位)
d 字符型 8 字符 00000000 日期(YYYYMMDD)
t 字符型 6 字符 000000 时间(HHMMSS)
string 字符型 可变 长度 0 变长字符串
xstring 字节型 可变 长度 0 变长字节串

不完整类型(需指定长度)

类型 类别 默认长度 初始值 说明
p 数值型 8 0 压缩数字;使用 DECIMALS 指定小数位
n 字符型 1 0...0 数字文本
c 字符型 1 ' ... '(空格) 文本字段
x 字节型 1 X'00...0' 十六进制字段
📌不完整类型的长度指定
不完整类型必须补充长度。有两种语法形式:
1
2
3
4
" 短格式(括号中的长度)
DATA lv_text(10) TYPE c.
" 扩展格式(LENGTH 附加项,6.10 起可用)
DATA lv_text TYPE c LENGTH 10.

ABAP 泛型

泛型不能完全定义数据对象的属性。它们不能用于通过 DATA 声明数据对象。它们仅用于为形式参数和字段符号指定类型。

泛型 描述 覆盖范围
any 任意数据类型 所有类型
data 任意数据类型(与参数的 any 相同) 所有类型
simple 基本类型 + 平坦结构 i, f, p, decfloat, d, t, c, n, x, string, xstring, 平坦结构
csequence 类字符序列 c, string
numeric 数值类型 i, f, p, decfloat16, decfloat34
clike 类字符型 c, string, d, t, n
xsequence 类字节序列 x, xstring
c 文本字段(泛型长度) 任意长度的 c
n 数字文本(泛型长度) 任意长度的 n
x 十六进制字段(泛型长度) 任意长度的 x
decfloat 十进制浮点数 decfloat16, decfloat34
any table 任意内表 标准、排序、哈希
index table 支持索引访问的表 标准、排序
standard table 标准表 仅标准表
sorted table 排序表 仅排序表
table 标准表(遗留) 仅标准表
object 任意对象引用 所有对象引用
⚠️泛型不能声明数据对象
DATA lv_val TYPE any.无效的。泛型仅用于为形式参数(USINGCHANGING)和字段符号(FIELD-SYMBOLS)指定类型。

类型来源:局部 vs. 全局

来源 定义位置 作用域 示例
ABAP 预定义类型 ABAP 运行时环境 内置 i, string, p
全局数据类型 ABAP 字典 系统范围 数据元素、结构、表类型
局部数据类型 程序中的 TYPES 语句 仅限程序内部 TYPES: ty_price TYPE p LENGTH 10 DECIMALS 3.
💡全局(字典)类型的优势
  • 可跨程序复用
  • 语义文档(字段标签、帮助文本)
  • 自动与数据库表结构保持一致
  • 更改会传播到所有使用该类型的程序

数据对象类别

1
2
3
4
5
6
7
8
9
10
11
12
13
                    Data Objects
|
+--------------+--------------+
| | |
Elementary Structures Internal Tables
(单一值) (组件) (行类型 + 表体)
| | |
+----+----+ 可嵌套至任意深度 可包含
| | 结构或
Flat Deep 引用
(i,f,p, (string,
d,t,c, xstring,
n,x) 引用)
类别 描述 示例
基本类型 单一类型的值 DATA lv_name TYPE string.
结构 组件集合(可嵌套至任意深度) DATA ls_flight TYPE sflight.
内表 行的表格集合 DATA lt_flights TYPE TABLE OF sflight.
引用 指向数据对象或对象实例的指针 DATA lr_ref TYPE REF TO data.

平坦 vs. 深层数据对象

属性 平坦 深层
长度 静态(编译时固定) 动态(运行时变化)
引用其他对象
基本类型 i, f, p, d, t, c, n, x string, xstring, 引用
结构 所有组件都是平坦的 至少有一个深层组件
内存 内联存储 堆引用 + 数据
📌只要有一个深层组件,结构就是深层的
即使嵌套结构中只有一个组件是 string 或内表,整个结构也被归类为深层。必须同时考虑 Include 和子结构。

字面量

类型 语法 数据类型 长度 说明
数字字面量 123, -45 i(在范围内)或 p 固定 范围:-2^31+1 到 2^31-1 对应类型 i
文本字段字面量 'Hello World' c 1–255 字符 尾部空格被忽略;用 '' 表示单引号
字符串字面量 `Hello World` string 0–255 字符 尾部空格保留;6.10 起可用
💡转义引号
  • 文本字段字面量中的单引号:输入两次 – 'John''s book'
  • 字符串字面量中的反引号:输入两次
  • 无限制:字符串字面量中可用单引号,文本字段字面量中可用反引号

可见性与生命周期

传统 ABAP 中的可见性

声明位置 可见性 说明
FORM/ENDFORM 之间 子程序内部局部 遮蔽同名的全局变量
FUNCTION/ENDFUNCTION 之间 函数模块内部局部 遮蔽同名的全局变量
MODULE/ENDMODULE 之间 全局 常见陷阱 – 不是局部的!
事件块内部 全局 常见陷阱 – 不是局部的!
TABLES 语句(任何位置) 全局(即使在子程序中) 甚至在外部 PERFORM 中也跨程序可见
⚠️MODULE 和事件块中的 DATA 是全局的
MODULE...ENDMODULE 内部或事件块内部的 DATA 语句创建的是全局数据对象,不是局部的。这是常见的 bug 来源。应在程序开头声明所有全局数据。

ABAP Objects 中的可见性(属性)

可见性区域 可访问范围
Private 仅同类的方法
Protected 同类的方法 + 子类
Public 任何外部调用者

数据对象的生命周期

数据对象 生命周期
全局数据对象 程序在内存中期间
对象属性 从实例化到最后一个引用被删除
局部数据对象(过程中) 所在模块化单元的生命周期
STATICS 声明的局部变量 整个程序生命周期(可见性仍为局部)
💡STATICS 延长生命周期,不改变可见性
STATICS 可用于函数模块、子程序和静态方法。它使局部数据具有全局变量的生命周期,但保持其可见性限制在模块化单元内。

考试/测试模式

关键词 / 问题 答案
“哪些类型是完整的?” i, f, decfloat16, decfloat34, d, t, string, xstring
“哪些类型是不完整的?” p, n, c, x
“泛型可以声明数据对象吗?” 不可以 – 仅用于为参数和字段符号指定类型
“包含一个 string 组件的结构是平坦的还是深层的?” 深层 – 任何深层组件使整个结构变为深层
“MODULE 中的 DATA 在哪里可见?” 全局(陷阱题)
“FORM 中的 DATA 在哪里可见?” 子程序内部局部
“STATICS 的作用是什么?” 将生命周期延长至程序生命周期,保持局部可见性
“类型 c 的初始值是什么?” 空格(' '
“数值类型的初始值是什么?” 0
“数字字面量 10000000000 是什么类型?” p(超出 i 范围)
“‘text’ 和 `text` 有什么区别?” 'text' = 类型 c(尾部空格被删除);`text` = 类型 string(尾部空格保留)

相关笔记

程序类型与模块化 (★★)

#abap-basics #program-structure

ABAP 程序结构

每个 ABAP 程序由四个结构部分组成。实际包含哪些部分取决于程序类型。

1
2
3
4
5
6
7
8
9
+=============================================+
| 引导语句(Introductory Statement) | <- 第一条语句(展开包含后)
+=============================================+
| 全局声明(Global Declarations) | <- DATA, CONSTANTS, TYPES, CLASS DEF, INTERFACE DEF
+---------------------------------------------+
| 实现(Implementations) | <- 局部类方法实现(CLASS ... IMPLEMENTATION)
+---------------------------------------------+
| 处理块(Processing Blocks) | <- 事件块、对话模块、过程
+=============================================+
📌处理块是不可分割的
处理块是一个不可分割的程序单元,必须连续编写。ABAP 中恰好有三种处理块类型。

处理块类型

类型 引入方式 结束方式 调用方
事件块 事件关键字(如 START-OF-SELECTION 由下一个处理块关键字隐式结束 ABAP 运行时环境
对话模块 MODULE ... ENDMODULE 显式 ENDMODULE 运行时环境在屏幕处理期间(流逻辑)
过程 FORM ... ENDFORMFUNCTION ... ENDFUNCTION、方法 显式结束关键字 从另一个处理块显式调用
💡事件块不能在源代码中调用
事件块只能由 ABAP 运行时环境在程序执行期间特定时间调用。不能通过编程方式调用。

程序类型

引导语句

程序类型 引导语句 说明
可执行程序 REPORT name ... [MESSAGE-ID id]. 可附加列表输出选项
函数组 FUNCTION-POOL name ... [MESSAGE-ID id]. 可附加列表输出选项
模块池 PROGRAM name [MESSAGE-ID id].
子程序池 (无单独语句)
类池(全局类) CLASS-POOL [MESSAGE-ID id]. 由类构建器生成
接口池(全局接口) INTERFACE-POOL. 由类构建器生成
类型池(类型组) TYPE-POOL name.
⚠️引导语句 vs 程序属性
引导语句不一定与程序属性中的程序类型匹配。编辑时只更改附加项,不要更改语句本身,以避免意外行为。

各程序类型允许的全局声明

声明 可执行程序 函数组 模块池 子程序池 类池 接口池 类型池
局部数据类型 是(签名+局部) 是(仅签名)
数据对象
常量 是(仅签名) 是(仅签名)
局部类
局部接口
💡接口池和类型池是纯声明性的
全局接口和类型组仅是数据类型和常量的集合。不能包含数据对象、局部类或局部接口。

各程序类型允许的处理块

处理块 可执行程序 函数组 模块池 子程序池 类池 接口池 类型池
事件块 完整集合 LOAD-OF-PROGRAM LOAD-OF-PROGRAM
对话模块
FORM(子程序)
函数模块
方法

可执行程序事件LOAD-OF-PROGRAMINITIALIZATIONSTART-OF-SELECTIONGETEND-OF-SELECTION

屏幕相关事件(可执行程序、函数组和模块池可用):

  • Dynpro:PBOPAIPOHPOV
  • 选择屏幕:AT SELECTION-SCREEN OUTPUTAT SELECTION-SCREEN
  • 列表:TOP-OF-PAGEEND-OF-PAGEAT LINE-SELECTIONAT USER-COMMAND
📌面向对象的程序类型限制子程序
子程序(FORM)不允许在类池、接口池或类型池中使用。

包含程序(Include Programs)

包含程序将 ABAP 源代码拆分为独立的仓库对象。它们不能有引导语句,也不能独立生成。

1
2
3
4
5
6
7
8
9
10
11
函数组 ZFG_EXAMPLE (FUNCTION-POOL)
|
+-- LZFG_EXAMPLETOP <-- 顶部包含(全局声明,TABLES, TYPES, DATA)
+-- LZFG_EXAMPLEUXX <-- 包含的包含(包含 INCLUDE 语句)
| +-- LZFG_EXAMPLEU01 <-- 函数模块 1
| +-- LZFG_EXAMPLEU02 <-- 函数模块 2
+-- LZFG_EXAMPLEF01 <-- FORM 例程(PERFORM)
+-- LZFG_EXAMPLEF02 <-- 额外的 FORM 例程
+-- LZFG_EXAMPLEO01 <-- PBO 模块
+-- LZFG_EXAMPLEI01 <-- PAI 模块
+-- LZFG_EXAMPLEE01 <-- 事件块

包含程序命名规范

后缀 内容 示例
TOP 全局声明 LZFG_EXAMPLETOP
Uxx 函数模块 LZFG_EXAMPLEU01
Fxx FORM 例程 LZFG_EXAMPLEF01
Oxx PBO 模块 LZFG_EXAMPLEO01
Ixx PAI 模块 LZFG_EXAMPLEI01
Exx 事件块 LZFG_EXAMPLEE01
💡包含程序规则
  • 包含程序必须包含完整语句(不能是部分语句)
  • 包含程序可以集成其他包含程序,但不能包含自身
  • 包含程序不必包含完整的处理块
  • INCLUDE 是除了引导语句之外唯一允许出现在程序顶部的语句
  • 使用报表 RSINCL00 生成包含程序的引用列表

考试/测试模式

关键词/问题 答案
“哪种程序类型不能包含处理块?” 接口池和类型池(纯声明性)
“函数组和模块池可以包含哪个事件?” LOAD-OF-PROGRAM
“函数模块可以在哪里定义?” 仅在函数组中
“子程序可以在类池中使用吗?” 不可以——面向对象程序类型中不允许
“引导语句之前可以出现什么?” INCLUDE 语句
“包含程序需要引导语句吗?” 不需要——它们不能有引导语句
“事件块中的 DATA 语句是局部还是全局?” 始终是全局(常见陷阱)
“什么命名后缀表示 FORM 包含?” Fxx(如 F01F02

相关笔记

ABAP 基础练习 (10题)

#practice #abap-basics #program-structure #data-types #open-sql

相关概念

📌核心模式(点击查看)
关键词 答案
每种类型的引导语句 REPORT/FUNCTION-POOL/PROGRAM/CLASS-POOL
完整 vs 不完整类型 完整:i,f,d,t,string,xstring;不完整:p,n,c,x
平坦 vs 深层 平坦=静态长度;深层=引用(string,内表,引用)
SELECT 变体 SINGLE、ENDSELECT 循环、INTO TABLE(数组)

第1题 - 程序引导语句 [记忆]

可执行程序的引导语句是什么?

📌点击查看答案
REPORT name ... [MESSAGE-ID id]。MESSAGE-ID 附加项是可选的,用于指定标准消息类。

第2题 - 处理块 [记忆]

说出 ABAP 程序中三种处理块的类型。

📌点击查看答案
  1. ABAP 事件块(由事件关键字引入,由运行时环境调用)
  2. 对话模块(MODULE…ENDMODULE,由屏幕流逻辑调用)
  3. 过程(FORM…ENDFORM, FUNCTION…ENDFUNCTION, 方法 – 显式调用)

第3题 - 程序类型限制 [记忆]

哪些程序类型不能包含任何处理块?

📌点击查看答案
接口池类型池 – 它们严格用于声明,仅包含类型和常量定义。

第4题 - Include 程序 [记忆]

ABAP include 程序有哪些规则?(至少说出三条)

📌点击查看答案
  • 不能有引导语句
  • 不能由编译器独立生成
  • 必须包含完整语句
  • 可以包含其他 include 但不能包含自身(不允许递归)
  • 不需要包含完整的处理块
  • 推荐命名:<main>_F01<main>_O01<main>_I01

第5题 - 数据类型分类 [记忆]

将每个 ABAP 预定义类型分类为完整或不完整:ipcfnstringxdtxstring

📌点击查看答案
类型 分类 说明
i 完整 固定长度
p 不完整 需要 LENGTH + DECIMALS
c 不完整 需要 LENGTH
f 完整 固定长度
n 不完整 需要 LENGTH
string 完整 可变长度
x 不完整 需要 LENGTH
d 完整 固定 8 字符
t 完整 固定 6 字符
xstring 完整 可变长度

第6题 - 泛型 [记忆]

ABAP 泛型的用途是什么?可以用它们声明数据对象吗?

📌点击查看答案
泛型仅用于为形式参数和字段符号指定类型。不能用于声明数据对象,因为它们不能完全定义数据对象的属性(如长度)。

第7题 - 平坦 vs 深层 [应用]

有一个结构包含以下组件:id TYPE iname TYPE stringamount TYPE p LENGTH 8 DECIMALS 2。这个结构是平坦的还是深层的?为什么?

📌点击查看答案
该结构是深层的,因为它包含至少一个深层组件(name TYPE string)。虽然 ip 是平坦的,但如果任何组件(在任何嵌套层级)是深层的(string、内表或引用),则整个结构被归类为深层。

第8题 - SELECT 变体 [应用]

需要将某航空公司的所有航班读取到内表中。应使用哪种 SELECT 变体?为什么?

📌点击查看答案
使用 SELECT ... INTO TABLE itab(数组读取)。这样一次操作就能将所有记录传输到内表。它比 SELECT ... ENDSELECT(顺序读取)更高效,因为它避免了循环开销并最小化了数据库交互。操作后始终检查 sy-subrc 以验证是否找到数据。

第9题 - 可见性与生命周期 [分析]

一个数据对象在方法实现中用 DATA 声明。比较它与同一程序全局声明部分中声明的数据对象的可见性和生命周期。

📌点击查看答案
方面 方法局部 DATA 全局 DATA
可见性 仅在方法内部 整个程序组
生命周期 方法调用时创建,方法退出时释放 内部会话持续期间一直存在
内存 每次调用的栈/堆 滚动区(静态)

方法局部变量支持封装和可重入性 – 每次方法调用都有自己的变量实例。


第10题 - Open SQL 错误处理 [应用]

执行 SELECT SINGLE * FROM sflight INTO ls_flight WHERE carrid = 'LH' 后,如果未找到记录,sy-subrc 的值是什么?应如何处理?

📌点击查看答案
sy-subrc = 4 表示未找到记录(8 表示 SELECT SINGLE 的记录过多不可能出现,但使用 UP TO 2 ROWS 时可能出现)。

始终立即检查:

1
2
3
4
SELECT SINGLE * FROM sflight INTO ls_flight WHERE carrid = 'LH'.
IF sy-subrc <> 0.
" 处理:消息、默认值或退出
ENDIF.

📌模式总结(点击查看)
关键词 答案
可执行程序引导语句 REPORT
无处理块 接口池、类型池
完整类型 i, f, d, t, string, xstring, decfloat16/34
不完整类型 p, n, c, x(需要 LENGTH)
深层数据对象 string, xstring, 内表, 引用
数组读取 SELECT ... INTO TABLE
sy-subrc = 0 找到记录
sy-subrc = 4 未找到记录

运行时与内存管理 (★★★)

#memory-mgmt

程序加载过程

当 ABAP 程序首次被调用时,它经过多阶段加载管道:

1
2
3
4
5
源代码              生成                  运行时对象              PXA 缓冲区
(仓库) → (生成的代码) → (可执行形式) → (共享内存)
.abap 加载版本 加载的实例 仅加载一次
(DB 表) (DB 表) (每个内部 (应用服务器
会话) 级别)
阶段 位置 范围 说明
源代码 数据库(仓库) 系统范围 ABAP 源文本
生成 数据库(加载版本) 系统范围 生成/编译的代码
运行时对象 PXA 缓冲区(程序执行区) 每个应用服务器 缓存的可执行代码
运行时实例 滚动区 每个内部会话 当前执行的运行时数据
💡PXA 在所有用户间共享
PXA 缓冲区在应用服务器级别保存运行时对象。如果用户 A 已经调用了程序 ZPROG,用户 B 调用同一程序时可以复用 PXA 条目 – 无需重新生成。

会话架构

1
2
3
4
5
6
7
8
9
10
11
用户会话(终端会话)
├── 外部会话 1(GUI 窗口)
│ ├── 内部会话(栈)
│ │ ├── 主程序 + 被调用程序
│ │ ├── 附加程序组 1
│ │ └── 附加程序组 2
│ └── ABAP 内存(EXPORT/IMPORT)
├── 外部会话 2(另一个 GUI 窗口)
│ ├── 内部会话(栈)
│ └── ABAP 内存(与会话 1 分开)
└── SAP 内存(SET/GET PARAMETER -- 所有外部会话共享)
会话类型 范围 生命周期
用户会话 一次 SAP 登录 直到注销
外部会话 一个 GUI 窗口/模式 直到窗口关闭
内部会话 栈上的一个程序调用 直到程序结束或栈展开

程序组

每个内部会话包含至少一个程序组。加载到同一组中的程序共享全局数据。

程序组类型 创建者 数据共享
主程序组 内部会话中的第一个程序 此组中所有程序共享全局数据
附加程序组 通过过程调用加载的函数组、类池 每个组有独立的全局数据

程序如何进入程序组

调用类型 程序组 风险
外部子程序调用(PERFORM form(prog) 同一(主)程序组 接口工作区的意外数据共享
函数模块调用(CALL FUNCTION 独立程序组(函数组加载到自己的组中) 安全 – 隔离的全局数据
类方法调用 独立程序组(类池) 安全 – 隔离
⚠️外部子程序调用很危险
外部 PERFORM 将目标程序加载到同一程序组,共享接口工作区(如经典选择屏幕或 GET/SET PARAMETER 内存区域)。这可能导致意外的副作用。建议使用函数模块或类方法。

程序调用的内存管理

语句 会话效果 程序组
SUBMIT ... AND RETURN 新内部会话压入栈 与调用者分离
CALL TRANSACTION 新内部会话压入栈 与调用者分离
SUBMIT(无 AND RETURN) 顶层内部会话被替换 新的主程序组
LEAVE TO TRANSACTION 整个栈被销毁,创建新会话 全新开始

会话栈行为

1
2
3
4
5
6
7
8
9
初始状态:                     SUBMIT AND RETURN 后:         LEAVE TO TRANSACTION 后:
(栈向上增长)
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 会话 A │ │ 会话 B │ │ 会话 X │
│ (程序 P1) │ │ (提交的 P2) │ │ (事务) │
└───────────────┘ │───────────────│ └───────────────┘
│ 会话 A │
│ (程序 P1) │ 所有先前的会话被销毁
└───────────────┘ ABAP 内存也被清除

函数组和类:会话行为

方面 函数组 类(ABAP Objects)
加载到 每个函数组独立的程序组 每个类独立的程序组
全局数据 每个内部会话实例 每个内部会话实例
跨会话共享? 否 – 每个会话有自己的副本 否 – 每个会话有自己的副本
生命周期 会话中首次调用 FM 时加载,持续到会话结束 首次创建对象时加载,持续到会话结束
📌同一函数组,不同会话 = 独立数据
如果两个内部会话调用同一函数组的 FM,每个会话获得函数组全局数据的独立副本。内部会话之间没有共享。

ABAP 内存 vs SAP 内存

方面 ABAP 内存 SAP 内存
语句 EXPORT ... TO MEMORY IDIMPORT ... FROM MEMORY IDFREE MEMORY ID SET PARAMETER IDGET PARAMETER ID
范围 每个外部会话 每个用户会话
内部会话间共享? 是 – 同一外部会话中的所有内部会话共享 是 – 所有外部会话共享
外部会话间共享?
支持的数据类型 任意(平坦、深层、对象) 仅平坦基本字段(通过 SPA/GPA 参数)
生命周期 直到外部会话关闭或 FREE MEMORY 直到用户注销
1
2
3
4
5
6
7
8
9
10
11
12
13
14
" ABAP 内存 -- 写入
EXPORT lv_data = 'Hello' TO MEMORY ID 'MY_DATA'.

" ABAP 内存 -- 读取
IMPORT lv_data = lv_result FROM MEMORY ID 'MY_DATA'.

" ABAP 内存 -- 清除
FREE MEMORY ID 'MY_DATA'.

" SAP 内存 -- 写入
SET PARAMETER ID 'BUK' FIELD '1000'.

" SAP 内存 -- 读取
GET PARAMETER ID 'BUK' FIELD lv_bukrs.

内存范围图示

1
2
3
4
5
6
7
8
9
10
11
12
13
用户会话
┌─────────────────────────────────────────────────────────────┐
│ SAP 内存(SET/GET PARAMETER -- 所有窗口共享) │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ 外部会话 1 │ │ 外部会话 2 │ │
│ │ ┌──────────────────────┐ │ │ ┌──────────────────────┐ │ │
│ │ │ ABAP 内存 1 │ │ │ │ ABAP 内存 2 │ │ │
│ │ │ (EXPORT/IMPORT) │ │ │ │ (EXPORT/IMPORT) │ │ │
│ │ │ 此外部会话中的 │ │ │ │ 此外部会话中的 │ │ │
│ │ │ 内部会话共享 │ │ │ │ 内部会话共享 │ │ │
│ │ └──────────────────────┘ │ │ └──────────────────────┘ │ │
│ └──────────────────────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

动态数据对象内存

深层数据对象(字符串、内表)使用基于引用的内存模型,包含三个部分:

组件 描述
引用 保存在声明变量中(指针)
头部 管理元数据(长度、容量、类型信息)
数据体 实际内容(字符、行)
1
2
3
4
5
变量(引用)         头部                 数据体
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ ref to data │──>│ length, cap, │───>│ actual content │
│ │ │ type info │ │ (chars / rows) │
└──────────────┘ └──────────────┘ └──────────────────┘

写入时复制语义(共享)

当字符串或内表赋值给另一个变量时,ABAP 使用写入时复制(共享)来节省内存:

阶段 发生了什么 内存
初始赋值 t2 = t1 仅复制引用;两者指向同一头部和数据 最小 – 仅一个指针
通过任一变量读取 两者读取同一共享数据体 无复制
通过一个变量写入/修改 数据体为修改的变量复制;另一个保持原始数据 仅在修改时复制
1
2
3
4
5
6
DATA(lv_text1) = |ABCDE|.
DATA(lv_text2) = lv_text1. " 共享:两者指向同一数据

lv_text2 = |XYZ|. " 写入时复制:lv_text2 现在有自己的副本
" lv_text1 仍 = 'ABCDE'
" lv_text2 = 'XYZ'
💡共享节省内存
对于传递给子程序或赋值给局部变量的大型内表,ABAP 最初仅复制引用。只有当数据实际被修改时才会进行昂贵的数据复制。

封装组件(Boxed Components)

封装组件使用 BOXED 附加项用于结构中的深层组件,启用初始值共享(IVS)。

语法

1
2
3
4
TYPES: BEGIN OF ty_structure,
field1 TYPE i,
sub TYPE ty_inner_struct BOXED, " 封装组件
END OF ty_structure.

封装组件的工作原理

方面 封装组件 普通深层组件
初始值内存 共享 – 所有初始实例指向一个共享头部/数据 每个实例分配自己的头部(即使为空)
非初始值内存 首次写入时分配自己的头部+数据体 已有自己的分配
使用场景 有大量实例且大多保持初始值的大型结构 大多数实例都会有数据时

初始值共享图示

1
2
3
4
5
6
7
8
9
10
11
12
不使用 BOXED:每个结构分配自己的(空)头部
┌─────────┐ ┌─────────┐ ┌─────────┐
│ sub ──────>│ (empty) │ │ (empty) │ │ (empty) │ (3 个头部)
└─────────┘ └─────────┘ └─────────┘ └─────────┘

使用 BOXED:所有初始实例共享一个头部
┌─────────┐ ┌─────────┐ ┌─────────┐
│ sub ──────>│ │ │ │ │ │
└─────────┘ │ │ │ │ │ │ (1 个共享头部)
└────┬────┘ └────┬────┘ └────┬────┘
└───────────┴───────────┘
共享初始值

何时使用封装组件

适用场景 原因
子结构通常为空的内表(多行) 每个空行节省一个头部分配
大型数据集中的嵌套结构 累计节省跨多个实例
默认/初始状态很常见 IVS 避免冗余的空分配
不适用场景 原因
大多数实例都会有数据 没有共享收益;增加开销
实例数量少 内存节省微乎其微
📌封装组件和初始值共享
使用 BOXED 附加项的封装组件启用初始值共享 – 所有未修改的实例共享单个内存分配用于初始(空)状态。仅在组件首次被写入时才单独分配内存。

考试/测试模式

关键词 / 模式 正确答案
PXA 缓冲区 应用服务器级别;保存运行时对象,所有用户共享
滚动区 每个内部会话;保存局部数据和程序实例
SUBMIT AND RETURN vs SUBMIT AND RETURN = 插入(新会话入栈);不带 = 替换(栈顶会话被替换)
LEAVE TO TRANSACTION 销毁整个栈;ABAP 内存被清除
外部子程序调用(PERFORM) 加载到同一程序组 – 有意外数据共享的风险
函数模块/方法调用 加载到独立程序组 – 隔离的数据
ABAP 内存范围 每个外部会话;其中的内部会话共享
SAP 内存范围 每个用户会话;所有外部会话共享
EXPORT/IMPORT/FREE MEMORY ID ABAP 内存操作
SET/GET PARAMETER ID SAP 内存操作(仅平坦字段)
写入时复制(共享) 赋值时复制引用;仅在修改时复制数据
BOXED 附加项 为深层结构组件启用初始值共享
同一函数组,不同会话 每个会话获得全局数据的独立副本 – 不共享
主程序组 由内部会话中的第一个程序创建

相关笔记

  • 程序调用 – SUBMIT、CALL TRANSACTION、LEAVE TO TRANSACTION 详情
  • 共享对象 – 通过共享内存实现跨会话和跨用户数据共享
  • [[_posts/SAP_MM/SCM500_采购流程/00-仪表盘/考试陷阱]] – 常见内存管理陷阱
  • [[_posts/SAP_MM/SCM500_采购流程/00-仪表盘/快速参考]] – 内存区域汇总表

共享对象 (★★)

#shared-objects #memory-mgmt

概念与动机

共享对象提供了一种在同一应用服务器上跨用户会话和外部会话共享数据的机制。与 ABAP 内存(仅限外部会话)或 SAP 内存(仅限平坦字段)不同,共享对象可以存储所有用户都可访问的复杂深层数据结构。

内存模型层次

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
应用服务器
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 用户会话 A 用户会话 B │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ 外部会话 1 │ │ 外部会话 1 │ │
│ │ ┌──────────────────┐ │ │ ┌──────────────────┐ │ │
│ │ │ 内部会话 │ │ │ │ 内部会话 │ │ │
│ │ │ + ABAP 内存 │ │ │ │ + ABAP 内存 │ │ │
│ │ └──────────────────┘ │ │ └──────────────────┘ │ │
│ │ SAP 内存 │ │ SAP 内存 │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 共享内存(应用服务器级别) │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 共享对象内存 │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ │ │
│ │ │ │ 区域 1 │ │ 区域 2 │ │ 区域 N │ │ │ │
│ │ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ │ │ │ │
│ │ │ │ │ 根对象 │ │ │ │ 根对象 │ │ │ │ │ │ │
│ │ │ │ └──────────┘ │ │ └──────────┘ │ │ │ │ │ │
│ │ │ └──────────────┘ └──────────────┘ └─────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘

与其他内存机制的比较

特性 ABAP 内存 SAP 内存 共享对象
范围 一个外部会话 一个用户会话 应用服务器上所有用户
数据类型 任意 仅平坦字段 任意(必须在启用共享内存的类中)
持久性 直到会话关闭 直到注销 直到显式删除或服务器重启
跨用户?
大小限制 滚动区限制 较小(SPA/GPA) 每个区域可配置

区域类

共享对象被组织为区域,每个区域由一个区域类管理。

角色 创建者
CL_SHM_AREA 所有区域类的基类 SAP(框架)
区域类(如 ZCL_SHM_MY_AREA 管理一个共享内存区域;通过事务 SHMA 生成 开发者(通过 SHMA)
区域句柄类 提供访问区域实例的句柄;与区域类一起自动生成 系统(自动生成)
数据/根类 包含作为属性的实际数据;由开发者创建 开发者

类关系

1
2
3
4
5
6
7
CL_SHM_AREA(SAP 基类)
└── ZCL_SHM_MY_AREA(区域类,由 SHMA 生成)
提供:ATTACH_FOR_WRITE, ATTACH_FOR_READ, ATTACH_FOR_UPDATE, DETACH

ZCL_MY_ROOT(数据类,由开发者创建)
包含:保存共享数据的属性
继承:无特殊要求(但必须兼容 SHM)

实现共享对象的步骤

步骤 1:定义共享内存区域(事务 SHMA)

操作 详情
事务 SHMA
区域名称 Z_SHM_CATALOG
属性 设置区域类名、根类、有效期、客户端依赖性
结果 生成区域类(如 ZCL_SHM_Z_SHM_CATALOG)和区域句柄类

步骤 2:创建根/数据类

1
2
3
4
5
6
7
8
9
10
11
CLASS zcl_catalog_root DEFINITION
PUBLIC
CREATE PUBLIC.

PUBLIC SECTION.
DATA: mt_products TYPE ztt_products, " 产品目录数据
mv_last_update TYPE timestampl.
METHODS: load_data,
get_product IMPORTING iv_id TYPE zproduct_id
RETURNING VALUE(rs_product) TYPE zs_product.
ENDCLASS.
📌根类要求
根类及其属性中使用的所有类型必须兼容 SHM。这意味着:
  • 不能引用不可 SHM 序列化的对象
  • 不能使用标记为 NOT FOR SHARED OBJECTS 的类
  • 所有引用的类型必须可在共享内存中使用

步骤 3:写入共享内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DATA: lo_area  TYPE REF TO zcl_shm_z_shm_catalog,
lo_root TYPE REF TO zcl_catalog_root.

" 1. 附加以写入
lo_area = zcl_shm_z_shm_catalog=>attach_for_write( ).

" 2. 在共享内存中创建根实例
CREATE OBJECT lo_root AREA HANDLE lo_area.

" 3. 填充根对象数据
lo_root->load_data( ).

" 4. 设置区域的根对象
lo_area->set_root( lo_root ).

" 5. 分离(提交更改到共享内存)
lo_area->detach( ).
步骤 方法 目的
附加 ATTACH_FOR_WRITE( ) 获取区域写锁;返回区域句柄
创建根 CREATE OBJECT ... AREA HANDLE 在共享内存中实例化根类
设置根 area->set_root( root ) 注册根对象为区域的根
分离 area->detach( ) 释放锁;使更改对读者可见

步骤 4:从共享内存读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DATA: lo_area TYPE REF TO zcl_shm_z_shm_catalog,
lo_root TYPE REF TO zcl_catalog_root.

" 1. 附加以读取
lo_area = zcl_shm_z_shm_catalog=>attach_for_read( ).

" 2. 访问根对象
lo_root = lo_area->root.

" 3. 使用数据
DATA(ls_product) = lo_root->get_product( iv_id = 'MAT01' ).

" 4. 分离
lo_area->detach( ).
步骤 方法 目的
附加 ATTACH_FOR_READ( ) 获取读锁;返回区域句柄
访问根 area->root 获取根对象的引用
分离 area->detach( ) 释放读锁

附加模式比较

模式 锁类型 使用场景 并发访问
ATTACH_FOR_READ 读锁 读取数据 允许多个读者同时访问
ATTACH_FOR_WRITE 写锁 创建或替换整个区域内容 阻塞读者和写者
ATTACH_FOR_UPDATE 写锁 修改现有内容 阻塞读者和写者
💡尽可能使用 ATTACH_FOR_READ
读锁允许多个并发读者。写锁和更新锁是排他的。设计共享对象时应以读多写少为原则,以获得最佳性能。

典型使用场景

目录(读多改少)

方面 详情
模式 一次性加载(或按计划),由多个用户读取
写入触发 首次访问或定时刷新
读取频率 非常高(每次用户请求)
示例 产品目录、配置数据、汇率
1
2
3
4
5
6
7
8
写入者(管理员/调度器):
ATTACH_FOR_WRITE → 加载数据 → 设置根 → DETACH
↓(共享内存已更新)
读取者(终端用户):
ATTACH_FOR_READ → 访问根 → 获取数据 → DETACH
ATTACH_FOR_READ → 访问根 → 获取数据 → DETACH
ATTACH_FOR_READ → 访问根 → 获取数据 → DETACH
...(多个并发读者)

购物车(跨会话数据)

方面 详情
模式 每个用户有自己的区域实例(按键区分)
写入触发 用户添加/删除项目
读取频率 每用户访问
示例 购物车、用户偏好、会话状态
⚠️共享对象仅限于单个应用服务器
共享内存是单个应用服务器本地的。如果系统有多个应用服务器,在服务器 A 上写入的数据在服务器 B 上不可见。跨服务器共享需要使用数据库表或其他持久化机制。

区域生命周期和失效

事件 行为
区域创建(首次写入) 分配内存,存储根对象
区域读取 返回引用;不复制数据
区域覆写 旧内容被替换;读者重新附加后获得新内容
区域失效 FREE_AREA 或 SHMA 事务;所有引用变为无效
服务器重启 所有共享内存被清除
1
2
" 使区域失效/释放
zcl_shm_z_shm_catalog=>free_area( ).

实现检查清单

步骤 事务/代码 完成?
1. 创建数据(根)类及其属性 SE24 / ADT [ ]
2. 在 SHMA 中定义共享内存区域 SHMA [ ]
3. 实现写入逻辑(附加 → 创建 → 设置 → 分离) 程序/方法 [ ]
4. 实现读取逻辑(附加 → 访问 → 分离) 程序/方法 [ ]
5. 处理异常(CX_SHM_* 类) Try/catch 块 [ ]

常见异常

异常 原因
CX_SHM_NO_ACTIVE_VERSION 区域尚未写入数据
CX_SHM_EXCLUSIVE_LOCK_ACTIVE 另一个进程持有写锁
CX_SHM_CHANGE attempts 尝试修改读锁保护的数据
CX_SHM_VERSION_LIMIT_EXCEEDED 区域版本过多

考试/测试模式

关键词 / 模式 正确答案
共享对象范围 同一应用服务器上的所有用户
定义区域的事务 SHMA
区域类的基类 CL_SHM_AREA
写入步骤 ATTACH_FOR_WRITE → CREATE OBJECT AREA HANDLE → set_root → DETACH
读取步骤 ATTACH_FOR_READ → 访问根 → DETACH
ATTACH_FOR_READ 锁定 允许多个读者并发
ATTACH_FOR_WRITE 锁定 排他 – 阻塞读者和写者
根类 开发者创建的类,以属性形式保存共享数据
跨服务器共享? – 共享内存是每个应用服务器独立的
使用场景:目录 读多改少 – 共享对象的理想场景
使用场景:购物车 跨会话数据,按用户创建区域实例
FREE_AREA 使共享内存区域内容失效并删除

相关笔记

  • 运行时与内存管理 – ABAP 内存、SAP 内存、会话架构
  • 程序调用 – 内部会话及其与内存的关系
  • [[_posts/SAP_MM/SCM500_采购流程/00-仪表盘/考试陷阱]] – 常见共享内存陷阱
  • [[_posts/SAP_MM/SCM500_采购流程/00-仪表盘/快速参考]] – 内存区域比较表

程序调用与内存练习 (10题)

#practice #memory-mgmt #program-structure #shared-objects

相关概念

📌核心模式(点击查看)
关键词 答案
插入 vs 新启动 AND RETURN = 插入;不带 AND RETURN = 替换
LEAVE TO TRANSACTION 销毁整个栈 + ABAP 内存
ABAP 内存范围 每个外部会话,内部会话共享
SAP 内存范围 每个用户会话,所有外部会话
封装组件 PXA 中的初始值共享,节省内存

第1题 - SUBMIT 变体 [记忆]

SUBMIT prog AND RETURNSUBMIT prog(不带 AND RETURN)有什么区别?

📌点击查看答案
  • SUBMIT prog AND RETURN插入被调用程序。新的内部会话压入栈。被调用程序结束后,在调用程序中 SUBMIT 语句之后继续执行。
  • SUBMIT prog(不带 AND RETURN):替换顶层内部会话。调用程序的会话被销毁。被调用程序的新会话替换它。

第2题 - CALL TRANSACTION 模式 [记忆]

CALL TRANSACTION 的 MODE 附加项可以取哪些值?各表示什么?

📌点击查看答案
行为
A 显示所有屏幕(默认)
E 仅在出错时显示
N 不显示屏幕(后台模式)

第3题 - BDCDATA 结构 [记忆]

在为 CALL TRANSACTION ... USING 填充 BDCDATA 内表时,哪些字段标识屏幕,哪些字段填充输入值?

📌点击查看答案
屏幕标识PROGRAMDYNPRODYNBEGIN = 'X'
字段填充FNAM(字段名或 BDC_OKCODE/BDC_CURSOR)、FVAL(字段值)

第4题 - LEAVE TO TRANSACTION 内存效果 [应用]

一个程序使用 LEAVE TO TRANSACTION 调用另一个程序。调用前使用 EXPORT TO MEMORY ID 'MY_DATA' 存储了数据。被调用程序能读取这些数据吗?为什么?

📌点击查看答案
不能。 LEAVE TO TRANSACTION 销毁整个内部会话栈,同时清除了 ABAP 内存。ABAP 内存与外部会话的内部会话关联,当它们全部被销毁时,内存被释放。只有 CALL TRANSACTION(插入模式)保留 ABAP 内存。

第5题 - 程序组 [记忆]

两个不同的内部会话调用同一函数组的函数模块时会发生什么?

📌点击查看答案
函数组分别加载到每个内部会话中。每个会话获得函数组全局数据的独立副本。会话之间没有意外数据共享的风险,但也不能通过函数组共享数据。

第6题 - ABAP 内存操作 [记忆]

说出操作 ABAP 内存的三条 ABAP 语句及其用途。

📌点击查看答案
  1. EXPORT ... TO MEMORY ID 'cluster_id' – 将数据簇写入 ABAP 内存
  2. IMPORT ... FROM MEMORY ID 'cluster_id' – 从 ABAP 内存读取数据簇
  3. FREE MEMORY ID 'cluster_id' – 显式释放 ABAP 内存中的数据簇

第7题 - SAP 内存 [应用]

需要在事务首屏的输入字段中预填值。该字段关联参数 ID ‘CAR’,且已启用”Set Parameter”。写出设置值的语句。

📌点击查看答案
1
2
SET PARAMETER ID 'CAR' FIELD lv_carrid.
CALL TRANSACTION 'BC402MCAR' AND SKIP FIRST SCREEN.

SET PARAMETER 填充 SAP 内存,事务在屏幕初始化时如果字段的”Get Parameter”已激活,就会读取该值。


第8题 - 封装组件 [应用]

何时应使用封装组件?至少说出两个必须满足的条件。

📌点击查看答案
封装组件在以下所有条件都满足时有益:
  1. 子结构/属性通常保持初始状态(优势 = 初始值共享)
  2. 子结构不太小(节省必须超过 8 字节引用开销)
  3. 结构使用频繁(实例多或程序执行频繁 – 节省与使用量成正比)

封装组件增加 8 字节的内部引用。如果子结构总是有数据或非常小,开销会超过收益。


第9题 - 共享对象架构 [分析]

比较共享对象方法与 ABAP 内存用于跨程序数据交换。各在什么时候适用?

📌点击查看答案
方面 ABAP 内存 共享对象
范围 一个外部会话 同一应用服务器上所有用户
生命周期 直到会话关闭 直到显式删除或服务器重启
数据类型 任意 ABAP 数据对象 类的实例(数据作为属性)
性能 快(同一进程) 非常快(共享内存,无数据库)
使用场景 被调用程序间的数据传递 目录、共享缓存、购物车

在单个用户的程序调用链内交换数据时使用 ABAP 内存。在多个用户需要并发访问而不读数据库时使用共享对象。


第10题 - 写入时复制 [分析]

解释内表的写入时复制语义。将一个内表赋值给另一个时会发生什么?

📌点击查看答案
当执行 itab2 = itab1 时,实际数据不会被复制。两个表共享同一个表体 – 仅表头/管理信息被复制。这节省了内存和运行时间。

实际复制仅在其中一个表被修改(写访问)时发生。此时共享体被复制,修改的表指向自己的副本。

例外:具有嵌套表行类型的表从不共享

这也适用于过程传参 – 按值传递在形式参数被修改之前不会产生复制开销。


📌模式总结(点击查看)
关键词 答案
SUBMIT AND RETURN 插入程序,保留调用者
LEAVE TO TRANSACTION 销毁栈,清除 ABAP 内存
BDCDATA 字段 PROGRAM+DYNPRO+DYNBEGIN 标识屏幕;FNAM+FVAL 填数据
ABAP 内存 EXPORT/IMPORT/FREE,每个外部会话
SAP 内存 SET/GET PARAMETER,每个用户会话
封装组件 适用条件:通常为初始、不太小、使用频繁
写入时复制 共享直到首次修改

内部数据处理练习 (12题)

#practice #arithmetic #string-processing #internal-tables #data-reference

相关概念

📌核心模式(点击查看)
关键词 答案
算术优先级 decfloat > 二进制浮点 > 压缩数 > 整数
** 运算符 即使全是 i 也强制浮点算术
正则 * vs + * = 0 或多次;+ = 1 或多次
标准表键访问 O(n) 线性
排序表键访问 O(log n) 二分
哈希表键访问 O(1) 常数
LOOP 中 ASSIGNING 无数据复制,直接修改

第1题 - 整数除法 [记忆]

DATA int TYPE i. int = 5 / 2. 的结果是什么?

📌点击查看答案
2。整数算术使用商业舍入。精确结果(2.5)舍入到最接近的偶数(对于半值),或根据操作简单截断。在 ABAP 整数算术中,5 / 2 = 2(向下舍入到整数)。

第2题 - 混合算术优先级 [记忆]

一个表达式包含类型 ifp 的变量。结果字段为 i 类型。使用哪种算术?

📌点击查看答案
二进制浮点算术。 优先级规则是:decfloat > 二进制浮点 > 压缩数 > 整数。因为有 f 类型操作数(且没有 decfloat),所以使用二进制浮点算术进行计算。结果然后转换为 i 类型。

第3题 - 大字面量类型 [应用]

DATA int TYPE i. int = 10000000000 / 3000000000 * 3. 结果是什么?为什么?

📌点击查看答案
10。 字面量 10000000000 超出了 i 类型的值范围,因此被视为 p 类型。这使得整个表达式使用压缩算术而非整数算术。使用压缩算术:10000000000 / 3000000000 = 3.333...,舍入为 3,然后 3 * 3 = 9.999...,转换为 i 时舍入为 10

第4题 - 字符串函数 [记忆]

说出字符串函数的三个类别,每类给出一个示例。

📌点击查看答案
类别 用途 示例
描述性 返回数值结果 strlen()count()find()
处理性 返回字符串 substring()to_upper()reverse()
谓词性 返回真假值 contains()matches()

第5题 - 正则表达式 [应用]

编写一个正则表达式,匹配以 “ABAP” 开头后跟恰好 3 个数字的字符串。

📌点击查看答案
ABAP\d{3}ABAP[0-9]{3}
  • ABAP 匹配字面文本
  • \d[0-9] 匹配单个数字
  • {3} 表示恰好 3 次

匹配示例:”ABAP123”、”ABAP456”


第6题 - 字符串模板 [记忆]

字符串模板的语法是什么?如何嵌入表达式?

📌点击查看答案
字符串模板使用 |...| 定界符。表达式用花括号嵌入:{expression}

示例:|Hello {lv_name}, the value is {lv_amount decimals = 2}|

格式化选项跟随在 {} 中的表达式后面,例如 {lv_date date = user}{lv_num width = 10 align = right}


第7题 - 表类型选择 [应用]

需要一个内表,满足:(a) 始终通过唯一键访问,(b) 永远不使用索引访问,(c) 单条记录查找需要最高性能。应选择哪种表类型?

📌点击查看答案
哈希表,使用 UNIQUE 键。
1
DATA: itab TYPE HASHED TABLE OF ty_line WITH UNIQUE KEY key_field.

哈希表提供 O(1) 常数时间键访问,要求唯一键,不支持索引访问 – 满足全部三个要求。


第8题 - 二分搜索 [应用]

使用 READ TABLE lt_standard WITH KEY field = 'X' BINARY SEARCH 时,搜索要正确工作必须满足什么条件?

📌点击查看答案
表必须在读取前按搜索字段升序排序(本例中的 field)。BINARY SEARCH 执行假设数据已排序的二分搜索算法。如果表未按该字段排序,结果不可预测 – 不报错但可能找到错误的行或找不到行。

第9题 - COLLECT 行为 [记忆]

COLLECT 语句如何与内表配合工作?

📌点击查看答案
COLLECT wa INTO itab 比较工作区的字符型字段来查找匹配行。如果找到匹配行,数值字段被累加。如果没有匹配,插入新行。

要求:行类型必须是平坦的(不含深层组件)。COLLECT 在使用与非数值字段匹配的排序或哈希键的表上效果最佳。


第10题 - 次键 [应用]

有一个排序表,主键为 carrid, connid。经常需要单独按 cityfrom 访问。如何优化?

📌点击查看答案
定义一个次键
1
2
3
DATA: itab TYPE SORTED TABLE OF ty_line
WITH NON-UNIQUE KEY carrid connid
WITH NON-UNIQUE SORTED KEY city_key COMPONENTS cityfrom.

然后使用:READ TABLE itab USING KEY city_key WITH KEY cityfrom = 'NYC'.

次键提供优化访问路径而不改变主键。USING KEY 附加项告诉系统使用次索引。


第11题 - 字段符号 vs 数据引用 [分析]

比较在循环中使用字段符号(<fs>)和数据引用(dref)访问内表行。各在什么时候更适合?

📌点击查看答案
方面 ASSIGNING <fs> REFERENCE INTO dref
访问 直接,像别名 间接通过 ->*dref->field
生命周期 仅在当前作用域有效 可以存储供后续使用
修改 直接修改 通过 ->* 或另一个 ASSIGN 访问
传递 不能传递字段符号本身 可以将引用传递给其他方法
空状态 IS ASSIGNED 检查 IS BOUND / IS INITIAL 检查

简单循环内修改时优先使用字段符号(更易读,开销更小)。需要存储行引用供后续使用或传递给其他方法时优先使用数据引用


第12题 - DELETE ADJACENT DUPLICATES [应用]

有一个按 carrid 排序的航班预订内表。运行 DELETE ADJACENT DUPLICATES FROM itab COMPARING carrid 后,所有重复的航空公司都会被删除吗?如果表未排序呢?

📌点击查看答案
如果按 carrid 排序:是的,所有重复项都被删除,因为相同的值是相邻的。

如果未排序:不会,仅删除恰好在相邻位置的重复项。不相邻的重复项会保留。DELETE ADJACENT DUPLICATES 仅检查连续的行 – 它不执行全局重复检查。

最佳实践:始终先排序,或使用 COMPARING ALL FIELDS / 指定字段列表。


📌模式总结(点击查看)
关键词 答案
整数除法 商业舍入,截断为整数
混合优先级 decfloat > f > p > i
** 运算符 强制浮点算术
正则 {n,m} n 到 m 次重复
哈希表 O(1) 键访问,无索引,仅唯一键
BINARY SEARCH 需要先按搜索键排序
COLLECT 累加数值,按非数值匹配
次键 WITH ... KEY name COMPONENTS ...
字段符号 vs 引用 FS:直接/作用域局部;Ref:可存储/可传递
0%