Mark的技术博客

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

吴让宇(Mark)

吴让宇(Mark)

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

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

SAP Fiori ABAP CDS OData Dify Docker AI LLM

最新文章

算术表达式 (★★)

#arithmetic #data-types

数值类型比较

类型 关键字 精度 范围 特殊行为
整数 i 4 字节,整数 -2^31 到 2^31-1 商业舍入用于除法
二进制浮点 f 8 字节,IEEE 754 ~1E-307 到 ~1E+308 舍入误差;17位显示
压缩数 p 1-16 字节,1-31 位 最多 31 位 需要 DECIMALS;需要定点算术属性
十进制浮点 16 decfloat16 8 字节 16位精度 笔纸算法;无舍入误差
十进制浮点 34 decfloat34 16 字节 34位精度 笔纸算法;无舍入误差

整数算术

  • 商业舍入:除法舍入到最接近的整数(不是截断)
  • DIV 运算符:整数除法返回商
  • MOD 运算符:整数取模返回余数
1
2
3
4
5
6
7
8
DATA: lv_int TYPE i.

lv_int = 5 / 2. " 结果:3(商业舍入)
lv_int = -5 / 2. " 结果:-2(向绝对值更大的方向舍入)

" DIV 和 MOD
lv_result = 7 DIV 2. " = 3
lv_result = 7 MOD 2. " = 1
📌整数除法使用商业舍入,不是截断。5 / 2 = 3,而不是 2

二进制浮点数(类型 f)

  • 基于 IEEE 754 标准(双精度)
  • 内部以二进制尾数 + 指数存储
  • 无法精确表示所有十进制小数
  • 显示使用 17 位有效数字(足以重建二进制值)
  • 比较可能因舍入而失败:0.1 + 0.2 <> 0.3 可能为真
1
2
3
4
DATA: lv_float TYPE f.

lv_float = '0.1'. " 必须使用文本字面量进行精确的十进制输入
lv_float = 0.1. " 转换时可能已有舍入误差
⚠️类型 f 有舍入误差。绝不要用于财务计算。应使用 pdecfloat

压缩数(类型 p)

  • 定点算术:小数点随值一起存储
  • 使用 LENGTH(字节)和 DECIMALS(小数位)定义
  • 总位数 = 2 * LENGTH - 1
  • **程序属性”定点算术”**必须激活
    • 如果未设置:计算中忽略小数位;仅用于 I/O 格式化
1
2
3
4
5
DATA: lv_packed TYPE p LENGTH 8 DECIMALS 2.

" 不带定点算术属性:
" 1.50 + 1.50 = 3.00 (存储/打印正确)
" 1.50 * 2 = 300 (错误!计算中未考虑小数位)
📌如果程序属性”定点算术”未设置,类型 p 的计算忽略小数位。结果仅在输出时显示正确,但内部算术将值当作整数处理。

十进制浮点数(decfloat16 / decfloat34)

  • 使用笔纸算法(基于十进制,非二进制)
  • 十进制小数无舍入误差
  • decfloat16:16 位有效数字,8 字节
  • decfloat34:34 位有效数字,16 字节
  • 财务和精度关键计算的最佳选择

混合算术类型确定

当不同数值类型出现在同一表达式中时,结果类型遵循优先级层次

1
2
3
4
5
优先级(从高到低):
decfloat16 / decfloat34 --> 十进制浮点算术
f --> 二进制浮点算术
p --> 压缩(定点)算术
i --> 整数算术
条件 使用的算术
任何 decfloat16/decfloat34 操作数或结果 十进制浮点
任何 f 操作数或结果(无 decfloat) 二进制浮点
任何 p 操作数或结果(无 f,无 decfloat) 定点(压缩)
所有 i 操作数和结果 整数
⚠️**(幂)运算符始终触发二进制或十进制浮点算术,即使所有操作数都是 i 类型。
1
2
3
4
5
" 示例:** 改变一切
DATA: lv_int TYPE i.

lv_int = ( 1 / 4 ) * ( 2 * 2 ). " 整数算术:(0) * 4 = 0
lv_int = ( 1 / 4 ) * ( 2 ** 2 ). " 浮点算术:0.25 * 4.0 = 1
⚠️超出 i 范围(|value| > 2,147,483,647)的数字字面量自动变为 p 类型。这会静默更改整个表达式的算术类型。
1
2
3
4
5
" 大字面量将算术改为压缩数
DATA: lv_int TYPE i.

lv_int = 1000000000 / 300000000 * 3. " 整数:结果 = 9
lv_int = 10000000000 / 3000000000 * 3. " 压缩数:结果 = 10

算术运算符汇总

运算符 含义 说明
+ - 加、减 标准
* / 乘、除 类型相关
DIV 整数商 仅整数类型
MOD 整数余数 仅整数类型
** 始终触发浮点算术
**= 幂并赋值 内联版本

选择正确的数值类型

1
2
3
4
5
6
7
8
9
10
+------------------+------------------------------------------+
| 使用场景 | 推荐类型 |
+------------------+------------------------------------------+
| 计数、索引 | i (最快,原生) |
| 财务计算 | p 或 decfloat34 (精确小数) |
| 科学计算 | f (大范围,IEEE 754) |
| 最高精度 | decfloat34 (34位,无舍入) |
| 高精度 | decfloat16 (16位,更少内存) |
| 遗留代码 | p (传统 ABAP) |
+------------------+------------------------------------------+

考试/测试模式

关键词/模式 答案
“商业舍入” 整数除法舍入到最接近的整数
“类型 f 舍入误差” IEEE 754 二进制无法精确表示所有十进制小数
“17 位” 类型 f 的显示精度
“定点算术” 程序属性;必须为类型 p 的小数位设置
“笔纸算法” decfloat 算法;无舍入误差
** 运算符” 无论操作数类型如何,都强制浮点算术
“字面量超出 i 范围” 自动变为 p 类型;改变表达式算术
“decfloat vs f” decfloat = 精确十进制;f = 带舍入误差的二进制
“DIV / MOD” 整数商和余数运算符
“混合表达式类型” 由表达式中最高优先级的类型决定

相关笔记

LOB 流与定位器 (★★)

#open-sql #lobs #database

LOB 类型

LOB 类型 ABAP 类型 数据库类型 内容
二进制 LOB xstring BLOB 二进制数据(图像、PDF 等)
字符 LOB string CLOB 文本数据(文档、XML 等)

访问方法比较

方法 传输方式 内存 使用场景
普通(直接) 完整 LOB 到 ABAP 高(完整内容在内存中) 小 LOB,需要全部内容
顺序读/写 低(逐块处理) 大 LOB,顺序处理
定位器 元数据 + 子序列 很低 检查长度、提取部分、在数据库端复制

普通访问(完整传输)

1
2
3
4
5
" 完整传输 - 将整个 LOB 加载到内存
SELECT document_content
INTO lv_string " 类型 STRING = 完整 CLOB 传输
FROM documents
WHERE doc_id = '001'.
⚠️普通访问将整个 LOB 传输到应用服务器。对于大文档(兆字节级),这会导致高内存消耗。

流(NW 7.0 EhP2+)

无需完整传输的顺序读/写。

读取器类

用途
CL_ABAP_DB_C_READER 读取 CLOB(字符)
CL_ABAP_DB_X_READER 读取 BLOB(二进制)
CL_ABAP_DB_C_WRITER 写入 CLOB
CL_ABAP_DB_X_WRITER 写入 BLOB(二进制)

流方法

方法 用途
DATA_AVAILABLE() 检查是否有更多数据可读
READ(length) 读取指定长度的下一块
SKIP(length) 跳过字节/字符
CLOSE() 关闭流并释放资源
IS_CLOSED() 检查流是否已关闭

流读取示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DATA: lo_reader TYPE REF TO cl_abap_db_c_reader.

SELECT document_content
INTO lo_reader " 流对象而非字符串
FROM documents
WHERE doc_id = '001'.

WHILE lo_reader->data_available( ) = abap_true.
lv_chunk = lo_reader->read( 1000 ). " 每次读取 1000 字符
" 处理 lv_chunk
ENDWHILE.

lo_reader->close( ).
ENDSELECT.

流写入示例

1
2
3
4
5
6
7
DATA: lo_writer TYPE REF TO cl_abap_db_x_writer.

INSERT INTO documents VALUES ( '002', lo_writer ).

lo_writer->write( lv_data_chunk1 ).
lo_writer->write( lv_data_chunk2 ).
lo_writer->close( ).
📌每个会话最多同时打开 16 个流。尽快关闭流以释放资源。

定位器

无需完整传输即可访问 LOB 元数据和子序列。可以在数据库端复制 LOB 而无需传输到应用服务器。

定位器类

用途
CL_ABAP_DB_C_LOCATOR 字符 LOB 定位器
CL_ABAP_DB_X_LOCATOR 二进制 LOB 定位器

定位器方法

方法 用途
GET_LENGTH() 获取 LOB 长度而不传输
FIND(substring) 在 LOB 中搜索
GET_SUBSTRING(offset, length) 提取 LOB 的一部分
CLOSE() 释放定位器

定位器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DATA: lo_locator TYPE REF TO cl_abap_db_c_locator.

SELECT document_content
INTO lo_locator
FROM documents
WHERE doc_id = '001'.

lv_length = lo_locator->get_length( ). " 无数据传输
lv_pos = lo_locator->find( 'IMPORTANT' ). " 在数据库端搜索
lv_excerpt = lo_locator->get_substring( " 仅传输这部分
offset = lv_pos - 50
length = 100 ).

lo_locator->close( ).
ENDSELECT.
💡定位器可以在数据库端直接复制 LOB(数据库端复制),完全避免传输到应用服务器。
📌每个 LUW 最多 1000 个打开的 LOB 句柄(流 + 定位器合计)。

流和定位器的缺点

缺点 详情
更高的数据库资源消耗 数据库保持游标/锁打开
数据库依赖性 并非所有数据库原生支持
复杂性 比简单 SELECT 代码更多
限制 16 个流,每个 LUW 1000 个 LOB 句柄

流 vs 定位器

特性 定位器
访问模式 仅顺序 随机访问
读取分块 是(READ(length) 是(GET_SUBSTRING
写入 是(写入器类)
搜索 是(FIND
获取长度 是(GET_LENGTH
数据库端复制
最大打开数 16 与流合计 1000

考试/测试模式

关键词 / 短语 答案
“最大打开流数” 每个会话 16 个
“每个 LUW 最大 LOB 句柄” 1000 个(流 + 定位器合计)
“BLOB 读取的流类” CL_ABAP_DB_X_READER
“CLOB 写入的流类” CL_ABAP_DB_C_WRITER
“定位器相比流的优势” 随机访问、搜索、获取长度、数据库端复制
“普通访问的缺点” 完整传输到内存(高消耗)
“DATA_AVAILABLE 方法” 检查流中是否有更多数据可用
“定位器上的 GET_LENGTH” 返回长度而不传输 LOB 内容

相关笔记

SQL 多表访问 (★★★)

#open-sql #sql-joins #database

数据库视图

方面 详情
定义位置 ABAP 字典(SE11)
可包含 表的 JOIN、选择、投影
在 Open SQL 中 当作透明表使用
维护 如果视图启用维护则可能
1
SELECT * FROM dd_view_name INTO TABLE lt_data WHERE ...
💡数据库视图封装 JOIN 逻辑。使用它们简化 ABAP 代码中重复的 JOIN。

INNER JOIN(内连接)

  • 仅返回两表中匹配的行
  • 最常见的 JOIN 类型
1
2
3
4
5
6
7
SELECT s~carrid, s~connid, p~cityfrom, p~cityto
INTO TABLE lt_result
FROM sflight AS s
INNER JOIN spfli AS p
ON s~carrid = p~carrid AND
s~connid = p~connid
WHERE s~carrid = 'LH'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
表 A(左)       表 B(右)
+----+------+ +----+-------+
| K1 | A1 | | K1 | B1 |
+----+------+ +----+-------+
| 01 | X | | 01 | Y |
| 02 | Z | | 03 | W |
+----+------+ +----+-------+

INNER JOIN 结果:
+----+------+----+-------+
| K1 | A1 | K1 | B1 |
+----+------+----+-------+
| 01 | X | 01 | Y | ← 仅匹配的键
+----+------+----+-------+

LEFT OUTER JOIN(左外连接)

  • 返回左表的所有行加上右表中匹配的行
  • 没有匹配时右侧列为 NULL
  • 右侧只允许一个表
1
2
3
4
5
6
SELECT s~carrid, s~connid, p~cityfrom
INTO TABLE lt_result
FROM sflight AS s
LEFT OUTER JOIN spfli AS p
ON s~carrid = p~carrid AND
s~connid = p~connid.
1
2
3
4
5
6
7
LEFT OUTER JOIN 结果:
+----+------+----+-------+
| K1 | A1 | K1 | B1 |
+----+------+----+-------+
| 01 | X | 01 | Y | ← 找到匹配
| 02 | Z | -- | NULL | ← 无匹配,右侧为 NULL
+----+------+----+-------+
⚠️LEFT OUTER JOIN 右侧只能有一个表。不能在右侧外连接到视图或其他 JOIN 结果。

JOIN 列命名

当两个表共享列名时,使用 alias~column 语法避免歧义:

1
2
3
FROM sflight AS s
INNER JOIN spfli AS p
ON s~carrid = p~carrid " alias~column 表示法
表示法 含义
alias~column 限定来自特定表的列
~column 快捷方式(如果不歧义)
column 如果在多个表中则歧义

子查询

标量子查询

在 WHERE 子句中返回单个值:

1
2
3
SELECT * FROM sflight
INTO TABLE lt_flights
WHERE price > ( SELECT AVG(price) FROM sflight ).

IN 子查询

返回一组值:

1
2
3
SELECT * FROM sflight
INTO TABLE lt_result
WHERE carrid IN ( SELECT carrid FROM scarr WHERE currcode = 'EUR' ).
子查询类型 返回值 配合使用
标量 单个值 =<>
集合 多个值 INNOT IN
📌子查询绕过 SAP 表缓冲(被缓冲视为 Native SQL 路径)。

FOR ALL ENTRIES

将数据库表与内表连接:

1
2
3
4
5
6
7
8
9
" 关键:先检查驱动表!
IF lt_keys IS NOT INITIAL.
SELECT carrid, connid, price
INTO TABLE lt_result
FROM sflight
FOR ALL ENTRIES IN lt_keys
WHERE carrid = lt_keys-carrid
AND connid = lt_keys-connid.
ENDIF.

FOR ALL ENTRIES 规则

规则 详情
检查 IS NOT INITIAL 空驱动表读取整个数据库表
自动去重 结果自动删除重复行
不适用于大数据量 驱动表很大时性能下降
不能使用 LIKE、NOT IN WHERE 中受限的运算符
如果驱动表(内表)为FOR ALL ENTRIES 的行为如同没有 WHERE 子句,读取整个数据库表。始终先检查 itab IS NOT INITIAL

性能提示

1
2
3
4
5
6
7
8
9
10
11
12
" 不好:驱动表未排序、有重复
SELECT * FROM sflight
FOR ALL ENTRIES IN lt_dirty
WHERE carrid = lt_dirty-carrid.

" 好:先精简驱动表
SORT lt_dirty BY carrid.
DELETE ADJACENT DUPLICATES FROM lt_dirty COMPARING carrid.

SELECT * FROM sflight
FOR ALL ENTRIES IN lt_dirty
WHERE carrid = lt_dirty-carrid.

性能反模式:循环中的 SELECT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
" 反模式:循环中的 SELECT
LOOP AT lt_flights INTO ls_flight.
SELECT SINGLE cityfrom INTO ls_flight-cityfrom
FROM spfli
WHERE carrid = ls_flight-carrid
AND connid = ls_flight-connid.
MODIFY lt_flights FROM ls_flight.
ENDLOOP.

" 正确:使用 JOIN 或预缓冲
SELECT s~carrid, s~connid, p~cityfrom
INTO TABLE lt_result
FROM sflight AS s
INNER JOIN spfli AS p
ON s~carrid = p~carrid AND s~connid = p~connid.
方式 数据库往返次数 性能
循环中的 SELECT n + 1(每行一次) 很差
JOIN 1 优秀
FOR ALL ENTRIES 1(或少量) 小集合时良好
预缓冲 + READ 1 + 内存查找 良好

JOIN 比较

特性 INNER JOIN LEFT OUTER JOIN
未匹配的左表行 丢弃 保留(右侧 = NULL)
右侧限制 仅一个表
绕过缓冲
ON 条件 必需 必需
默认 JOIN 类型 INNER(关键字可选) 必须指定 OUTER

考试/测试模式

关键词 / 短语 答案
“FOR ALL ENTRIES 空表” 读取整个数据库表 – 始终检查 IS NOT INITIAL
“LEFT OUTER JOIN 右侧限制” 右侧只允许一个表
“JOIN 和表缓冲” JOIN 绕过 SAP 表缓冲
“别名列语法” alias~column 消除歧义
“FOR ALL ENTRIES 去重处理” 结果集自动删除重复项
“循环中的 SELECT” 反模式;使用 JOIN 或预缓冲
“精简驱动表” FOR ALL ENTRIES 前 SORT + DELETE ADJACENT DUPLICATES
“标量子查询返回” 单个值
“IN 子查询返回” 一组值

相关笔记

性能与技巧练习 (10题)

#practice #performance #best-practices #analysis-tools

相关概念

📌核心模式(点击查看)
关键词 答案
循环中的 SELECT 反模式 – 使用 JOIN 或预缓冲
标准表线性搜索 改用排序/哈希表
LOOP 中的 ASSIGNING 避免数据复制
代码检查器事务 SCI
SQL 跟踪事务 ST05
运行时分析提示 使用 Aggregation = None 变体

第1题 - 反模式:循环中的 SELECT [记忆]

为什么 LOOP 中的 SELECT 被视为性能反模式?

📌点击查看答案
每次迭代触发一次独立的数据库访问,导致:
  • 高网络开销(n 行需要 n 次往返)
  • 数据库因重复语句准备和执行而承受负载
  • 绕过潜在的缓冲优化

应改用 JOIN、FOR ALL ENTRIES 或在循环前将数据预缓冲到内表。


第2题 - 内表类型性能 [应用]

有一个包含 100,000 条记录的标准表,需要按键读取 50,000 次。做什么改变能带来最大的性能提升?

📌点击查看答案
改为使用读取键的哈希表
1
2
3
4
5
" 之前:每次读取 O(n) = 100,000 * 50,000 次操作
DATA: itab TYPE STANDARD TABLE OF ...

" 之后:每次读取 O(1) = 50,000 次哈希查找
DATA: itab TYPE HASHED TABLE OF ... WITH UNIQUE KEY key_field

或者如果还需要索引访问,使用排序表。两者都比标准表线性搜索快得多。


第3题 - ASSIGNING 性能 [记忆]

LOOP AT itab ASSIGNING <fs> 相比 LOOP AT itab INTO wa 有什么性能优势?

📌点击查看答案
ASSIGNING 提供指向表行的直接引用 – 无数据复制。INTO wa 每次迭代都将整行复制到工作区,而 MODIFY itab FROM wa 又复制回去。

对于大型行结构或大量迭代,ASSIGNING 消除了所有复制开销。通过字段符号的更改直接修改表行。


第4题 - SELECT 优化 [应用]

程序读取 SELECT * FROM sbook WHERE customid = lv_id,但只使用 BOOKID、FLDATE 和 PRICE 字段。应如何优化?

📌点击查看答案
仅指定需要的列:
1
2
3
SELECT bookid fldate price FROM sbook
INTO TABLE lt_bookings
WHERE customid = lv_id.

好处:减少数据库数据传输、减少应用服务器内存、可能使用覆盖索引。


第5题 - 表缓冲策略 [应用]

何时应使用 SAP 表缓冲,何时应避免?

📌点击查看答案
使用缓冲的条件:
  • 表频繁读取但很少更改
  • 表相对较小(完全缓冲)或按泛型键访问
  • 与数据库的数据一致性不是关键(可接受轻微延迟)

避免缓冲的条件:

  • 表频繁更改(失效开销)
  • 表非常大(内存压力)
  • 需要实时数据(缓冲可能过期)
  • 访问始终通过 JOIN(缓冲会被绕过)

第6题 - 代码检查器使用 [记忆]

代码检查器使用什么事务?主要检查类别有哪些?

📌点击查看答案
事务 SCI。主要检查类别:
  • 通用检查:数据格式
  • 性能检查:WHERE 分析、缓冲绕过、低效表访问
  • 安全检查:跨客户端访问、不充分的权限检查
  • 语法检查/生成:扩展程序检查
  • 编程规范:命名规范
  • 搜索功能:令牌和语句搜索

第7题 - SQL 跟踪分析 [记忆]

SELECT 语句的 SQL 跟踪操作有哪些?各自做什么?

📌点击查看答案
  1. PREPARE:解析 SQL 文本,确定访问策略(优化器),创建执行计划
  2. OPEN/REOPEN:用实际值替换绑定变量,打开游标
  3. FETCH:将记录从数据库服务器传输到应用服务器

对于写操作,EXEC/REEXEC 替代 OPEN/REOPEN 和 FETCH。
SAP 游标缓存可以跳过相同 SQL 结构的 PREPARE。


第8题 - 运行时分析 [应用]

需要找出程序中最耗时的 ABAP 语句。描述使用运行时分析的步骤。

📌点击查看答案
  1. 调用运行时分析(事务 SAT 或 SE30)
  2. 创建 Aggregation = None 的变体以获取详细结果
  3. 输入程序名并执行
  4. 执行后点击分析
  5. 使用分组命中列表查看按调用类型(子程序、FM、方法、表操作)的时间分布
  6. 总时间降序排列找到最耗时的操作
  7. 双击单个项目并按显示源代码查看精确的语句

第9题 - 性能比较 [分析]

一位同事建议在 1000 条记录的循环中使用 SELECT SINGLE,对比单次 FOR ALL ENTRIES。比较两种方案。

📌点击查看答案
方案 数据库调用 性能
循环中的 SELECT SINGLE 1000 次调用 非常差(1000 次往返)
FOR ALL ENTRIES 1 次调用(或少量) 好得多

FOR ALL ENTRIES 远优于循环方式,因为它将所有查找合并到单个 SQL 语句中。但需确保驱动表非空且不太大。

更好的方案:如果两个数据库表之间有关系,使用 JOIN,因为它完全避免了中间步骤。


第10题 - 新旧调试器对比 [分析]

何时需要使用新调试器而非传统调试器?

📌点击查看答案
在以下情况使用新调试器:
  • 调试转换出口字段出口(旧调试器不支持)
  • 在调试会话之间保持调试器设置
  • 使用可配置 UI和多个桌面
  • 避免干扰应用程序的滚动区

新调试器在独立的外部会话中运行,消除了干扰。但它需要一个可用的外部会话(每用户最多 6 个 – 如果已打开 6 个,则无法启动新调试器)。


📌模式总结(点击查看)
关键词 答案
循环中的 SELECT 反模式;使用 JOIN/预缓冲
哈希表查找 O(1) vs 标准表的 O(n)
ASSIGNING 无复制,直接修改
SELECT * 反模式;指定列
何时缓冲 读多改少,小表
SCI 代码检查器事务
ST05 SQL 跟踪事务
PREPARE/OPEN/FETCH SELECT 的 SQL 跟踪操作

运行时类型识别(RTTI) (★★★)

#dynamic-prog #rtti #data-types

概述

运行时类型识别(RTTI)允许使用类型描述对象在运行时描述数据类型和对象类型。每种 ABAP 类型映射到一个特定的 RTTI 类实例,提供关于类型结构、组件和属性的元数据。

RTTI 类层次结构

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
CL_ABAP_TYPEDESCR(根类)
| kind: kind_elem, kind_struct, kind_table, kind_ref, kind_class, kind_intf
| 方法: describe_by_name(), describe_by_data(), describe_by_object_ref()
|
+-- CL_ABAP_DATADESCR
| | +-- CL_ABAP_ELEMDESCR
| | 内置类型: GET_I, GET_F, GET_C, GET_STRING, GET_P ...
| | DDIC: get_ddic_field_list()
| |
| +-- CL_ABAP_REFDESCR
| | get_ref_type() -> 基础类型描述符
| |
| +-- CL_ABAP_STRUCTDESCR
| | get_components() -> 组件表
| | get_ddic_field_list() -> DDIC 字段信息
| |
| +-- CL_ABAP_TABLEDESCR
| | get_table_line_type() -> 行类型描述符
| | key: 表键信息
| |
| +-- CL_ABAP_ENUMDESCR
|
+-- CL_ABAP_OBJECTDESCR
+-- CL_ABAP_CLASSDESCR
| 方法、属性、继承信息
+-- CL_ABAP_INTFDESCR
接口方法信息

关键工厂方法(CL_ABAP_TYPEDESCR)

方法 用途 返回值
describe_by_name( name ) 获取命名类型的描述符(DDIC/全局) 类型描述符对象
describe_by_data( dobj ) 从数据对象获取描述符 类型描述符对象
describe_by_object_ref( ref ) 从对象引用获取描述符 对象类型描述符
describe_by_ref( ref ) 从数据引用获取描述符 类型描述符对象
1
2
3
4
5
6
7
8
" 通过数据对象
DATA(lo_descr) = cl_abap_typedescr=>describe_by_data( lv_value ).

" 通过类型名称
DATA(lo_descr) = cl_abap_typedescr=>describe_by_name( 'STRING' ).

" 通过对象引用
DATA(lo_obj_descr) = cl_abap_typedescr=>describe_by_object_ref( oref ).

KIND 属性值

常量 描述符类 含义
kind_elem E CL_ABAP_ELEMDESCR 基本类型
kind_struct S CL_ABAP_STRUCTDESCR 结构类型
kind_table T CL_ABAP_TABLEDESCR 内表类型
kind_ref R CL_ABAP_REFDESCR 引用类型
kind_class C CL_ABAP_CLASSDESCR 类类型
kind_intf I CL_ABAP_INTFDESCR 接口类型
1
2
3
4
5
6
7
8
CASE lo_descr->kind.
WHEN cl_abap_typedescr=>kind_elem.
" 基本类型
WHEN cl_abap_typedescr=>kind_struct.
" 结构
WHEN cl_abap_typedescr=>kind_table.
" 内表
ENDCASE.

CL_ABAP_ELEMDESCR

描述基本(内置)类型。

1
2
3
4
5
6
7
8
DATA: lo_elem TYPE REF TO cl_abap_elemdescr.
lo_elem ?= cl_abap_typedescr=>describe_by_data( lv_string ).

" 访问属性
WRITE: / lo_elem->type_kind, " 类型种类字符
lo_elem->length, " 内部长度
lo_elem->output_length, " 输出长度
lo_elem->decimals. " 小数位数(类型 p)
属性 类型 含义
type_kind typekind 单字符:CNIPFDTg(string)、y(xstring) …
length i 内部长度(字节)
output_length i 输出长度
decimals i 小数位数

内置类型的静态工厂方法

方法 创建以下类型的描述符
GET_C( len ) 类型 c,指定长度
GET_N( len ) 类型 n,指定长度
GET_X( len ) 类型 x,指定长度
GET_P( len decimals ) 类型 p,指定长度和小数位
GET_I() 类型 i
GET_F() 类型 f
GET_STRING() 类型 string
GET_XSTRING() 类型 xstring

CL_ABAP_STRUCTDESCR

描述结构化类型并提供对组件的访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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).
WRITE: / ls_comp-name, ls_comp-type->kind.
ENDLOOP.

" 获取 DDIC 字段列表(用于 DDIC 结构)
DATA: lt_ddic TYPE ddfields.
lt_ddic = lo_struct->get_ddic_field_list( ).
方法 返回值 用途
get_components() component_table 所有组件及名称、类型描述符
get_ddic_field_list() ddfields DDIC 元数据(域、数据元素、文本)

组件表结构

字段 类型 内容
name string 组件名称
type REF TO cl_abap_datadescr 组件的类型描述符
as_include abap_bool 是否为 include 结构?
suffix string include 命名冲突的后缀

CL_ABAP_TABLEDESCR

描述内表类型。

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

lo_table ?= cl_abap_typedescr=>describe_by_data( lt_any_table ).

" 获取行类型
lo_line = lo_table->get_table_line_type( ).

" 访问表类别和键
WRITE: / lo_table->table_kind, " tablekind_standard/sorted/hashed
lo_table->key. " 键信息
属性 含义
table_kind tablekind_standardtablekind_sortedtablekind_hashed
key 键组件和键类别
key_defkind 键定义类型:default、table line、user、empty
方法 返回值 用途
get_table_line_type() REF TO cl_abap_datadescr 行类型的描述符

CL_ABAP_REFDESCR

描述引用类型。

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

lo_ref ?= cl_abap_typedescr=>describe_by_data( dref_any ).
lo_base = lo_ref->get_referenced_type( ). " 它指向什么?

CL_ABAP_OBJECTDESCR(及子类)

描述类和接口。

1
2
3
4
5
6
7
8
DATA: lo_class TYPE REF TO cl_abap_classdescr.

lo_class ?= cl_abap_typedescr=>describe_by_object_ref( oref ).

" 访问方法和属性
LOOP AT lo_class->methods INTO DATA(ls_meth).
WRITE: / ls_meth-name.
ENDLOOP.
子类 关键属性
CL_ABAP_CLASSDESCR methodsattributessuper_class
CL_ABAP_INTFDESCR methodsattributes(接口的)

DESCRIBE FIELD 语句

一种更简单的、基于语句的基本类型信息获取方式:

1
2
3
4
5
6
7
8
9
10
DATA: lv_type    TYPE c LENGTH 1.
DATA: lv_length TYPE i.
DATA: lv_olength TYPE i.
DATA: lv_decimals TYPE i.

DESCRIBE FIELD lv_data
TYPE lv_type
LENGTH lv_length IN BYTE MODE
OUTPUT-LENGTH lv_olength
DECIMALS lv_decimals.
附加项 返回值 说明
TYPE typ 单字符类型种类 CIPFg
LENGTH n 内部长度 使用 IN BYTE MODEIN CHARACTER MODE
OUTPUT-LENGTH n 输出长度
DECIMALS n 小数位数 仅对类型 p 有意义
💡DESCRIBE FIELD vs RTTI
DESCRIBE FIELD 在快速检查时更简单。RTTI 提供更丰富的元数据并能处理复杂类型(结构、表)。超出基本类型/长度查询的场景优先使用 RTTI。

RTTI 类型解析流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
数据对象 / 类型名称 / 对象引用
|
v
describe_by_*() --> CL_ABAP_TYPEDESCR
| |
v v
检查 ->kind 向下转型 (?=) 到具体类
| |
v v
kind_elem? --> CL_ABAP_ELEMDESCR --> type_kind, length, decimals
kind_struct? --> CL_ABAP_STRUCTDESCR --> get_components(), get_ddic_field_list()
kind_table? --> CL_ABAP_TABLEDESCR --> get_table_line_type(), key
kind_ref? --> CL_ABAP_REFDESCR --> get_referenced_type()
kind_class? --> CL_ABAP_CLASSDESCR --> methods, attributes
kind_intf? --> CL_ABAP_INTFDESCR --> methods, attributes

考试/测试模式

关键词 / 短语 答案
“RTTI 根类” CL_ABAP_TYPEDESCR
“从数据对象获取描述符” describe_by_data( dobj )
“从类型名称获取描述符” describe_by_name( 'TYPE_NAME' )
“从对象引用获取描述符” describe_by_object_ref( oref )
“KIND 属性值” kind_elemkind_structkind_tablekind_refkind_classkind_intf
“获取结构组件” CL_ABAP_STRUCTDESCR=>get_components()
“获取 DDIC 字段信息” CL_ABAP_STRUCTDESCR=>get_ddic_field_list()
“获取表行类型” CL_ABAP_TABLEDESCR=>get_table_line_type()
“DESCRIBE FIELD 返回类型种类” 单字符:TYPE typ 附加项
“RTTI 向下转型运算符” ?=(从基类到具体描述符的窄化转型)
“基本类型属性” type_kindlengthoutput_lengthdecimals
“引用类型的基础类型” CL_ABAP_REFDESCR=>get_referenced_type()

相关笔记

数据库接口与表缓冲 (★★)

#open-sql #database #table-buffer

架构概述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-------------------+
| ABAP 程序 |
+--------+----------+
| Open SQL / Native SQL
+--------v----------+
| 数据库接口 |
| + Open SQL 解析器|
| + 表缓冲 |
| + 游标缓存 |
| + Native SQL 接口|
+--------+----------+
|
+--------v----------+
| 数据库 |
+-------------------+
组件 作用
Open SQL 解析器 检查语法,转换为数据库特定 SQL
Native SQL 接口 将 Native SQL 直接传递给数据库
表缓冲 应用服务器上的本地缓存
游标缓存 复用已解析/已准备好的游标

SAP 表缓冲 vs 数据库缓冲

方面 SAP 表缓冲 数据库缓冲
位置 应用服务器(共享内存) 数据库服务器
管理者 SAP 内核 / 数据库接口 DBMS 本身
目的 完全避免数据库往返 在 RAM 中缓存磁盘块
范围 系统中所有应用服务器 单个数据库服务器
📌这是完全不同的概念。SAP 表缓冲位于 ABAP 程序和数据库接口之间。

缓冲类型

类型 使用场景 行为
完全缓冲 小表,很少更改 首次访问时加载整个表
泛型缓冲 按泛型键(部分键)访问 加载匹配泛型键的记录
单条记录缓冲 仅按完整键访问 按需加载单条记录
1
2
3
完全:    SELECT * FROM buffered_tab → 一次加载整个表
泛型: SELECT * FROM tab WHERE key1 = 'A' → 加载所有 'A*' 记录
单条: SELECT * FROM tab WHERE key1='A' AND key2='01' → 加载一条记录

缓冲同步

  • 当记录被更改时,所有应用服务器上的缓冲条目被失效
  • 失效使用同步表,定期检查
  • 跨服务器的更改和缓冲失效之间可能有延迟
  • BYPASSING BUFFER 强制直接从数据库读取
⚠️缓冲同步不是即时的。其他服务器可能短暂读取过期数据。

缓冲绕过触发条件

当 SELECT 使用以下内容时,SAP 表缓冲被绕过

绕过条件 示例
JOIN SELECT ... FROM a JOIN b ...
子查询 WHERE col IN (SELECT ...)
聚合函数 SUM()COUNT()AVG()
ORDER BY 按非键字段排序
GROUP BY 分组结果
DISTINCT 去重
💡对于缓冲表,使用简单的单表 SELECT 配合键字段以利用缓冲。如果需要缓冲命中,避免使用上述构造。

数据库索引

索引类型 描述
主索引 从主键字段自动创建
次索引 在 ABAP 字典中定义的替代访问路径
  • 优化器根据 WHERE 子句确定使用哪个索引
  • 选择良好的次索引可大幅提升读取性能
    | 索引过多会减慢 INSERT/UPDATE/DELETE |
1
2
3
4
5
6
表:SPFLI
主索引: MANDT + CARRID + CONNID
次索引: MANDT + CITYFROM + CITYTO (用于路线查找)

SELECT ... WHERE carrid = 'LH' AND connid = '0400' → 主索引
SELECT ... WHERE cityfrom = 'NYC' AND cityto = 'SFO' → 次索引

考试/测试模式

关键词 / 短语 答案
“什么会绕过表缓冲?” JOIN、子查询、聚合函数、ORDER BY、GROUP BY、DISTINCT
“完全缓冲何时加载?” 首次访问时加载整个表
“泛型缓冲使用什么?” 部分键(泛型键字段)
“缓冲失效机制?” 更改的记录通过同步表在所有应用服务器上触发失效
“谁决定访问策略?” 数据库优化器
“主索引由什么组成?” 主键字段(加上客户端)

相关笔记

ABAP 性能优化 (★★★)

#performance #internal-tables #abap-basics

内表类型选择

访问模式 最佳表类型 访问速度
按键单条记录查找 哈希表 O(1) 常数
按键范围顺序读取 排序表 O(log n) 二分
需要索引访问 排序表或标准表 O(1) 按索引
排序 / 重新排序 标准表 灵活排序
批量插入 / 追加 标准表 高效追加
💡根据主要访问模式选择表类型,而不是根据数据加载方式。

避免循环中的复制

ASSIGNING / REFERENCE INTO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
" 不好:每次迭代都复制数据
LOOP AT lt_data INTO ls_data.
ls_data-processed = abap_true.
MODIFY lt_data FROM ls_data.
ENDLOOP.

" 好:无复制,直接修改
LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<ls_data>).
<ls_data>-processed = abap_true. " 直接写入,不需要 MODIFY
ENDLOOP.

" 好:引用(供后续使用)
LOOP AT lt_data REFERENCE INTO lr_data.
lr_data->*-processed = abap_true.
ENDLOOP.
方法 复制? 修改方式 适用场景
INTO wa 是(复制) 需要 MODIFY 简单读取
ASSIGNING <fs> 通过字段符号直接 原地修改
REFERENCE INTO dref 通过 dref->* 后续需要引用
📌使用 ASSIGNING <fs>REFERENCE INTO 避免在每次迭代时创建表行副本。对于宽行或大量迭代,这是显著的性能提升。

字符串性能

反模式 更好的方法
循环中的连接 构建内表,然后 CONCATENATE LINES OF
重复 CONCATENATE 字符串模板(|...{expr}...|
SHIFT + CONCATENATE shift_left()shift_right() 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
" 不好:循环中的连接
LOOP AT lt_names INTO ls_name.
CONCATENATE lv_result ls_name-name INTO lv_result SEPARATED BY space.
ENDLOOP.

" 好:表连接
LOOP AT lt_names INTO ls_name.
APPEND ls_name-name TO lt_parts.
ENDLOOP.
lv_result = concat_lines_of( table = lt_parts sep = ` ` ).

" 好:字符串模板
lv_text = |Flight { lv_carrid }-{ lv_connid } on { lv_date DATE = USER }|.

类型转换开销

情况 影响 解决方案
比较不同类型 每行隐式转换 使用相同类型
p 赋值给 i 运行时转换 声明时匹配类型
READ TABLE ... WITH KEY 类型不匹配 每次比较都转换 使用类型化的键
💡确保比较操作数具有相同类型。循环或表操作中的隐式转换会成倍增加开销。

表键选择指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
标准表:
- 使用场景:顺序处理、频繁排序、小表
- 键访问:线性 O(n)
- 索引访问:是
- BINARY SEARCH:SORT 后可用

排序表:
- 使用场景:部分顺序读取,同时需要键和索引
- 键访问:二分 O(log n)
- 索引访问:是
- 次键:可用

哈希表:
- 使用场景:按唯一键单条记录查找
- 键访问:哈希 O(1)
- 索引访问:否
- 最适合:大型查找表

内存考虑

方面 详情
INITIAL SIZE n 预分配内存;已知大小时使用以避免重新分配
FREE itab 释放整个表内存(头部 + 表体)
REFRESH itab 清除表体但可能保留头部内存
CLEAR itab 对内表等同于 REFRESH
深层表 包含字符串/表的表使用更多内存;完成后 FREE
1
2
3
4
5
6
" 为已知大小预分配
DATA: lt_flights TYPE STANDARD TABLE OF sflight
WITH INITIAL SIZE 1000.

" 完成后释放内存
FREE: lt_large_table, lt_temp_table.

性能汇总表

领域 反模式 最佳实践
表访问 标准表 + 线性搜索 哈希表(键)或排序表(范围)
循环修改 INTO wa + MODIFY ASSIGNING <fs>
字符串构建 循环连接 字符串模板、表连接
类型匹配 比较中混合类型 相同数据类型
内存 无 INITIAL SIZE,无 FREE 预分配,完成后释放

考试/测试模式

关键词 / 短语 答案
“单条记录查找的最佳表类型” 哈希表 (O(1))
“ASSIGNING 在 LOOP 中的优势” 无行数据复制;直接修改
“标准表使用 BINARY SEARCH” 需要先按相同键 SORT
“字符串构建最佳实践” 字符串模板或 concat_lines_of()
“INITIAL SIZE 用途” 预分配内存避免重新分配
“FREE vs REFRESH” FREE 释放所有内存;REFRESH 清除表体
“类型转换开销” 比较操作数使用相同类型
“哈希表限制” 无索引访问(READ … INDEX、LOOP … FROM/TO)

相关笔记

SQL WHERE 条件与 INTO 子句 (★★)

#open-sql #database

WHERE 运算符

运算符 含义 示例
= 等于 WHERE carrid = 'LH'
<> 不等于 WHERE carrid <> 'LH'
<, >, <=, >= 比较 WHERE price > 500
LIKE 模式匹配(% 任意,_ 单个) WHERE name LIKE 'S%'
IN 集合成员 WHERE carrid IN ('LH', 'BA')
BETWEEN 范围(包含边界) WHERE price BETWEEN 100 AND 500
IS NULL 空值检查 WHERE city IS NULL
NOT 否定(与上述组合) WHERE NOT carrid IN ('LH')
💡使用 LIKE 配合 % 进行”包含”模式匹配。使用 LIKE 'text%'(前缀)进行索引友好的搜索。

INTO 变体

变体 使用场景 行为
INTO wa 单行到工作区 覆盖工作区
INTO TABLE itab 所有行到内表 填充表(覆盖已有内容)
APPENDING TABLE itab 添加行到已有表 追加而不清空
INTO CORRESPONDING FIELDS OF wa 按列名匹配 仅填充匹配的字段
INTO (f1, f2, ...) 变量内联列表 基于位置的赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
" 单行
SELECT SINGLE carrid connid INTO wa FROM spfli WHERE ...

" 所有行到表
SELECT carrid connid INTO TABLE lt_spfli FROM spfli WHERE ...

" 追加到已有表
SELECT carrid connid INTO APPENDING TABLE lt_spfli FROM spfli WHERE ...

" 对应字段
SELECT carrid connid INTO CORRESPONDING FIELDS OF ls_flight FROM sflight WHERE ...

" 内联变量
SELECT SINGLE carrid connid INTO (lv_carr, lv_conn) FROM spfli WHERE ...

PACKAGE SIZE 处理大数据量

1
2
3
4
5
6
7
8
SELECT carrid connid
INTO TABLE lt_package
FROM spfli
PACKAGE SIZE 1000. " 每次处理 1000 行

" 在此处处理 lt_package

ENDSELECT.
方面 详情
用途 以可管理的块处理大型结果集
机制 反复填充内表,通过 ENDSELECT 循环
内存 受控的内存消耗
要求 必须与 SELECT ... ENDSELECT 循环配合使用
📌PACKAGE SIZE 需要 SELECT ... ENDSELECT 结构。INTO TABLE 对每个包反复填充。

行数限制

语句 行为
SELECT SINGLE ... 获取一行;应使用完整键;不允许 ORDER BY
SELECT ... UP TO n ROWS 最多获取 n 行;可与 ORDER BY 配合
1
2
3
4
5
" 按完整键获取一行
SELECT SINGLE * FROM spfli INTO wa WHERE carrid = 'LH' AND connid = '0400'.

" 最佳匹配(带排序)
SELECT * FROM spfli INTO TABLE lt_tab UP TO 10 ROWS ORDER BY distance.
⚠️SELECT SINGLE 应使用完整的主键。如果 WHERE 不能唯一标识一行,结果未定义(返回第一个匹配,取决于数据库)。当你需要单行但缺少完整键时,使用 SELECT ... UP TO 1 ROWS

SELECT SINGLE vs UP TO 1 ROWS

特性 SELECT SINGLE SELECT UP TO 1 ROWS
需要完整键 是(推荐)
允许 ORDER BY
用途 按键获取唯一行 任意单行
性能 有完整键时略优 无完整键时相同

考试/测试模式

关键词 / 短语 答案
“APPENDING vs INTO TABLE” APPENDING 添加到已有内容;INTO TABLE 覆盖
“PACKAGE SIZE 需要什么?” SELECT … ENDSELECT 循环
“SELECT SINGLE 的要求?” 应使用完整主键
“UP TO n ROWS 配合 ORDER BY?” 允许 – 排序后返回前 n 行
“INTO CORRESPONDING FIELDS” 按列名匹配,忽略不匹配的
“LIKE 通配符?” % = 任意序列,_ = 单个字符

相关笔记

SQL 聚合与处理 (★★★)

#open-sql #database

聚合函数

函数 返回值 说明
COUNT(*) 行数 计算所有行,包括 NULL
COUNT(DISTINCT col) 不同值的数量 排除重复和 NULL
SUM(col) 值的总和 仅数值列
AVG(col) 平均值 仅数值列
MAX(col) 最大值 适用于任何可比较类型
MIN(col) 最小值 适用于任何可比较类型
📌聚合函数忽略 NULL 值(COUNT(*) 除外)。如果所有值都是 NULL,SUMAVG 返回 NULL。
1
2
3
4
5
6
7
8
9
10
11
12
" 计算行数
SELECT COUNT(*) INTO lv_count FROM sflight WHERE carrid = 'LH'.

" 多个聚合
SELECT carrid,
COUNT(*) AS cnt,
SUM(price) AS total,
AVG(price) AS avg_price,
MAX(price) AS highest
INTO TABLE lt_summary
FROM sflight
GROUP BY carrid.

GROUP BY

  • 将共享列值的行分组
  • SELECT 中每个非聚合列必须出现在 GROUP BY 中
  • 在 SELECT 列表之前评估
1
2
3
4
SELECT carrid, connid, COUNT(*) AS cnt
INTO TABLE lt_counts
FROM sflight
GROUP BY carrid, connid.
⚠️如果你选择了不在 GROUP BY 中且未聚合的列,结果未定义且取决于数据库。

HAVING

  • 过滤(不是单个行)
  • 在 GROUP BY 之后、SELECT 之前应用
  • 条件中可使用聚合函数
1
2
3
4
5
SELECT carrid, COUNT(*) AS cnt
INTO TABLE lt_result
FROM sflight
GROUP BY carrid
HAVING COUNT(*) > 10. " 仅行数 > 10 的组
子句 过滤对象 应用时机
WHERE 单个行 分组之前
HAVING 分组之后

ORDER BY

语法 行为
ORDER BY col1 col2 按指定列排序
ORDER BY 1 3 按列位置排序(从 1 开始)
ORDER BY col1 ASCENDING col2 DESCENDING 显式方向
1
2
3
4
SELECT carrid, price
INTO TABLE lt_sorted
FROM sflight
ORDER BY carrid ASCENDING price DESCENDING.
💡ORDER BY 绕过 SAP 表缓冲。对于缓冲表的排序输出,改为在 ABAP 中对内表排序。

DISTINCT

  • 从结果集中删除重复行
  • 应用于整行,而非单个列
1
2
3
4
" 唯一城市对
SELECT DISTINCT cityfrom, cityto
INTO TABLE lt_routes
FROM spfli.
⚠️DISTINCT 应用于完整的结果行。它不与单个聚合列表达式组合(COUNT(DISTINCT col) 除外)。

SELECT 处理方式

方式 语法 使用场景
一次性 SELECT ... INTO TABLE itab 中小型结果集
循环 SELECT ... ENDSELECT 逐行处理
分包 SELECT ... PACKAGE SIZE n ... ENDSELECT 大数据量分块
游标 OPEN CURSOR ... FETCH ... CLOSE CURSOR 精细控制

基于游标的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DATA: lv_cursor TYPE cursor.

OPEN CURSOR lv_cursor FOR
SELECT carrid, connid, price
FROM sflight
WHERE carrid = 'LH'
ORDER BY price.

DO.
FETCH NEXT CURSOR lv_cursor
INTO TABLE lt_batch
PACKAGE SIZE 100.
IF sy-subrc <> 0.
EXIT.
ENDIF.
" 处理 lt_batch
ENDDO.

CLOSE CURSOR lv_cursor.
步骤 语句 用途
1 OPEN CURSOR c FOR SELECT ... 定义并持有查询
2 FETCH NEXT CURSOR c INTO TABLE ... PACKAGE SIZE n 获取下一批
3 CLOSE CURSOR c 释放资源
💡游标允许你暂停和恢复获取。在需要混合数据库读取与处理,或多个游标需要交错访问时很有用。
⚠️打开的游标占用数据库资源。完成后务必关闭。最大打开游标数受数据库配置限制。

SELECT … ENDSELECT vs INTO TABLE

特性 SELECT … ENDSELECT INTO TABLE
处理方式 逐行(循环) 一次性全部
内存 每次迭代较低 所有行在内存中
性能 更多数据库往返 单次传输
使用场景 需要逐行逻辑时 需要批量数据时
配合 PACKAGE SIZE 受控的分块 不适用

处理顺序汇总

1
2
3
4
5
6
7
8
1. FROM     → 确定源表
2. WHERE → 过滤单个行
3. GROUP BY → 对剩余行分组
4. HAVING → 过滤组
5. SELECT → 应用聚合,选择列
6. DISTINCT → 删除重复项
7. ORDER BY → 排序结果
8. INTO → 传输到 ABAP

考试/测试模式

关键词 / 短语 答案
“HAVING 过滤什么?” 组(GROUP BY 之后),不是单个行
“ORDER BY 位置编号” SELECT 列表中从 1 开始的列位置
“DISTINCT 范围” 整个结果行(不是按列)
“COUNT(*) vs COUNT(col)” COUNT(*) 计算所有行;COUNT(col) 排除 NULL
“何时使用游标?” 精细控制、交错处理、恢复获取
“聚合 + GROUP BY 规则” 所有非聚合的 SELECT 列必须在 GROUP BY 中
“处理顺序” FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY

相关笔记

常见错误与编程规范 (★★)

#best-practices #abap-basics

常见编程错误

错误 后果 预防
数据库操作后缺少 sy-subrc 检查 静默数据丢失 始终检查 sy-subrc
使用 SELECT * 而非指定列 不必要的数据传输 列出需要的列
循环中的 SELECT n+1 次数据库往返 使用 JOIN 或预缓冲
错误的表类型(标准表用于查找) 线性搜索 O(n) 使用排序表或哈希表
未排序的表 + BINARY SEARCH 不可预测的结果 先排序再搜索
代码中遗留 BREAK-POINT 生产环境调试器 使用检查点组
算术中的类型不匹配 隐式转换错误 使用显式类型
忽略异常 未捕获的转储 使用 TRY/CATCH 处理

命名规范

客户命名空间

前缀 范围
Z* 客户开发
Y* 客户开发(备选)

对象命名

对象 前缀 示例
程序 ZY ZMY_PROGRAM
ZCL_ ZCL_ORDER_HANDLER
接口 ZIF_ ZIF_PAYMENT_PROCESSOR
函数组 Z ZFG_ORDER
函数模块 Z Z_CALCULATE_TAX
Z ZCUSTOMERS
结构 Z ZS_ORDER_ITEM
表类型 Z ZTT_ORDER_ITEMS
数据元素 Z ZDE_ORDER_STATUS
Z ZDOM_STATUS

变量命名(程序内部)

前缀 类型 示例
lv_ 局部变量 lv_countlv_name
gv_ 全局变量 gv_status
ls_ 结构 / 工作区 ls_flight
lt_ 内表 lt_flights
lw_ 工作区(备选) lw_result
lr_ 引用 / 范围 lr_datalr_range
lo_ 对象引用 lo_handler
mc_ 常量 mc_max_rows
co_ 常量(备选) co_initial_date

程序组织

Include 结构

1
2
3
4
5
6
7
8
9
+----------------------------------+
| 主程序 |
| INCLUDE zprog_top. | ← Top include(数据声明)
| INCLUDE zprog_cls. | ← 类定义
| INCLUDE zprog_pbo. | <-- PBO 模块(对话程序)
| INCLUDE zprog_pai. | <-- PAI 模块(对话程序)
| INCLUDE zprog_f01. | <-- FORM 例程
| INCLUDE zprog_f02. | <-- FORM 例程(按功能分组)
+----------------------------------+
Include 用途
Top include(_top 全局数据声明、TYPES、CONSTANTS
类 include(_cls 局部类定义和实现
Form include(_f01_f02、…) 按功能分组的模块化 FORM 例程
💡Top include 模式集中了所有数据声明。这防止重复声明并提供程序数据的清晰概览。

模块化建议

建议 原因
优先使用方法而非 FORM 方法在类中:更好的封装、继承、接口
使用类(局部或全局) 面向对象设计更易维护
保持模块化单元简短 更容易测试和理解
每个单元单一职责 每个方法/FORM 应做一件事
避免过程中使用全局变量 显式传递参数

错误处理

级别 方法
数据库操作 立即检查 sy-subrc
基于类的异常 TRY ... CATCH ... ENDTRY 配合异常类
断言 ASSERT 用于开发者不变量
函数模块 通过 EXCEPTIONS 或基于类的异常处理
返回值 使用返回参数,而不仅是 sy-subrc
1
2
3
4
5
6
7
8
9
10
11
12
" 好:立即检查 sy-subrc
SELECT SINGLE * FROM sflight INTO ls_flight WHERE carrid = 'LH'.
IF sy-subrc <> 0.
" 处理错误
ENDIF.

" 好:基于类的异常处理
TRY.
lo_handler->process_order( ls_order ).
CATCH cx_static_check INTO lx_error.
MESSAGE lx_error->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
ENDTRY.

代码可读性

实践 示例
有意义的命名 lv_flight_count 而非 lv_ncnt
注释解释”为什么”而非”什么” " 检查授权(业务规则 XYZ) 而非 " 设置标志为 X
一致的格式 对齐赋值,使用缩进
分组相关语句 一起声明所有数据,按逻辑顺序处理
避免深层嵌套 提取到辅助方法

考试/测试模式

关键词 / 短语 答案
“客户命名空间前缀” Z*Y*
“Top include 用途” 集中数据声明
“方法 vs FORM” 优先使用方法(更好的封装、OO 特性)
“常见错误:循环中的 SELECT” n+1 问题;使用 JOIN 或预缓冲
“sy-subrc 检查时机” 在设置它的操作之后立即检查
“注释最佳实践” 解释为什么,不是什么
“异常处理方法” 基于类的异常配合 TRY/CATCH
“对象引用的变量前缀” lo_
“内表的变量前缀” lt_

相关笔记

0%