Mark的技术博客

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

吴让宇(Mark)

吴让宇(Mark)

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

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

SAP Fiori ABAP CDS OData Dify Docker AI LLM

最新文章

内表类型 (★★★)

#internal-tables

三种表类型比较

属性 标准表(STANDARD) 排序表(SORTED) 哈希表(HASHED)
键访问 线性搜索 O(n) 二分搜索 O(log n) 哈希搜索 O(1)
索引访问
排序顺序 未排序(按需排序) 始终按键排序 不适用(无顺序)
键唯一性 非唯一 唯一或非唯一 仅唯一
填充策略 APPEND(末尾追加快) INSERT(维护排序) INSERT(哈希桶)
最佳用途 顺序处理、排序、临时存储 部分顺序读取、有序访问 按键的单条记录查找
📌哈希表不支持索引访问。不能在哈希表上使用 READ TABLE ... INDEXLOOP AT ... FROM/TOINSERT ... INDEXDELETE ... INDEX

表键类别

标准键

1
DATA: lt_itab TYPE STANDARD TABLE OF struc WITH DEFAULT KEY.
  • 组件:行类型中所有平坦的非数值字段
  • 如果行类型没有合适字段:整行为键
  • 空标准键:没有任何键字段

主键

键类型 适用范围 语法
唯一 排序表、哈希表 WITH UNIQUE KEY comp1 comp2
非唯一 标准表、排序表 WITH NON-UNIQUE KEY comp1 comp2
1
2
3
4
5
6
7
" 带唯一主键的排序表
DATA: lt_sorted TYPE SORTED TABLE OF struc
WITH UNIQUE KEY id.

" 带非唯一键的标准表
DATA: lt_std TYPE STANDARD TABLE OF struc
WITH NON-UNIQUE KEY name.

次键(详见 特殊表技术

  • NW 7.0 EhP2 起可用
  • 独立于主键定义
  • 可以是 SORTED(唯一/非唯一)或 HASHED(唯一)

键访问方法

READ TABLE WITH TABLE KEY

1
2
3
4
5
6
7
8
" 标准表:线性搜索(大表较慢)
READ TABLE lt_standard INTO ls_wa WITH TABLE KEY id = '123'.

" 排序表:二分搜索(快)
READ TABLE lt_sorted INTO ls_wa WITH TABLE KEY id = '123'.

" 哈希表:哈希搜索(最快)
READ TABLE lt_hashed INTO ls_wa WITH TABLE KEY id = '123'.
  • WITH TABLE KEY:使用表的主键(或次键)
  • 根据表类型自动优化

READ TABLE WITH KEY

1
2
" 通用键搜索 - 始终线性搜索,即使在排序/哈希表上!
READ TABLE lt_sorted INTO ls_wa WITH KEY name = 'Smith'.
⚠️WITH KEY(不带 TABLE)始终执行线性搜索,即使在排序或哈希表上也是如此。只有 WITH TABLE KEY 使用优化的访问方法。

索引访问方法

1
2
3
4
5
6
7
8
9
10
+-------------------+-----------------------------+-------------------+
| 操作 | 语句 | 表类型 |
+-------------------+-----------------------------+-------------------+
| 按索引读取 | READ TABLE ... INDEX n | 标准、排序 |
| 范围循环 | LOOP AT ... FROM n TO m | 标准、排序 |
| 按索引修改 | MODIFY ... INDEX n | 标准、排序 |
| 按索引删除 | DELETE ... INDEX n | 标准、排序 |
| 按索引插入 | INSERT ... INDEX n | 仅标准表 |
| 末尾追加 | APPEND ... | 仅标准表 |
+-------------------+-----------------------------+-------------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
" 索引读取
READ TABLE lt_sorted INTO ls_wa INDEX 5.

" 范围循环
LOOP AT lt_sorted INTO ls_wa FROM 10 TO 20.
" 处理第 10 到 20 行
ENDLOOP.

" 按索引修改
MODIFY lt_standard FROM ls_wa INDEX 3.

" 按索引删除
DELETE lt_standard INDEX 7.

选择正确的表类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
                  主要访问模式是什么?
|
+---------------+---------------+
| |
按键访问 索引/顺序访问
| |
+------+------+ +--------+--------+
| | | |
大部分是 需要排序 顺序处理 填充后需要
单条查找 顺序用于 (用 APPEND 排序吗?
范围读取 填充)
| | | +----+----+
HASHED SORTED STANDARD 是:STANDARD
(唯一键) (唯一或 (用 APPEND + SORT
非唯一) 填充) 排序
否:SORTED(自动
维护顺序)
场景 推荐类型 原因
按键查找单条记录 哈希表 O(1) 访问
按键顺序处理记录 排序表 始终排序,二分搜索
收集后排序 标准表 APPEND 快,按需 SORT
部分范围读取(键边界) 排序表 二分搜索 + 顺序
临时无序存储 标准表 最简单,最灵活
按键计数/聚合 哈希表 + COLLECT 唯一键确保正确聚合

表类型声明示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
" 标准表
TYPES: ty_standard TYPE STANDARD TABLE OF struc
WITH NON-UNIQUE KEY id.

" 排序表
TYPES: ty_sorted TYPE SORTED TABLE OF struc
WITH UNIQUE KEY id.

" 哈希表
TYPES: ty_hashed TYPE HASHED TABLE OF struc
WITH UNIQUE KEY id.

" 内联声明(默认标准表)
DATA(lt_inline) = VALUE ty_standard( ).

键访问性能汇总

1
2
3
4
5
表类型           WITH TABLE KEY    WITH KEY     INDEX
----------- -------------- --------- -----
STANDARD O(n) 线性 O(n) 线性 O(1)
SORTED O(log n) 二分 O(n) 线性 O(1)
HASHED O(1) 哈希 O(n) 线性 不适用
💡按主键搜索时始终使用 WITH TABLE KEY 以获得该表类型的最佳性能。

考试/测试模式

关键词/模式 答案
“哈希表无索引” 不能在哈希表上使用 INDEX、FROM/TO 或位置访问
“WITH TABLE KEY vs WITH KEY” TABLE KEY 使用优化访问;WITH KEY 始终线性
“排序表始终有序” 记录自动按键顺序维护
“哈希表唯一键” 哈希表要求唯一键;重复会导致 CX_SY_ITAB_DUPLICATE_KEY
“标准表线性搜索” 键访问 O(n);排序后可使用 BINARY SEARCH
“单条记录查找最佳” 哈希表 (O(1))
“范围读取最佳” 排序表(二分 + 顺序)
“APPEND vs INSERT” APPEND 在末尾添加(标准表);INSERT 维护排序(排序/哈希)
“标准键组件” 行类型中所有平坦的非数值字段

相关笔记

数据引用与字段符号 (★★★)

#data-reference #internal-tables

概述

特性 数据引用 字段符号
声明 TYPE REF TO DATA FIELD-SYMBOLS <fs> TYPE ...
赋值 GET REFERENCE OF ... INTO dref ASSIGN ... TO <fs>
访问 dref->*(解引用) <fs>(直接使用)
检查有效性 IS BOUND IS ASSIGNED
取消 CLEAR dref UNASSIGN <fs>
内存模型 指向数据对象的指针 数据对象的别名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
数据引用 vs 字段符号:
+-------------------------------------------------+
| |
| 数据对象 dref (TYPE REF TO DATA) |
| +-----------+ +-----------+ |
| | 值:42 | <----| 地址 | |
| +-----------+ +-----------+ |
| ^ | |
| | | dref->* |
| +-------------------+ (解引用) |
| |
| 数据对象 <fs> (FIELD-SYMBOL) |
| +-----------+ +-----------+ |
| | 值:42 | <----| 别名 | |
| +-----------+ +-----------+ |
| ^ | |
| | | <fs> |
| +-------------------+ (直接访问) |
| |
+-------------------------------------------------+

数据引用

声明和创建

1
2
3
4
5
6
7
8
9
10
" 声明
DATA: lr_data TYPE REF TO DATA.

" 获取已有数据对象的引用
DATA: lv_value TYPE i VALUE 42.
GET REFERENCE OF lv_value INTO lr_data.

" 在运行时创建匿名数据对象
CREATE DATA lr_data TYPE i.
CREATE DATA lr_data TYPE (lv_type_name). " 动态类型

解引用

1
2
3
" 通过解引用访问值
lr_data->* = 100.
WRITE: / lr_data->*.
⚠️解引用未绑定的引用(IS NOT BOUND)会导致运行时错误(CX_SY_REF_IS_INITIAL)。解引用前始终检查 IS BOUND

IS BOUND vs IS INITIAL

检查 含义
lr_data IS BOUND 引用指向一个数据对象
lr_data IS INITIAL 引用是初始的(空指针)
lr_data IS NOT BOUND 引用不指向任何对象
1
2
3
4
5
IF lr_data IS BOUND.
WRITE: / lr_data->*.
ELSE.
WRITE: / '引用未绑定'.
ENDIF.
📌IS BOUNDIS INITIAL 对于引用变量是对立的。检查有效性时使用 IS BOUND 更清晰。

字段符号

声明和赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
" 声明
FIELD-SYMBOLS: <fs> TYPE i,
<fs_any> TYPE any, " 泛型
<fs_struc> TYPE struc. " 结构化

" 赋值数据对象
ASSIGN lv_value TO <fs>.

" 带类型转换的赋值
ASSIGN lv_hex TO <fs> TYPE c. " 将字节解释为字符

" 取消赋值
UNASSIGN <fs>.

" 检查赋值
IF <fs> IS ASSIGNED.
WRITE: / <fs>.
ENDIF.
检查 含义
<fs> IS ASSIGNED 字段符号指向一个数据对象
<fs> IS NOT ASSIGNED 字段符号未赋值
💡UNASSIGN <fs> 后,字段符号不再赋值。访问未赋值的字段符号会导致运行时错误。

与内表配合使用

访问方法概述

语句 使用字段符号 使用数据引用
LOOP AT ASSIGNING <fs> REFERENCE INTO dref
READ TABLE ASSIGNING <fs> REFERENCE INTO dref
INSERT ASSIGNING <fs> REFERENCE INTO dref
MODIFY 隐式(无额外语法) 不适用

LOOP AT … ASSIGNING

1
2
3
4
" 通过字段符号直接修改 - 无数据复制
LOOP AT lt_itab ASSIGNING FIELD-SYMBOL(<fs_row>).
<fs_row>-amount = <fs_row>-amount * 2. " 直接修改表
ENDLOOP.

LOOP AT … REFERENCE INTO

1
2
3
4
" 每行的引用
LOOP AT lt_itab REFERENCE INTO DATA(lr_row).
lr_row->*-amount = lr_row->*-amount * 2. " 通过解引用修改表
ENDLOOP.

READ TABLE … ASSIGNING

1
2
3
4
5
6
7
" 读取到字段符号
READ TABLE lt_itab ASSIGNING FIELD-SYMBOL(<fs_row>)
WITH TABLE KEY id = '123'.

IF sy-subrc = 0.
<fs_row>-status = 'X'. " 直接修改
ENDIF.

READ TABLE … REFERENCE INTO

1
2
3
4
5
6
7
" 读取到引用
READ TABLE lt_itab REFERENCE INTO DATA(lr_row)
WITH TABLE KEY id = '123'.

IF sy-subrc = 0.
lr_row->*-status = 'X'. " 通过解引用修改
ENDIF.

INSERT … ASSIGNING / REFERENCE INTO

1
2
3
4
5
6
" 插入并获取新行的引用/字段符号
INSERT ls_wa INTO TABLE lt_itab ASSIGNING FIELD-SYMBOL(<fs_new>).
INSERT ls_wa INTO TABLE lt_itab REFERENCE INTO DATA(lr_new).

" 继续操作新行
<fs_new>-created_at = sy-uzeit.

使用字段符号的 MODIFY

1
2
3
4
5
" 字段符号直接修改表行 - 不需要 MODIFY 语句!
LOOP AT lt_itab ASSIGNING FIELD-SYMBOL(<fs_row>).
<fs_row>-processed = abap_true.
" 不需要 MODIFY;<fs_row> 已经指向表行
ENDLOOP.

性能优势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+----------------------------------------------------------+
| 数据复制比较 |
+----------------------------------------------------------+
| |
| INTO wa: 将行数据复制到工作区 |
| +---------+ +---------+ 必须使用 MODIFY |
| | 表 | --> | WA 副本 | 写回到表 |
| +---------+ +---------+ (双重复制) |
| |
| ASSIGNING <fs>:无复制 - 直接指针 |
| +---------+ +---------+ 通过 <fs> 的更改 |
| | 表 | <-- | <fs> | 直接写入表 |
| +---------+ +---------+ (零复制) |
| |
| REFERENCE INTO:无复制 - 引用指针 |
| +---------+ +---------+ 通过 dref->* 的更改 |
| | 表 | <-- | dref | 直接写入表 |
| +---------+ +---------+ (零复制) |
| |
+----------------------------------------------------------+
方法 数据复制 修改方式 性能
INTO wa 是(复制进 + 复制回) 需要 MODIFY 最慢
ASSIGNING <fs> 无复制 通过 <fs> 直接 最快
REFERENCE INTO dref 无复制 通过 dref->*
💡循环内表时始终使用 ASSIGNING <fs>REFERENCE INTO dref,特别是大表。修改行时避免使用 INTO wa

完整方法比较

1
2
3
4
5
6
7
8
9
操作              INTO wa        ASSIGNING <fs>    REFERENCE INTO dref
---------------- ----------- -------------- -------------------
LOOP AT itab 复制行 无复制 无复制
READ TABLE 复制行 无复制 无复制
INSERT 不适用 获取新行 <fs> 获取新行 dref
MODIFY 需要 不需要 不适用

数据访问 wa-field <fs>-field dref->*-field
检查有效性 sy-subrc IS ASSIGNED IS BOUND

考试/测试模式

关键词/模式 答案
“ASSIGNING vs INTO” ASSIGNING = 无复制,直接修改;INTO = 数据复制
“REFERENCE INTO” 到行的数据引用;通过 dref->* 访问
“IS BOUND” 引用变量指向一个数据对象
“IS ASSIGNED” 字段符号指向一个数据对象
“用 ->* 解引用” dref->* 访问被引用的数据对象
“泛型引用解引用” TYPE REF TO DATA 必须使用 ASSIGN dref->* TO <fs> 处理泛型
“使用字段符号的 MODIFY” 不需要;对 <fs> 的更改直接修改表
“性能” ASSIGNING 和 REFERENCE INTO 避免数据复制
“GET REFERENCE OF” 获取已有数据对象的引用指针
“CREATE DATA” 在运行时创建匿名数据对象
“UNASSIGN” 移除字段符号的赋值
“insert + assigning” INSERT ... INTO TABLE ... ASSIGNING <fs> 获取新行的访问

相关笔记

动态编程技术 (★★)

#dynamic-prog

概述

动态编程是指在运行时而非设计时指定类型长度偏移量名称。这使得程序能灵活、通用地适应不同的数据结构和用户输入。

技术一览

技术 机制 典型用途
动态语言元素 运行时替换令牌 通用工具
字段符号 动态数据访问 处理未知结构
RTTI / RTTC 类型检查和创建 框架、工具
程序生成 运行时创建并执行代码 动态报表

令牌替换

有两种方式使语句动态化:

令牌类型 语法 示例 关键规则
替换字面量 数据对象直接在语句中 WRITE: / var. 值替换字面量
替换标识符 数据对象放在括号中 WRITE: / (lv_field). 值替换名称;括号内不能有空格
⚠️动态标识符括号内不能有空格
CALL FUNCTION (lv_name) 是正确的。
CALL FUNCTION ( lv_name )错误的,会导致语法错误或意外行为。

动态 Open SQL

1
2
3
4
5
6
7
8
9
10
" 动态表名
SELECT * FROM (lv_tabname) INTO TABLE @lt_data.

" 动态 WHERE 条件
SELECT * FROM spfli INTO TABLE @lt_flights
WHERE (lv_where_clause).

" 两者都动态
SELECT * FROM (lv_tab) INTO TABLE @lt_result
WHERE (lv_cond).
SQL 子句 动态语法 说明
FROM (lv_tabname) 表名在变量中
WHERE (lv_condition) 整个条件作为字符串
字段列表 不能完全动态 使用 * 或静态指定列

动态函数模块调用

1
2
3
4
5
6
7
8
9
DATA: lv_fm_name TYPE funcname VALUE 'Z_MY_FUNCTION'.

CALL FUNCTION (lv_fm_name)
EXPORTING
iv_param = lv_value
IMPORTING
ev_result = lv_result
EXCEPTIONS
others = 1.
💡何时使用
当函数模块名称在运行时确定时(例如从定制表或用户选择中获取),使用动态 FM 调用。

动态方法调用

1
2
3
4
5
6
DATA: pt TYPE abap_parmbind_tab,
et TYPE abap_excpbind_tab.

CALL METHOD (lv_method_name)
PARAMETER-TABLE pt
EXCEPTION-TABLE et.
附加项 用途
PARAMETER-TABLE 通过内表动态传递参数
EXCEPTION-TABLE 通过内表动态处理异常

参数表行包含 name(参数名)和 value(实际参数的引用)。异常表行包含 name(异常名)和 value(要分配的 sy-subrc 值)。

使用动态数据的 CALL TRANSACTION

1
2
3
4
5
6
7
DATA: lt_bdcdata TYPE TABLE OF bdcdata.

" 动态填充 BDC 表...
CALL TRANSACTION 'MM01' USING lt_bdcdata
MODE 'N'
UPDATE 'S'
MESSAGES INTO lt_messages.

程序生成

INSERT REPORT

1
2
" 从源代码内表生成程序
INSERT REPORT lv_prog_name FROM lt_source_code.
方面 详情
来源 string 或类似类型的内表
结果 程序被添加到仓库(临时或可传输)
使用场景 动态报表生成

GENERATE SUBROUTINE POOL

1
2
3
4
5
6
7
8
9
10
DATA: lv_prog TYPE string.

GENERATE SUBROUTINE POOL lt_source
NAME lv_prog
MESSAGE lv_msg
LINE lv_line
WORD lv_word.

" 调用生成的子程序
PERFORM (lv_form_name) IN PROGRAM (lv_prog).
方面 详情
用途 生成并执行临时子程序池
生命周期 仅在内部会话期间存在
错误处理 MESSAGELINEWORD 附加项捕获语法错误
📌程序生成 vs 动态调用
程序生成在运行时创建新的可执行代码。动态调用(FM、方法)调用的是名称在运行时解析的已有代码。生成代价更高,应谨慎使用。

比较:静态 vs 动态

方面 静态 动态
语法检查 完整的编译时检查 仅在运行时发现错误
性能 最优 名称解析有轻微开销
灵活性 固定 适应运行时条件
安全性 高(编译器捕获) 较低(可能出现运行时错误)
调试 直接 更难追踪
⚠️运行时错误风险
动态规格绕过语法检查。拼写错误的名称、错误的类型或缺失的对象会导致运行时错误(短转储),编译器无法捕获。使用前始终验证动态输入。

考试/测试模式

关键词 / 短语 答案
“动态标识符语法” 数据对象在括号中,不能有空格:(lv_name)
“动态令牌替换字面量” 数据对象直接用在语句位置
“SELECT 中动态 WHERE” WHERE (lv_condition),条件为字符串变量
“CALL FUNCTION 动态” CALL FUNCTION (lv_fm_name)
“动态方法参数传递” PARAMETER-TABLE pt,使用 abap_parmbind_tab
“INSERT REPORT 用途” 从源代码表在仓库中创建程序
“GENERATE SUBROUTINE POOL” 从源代码创建临时可执行子程序池
“动态编程的主要风险” 语法检查无法检测的运行时错误

相关笔记

程序调用 (★★★)

#program-structure #memory-mgmt

同步程序调用:概述

同步程序调用在返回控制权之前执行被调用程序。有两种模式:

模式 类比 对调用者的影响
插入(AND RETURN) 压入栈 调用者暂停,被调用程序结束后恢复
新启动(无 AND RETURN) 替换当前 调用者终止,被调用程序替换它

SUBMIT 语句

SUBMIT 变体比较

变体 行为 会话效果
SUBMIT prog AND RETURN 插入:被调用程序结束后调用者恢复 新的内部会话压入栈顶
SUBMIT prog 替换:调用者被终止 顶层内部会话被替换
SUBMIT ... VIA SELECTION-SCREEN 显示被调用程序的选择屏幕 与基本变体相同
1
2
3
4
5
" 插入 — 报表结束后调用者恢复
SUBMIT zreport AND RETURN.

" 替换 — 调用者终止,zreport 替换它
SUBMIT zreport.

使用 SUBMIT 传递数据

方法 语法 说明
单值 WITH p_param VALUE 传递值给参数
选择选项单值 WITH s_selopt EQ value 添加单条条件
选择选项范围 WITH s_selopt IN range_tab range_tab 必须为 TYPE RANGE
变式 USING VARIANT var_name 使用已保存的变式
💡选择选项的 RANGE 类型
要传递选择选项,声明一个范围表:
1
2
3
DATA: r_bukrs TYPE RANGE OF bkpf-bukrs.
r_bukrs = VALUE #( ( sign = 'I' option = 'EQ' low = '1000' ) ).
SUBMIT zreport WITH s_bukrs IN r_bukrs AND RETURN.

范围表必须包含字段:SIGN、OPTION、LOW、HIGH。

CALL TRANSACTION

语法和附加项

附加项 用途
AND SKIP FIRST SCREEN 如果必填字段已预填,则跳过初始屏幕
MODE mode 控制屏幕显示:A(全部)、E(仅错误)、N(不显示)
UPDATE upd 更新模式:A(异步)、S(同步)、L(本地更新)
USING bdc_tab 使用批输入表驱动事务
MESSAGES INTO itab 将所有消息收集到内表中
1
2
3
4
5
CALL TRANSACTION 'MM01'
USING lt_bdcdata
MODE 'N'
UPDATE 'S'
MESSAGES INTO lt_messages.

显示模式

模式 代码 描述
显示所有屏幕 A 每个屏幕都显示(默认)
仅显示错误 E 仅在出错时显示屏幕
不显示 N 后台处理,不显示任何屏幕

更新模式

模式 代码 行为
异步 A 不等待更新完成(默认)
同步 S 等待更新完成;可通过 sy-subrc 检查成功
本地 L 在当前 LUW 中更新(调试有用)
📌同步更新最安全
当需要确认事务成功时,始终使用 UPDATE 'S'。使用 UPDATE 'A' 时,sy-subrc 仅反映事务调用结果,而非数据库更新结果。

LEAVE TO TRANSACTION

1
LEAVE TO TRANSACTION 'MM01'.
方面 行为
会话效果 销毁整个内部会话栈
ABAP 内存 被清除 – 所有 EXPORT TO MEMORY 数据丢失
比较 CALL TRANSACTION(插入)不同,这是新启动
⚠️LEAVE TO TRANSACTION 清除 ABAP 内存
因为整个栈被销毁,ABAP 内存中所有数据(EXPORT/IMPORT)都会丢失。这是常见的考试陷阱。参见 [[_posts/SAP_MM/SCM500_采购流程/00-仪表盘/考试陷阱]]。

BDCDATA 结构

使用 CALL TRANSACTION ... USING bdc_tab 时,BDCDATA 表驱动屏幕字段:

字段 类型 描述
PROGRAM CHAR 40 ABAP 程序名
DYNPRO CHAR 4 屏幕编号
DYNBEGIN CHAR 1 X = 屏幕头行, = 字段行
FNAM CHAR 132 字段名(或 BDC_OKCODE 表示功能码)
FVAL CHAR 132 字段值

BDCDATA 表布局

1
2
3
4
5
6
7
8
+-------------------+--------+----------+------------------+------------------+
| PROGRAM | DYNPRO | DYNBEGIN | FNAM | FVAL |
+-------------------+--------+----------+------------------+------------------+
| SAPLMGMM | 0060 | X | | |
| | | | RMMG1-MATNR | MAT01 |
| | | | RMMG1-MBRSH | M |
| | | | BDC_OKCODE | =ENTR |
+-------------------+--------+----------+------------------+------------------+
💡BDCDATA 模式
每个屏幕以头行(DYNBEGIN = 'X')开始,包含 PROGRAM 和 DYNPRO。后续所有 DYNBEGIN = ' ' 的行设置字段值。使用 FNAM 中的 BDC_OKCODE 触发操作。

收集消息

1
2
3
4
5
6
7
8
9
10
DATA: lt_messages TYPE TABLE OF bdcmsgcoll.

CALL TRANSACTION 'MM01'
USING lt_bdcdata
MODE 'N'
MESSAGES INTO lt_messages.

LOOP AT lt_messages INTO DATA(ls_msg).
" ls_msg-MSGTYP, ls_msg-MSGID, ls_msg-MSGNR, ls_msg-MSGV1...
ENDLOOP.

会话栈图示

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
CALL TRANSACTION / SUBMIT AND RETURN(插入):
==============================================

外部会话
┌──────────────────────────────────┐
│ 内部会话栈 │
│ ┌────────────────────────────┐ │
│ │ 被调用程序(新栈顶) │ │
│ ├────────────────────────────┤ │
│ │ 调用者(已暂停) │ │
│ ├────────────────────────────┤ │
│ │ ... 更早的会话 ... │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘


LEAVE TO TRANSACTION(新启动):
=================================

外部会话
┌──────────────────────────────────┐
│ 内部会话栈 │
│ ┌────────────────────────────┐ │
│ │ 目标事务(唯一) │ │ ← 所有先前的会话被销毁
│ └────────────────────────────┘ │
└──────────────────────────────────┘


SUBMIT 不带 AND RETURN(替换):
=====================================

外部会话
┌──────────────────────────────────┐
│ 内部会话栈 │
│ ┌────────────────────────────┐ │
│ │ 被提交程序(栈顶) │ │ ← 替换了旧的栈顶会话
│ ├────────────────────────────┤ │
│ │ ... 更早的会话 ... │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘

考试/测试模式

关键词 / 模式 正确答案
SUBMIT … AND RETURN 插入 – 调用者恢复;新的内部会话压入栈
SUBMIT 不带 AND RETURN 替换 – 调用者终止;栈顶会话被替换
LEAVE TO TRANSACTION 销毁整个栈并清除 ABAP 内存
CALL TRANSACTION … MODE ‘N’ 不显示屏幕(后台)
CALL TRANSACTION … UPDATE ‘S’ 同步更新 – 等待数据库提交
BDCDATA DYNBEGIN = ‘X’ 标记屏幕头行(PROGRAM + DYNPRO)
WITH select-option IN range 范围表必须为 TYPE RANGE,包含 SIGN/OPTION/LOW/HIGH
MESSAGES INTO itab 将事务消息收集到 BDCMSGCOLL 表中
AND SKIP FIRST SCREEN 仅在必填字段已预填时跳过初始屏幕
区别:CALL TRANSACTION vs LEAVE TO TRANSACTION CALL 插入(栈增长),LEAVE 销毁(栈重置)

相关笔记

  • 运行时与内存管理 – 内部/外部会话、程序组、ABAP/SAP 内存
  • 共享对象 – 跨会话数据共享的替代方案
  • [[_posts/SAP_MM/SCM500_采购流程/00-仪表盘/考试陷阱]] – LEAVE TO TRANSACTION 清除 ABAP 内存
  • [[_posts/SAP_MM/SCM500_采购流程/00-仪表盘/快速参考]] – 程序调用汇总表

泛型与动态访问 (★★)

#dynamic-prog #data-types #data-reference

概述

泛型数据类型不能直接用于声明数据对象。它们用作形式参数类型和字段符号类型,实际类型在运行时从赋值的数据对象中解析。这是编写灵活、可复用 ABAP 代码的基础。

ABAP 泛型数据类型

泛型 覆盖的完整类型 说明
any 所有数据类型 最泛型;不保证任何操作
any table 所有表类型 泛型内表
c c, d, n, t, string 类字符型
clike c, d, n, t, string 类字符型,可用于字符串函数
csequence c, string 字符序列(用于字符串函数)
data 所有数据类型 any 相同
decfloat decfloat16, decfloat34 十进制浮点
index table 标准、排序表 支持索引访问的表
n n 数字文本
numeric i, f, p, decfloat16, decfloat34 可进行数值计算
object 所有对象引用 泛型对象引用
simple 基本类型 + 结构(平坦) 可比较和排序
sorted table 仅排序表 特定表类别
standard table 仅标准表 特定表类别
table 所有表类型 any table 相同
x x, xstring 类字节型
xsequence x, xstring 字节序列
📌不能用泛型声明数据对象
DATA: lv_var TYPE any.不允许。泛型仅对以下有效:
  • 过程的形式参数(子程序、FM、方法)
  • 字段符号
  • 类型引用(TYPE REF TO data

泛型类型参数

当形式参数具有泛型类型时,具体类型在运行时从传入的实际参数确定:

1
2
3
4
5
6
FORM process USING p_data TYPE any.
" p_data 继承实际参数的类型
ENDFORM.

PERFORM process USING lv_string. " p_data 表现为 STRING
PERFORM process USING lv_int. " p_data 表现为 I
上下文 类型解析
FORM ... USING p TYPE any 调用时从实际参数
方法参数 TYPE any 调用时从实际参数
FM 参数 TYPE any 调用时从实际参数

泛型类型字段符号

字段符号从赋值的数据对象继承其类型:

1
2
3
4
FIELD-SYMBOLS: <fs> TYPE any.

ASSIGN lv_string TO <fs>. " <fs> 现在表现为 STRING
ASSIGN lv_int TO <fs>. " <fs> 现在表现为 I
💡字段符号与泛型
泛型类型的字段符号是一个”占位符”,采用所赋值对象的类型和值。这是泛型数据访问的主要机制。

泛型数据引用

1
DATA: dref TYPE REF TO data.
属性 行为
声明 TYPE REF TO data 创建泛型数据引用
赋值 可通过 GET REFERENCE OF ... INTO dref 指向任何数据对象
解引用 不能直接使用 dref->*;必须使用 ASSIGN
⚠️泛型数据引用解引用
TYPE REF TO data 变量不能直接用 ->* 解引用。必须使用 ASSIGN dref->* TO <fs> 来访问被引用的数据。
1
2
3
4
5
6
DATA: dref TYPE REF TO data.
FIELD-SYMBOLS: <fs> TYPE any.

CREATE DATA dref TYPE string.
ASSIGN dref->* TO <fs>. " 正确:使用 ASSIGN
<fs> = 'Hello'. " 通过字段符号访问

使用字段符号的类型转换

ASSIGN … CASTING

将赋值数据对象的内存区域视为具有不同类型:

1
2
3
FIELD-SYMBOLS: <hex> TYPE x.

ASSIGN lv_string TO <hex> CASTING. " 将字符串内存解释为原始字节
CASTING 变体 语法 效果
隐式类型 ASSIGN dobj TO <fs> CASTING 使用字段符号的声明类型
显式类型 ASSIGN dobj TO <fs> CASTING TYPE typ 使用指定类型
显式小数 ASSIGN dobj TO <fs> CASTING TYPE p DECIMALS 2 带小数的压缩数
💡CASTING 不转换数据
CASTING 以不同类型重新解释相同的内存内容。它转换数据。实际类型转换请使用转换函数。

ASSIGN … CASTING TYPE(动态)

1
ASSIGN lv_data TO <fs> CASTING TYPE (lv_type_name).

类型名称在运行时动态解析。

动态 ASSIGN

结构中的动态组件访问

1
2
3
4
5
6
7
8
9
10
FIELD-SYMBOLS: <comp> TYPE any.

" 按组件索引
ASSIGN COMPONENT 3 OF STRUCTURE ls_struct TO <comp>.

" 按组件名(动态)
ASSIGN COMPONENT 'FIELD_NAME' OF STRUCTURE ls_struct TO <comp>.

" 按变量名
ASSIGN COMPONENT lv_comp_name OF STRUCTURE ls_struct TO <comp>.
变体 语法 使用场景
按索引 ASSIGN COMPONENT n OF STRUCTURE 遍历所有组件
按名称(字面量) ASSIGN COMPONENT 'NAME' OF STRUCTURE 已知组件名,静态
按名称(变量) ASSIGN COMPONENT lv_name OF STRUCTURE 组件名在运行时确定

动态属性访问

1
2
3
4
5
6
7
FIELD-SYMBOLS: <attr> TYPE any.

" 动态访问类属性
ASSIGN (lcl_my_class=>lv_attr_name) TO <attr>.

" 通过对象引用访问实例属性
ASSIGN oref->('ATTRIBUTE_NAME') TO <attr>.
📌动态名称的括号语法
括号中的类/属性名遵循与其他动态令牌相同的规则:(name),内部不能有空格

动态结构组件访问模式

1
2
3
4
5
6
7
8
9
10
11
12
" 泛型:处理任意结构的所有组件
FIELD-SYMBOLS: <comp> TYPE any.
DATA: lo_struct TYPE REF TO cl_abap_structdescr.
DATA: lt_comp TYPE cl_abap_structdescr=>component_table.

lo_struct ?= cl_abap_typedescr=>describe_by_data( ls_any_struct ).
lt_comp = lo_struct->get_components( ).

LOOP AT lt_comp INTO DATA(ls_comp).
ASSIGN COMPONENT ls_comp-name OF STRUCTURE ls_any_struct TO <comp>.
" 泛型处理 <comp> ...
ENDLOOP.

使用数据引用的类型转换

操作 语法 说明
向上转型 oref_super = oref_sub 宽化;始终安全
向下转型 oref_sub ?= oref_super 窄化;可能抛出 CX_SY_MOVE_CAST_ERROR
数据引用向上转型 dref_any = dref_typed 安全:从特定到泛型
数据引用向下转型 dref_typed ?= dref_any 有风险:必须检查兼容性
1
2
3
4
5
6
7
8
9
" 向上转型(安全)
DATA: dref_any TYPE REF TO data.
DATA: dref_str TYPE REF TO string.

CREATE DATA dref_str TYPE string.
dref_any = dref_str. " 向上转型:始终成功

" 向下转型(有风险)
dref_str ?= dref_any. " 向下转型:可能抛出异常
⚠️向下转型需要 ?= 运算符
引用的窄化赋值需要 ?=(或内联的 CAST #(...))。标准 = 仅适用于向上转型。使用 TRY ... CATCH cx_sy_move_cast_error 确保安全。

比较:字段符号 vs 数据引用

方面 字段符号 数据引用
声明 FIELD-SYMBOLS <fs> TYPE any DATA dref TYPE REF TO data
赋值 ASSIGN dobj TO <fs> GET REFERENCE OF dobj INTO dref
访问 直接:<fs> 通过解引用:ASSIGN dref->* TO <fs>
可传递给 FM 否(不是数据对象) 是(是数据对象)
可在内表中
生命周期 直到取消赋值或超出作用域 直到被垃圾回收
类型转换 ASSIGN ... CASTING 使用 ?= 向上/向下转型

考试/测试模式

关键词 / 短语 答案
“泛型不能声明数据对象” anydataany tableclikesimple
“字段符号类型在运行时确定” 从赋值的数据对象继承类型
“TYPE REF TO DATA 解引用” 必须使用 ASSIGN dref->* TO <fs>;不能直接用 ->*
“ASSIGN COMPONENT 语法” ASSIGN COMPONENT (n|name) OF STRUCTURE struct TO <fs>
“动态属性访问” ASSIGN (class=>attr) TO <fs>ASSIGN oref->('ATTR') TO <fs>
“CASTING 不转换” 以不同类型重新解释相同内存
“引用向下转型运算符” ?=(窄化);可能抛出 CX_SY_MOVE_CAST_ERROR
“泛型引用 vs 类型化引用” TYPE REF TO data 是泛型;TYPE REF TO string 是完整的

相关笔记

字符串处理与正则表达式 (★★★)

#string-processing #data-types

Unicode vs 非 Unicode

方面 Unicode 系统 非 Unicode 系统
字符编码 ISO/IEC 10646 (UTF-16) 依赖代码页
每字符字节数 2(ABAP 中固定) 1(依赖代码页)
字符集 所有 Unicode 字符 限于代码页
现代 SAP 默认 仅遗留系统
📌所有现代 SAP 系统都是 Unicode 系统。字符串处理必须考虑每个字符 2 字节。

按类型的数据对象类别

类别 类型 描述
字节型 x, xstring 原始字节;无字符语义
字符型 c, n, d, t, string 字符语义;受代码页影响
数值型 i, f, p, decfloat16, decfloat34 可隐式转换为字符型
💡日期(d)和时间(t)是字符型字段。它们支持特殊算术,但以字符串形式存储和处理。

字符串语句

语句 用途 关键附加项
FIND 搜索子串/正则 REGEXFIRST OCCURRENCEALL OCCURRENCES
REPLACE 替换子串/正则 REGEXSECTION OFFSETOF
CONCATENATE 连接字符串 SEPARATED BYRESPECTING BLANKS
SPLIT 分割字符串到表 AT
TRANSLATE 转换大小写/替换字符 TO UPPER/LOWERUSING
CONDENSE 删除多余空格 NO-GAPS
SHIFT 移动字符串内容 LEFTRIGHTCIRCULARBY n PLACES
STRLEN 获取长度 返回整数

IN BYTE MODE / IN CHARACTER MODE

  • IN CHARACTER MODE:默认;按字符处理
  • IN BYTE MODE:按字节处理;字节型(x/xstring)操作必需
1
2
FIND lv_byte IN BYTE MODE.
SHIFT lv_string BY 2 PLACES IN CHARACTER MODE.

字符串函数

描述性函数

函数 返回值 说明
strlen( arg ) 字符串长度 字符长度
xstrlen( arg ) xstring 长度 字节长度
numofchar( arg ) 字符数 忽略尾部空格
find( val, sub ) 子串偏移量 未找到返回 -1
find( val, regex ) 正则匹配偏移量 默认区分大小写
count( val, sub ) 计数出现次数 子串或正则
contains( val, sub ) 布尔值 找到子串则为真
matches( val, regex ) 布尔值 正则匹配则为真

处理函数

函数 返回值 说明
substring( val, off, len ) 子串 偏移量和长度
to_upper( val ) 大写
to_lower( val ) 小写
reverse( val ) 反转字符串
repeat( val, n ) 重复字符串
shift_left( val, n ) 左移
shift_right( val, n ) 右移
concat_lines_of( itab, sep ) 连接字符串 从内表

谓词函数

函数 返回值 用法
contains( val, sub ) 布尔值 IF contains( lv_text, abc ).
contains( val, regex ) 布尔值 正则变体
matches( val, regex ) 布尔值 完全匹配检查
💡字符串函数可以链式调用:to_upper( substring( val = lv_text off = 0 len = 5 ) )

字符串表达式和模板

字符串模板

1
DATA(lv_result) = |Hello { lv_name }, today is { sy-datum DATE = USER }|.

语法:|literal {expression option} literal|

  • 嵌入表达式用 { } 包围
  • { } 外为字面文本
  • 反斜杠 \ 用于转义:\{\}\\

字符串连接运算符

1
DATA(lv_full) = lv_first && ` ` && lv_last.
  • && 连接类字符串操作数
  • 两个操作数都转换为 string

嵌入表达式的格式化选项

选项 效果
WIDTH = n 整数 最小输出宽度
ALIGN = LEFTRIGHTCENTER 宽度内对齐
PAD = 字符 填充字符(默认:空格)
CASE = RAWUPPERLOWER 大小写转换
SIGN = LEFTLEFTPLUSLEFTSPACERIGHTRIGHTPLUSRIGHTSPACE 符号显示
DECIMALS = n 整数 小数位数
DATE = RAWISOUSERENVIRONMENT 日期格式
TIME = RAWISOUSERENVIRONMENT 时间格式
CURRENCY = 货币代码 依赖货币的格式化
COUNTRY = 国家代码 依赖国家的格式化
ALPHA = INOUT 转换出口 ALPHA
NUMBER = RAWUSERENVIRONMENT 数字格式
1
2
3
4
5
" 示例
|Value: { lv_num ALIGN = RIGHT WIDTH = 10 PAD = '0' }|
|Date: { sy-datum DATE = ISO }| " 2026-05-10
|Name: { lv_name CASE = UPPER }|
|Amount: { lv_price DECIMALS = 2 CURRENCY = 'EUR' }|

正则表达式

单字符模式

模式 匹配 示例
. 任意单个字符 a.c 匹配 abcaXc
\d 任意数字(0-9) \d\d 匹配 42
\w 任意单词字符(字母数字 + _ \w+ 匹配 hello_1
\s 任意空白字符
[A-Z] 字符类:A 到 Z
[0-9] 字符类:数字
[abc] 字符类:a、b 或 c
[^abc] 否定类:非 a、b、c

字符串模式(量词和分组)

模式 含义 贪婪?
{n} 恰好 n 次 -
{n,m} n 到 m 次 是(贪婪)
{n,m}? n 到 m 次 否(懒惰)
? 零次或一次
* 零次或多次
+ 一次或多次
` ` 选择(或)
( ) 捕获组
(?: ) 非捕获组
\1, \2 反向引用

最左最长规则

  • 正则引擎在字符串中找到最左的匹配
  • 在同一位置的多个匹配中,选择最长的(贪婪)
  • 懒惰量词(?*?+?)优先选择最短匹配
1
2
3
4
" 模式:a.*b  匹配 "aXbYb"
" 贪婪匹配:aXbYb (从最左 'a' 开始的最长匹配)
" 模式:a.*?b 匹配 "aXbYb"
" 懒惰匹配:aXb (从最左 'a' 开始的最短匹配)

在 ABAP 语句中使用正则

1
2
3
4
5
6
7
8
9
10
11
12
13
" FIND 使用正则
FIND REGEX '\d{4}-\d{2}-\d{2}' IN lv_text.
FIND ALL OCCURRENCES OF REGEX '[A-Z]+' IN lv_text RESULTS lt_results.

" REPLACE 使用正则
REPLACE ALL OCCURRENCES OF REGEX '\s+' IN lv_text WITH ` `.

" 字符串函数使用正则
IF matches( val = lv_email regex = '[\w.]+@[\w]+\.[\w]+' ).
" 有效的邮箱模式
ENDIF.

DATA(lv_count) = count( val = lv_text regex = '\d' ).

日期和时间作为字符串

类型 长度 格式 算术
d(日期) 8 字符 YYYYMMDD 支持 + n / - n(天),减法 = 相隔天数
t(时间) 6 字符 HHMMSS 支持 + n / - n(秒)
1
2
3
4
5
DATA: lv_date TYPE d VALUE '20260510',
lv_tomorrow TYPE d.

lv_tomorrow = lv_date + 1. " 20260511
DATA(lv_days) = lv_tomorrow - lv_date. " 1
💡日期和时间字段以字符串形式存储但支持隐式算术。这是 ABAP 的特殊功能,不是通用的字符串算术。

考试/测试模式

关键词/模式 答案
“Unicode 每字符字节数” Unicode 系统中每字符 2 字节
“字节型 vs 字符型” x/xstring = 字节;c/n/d/t/string = 字符
| \{ \} 模板” 带嵌入表达式的字符串模板
&& 运算符” 表达式中的字符串连接
“IN BYTE MODE” x/xstring 类型操作必需
find() vs contains() find 返回偏移量;contains 返回布尔值
matches() 函数” 完全正则匹配;返回布尔值
“最左最长” 正则匹配规则:找最左,然后最长
(?: ) 非捕获组” 不创建反向引用的分组
“DATE = ISO” 在字符串模板中将日期格式化为 YYYY-MM-DD
CONDENSE NO-GAPS 删除字符串中所有空格
“字符串模板转义” 使用 \{\} 表示字面大括号
“CURRENCY 格式化” 模板中依赖货币的小数位数

相关笔记

内表特殊技术 (★★★)

#internal-tables

二分搜索(BINARY SEARCH)

  • 要求读取前数据按读取键排序
  • 在标准表上:必须先 SORT,再使用 BINARY SEARCH 读取
  • 在排序表上:自动(WITH TABLE KEY 内置二分搜索)
  • 如果表未按读取键排序,结果不可预测(不报错)
1
2
3
4
5
6
" 标准表:必须先排序
SORT lt_standard BY name.
READ TABLE lt_standard INTO ls_wa WITH KEY name = 'Smith' BINARY SEARCH.

" 排序表:自动
READ TABLE lt_sorted INTO ls_wa WITH TABLE KEY name = 'Smith'.
⚠️在未排序的表上使用 BINARY SEARCH 不会报错。它只会返回不正确或不可预测的结果。务必确保排序顺序与读取键匹配。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
标准表使用 BINARY SEARCH:
+--------------------+
| 1. SORT 按读取键 | <-- 二分搜索前必需
+--------------------+
|
+--------------------+
| 2. READ TABLE ... | <-- BINARY SEARCH 附加项
| WITH KEY ... |
| BINARY SEARCH |
+--------------------+

排序表:
+--------------------+
| READ TABLE ... | <-- 二分搜索自动执行
| WITH TABLE KEY ... | (不需要 BINARY SEARCH)
+--------------------+

DELETE ADJACENT DUPLICATES(删除相邻重复项)

  • 仅删除相邻的重复行
  • 必须先排序(按比较字段)
  • COMPARING 附加项指定比较哪些字段
1
2
3
4
5
6
7
8
" 先排序!
SORT lt_data BY id.

" 按指定字段比较删除相邻重复项
DELETE ADJACENT DUPLICATES FROM lt_data COMPARING id.

" 比较所有字段删除相邻重复项(默认)
DELETE ADJACENT DUPLICATES FROM lt_data.
附加项 行为
(无 COMPARING) 比较字符型和数值型的所有字段
COMPARING f1 f2 ... 仅比较指定字段
COMPARING ALL FIELDS 显式比较所有字段
📌DELETE ADJACENT DUPLICATES 仅删除相邻的重复项。如果表未先排序,不相邻的重复项会保留。

COLLECT(收集)

  • 将数值字段添加到匹配键的已有行
  • 如果没有匹配行,插入新行
  • 键比较基于所有非数值字段
  • 仅适用于平坦行类型(不含深层结构)
1
2
3
4
5
6
7
8
9
10
11
12
13
DATA: BEGIN OF ls_totals,
category TYPE string,
amount TYPE i,
END OF ls_totals.
DATA: lt_totals LIKE TABLE OF ls_totals.

" 首次出现:插入新行
ls_totals-category = 'A'. ls_totals-amount = 10.
COLLECT ls_totals INTO lt_totals.

" 找到匹配键:累加金额(10 + 20 = 30)
ls_totals-category = 'A'. ls_totals-amount = 20.
COLLECT ls_totals INTO lt_totals.
1
2
3
4
5
6
7
COLLECT 处理过程:
+--------------------------------------------+
| 1. 提取非数值字段作为键 |
| 2. 搜索具有相同键的已有行 |
| +--> 找到:将数值字段加到该行 |
| +--> 未找到:插入新行 |
+--------------------------------------------+
⚠️COLLECT 仅适用于平坦行类型。深层结构(包含内表、字符串、引用)会导致运行时错误。
特性 行为
键字段 所有非数值字段
数值字段 求和(累加)
行类型 仅平坦
表类型 任意(但带唯一键的哈希表最理想)

次键(NW 7.0 EhP2)

用途

  • 在主键之外为表定义额外的键
  • 通过不同字段组合实现优化访问
  • 避免对不同访问模式反复排序标准表

次键类型

类型 语法 唯一性 访问
NON-UNIQUE SORTED WITH NON-UNIQUE SORTED KEY 非唯一 二分搜索 O(log n)
UNIQUE HASHED WITH UNIQUE HASHED KEY 唯一 哈希搜索 O(1)

语法

1
2
3
4
5
6
7
8
9
10
TYPES: BEGIN OF ty_struc,
id TYPE i,
name TYPE string,
dept TYPE string,
END OF ty_struc.

TYPES: ty_employees TYPE STANDARD TABLE OF ty_struc
WITH NON-UNIQUE KEY id
WITH NON-UNIQUE SORTED KEY sec_name COMPONENTS name
WITH UNIQUE HASHED KEY sec_dept COMPONENTS dept.

使用

1
2
3
4
5
6
7
8
9
10
DATA: lt_emp TYPE ty_employees.

" 使用次键读取
READ TABLE lt_emp INTO ls_wa USING KEY sec_name
WITH KEY name = 'Smith'.

" 使用次键循环
LOOP AT lt_emp INTO ls_wa USING KEY sec_name.
" 按 name 顺序处理
ENDLOOP.

何时使用次键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+------------------------------------------------------+
| 何时需要次键? |
+------------------------------------------------------+
| |
| 1. 同一表需要通过不同键字段访问 |
| (例如,按 ID 和按 name) |
| |
| 2. 标准表需要按非主键字段排序访问 |
| |
| 3. 避免每次不同读取模式前显式 SORT |
| |
| 不应使用的情况: |
| - 仅有一种访问模式 |
| - 表很小(< 100 行) |
| - 填充比率远高于读取比率 |
+------------------------------------------------------+
💡次键有代价:填充/修改表时,所有次键索引必须更新。仅在读取优化收益超过填充代价时使用。

INSERT INTO TABLE vs APPEND

语句 行为 表类型兼容性
APPEND wa TO itab 在末尾添加行 仅标准表
INSERT wa INTO TABLE itab 根据表类型插入 所有表类型
INSERT wa INTO itab INDEX n 在位置 n 插入 仅标准表
1
2
3
4
5
6
7
" APPEND:总是在末尾(标准表)
APPEND ls_wa TO lt_standard.

" INSERT INTO TABLE:根据类型行为不同
INSERT ls_wa INTO TABLE lt_standard. " 在末尾追加
INSERT ls_wa INTO TABLE lt_sorted. " 插入到正确排序位置
INSERT ls_wa INTO TABLE lt_hashed. " 插入到哈希桶
💡使用 INSERT INTO TABLE 进行泛型编程。它能正确适用于所有表类型。

表表达式(新语法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
" 使用表表达式读取行
DATA(ls_row) = lt_itab[ id = '123' ].

" 使用次键
DATA(ls_row2) = lt_itab[ USING KEY sec_name name = 'Smith' ].

" 安全访问使用 OPTIONAL(防止 CX_SY_ITAB_LINE_NOT_FOUND)
DATA(ls_safe) = VALUE #( lt_itab[ id = '999' ] OPTIONAL ).

" 检查存在性
IF line_exists( lt_itab[ id = '123' ] ).
" 行存在
ENDIF.

" 获取匹配行的索引
DATA(lv_idx) = line_index( lt_itab[ name = 'Smith' ] ).
表达式/函数 返回值 未找到时异常?
itab[ key = val ] 行(副本) 是:CX_SY_ITAB_LINE_NOT_FOUND
... OPTIONAL 默认值 否(返回初始值)
line_exists( ... ) 布尔值
line_index( ... ) 索引或 0
⚠️表表达式 itab[ ... ] 在没有行匹配时抛出 CX_SY_ITAB_LINE_NOT_FOUND。使用 OPTIONALline_exists() 进行安全访问。

考试/测试模式

关键词/模式 答案
“BINARY SEARCH 未排序表” 返回不可预测的结果;不报错
“DELETE ADJACENT DUPLICATES” 仅删除相邻重复项;需先排序
“COMPARING 附加项” 指定重复比较的字段
“COLLECT 仅限平坦类型” 深层结构导致运行时错误
“COLLECT 键字段” 所有非数值字段组成键
“次键类型” NON-UNIQUE SORTED 或 UNIQUE HASHED
“USING KEY” 指定用于访问的次键
“INSERT INTO TABLE” 泛型插入;适用于所有表类型
“APPEND” 仅标准表;在末尾添加
“表表达式异常” 未找到行时抛出 CX_SY_ITAB_LINE_NOT_FOUND
“line_exists()” 返回布尔值;无异常
“何时使用次键” 同一表有多种访问模式

相关笔记

动态编程练习 (10题)

#practice #dynamic-prog #rtti #rttc #data-reference

相关概念

📌核心模式(点击查看)
关键词 答案
动态字面量令牌 数据对象直接在语句中
动态标识符令牌 数据对象在括号中,不能有空格
泛型 REF TO DATA 必须使用 ASSIGN 解引用
RTTI 根类 CL_ABAP_TYPEDESCR
RTTC 限制 不能动态创建类/接口

第1题 - 动态令牌 [记忆]

用动态令牌替换字面量和替换标识符的语法区别是什么?

📌点击查看答案
  • 替换字面量:数据对象直接出现在语句中。示例:WRITE lv_text.,其中 lv_text 包含文本。
  • 替换标识符:数据对象必须在不带空格的括号中。示例:CALL FUNCTION (lv_fm_name).,其中 lv_fm_name 包含函数模块名称。

第2题 - 动态 SQL [应用]

编写一个动态 SELECT 语句,表名和 WHERE 条件都在运行时确定。

📌点击查看答案
1
2
3
4
DATA: lv_table TYPE string VALUE 'SFLIGHT',
lv_where TYPE string VALUE `carrid = 'LH'`.

SELECT * FROM (lv_table) INTO TABLE lt_data WHERE (lv_where).

FROM 子句和 WHERE 子句都使用括号中的动态令牌。


第3题 - 动态方法调用 [记忆]

如何动态地向方法调用传递参数?

📌点击查看答案
使用 PARAMETER-TABLEEXCEPTION-TABLE 附加项:
1
2
3
CALL METHOD (lv_method) OF lo_obj
PARAMETER-TABLE lt_params
EXCEPTION-TABLE lt_exceptions.

lt_params 类型为 ABAP_PARMBIND_TAB,包含名称-值对。lt_exceptions 类型为 ABAP_EXCPBIND_TAB。


第4题 - 泛型数据引用 [应用]

使用 TYPE REF TO DATA 创建数据引用。为什么不能直接用 ->* 访问数据?

📌点击查看答案
因为 TYPE REF TO DATA泛型类型 – 编译器在编译时不知道被引用数据对象的结构。必须使用 ASSIGN 解引用:
1
2
3
4
5
DATA: dref TYPE REF TO data.
FIELD-SYMBOLS: <fs> TYPE any.
CREATE DATA dref TYPE i.
ASSIGN dref->* TO <fs>.
<fs> = 42.

第5题 - ASSIGN CASTING [记忆]

ASSIGN ... CASTING 的作用是什么?何时使用?

📌点击查看答案
ASSIGN ... CASTING 让你将数据对象的内存区域视为具有不同类型。字段符号的类型被强加到赋值的数据上而不进行转换。

变体:

  • ASSIGN dref->* TO <fs> CASTING. – 使用字段符号的类型
  • ASSIGN dref->* TO <fs> CASTING TYPE ty_my_type. – 使用显式类型

使用场景:处理需要强加特定结构类型的泛型数据引用时。


第6题 - RTTI 类层次结构 [记忆]

说出用于数据类型描述的主要 RTTI 类及其各自描述的内容。

📌点击查看答案
描述
CL_ABAP_TYPEDESCR 根类(所有类的基类)
CL_ABAP_DATADESCR 所有数据类型
CL_ABAP_ELEMDESCR 基本类型(i, f, c, string 等)
CL_ABAP_STRUCTDESCR 结构类型
CL_ABAP_TABLEDESCR 内表类型
CL_ABAP_REFDESCR 引用类型
CL_ABAP_CLASSDESCR 类类型
CL_ABAP_INTFDESCR 接口类型

第7题 - RTTI describe_by 方法 [应用]

编写代码获取名为 ‘SPFLI’ 的结构的类型描述并确定其 KIND。

📌点击查看答案
1
2
3
4
5
6
7
8
9
10
DATA: lo_type TYPE REF TO cl_abap_typedescr.

cl_abap_typedescr=>describe_by_name(
EXPORTING p_name = 'SPFLI'
RECEIVING p_descr_ref = lo_type
EXCEPTIONS type_not_found = 1 ).

IF lo_type->kind = cl_abap_typedescr=>kind_struct.
" 是结构
ENDIF.

KIND 值:kind_elemkind_structkind_tablekind_refkind_classkind_intf


第8题 - 运行时 CREATE DATA [应用]

编写代码动态创建行类型与数据库表 SPFLI 匹配的内表。

📌点击查看答案
1
2
3
4
5
DATA: gr_data TYPE REF TO data.
FIELD-SYMBOLS: <fs_table> TYPE ANY TABLE.

CREATE DATA gr_data TYPE TABLE OF spfli.
ASSIGN gr_data->* TO <fs_table>.

或完全动态:

1
2
3
DATA: lv_tabname TYPE string VALUE 'SPFLI'.
CREATE DATA gr_data TYPE TABLE OF (lv_tabname).
ASSIGN gr_data->* TO <fs_table>.

第9题 - RTTC 表类型创建 [分析]

解释使用 RTTC 动态创建表类型然后用于创建数据对象的步骤。

📌点击查看答案
步骤:
  1. 获取行类型描述符:lo_line = cl_abap_typedescr=>describe_by_name('SPFLI').
  2. 向下转型为数据描述符:lo_data ?= lo_line.
  3. 创建表类型:lo_table = cl_abap_tabledescr=>create( p_line_type = lo_data ).
  4. 使用动态类型创建数据对象:CREATE DATA dref TYPE HANDLE lo_table.
  5. 解引用:ASSIGN dref->* TO <fs_table>.

create() 的可选参数:p_table_kind(standard/sorted/hashed)、p_uniquep_keyp_key_kind


第10题 - RTTC 限制 [分析]

RTTC 有哪些限制?至少说出两件不能做的事情。

📌点击查看答案
  1. 不能生成类或接口 – RTTC 仅限于数据类型
  2. 不能创建不作为构建块已存在的类型(例如,组件需要已有的 RTTI 描述符)
  3. 动态创建的类型是匿名的 – 它们在类型系统中没有名称,只能通过 HANDLE 引用
  4. 使用 HANDLE 的 CREATE DATA 创建的是匿名数据对象,没有名称 – 只能通过引用访问

📌模式总结(点击查看)
关键词 答案
字面量令牌 数据对象直接使用
标识符令牌 括号,不能有空格
泛型 REF TO DATA 必须 ASSIGN 解引用
CASTING 强加类型但不转换
RTTI 根类 CL_ABAP_TYPEDESCR
describe_by_name 通过名称字符串获取类型
KIND 值 elem, struct, table, ref, class, intf
RTTC 创建表 describe_by_name → create → CREATE DATA HANDLE

运行时类型创建(RTTC) (★★★)

#dynamic-prog #rttc #rtti #data-types

概述

运行时类型创建(RTTC)增强了 RTTI,增加了在运行时生成新数据类型的能力(自 SAP Web AS 6.40 起可用)。这意味着可以构建 DDIC 或程序源代码中不存在的类型,然后基于这些动态生成的类型创建数据对象。

📌RTTC 仅创建数据类型
RTTC 可以生成基本类型、结构类型、表类型和引用类型。它不能动态生成类或接口。

CREATE OBJECT 使用动态 TYPE

创建对象时类名在运行时确定:

1
2
3
4
5
6
7
8
9
DATA: oref TYPE REF TO object.

" 动态类名
CREATE OBJECT oref TYPE (lv_classname).

" 使用参数表
DATA: pt TYPE abap_parmbind_tab.
CREATE OBJECT oref TYPE (lv_classname)
PARAMETER-TABLE pt.
变体 语法 使用场景
静态类型 CREATE OBJECT oref TYPE lcl_my_class 编译时已知
动态类型 CREATE OBJECT oref TYPE (lv_name) 类名来自变量
带参数表 CREATE OBJECT oref TYPE (lv_name) PARAMETER-TABLE pt 动态构造函数参数
💡对象引用类型
对于动态实例化,将引用声明为 TYPE REF TO object(泛型),之后如需可向下转型(?=)到具体类型。

CREATE DATA:动态数据对象创建

CREATE DATA 为通过数据引用访问的匿名数据对象分配内存。

变体

变体 语法 类型来源
隐式 CREATE DATA dref. 从引用变量的静态类型
显式静态 CREATE DATA dref TYPE i. 内置、命名或 LIKE 类型
显式表 CREATE DATA dref TYPE TABLE OF typ. 指定类型的表
动态名称 CREATE DATA dref TYPE (lv_typename). 类型名在变量中
RTTC 句柄 CREATE DATA dref TYPE HANDLE lo_rtti. 从 RTTI/RTTC 描述符对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DATA: dref TYPE REF TO data.

" 隐式(dref 必须有类型,如 TYPE REF TO string)
CREATE DATA dref. " 创建 STRING 类型的对象

" 显式静态
CREATE DATA dref TYPE string. " 创建 STRING

" 显式命名类型
CREATE DATA dref TYPE zmy_structure. " 创建 DDIC 结构

" 动态类型名称
DATA(lv_type) = 'STRING'.
CREATE DATA dref TYPE (lv_type). " 运行时解析类型

" 表类型
CREATE DATA dref TYPE STANDARD TABLE OF string WITH EMPTY KEY.

" RTTC 句柄(最灵活)
DATA(lo_elem) = cl_abap_elemdescr=>get_string( ).
CREATE DATA dref TYPE HANDLE lo_elem. " 从描述符创建 STRING

访问匿名数据对象

1
2
3
4
5
FIELD-SYMBOLS: <fs> TYPE any.

CREATE DATA dref TYPE string.
ASSIGN dref->* TO <fs>.
<fs> = 'Dynamic content'.
⚠️始终使用 ASSIGN 访问
CREATE DATA 后,匿名数据对象只能通过数据引用访问。使用 ASSIGN dref->* TO <fs> 来操作内容。

RTTC 类型创建方法

CL_ABAP_ELEMDESCR – 基本类型

方法 创建类型 参数
GET_I() i
GET_F() f
GET_C( len ) c 长度
GET_STRING() string
GET_X( len ) x 长度
GET_XSTRING() xstring
GET_P( len decimals ) p 长度、小数位
GET_N( len ) n 长度
GET_D() d
GET_T() t
1
2
3
" 创建带 10 位数字和 2 位小数的压缩类型
DATA(lo_p) = cl_abap_elemdescr=>get_p( p_length = 5 p_decimals = 2 ).
CREATE DATA dref TYPE HANDLE lo_p.

CL_ABAP_STRUCTDESCR – 结构类型

从组件列表创建结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DATA: lt_comp TYPE cl_abap_structdescr=>component_table.
DATA: lo_struct TYPE REF TO cl_abap_structdescr.

" 构建组件表
APPEND VALUE #( name = 'ID' type = cl_abap_elemdescr=>get_i( ) ) TO lt_comp.
APPEND VALUE #( name = 'NAME' type = cl_abap_elemdescr=>get_string( ) ) TO lt_comp.
APPEND VALUE #( name = 'AMT' type = cl_abap_elemdescr=>get_p( p_length = 8 p_decimals = 2 ) ) TO lt_comp.

" 创建结构描述符
lo_struct = cl_abap_structdescr=>create( p_components = lt_comp ).

" 使用动态结构类型创建数据对象
CREATE DATA dref TYPE HANDLE lo_struct.
ASSIGN dref->* TO FIELD-SYMBOL(<fs>).

" 访问组件
ASSIGN COMPONENT 'ID' OF STRUCTURE <fs> TO FIELD-SYMBOL(<id>).
<id> = 42.
参数 类型 含义
p_components component_table 名称 + 类型描述符对的列表
p_strict abap_bool 严格模式(默认 = abap_true)

CL_ABAP_TABLEDESCR – 表类型

从行类型和键定义创建表类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DATA: lo_table TYPE REF TO cl_abap_tabledescr.
DATA: lo_line TYPE REF TO cl_abap_datadescr.

" 获取行类型(从已有结构或 RTTC 创建的类型)
lo_line = cl_abap_typedescr=>describe_by_name( 'SPFLI' ).

" 创建标准表类型
lo_table = cl_abap_tabledescr=>create(
p_line_type = lo_line
p_table_kind = cl_abap_tabledescr=>tablekind_standard
p_unique = abap_false
).

" 创建数据对象
CREATE DATA dref TYPE HANDLE lo_table.
ASSIGN dref->* TO FIELD-SYMBOL(<ft>).
参数 类型 含义
p_line_type REF TO cl_abap_datadescr 行类型的描述符
p_table_kind tablekind tablekind_standardtablekind_sortedtablekind_hashed
p_unique abap_bool 是否唯一键?
p_key key_table 键组件
p_key_kind keydefkind keydefkind_defaultkeydefkind_table_linekeydefkind_user

CL_ABAP_REFDESCR – 引用类型

创建引用类型描述符:

1
2
3
4
5
6
7
8
DATA: lo_ref TYPE REF TO cl_abap_refdescr.
DATA: lo_base TYPE REF TO cl_abap_datadescr.

lo_base = cl_abap_elemdescr=>get_i( ).
lo_ref = cl_abap_refdescr=>create( p_base_type = lo_base ).

" 创建 TYPE REF TO i
CREATE DATA dref TYPE HANDLE lo_ref.
参数 类型 含义
p_base_type REF TO cl_abap_datadescr 引用指向的类型

完整示例:从 DDIC 结构动态创建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
DATA: dref TYPE REF TO data.
FIELD-SYMBOLS: <ft> TYPE ANY TABLE.

" 步骤 1:从 DDIC 获取行类型描述符
DATA(lo_line) = cl_abap_structdescr=>describe_by_name( 'SFLIGHT' ).

" 步骤 2:创建表类型描述符
DATA(lo_tab) = cl_abap_tabledescr=>create(
p_line_type = lo_line
p_table_kind = cl_abap_tabledescr=>tablekind_sorted
p_unique = abap_true
p_key = VALUE #(
( name = 'CARRID' )
( name = 'CONNID' )
( name = 'FLDATE' ) )
).

" 步骤 3:创建数据对象
CREATE DATA dref TYPE HANDLE lo_tab.

" 步骤 4:访问
ASSIGN dref->* TO <ft>.
SELECT * FROM sflight INTO TABLE @<ft>.

流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DDIC / 已有类型
|
v
describe_by_name() 或 describe_by_data()
|
v
行类型描述符 (CL_ABAP_STRUCTDESCR)
|
v
CL_ABAP_TABLEDESCR=>create( line_type, key, ... )
|
v
表类型描述符 (CL_ABAP_TABLEDESCR)
|
v
CREATE DATA dref TYPE HANDLE <descriptor>
|
v
ASSIGN dref->* TO <ft>
|
v
像普通内表一样使用 <ft>

CREATE DATA 汇总表

你想要的 如何创建
简单类型变量 CREATE DATA dref TYPE typename
从运行时名称的变量 CREATE DATA dref TYPE (lv_name)
从组件的结构 构建组件表,CL_ABAP_STRUCTDESCR=>create()TYPE HANDLE
从任意行类型的表 CL_ABAP_TABLEDESCR=>create()TYPE HANDLE
引用类型 CL_ABAP_REFDESCR=>create()TYPE HANDLE
已知类型的匿名数据对象 CREATE DATA dref TYPE ... + ASSIGN dref->*

比较:RTTI vs RTTC

方面 RTTI RTTC
用途 检查已有类型 在运行时创建新类型
输入 数据对象、类型名称、引用 组件列表、类型描述符
输出 带元数据的类型描述符 新类型描述符 + 数据对象
相同层次结构(CL_ABAP_*DESCR) 子类增加 create() 方法
限制 不能创建类型 不能创建类/接口
SAP 版本 始终可用 SAP Web AS 6.40+
📌RTTC 限制
RTTC 可以生成数据类型(基本、结构、表、引用),但不能生成类或接口。动态类实例化仅使用已有类。

考试/测试模式

关键词 / 短语 答案
“RTTC 用途” 在运行时生成新数据类型
“CREATE DATA TYPE HANDLE” 使用 RTTI/RTTC 描述符对象确定类型
“CREATE DATA TYPE (name)” 类型名从变量动态解析
“CL_ABAP_STRUCTDESCR=>create 参数” p_components – 名称 + 类型对的组件表
“CL_ABAP_TABLEDESCR=>create 参数” p_line_typep_table_kindp_uniquep_key
“CREATE DATA 后访问” ASSIGN dref->* TO <fs>
“CREATE OBJECT 动态 TYPE” CREATE OBJECT oref TYPE (lv_classname)
“RTTC 不能创建” 类和接口 – 仅数据类型
“CREATE DATA 隐式” 从引用变量声明的静态类型
“RTTC 最低 SAP 版本” SAP Web AS 6.40
“基本类型创建方法” CL_ABAP_ELEMDESCR=>GET_C()GET_I()GET_P()
“匿名数据对象” CREATE DATA 创建,通过数据引用访问

相关笔记

Open SQL 进阶练习 (10题)

#practice #open-sql #sql-joins #database #table-buffer #lobs

相关概念

📌核心模式(点击查看)
关键词 答案
FOR ALL ENTRIES 空表 读取整个数据库表 – 始终检查 NOT INITIAL
JOIN 绕过缓冲 表缓冲对 JOIN 无效
聚合函数 COUNT、SUM、AVG、MAX、MIN 配合 GROUP BY
左外连接 左表所有行 + 右表匹配;右侧仅一个表
LOB 流 最多 16 个同时打开;尽快关闭

第1题 - 表缓冲类型 [记忆]

说出三种 SAP 表缓冲类型及其各自的使用场景。

📌点击查看答案
缓冲类型 行为
完全缓冲 首次访问时加载整个表。用于小且很少更改的表。
泛型缓冲 加载匹配泛型键区域的记录。用于按部分键聚簇访问的场景。
单条记录缓冲 按需加载单条记录。用于大表且分散访问的场景。

第2题 - 缓冲绕过 [记忆]

哪些 Open SQL 语句或附加项会绕过 SAP 表缓冲?

📌点击查看答案
以下内容会绕过表缓冲:
  • JOIN(任何类型)
  • 子查询WHERE col IN (SELECT ...)
  • 聚合函数(SUM、COUNT、AVG 等)
  • GROUP BY / HAVING
  • ORDER BY(当不使用排序表时)
  • DISTINCT
  • SELECT ... FOR UPDATE
  • Native SQL

第3题 - INTO 变体 [记忆]

INTO TABLEAPPENDING TABLE 有什么区别?

📌点击查看答案
  • INTO TABLE itab:用结果集覆盖内表。之前的内容丢失。
  • APPENDING TABLE itab:将结果集追加到内表的已有内容中。之前的行保留。

第4题 - PACKAGE SIZE [应用]

需要处理数据库表中的 100 万条记录,但要限制内存使用。如何实现?

📌点击查看答案
使用 PACKAGE SIZE
1
2
3
4
SELECT * FROM large_table INTO TABLE lt_data PACKAGE SIZE 10000
WHERE conditions.
" 处理 lt_data(每次 10,000 行)
ENDSELECT.

PACKAGE SIZE n 每次获取 n 行,保持内存使用受控。SELECT…ENDSELECT 循环重复直到所有行处理完毕。


第5题 - INNER vs OUTER JOIN [记忆]

用示例解释 INNER JOIN 和 LEFT OUTER JOIN 的区别。

📌点击查看答案
  • INNER JOIN:仅返回两表中 ON 条件都匹配的行。
  • LEFT OUTER JOIN:返回左表的所有行,加上右表中匹配的行。如果没有匹配,右侧列为初始值。

示例:SBOOK INNER JOIN SCARR -> 仅有匹配航空公司的预订。SBOOK LEFT OUTER JOIN STRAVELAG -> 所有预订,包括没有旅行社的(旅行社字段为初始值)。


第6题 - FOR ALL ENTRIES 陷阱 [应用]

如果执行 SELECT ... FOR ALL ENTRIES IN lt_empty ...,其中 lt_empty 没有行,会发生什么?如何预防?

📌点击查看答案
如果驱动表为FOR ALL ENTRIES 读取整个数据库表(无边界条件)。这会导致严重的性能问题。

预防方法:

1
2
3
4
5
IF lt_driving IS NOT INITIAL.
SELECT * FROM dbtab INTO TABLE lt_result
FOR ALL ENTRIES IN lt_driving
WHERE key = lt_driving-key.
ENDIF.

始终在 FOR ALL ENTRIES 之前检查 IS NOT INITIAL


第7题 - 子查询 [应用]

编写一个 SELECT 读取至少有一个航班连接的所有航空公司。

📌点击查看答案
1
2
SELECT * FROM scarr INTO TABLE lt_carriers
WHERE carrid IN ( SELECT DISTINCT carrid FROM spfli ).

子查询返回 SPFLI 表中存在的所有承运人 ID。仅从 SCARR 中选择匹配的承运人。


第8题 - 聚合函数 [应用]

编写一个 SELECT 返回每个航空公司的预订总数和最高价格。

📌点击查看答案
1
2
3
4
5
6
SELECT carrid,
COUNT(*) AS booking_count,
MAX( price ) AS max_price
FROM sbook
GROUP BY carrid
INTO TABLE lt_result.

GROUP BY carrid 按航空公司分组,COUNT(*) 计算每组的预订数,MAX(price) 找出每组的最高价格。


第9题 - LOB 定位器 [分析]

比较 LOB 定位器和 LOB 流。各在什么时候使用?

📌点击查看答案
方面 定位器
访问模式 对子序列的随机访问 顺序读/写
关键方法 GET_LENGTH, GET_SUBSTRING, FIND READ(length), DATA_AVAILABLE, SKIP
复制 LOB 可在数据库端复制而不传输 不用于复制
资源使用 较高的数据库资源消耗 较低的数据库资源使用
与内表兼容 兼容 不兼容
最大打开数 每个 LUW 1000 同时 16 个

当需要元数据、子字符串或在数据库端复制 LOB 时使用定位器。当需要顺序处理大 LOB 时(如逐段读取大文本文件)使用


第10题 - 游标处理 [分析]

比较 SELECT ... INTO TABLE(数组读取)与基于游标的处理(OPEN CURSOR / FETCH)。各在什么时候更优?

📌点击查看答案
方面 INTO TABLE 游标 (OPEN/FETCH)
数据传输 一次性全部 分块 (FETCH)
内存 高(整个结果集) 受控(按次获取)
数据库游标 自动管理 显式管理
可重复 否(必须重新 SELECT) 是(RESET 游标)
使用场景 中小型结果集 超大数据集,顺序处理

INTO TABLE 更简单,适用于可管理的结果集。游标适用于无法同时将所有行保存在内存中的超大数据集。PACKAGE SIZE 提供类似的内存控制而无需显式游标管理。


📌模式总结(点击查看)
关键词 答案
缓冲类型 完全、泛型、单条记录
缓冲绕过 JOIN、子查询、聚合、GROUP/ORDER/DISTINCT
INTO vs APPENDING INTO 覆盖,APPENDING 添加
FOR ALL ENTRIES 始终先检查 NOT INITIAL
INNER vs OUTER JOIN INNER=两边都匹配;LEFT OUTER=左表全部+匹配
聚合 COUNT、SUM、AVG、MAX、MIN 配合 GROUP BY
定位器 vs 流 随机访问 vs 顺序;1000 vs 16 限制
0%