ABAP 的全称是 Advanced Business Application Programming-高级业务应用编程语言。
ABAP 是集成在 SAP 系统上的,底层是 SAP 的 ABAP 虚拟机,用来开发 C/S 架构的程序。Java 属于通用的开发语言,可以进行各种领域各种平台的开发,底层是 JVM,一般开发基于 B/S 架构的程序。

ABAP DEV

语法

CHECK、 EXIT、 RETURN 命令的区别

  • CHECK
    CHECK 后面要跟一个表达式,当表达式值为假(false)时,CHECK 发生作用,退出循环(LOOP)或处理程序 (Processing Block)。
    如果 CHECK 出现在循环中,则发生作用时,退出的是当前一次循环操作,程序会继续执行下一次循环操作,其作用类似于 Continue (Java 或 C++中 Continue 也是如此)。
    如果 CHECK 出现在循环以外,则发生作用时,退出的是当前执行的程序块,例如一个 FORM、 METHOD 或 EVENT。

  • EXIT
    EXIT 如果出现在循环中,退出的是整个循环操作,程序会从循环结束处开始继续执行,其作用相当于 Java 与 C++中的 Break。
    EXIT 如果出现在循环之外,退出的是当前执行的程序块(Processing Block),例如一个 FORM、 METHOD 或 EVENT,其作用与 RETURN 类似。

  • RETURN
    RETURN 用来退出当前执行的程序块,例如一个 FORM、 METHOD 或 EVENT。不管是否出现在循环(L00P)中,RETURN 都会退出当前执行的程序块,而不仅仅是退出循环。虽然 ABAP 中 EXIT 和 RETURN 都可以用来实现退出当前执行的语句块,但 SAP 的帮助文件建议只在循环中使用 EXIT,其他情况下要退出当前执行进程,使用 RETURN。

示例代码:

" CHECK 示例:在循环中跳过某些条件
DATA: itab TYPE TABLE OF i.
DO 10 TIMES.
APPEND sy-index TO itab.
ENDDO.

LOOP AT itab INTO DATA(wa).
" 跳过偶数
CHECK wa MOD 2 <> 0.
WRITE: / wa.
ENDLOOP.
" 输出: 1, 3, 5, 7, 9

" EXIT 示例:在循环中提前退出
LOOP AT itab INTO wa.
IF wa = 5.
EXIT. " 退出整个循环
ENDIF.
WRITE: / wa.
ENDLOOP.
" 输出: 1, 2, 3, 4

" RETURN 示例:在 FORM 中提前返回
DATA: lv_result TYPE i.
PERFORM calculate USING 10 CHANGING lv_result.
WRITE: / '结果:', lv_result.

FORM calculate USING p_input TYPE i
CHANGING p_result TYPE i.
IF p_input < 0.
p_result = 0.
RETURN. " 提前返回
ENDIF.
p_result = p_input * 2.
ENDFORM.

" 区别总结:
" 1. CHECK:跳过当前循环迭代,继续下一次循环
" 2. EXIT:退出整个循环,继续循环之后的代码
" 3. RETURN:退出当前程序块(FORM/METHOD/EVENT)

初始化内表有几种方式

  • 要初始化有或没有表头的内表,用 REFRESH 语句。
  • 如果使用没有表格工作区城的内表,可以使用 CLEAR 语句代替 REFRESH 语。
  • 如果使用有表头行的内表,CLEAR 语句仅清除表格工作区城,要重置整个内表而不清除表格工作区城,使用 REFRESH 语句或 CLEAR 语句。
  • 使用 REFRESH 或 CLEAR 初始化内表后,系统保持在内存中保留的空间。可以用 FREE 语句释放内存。

示例代码:

" 定义内表
DATA: itab1 TYPE TABLE OF mara WITH HEADER LINE,
itab2 TYPE TABLE OF mara.

" 添加数据
SELECT * FROM mara INTO TABLE itab1 UP TO 5 ROWS.
SELECT * FROM mara INTO TABLE itab2 UP TO 5 ROWS.

WRITE: / '初始行数:', lines( itab1 ).

" 方式1:REFRESH - 清空内表数据,保留表头行
REFRESH itab1.
WRITE: / 'REFRESH 后行数:', lines( itab1 ). " 0
WRITE: / 'REFRESH 后表头:', itab1-matnr. " 有值

" 重新添加数据
SELECT * FROM mara INTO TABLE itab1 UP TO 5 ROWS.

" 方式2:CLEAR - 清空表头行和内表数据
CLEAR itab1.
WRITE: / 'CLEAR 后行数:', lines( itab1 ). " 0
WRITE: / 'CLEAR 后表头:', itab1-matnr. " 空

" 重新添加数据
SELECT * FROM mara INTO TABLE itab1 UP TO 5 ROWS.

" 方式3:CLEAR[] - 只清空内表数据,保留表头行
CLEAR itab1[].
WRITE: / 'CLEAR[] 后行数:', lines( itab1 ). " 0
WRITE: / 'CLEAR[] 后表头:', itab1-matnr. " 有值

" 重新添加数据
SELECT * FROM mara INTO TABLE itab1 UP TO 5 ROWS.

" 方式4:FREE - 清空内表数据并释放内存
FREE itab1.
WRITE: / 'FREE 后行数:', lines( itab1 ). " 0
WRITE: / 'FREE 后表头:', itab1-matnr. " 有值

" 对于不带表头行的内表
SELECT * FROM mara INTO TABLE itab2 UP TO 5 ROWS.
WRITE: / '初始行数:', lines( itab2 ).

" CLEAR 和 REFRESH 效果相同
CLEAR itab2.
WRITE: / 'CLEAR 后行数:', lines( itab2 ). " 0

SELECT * FROM mara INTO TABLE itab2 UP TO 5 ROWS.
REFRESH itab2.
WRITE: / 'REFRESH 后行数:', lines( itab2 ). " 0

SELECT * FROM mara INTO TABLE itab2 UP TO 5 ROWS.
FREE itab2.
WRITE: / 'FREE 后行数:', lines( itab2 ). " 0

" 初始化方式总结:
" 1. REFRESH:清空内表数据,保留表头行(有表头行时)
" 2. CLEAR:清空表头行和内表数据(有表头行时)
" 3. CLEAR[]:只清空内表数据,保留表头行
" 4. FREE:清空内表数据并释放内存

一个程序如何调用另外一个程序?

调用其他可执行程序使用”SUBMIT+程序名”语向,使用 WITH 关键字,可传递选择画面参数。使用 AND RETURN 关键字,可在该程序执行完毕后,自动返回调用他的程序。

示例代码:

" 方式1:SUBMIT 调用可执行程序
" 调用程序 ZOTHER_PROGRAM,并传递参数
SUBMIT zother_program
WITH p_matnr = '0000000001'
WITH p_werks = '1000'
AND RETURN.

" 方式2:SUBMIT 调用并返回
SUBMIT zother_program
WITH p_matnr = '0000000001'
AND RETURN.

" 方式3:SUBMIT 调用不返回
SUBMIT zother_program
WITH p_matnr = '0000000001'.

" 方式4:使用 CALL TRANSACTION 调用事务代码
CALL TRANSACTION 'SE38'.

" 方式5:使用 CALL TRANSACTION 并跳过第一个屏幕
CALL TRANSACTION 'SE38' AND SKIP FIRST SCREEN.

" 方式6:使用 LEAVE TO TRANSACTION
LEAVE TO TRANSACTION 'SE38'.

" 注意事项:
" 1. SUBMIT 用于调用可执行程序(REPORT)
" 2. CALL TRANSACTION 用于调用事务代码
" 3. AND RETURN 表示调用完毕后返回原程序
" 4. AND SKIP FIRST SCREEN 表示跳过第一个屏幕
" 5. LEAVE TO TRANSACTION 会离开当前程序

在一个程序中如何调用其他事物代码?

调用其他事务代码使用”CALL TRANSACTION + 事务代码”,使用 AND SKIP FIRST SCREEN 可以跳过第一个画面,进入下一个画面。

示例代码:

" 方式1:调用事务代码
CALL TRANSACTION 'SE38'.

" 方式2:调用事务代码并跳过第一个屏幕
CALL TRANSACTION 'SE38' AND SKIP FIRST SCREEN.

" 方式3:使用 LEAVE TO TRANSACTION
LEAVE TO TRANSACTION 'SE38'.

" 方式4:使用 SET/GET PARAMETER ID 传递数据
" 设置参数
SET PARAMETER ID 'RID' FIELD 'ZMY_PROGRAM'.

" 调用事务代码
CALL TRANSACTION 'SE38' AND SKIP FIRST SCREEN.

" 注意事项:
" 1. CALL TRANSACTION 会挂起当前程序,调用完毕后返回
" 2. LEAVE TO TRANSACTION 会离开当前程序,不返回
" 3. AND SKIP FIRST SCREEN 跳过第一个屏幕
" 4. 可以使用 SET/GET PARAMETER ID 传递数据
" 5. 事务代码必须在系统中存在

在进行画面跳转时,CAL SCREEN 与 LEAVE TO SCREEN 的区别

CALL SCREEN 是将正在运行的画面挂起,进入所调用的画面,当使用 LEAVE TO SCREEN O 时,能够返回原画面,可理解为嵌套调用:而 LEAVE TO SCREEN 是立即结束本画面的执行,调用所指定的画面,在调用画面中,无法再返回原画面。

示例代码:

" CALL SCREEN 示例:嵌套调用
" 当前屏幕:100
" 调用屏幕:200

MODULE user_command_0100 INPUT.
CASE sy-ucomm.
WHEN 'NEXT'.
" 挂起屏幕100,进入屏幕200
CALL SCREEN 200.
" 从屏幕200返回后,继续执行
WRITE: / '从屏幕200返回'.
ENDCASE.
ENDMODULE.

" 在屏幕200中
MODULE user_command_0200 INPUT.
CASE sy-ucomm.
WHEN 'BACK'.
" 返回屏幕100
LEAVE TO SCREEN 0.
ENDCASE.
ENDMODULE.

" LEAVE TO SCREEN 示例:直接跳转
" 当前屏幕:100
" 跳转到屏幕:200

MODULE user_command_0100 INPUT.
CASE sy-ucomm.
WHEN 'NEXT'.
" 立即结束屏幕100,进入屏幕200
LEAVE TO SCREEN 200.
" 这行代码不会执行
WRITE: / '这行不会执行'.
ENDCASE.
ENDMODULE.

" 区别总结:
" 1. CALL SCREEN:嵌套调用,可以返回原画面
" 2. LEAVE TO SCREEN:直接跳转,无法返回原画面
" 3. CALL SCREEN 使用 LEAVE TO SCREEN 0 返回
" 4. LEAVE TO SCREEN 会立即结束当前屏幕的处理

" 注意事项:
" 1. CALL SCREEN 会挂起当前屏幕序列
" 2. LEAVE TO SCREEN 会结束当前屏幕序列
" 3. 使用 CALL SCREEN 时,要注意屏幕嵌套深度
" 4. 使用 LEAVE TO SCREEN 时,要考虑是否需要返回

与索引相关的系统字段有哪些?分别有什么作用?

SY-INDEX 存储 DO 循环的索引;
SY-TABIX 存储 LOOP 循环到内表的第几行;
SY-DBCNT 存储从数据库中读取到的满足条件的记录条数;
SY-LISEL 存储被选中行的内容,在事件 ATLINE-SELECTION 时起作用,该系统字段中的内容到下一个屏幕的时候还存在。

示例代码:

" SY-INDEX 示例:DO 循环的索引
DATA: lv_count TYPE i.
DO 5 TIMES.
lv_count = sy-index.
WRITE: / '循环次数:', lv_count.
ENDDO.
" 输出:
" 循环次数: 1
" 循环次数: 2
" 循环次数: 3
" 循环次数: 4
" 循环次数: 5

" SY-TABIX 示例:LOOP 循环到内表的第几行
DATA: itab TYPE TABLE OF mara.
SELECT * FROM mara INTO TABLE itab UP TO 5 ROWS.

LOOP AT itab INTO DATA(wa).
WRITE: / '内表行号:', sy-tabix, '物料:', wa-matnr.
ENDLOOP.
" 输出:
" 内表行号: 1 物料: 0000000001
" 内表行号: 2 物料: 0000000002
" 内表行号: 3 物料: 0000000003
" 内表行号: 4 物料: 0000000004
" 内表行号: 5 物料: 0000000005

" SY-DBCNT 示例:从数据库中读取到的满足条件的记录条数
SELECT * FROM mara INTO TABLE itab
WHERE matnr LIKE '000000000%'.

WRITE: / '数据库读取记录数:', sy-dbcnt.

" SY-LISEL 示例:被选中行的内容
" 在交互式报表中使用
AT LINE-SELECTION.
WRITE: / '选中行内容:', sy-lisel.

" 系统字段总结:
" 1. SY-INDEX:DO 循环的当前次数
" 2. SY-TABIX:LOOP 循环的当前行号
" 3. SY-DBCNT:数据库操作的记录数
" 4. SY-LISEL:选中行的内容(交互式报表)
" 5. SY-SUBRC:操作的返回码(0=成功)

LOOP 循环中 AT 控制逻辑有哪些?分别有什么作用

  • AT FIRST 功能:循环中第一条记录时调用。
  • AT LAST 功能:循环中最后一条记录时调用。
  • AT NEW 功能:循环中当指定字段之前(包括指定字段)的组合和上一条记录不一样时调用。
  • AT END OF 功能:循环中当指定字段之前(包括指定字段)的组合和下一条记录不一样时调用。
  • AT 事件的注意点:AT 事件中的工作区不同于 L00P 循环的工作区,该工作区中位于指定字段后面的值将不会被读取,如果需要用到则需 read 重新抽取一次。

示例代码:

" 定义内表
TYPES: BEGIN OF ty_data,
bukrs TYPE bukrs, " 公司代码
gjahr TYPE gjahr, " 会计年度
belnr TYPE belnr_d, " 凭证号
amount TYPE p LENGTH 8 DECIMALS 2,
END OF ty_data.

DATA: itab TYPE TABLE OF ty_data,
wa TYPE ty_data.

" 添加测试数据
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000001'. wa-amount = 100.
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000002'. wa-amount = 200.
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2025'. wa-belnr = '0000000003'. wa-amount = 300.
APPEND wa TO itab.
wa-bukrs = '2000'. wa-gjahr = '2024'. wa-belnr = '0000000004'. wa-amount = 400.
APPEND wa TO itab.

" 排序(AT 事件需要排序后的数据)
SORT itab BY bukrs gjahr.

" 使用 AT 事件
LOOP AT itab INTO wa.
AT FIRST.
WRITE: / '=== 开始处理 ==='.
ENDAT.

AT NEW bukrs.
WRITE: / '公司代码:', wa-bukrs.
ENDAT.

AT NEW gjahr.
WRITE: / ' 会计年度:', wa-gjahr.
ENDAT.

WRITE: / ' 凭证号:', wa-belnr, '金额:', wa-amount.

AT END OF gjahr.
WRITE: / ' 年度小计'.
ENDAT.

AT END OF bukrs.
WRITE: / '公司小计'.
ENDAT.

AT LAST.
WRITE: / '=== 处理结束 ==='.
ENDAT.
ENDLOOP.

" AT 事件总结:
" 1. AT FIRST:循环开始时执行一次
" 2. AT LAST:循环结束时执行一次
" 3. AT NEW f:字段 f 或其左侧字段变化时执行
" 4. AT END OF f:字段 f 或其左侧字段即将变化时执行
" 5. AT 事件需要数据按指定字段排序

MESSAGE 消息有哪些类型和含义?如何自定义 MESSAGE 消息类?

A:Abend,立刻终止当前事务,弹出终止对话框。
X:Exit,立刻终止当前事务,进入 DUMP 的界面。
E:错误,画面的状态栏会弹出一个错误提示信息。
I:信息,弹出一个消息对话框。
S:确认,画面的状态栏会弹出一个正确提示信息。
W:警告,画面的状态栏会弹出一个警告提示信息。
可以通过语句 DISPLAY LIKE mtype 取代原错误图标:自定义 MESSAGE 消息类,通过事务代码 SE91

示例代码:

" 消息类型示例
MESSAGE '操作成功' TYPE 'S'. " 成功消息,在状态栏显示
MESSAGE '输入错误' TYPE 'E'. " 错误消息,在状态栏显示
MESSAGE '确认操作' TYPE 'I'. " 信息消息,弹出对话框
MESSAGE '警告信息' TYPE 'W'. " 警告消息,在状态栏显示
MESSAGE '程序异常' TYPE 'A'. " 终止消息,终止当前事务
MESSAGE '系统错误' TYPE 'X'. " 退出消息,进入 DUMP 界面

" 使用 DISPLAY LIKE 改变消息图标
MESSAGE '操作成功' TYPE 'S' DISPLAY LIKE 'E'. " 成功消息,但显示错误图标

" 自定义消息类示例
" 1. 使用事务代码 SE91 创建消息类
" 2. 在消息类中定义消息编号和文本
" 3. 在程序中使用消息类

" 假设消息类 ZMSG 定义了以下消息:
" 001: 物料 & 不存在
" 002: 工厂 & 无权限
" 003: 操作成功

" 使用消息类
DATA: lv_matnr TYPE matnr VALUE '0000000001'.

" 使用消息编号
MESSAGE i001(zmsg) WITH lv_matnr.

" 使用消息编号和参数
MESSAGE e002(zmsg) WITH '1000'.

" 在选择屏幕中使用消息
AT SELECTION-SCREEN.
IF p_matnr IS INITIAL.
MESSAGE '请输入物料号' TYPE 'E'.
ENDIF.

" 消息类型总结:
" A - 终止:终止当前事务,弹出终止对话框
" X - 退出:终止当前事务,进入 DUMP 界面
" E - 错误:在状态栏显示错误信息
" I - 信息:弹出消息对话框
" S - 成功:在状态栏显示成功信息
" W - 警告:在状态栏显示警告信息

ABAP 中,如何自定义异常类?如何捕捉异常?

通过事务代码 SE24,在 Class Type 中,选择 Exeeption Class 即可定义,在 TRY CATCH 语句中就能够使用。
捕获异常:语法和 JAVA 相似
TRY
CATCH 异常类
ENDTRY.

示例代码:

" 方式1:使用系统异常类
DATA: lx_error TYPE REF TO cx_root.

TRY.
" 可能发生异常的代码
DATA(lv_result) = 1 / 0.
CATCH cx_root INTO lx_error.
WRITE: / '捕获异常:', lx_error->get_text( ).
ENDTRY.

" 方式2:自定义异常类
" 1. 使用事务代码 SE24 创建异常类
" 2. 选择 Class Type 为 Exception Class
" 3. 定义异常属性和方法

" 假设自定义异常类 ZCX_MY_EXCEPTION
DATA: lx_my_error TYPE REF TO zcx_my_exception.

TRY.
" 调用可能抛出异常的方法
" zcl_my_class=>method( ).
CATCH zcx_my_exception INTO lx_my_error.
WRITE: / '自定义异常:', lx_my_error->get_text( ).
ENDTRY.

" 方式3:使用 RAISE EXCEPTION 抛出异常
DATA: lx_error TYPE REF TO cx_root.

TRY.
" 抛出异常
RAISE EXCEPTION TYPE cx_sy_zerodivide.
CATCH cx_sy_zerodivide.
WRITE: / '捕获除零异常'.
ENDTRY.

" 方式4:在方法中抛出异常
METHODS: my_method
EXCEPTIONS
my_exception.

METHOD my_method.
IF error_condition = 'X'.
RAISE my_exception.
ENDIF.
ENDMETHOD.

" 调用方法并处理异常
CALL METHOD my_method
EXCEPTIONS
my_exception = 1
OTHERS = 2.

IF sy-subrc <> 0.
WRITE: / '方法调用失败'.
ENDIF.

" 异常处理总结:
" 1. TRY...CATCH...ENDTRY:捕获异常
" 2. RAISE EXCEPTION:抛出异常
" 3. EXCEPTIONS:在方法中声明异常
" 4. cx_root:所有异常类的基类
" 5. 自定义异常类:通过 SE24 创建

什么是事物变式?事务变式有什么用?

事务变式是一组屏幕变式,用于预定义屏雅行为和默认值。通过使用变式功能,可以將用户不需要的字段、子屏幕及全屏幕从用户视图中取消。可以给任何输入宇段设置默认值,字段也可以不用带”Ready for Input” 状态。只能为对话和报表事务创建事务变式:变式中只能包含普通屏幕、子屏幕及对话屏幕。
开发人员可以使用 GuiXT 脚本语言 t 通过事务变式维护对屏幕进行修改。修改屏幕布局的方式有:插入按钮、值帮助(Value Helps)、移动对象、插入屏幕等。

示例代码:

" 事务变式示例
" 1. 使用事务代码 SHD0 创建事务变式
" 2. 输入事务代码(如 SE38)
" 3. 点击"创建变式"
" 4. 在变式中设置字段默认值和可见性

" 使用事务变式
" 方式1:在程序中调用带变式的事务
CALL TRANSACTION 'SE38' USING 'ZMY_VARIANT'.

" 方式2:在选择屏幕中使用变式
REPORT zmy_program.
TABLES: mara.

SELECT-OPTIONS: s_matnr FOR mara-matnr.
PARAMETERS: p_werks TYPE werks_d.

" 使用变式
INITIALIZATION.
" 设置默认值
s_matnr-sign = 'I'.
s_matnr-option = 'EQ'.
s_matnr-low = '0000000001'.
APPEND s_matnr.

p_werks = '1000'.

" 事务变式的作用:
" 1. 预定义屏幕行为和默认值
" 2. 隐藏不需要的字段
" 3. 设置字段为只读
" 4. 创建按钮和值帮助
" 5. 修改屏幕布局

" 创建事务变式的步骤:
" 1. 事务代码 SHD0
" 2. 输入事务代码
" 3. 点击"创建变式"
" 4. 设置变式名称和描述
" 5. 在屏幕中设置字段属性
" 6. 保存并激活变式

" 事务变式的应用场景:
" 1. 为不同用户组提供不同的屏幕界面
" 2. 简化复杂事务的操作
" 3. 提供默认值,减少用户输入
" 4. 限制用户只能访问特定功能
" 5. 创建自定义的屏幕布局

RANGE 表内的字段有哪些?分别有什么含义?

RANGE 表包括 SIGN、OPTION、LOW、 HIGH 四个字段。RANGE 表和 select-option 的变量差不多,sign = ‘I’ 或 sign = ‘E’, 是指 INCLUDE 和 EXCLUDE, OPTION = ‘EQ’ 或其他操作符,LOW 是指最少值(操作符是范围的话),HIGH 指最大值(如果操作符是’EQ’,该字段不起作用)。

示例代码:

" 定义 RANGE 表
DATA: rng_matnr TYPE RANGE OF mara-matnr,
rng_werks TYPE RANGE OF marc-werks,
wa_range LIKE LINE OF rng_matnr.

" 方式1:直接添加数据
wa_range-sign = 'I'. " INCLUDE
wa_range-option = 'EQ'. " 等于
wa_range-low = '0000000001'.
APPEND wa_range TO rng_matnr.

wa_range-sign = 'I'. " INCLUDE
wa_range-option = 'BT'. " BETWEEN
wa_range-low = '0000000010'.
wa_range-high = '0000000020'.
APPEND wa_range TO rng_matnr.

wa_range-sign = 'E'. " EXCLUDE
wa_range-option = 'EQ'. " 等于
wa_range-low = '0000000015'.
APPEND wa_range TO rng_matnr.

" 方式2:使用 SELECT-OPTIONS
SELECT-OPTIONS: s_matnr FOR mara-matnr.

" 使用 RANGE 表
SELECT * FROM mara INTO TABLE itab_mara
WHERE matnr IN rng_matnr.

" RANGE 表字段说明:
" SIGN:'I' = INCLUDE(包含),'E' = EXCLUDE(排除)
" OPTION:操作符
" 'EQ' = 等于(=)
" 'NE' = 不等于(<>)
" 'GT' = 大于(>)
" 'LT' = 小于(<)
" 'GE' = 大于等于(>=)
" 'LE' = 小于等于(<=)
" 'BT' = BETWEEN(在...之间)
" 'NB' = NOT BETWEEN(不在...之间)
" 'CP' = CONTAINS PATTERN(包含模式)
" 'NP' = NOT CONTAINS PATTERN(不包含模式)
" LOW:下限值
" HIGH:上限值(仅当 OPTION 为 'BT' 或 'NB' 时使用)

" 示例:物料号范围
DATA: rng_matnr2 TYPE RANGE OF mara-matnr.

" 0000000001 到 0000000010
APPEND VALUE #( sign = 'I' option = 'BT' low = '0000000001' high = '0000000010' ) TO rng_matnr2.

" 排除 0000000005
APPEND VALUE #( sign = 'E' option = 'EQ' low = '0000000005' ) TO rng_matnr2.

" 使用 RANGE 表查询
SELECT * FROM mara INTO TABLE itab_mara
WHERE matnr IN rng_matnr2.

" 结果:包含 0000000001 到 0000000010,但排除 0000000005

对于 FIELD SYMBOL 斌值将使用什么关键字?

ASSIGN

示例代码:

DATA: lv_var TYPE c LENGTH 10 VALUE 'Hello'.
FIELD-SYMBOLS: <fs> TYPE c.

" 将变量赋值给字段符号
ASSIGN lv_var TO <fs>.
WRITE: / <fs>. " 输出: Hello

" 动态赋值
DATA: lv_fieldname TYPE string VALUE 'LV_VAR'.
ASSIGN (lv_fieldname) TO <fs>.
WRITE: / <fs>. " 输出: Hello

" 内表行赋值
DATA: itab TYPE TABLE OF mara.
FIELD-SYMBOLS: <wa> LIKE LINE OF itab.
LOOP AT itab ASSIGNING <wa>.
" 直接修改内表行
<wa>-matnr = 'NEW_VALUE'.
ENDLOOP.

PERFORM 调用子程序时,分别对 USING、 TABLES、 CHANGING 3 个参数有什么影响?

USING 可以传入单值、工作区,加 VALUE 参数后只能使用,不能对值做修改,PERFORM 结束后传入参数的值还是传入之前的状态。
TABLES 只能传入一个内表,程序逻辑可以对其进行修改,PERFORM 结束后内表的值是影响后的值。
CHANGING 可以传入内表、单值,程序逻辑可以对其进行修改,PERFORM 结束后传入参数的值是影响后的值。
注意,在传入工作区、单值的情況下,USING 不加 VALUE 参数与 CHANGING 结果相同。

示例代码:

" 定义变量
DATA: lv_us TYPE i VALUE 10,
lv_ch TYPE i VALUE 20,
lv_usv TYPE i VALUE 30,
lv_chv TYPE i VALUE 40.

" 定义内表
DATA: itab TYPE TABLE OF mara.
SELECT * FROM mara INTO TABLE itab UP TO 3 ROWS.

" 调用 FORM
PERFORM test_form USING lv_us lv_ch lv_usv lv_chv
TABLES itab
CHANGING lv_ch.

WRITE: / 'USING 后:', lv_us. " 改变 (引用传递)
WRITE: / 'CHANGING 后:', lv_ch. " 改变 (引用传递)
WRITE: / 'USING VALUE 后:', lv_usv. " 不改变 (值传递)
WRITE: / 'CHANGING VALUE 后:', lv_chv. " 改变 (值传递,正常结束)

" FORM 定义
FORM test_form USING p_us p_ch p_usv p_chv
TABLES p_itab TYPE mara
CHANGING p_ch TYPE i.
p_us = 100. " 修改引用传递的值
p_ch = 200. " 修改引用传递的值
p_usv = 300. " 修改值传递的值(不影响原变量)
p_chv = 400. " 修改值传递的值(正常结束后影响原变量)

" 修改内表
LOOP AT p_itab INTO DATA(wa).
wa-matnr = 'NEW_' && wa-matnr.
MODIFY p_itab FROM wa.
ENDLOOP.
ENDFORM.

" 输出结果:
" USING 后: 100 (引用传递,被修改)
" CHANGING 后: 200 (引用传递,被修改)
" USING VALUE 后: 30 (值传递,不改变)
" CHANGING VALUE 后: 40 (值传递,正常结束后改变)

" 内表也被修改
LOOP AT itab INTO DATA(wa).
WRITE: / wa-matnr.
ENDLOOP.

如何在程序间传送数据?

可以使用 EXPORT 语句在 ABAP/4 内存中存储数据字段簇。
语句为:EXPORT TO MEMORY ID .
而调用程序就会检索数据。
语句为:IMPORT … < OBJECTN> FROM MEMORY ID < ID-NAME>。
其中:ID 参数标识唯一的数据簇。

示例代码:

" 程序1:导出数据到 ABAP 内存
REPORT zprogram1.

DATA: lv_var1 TYPE string VALUE 'Hello',
lv_var2 TYPE i VALUE 100,
lv_var3 TYPE dats VALUE '20240101'.

" 导出到 ABAP 内存
EXPORT lv_var1 lv_var2 lv_var3 TO MEMORY ID 'ZTEST_MEMORY'.

WRITE: / '数据已导出到 ABAP 内存'.
WRITE: / '变量1:', lv_var1.
WRITE: / '变量2:', lv_var2.
WRITE: / '变量3:', lv_var3.

" 程序2:从 ABAP 内存导入数据
REPORT zprogram2.

DATA: lv_import_var1 TYPE string,
lv_import_var2 TYPE i,
lv_import_var3 TYPE dats.

" 从 ABAP 内存导入
IMPORT lv_var1 lv_var2 lv_var3 FROM MEMORY ID 'ZTEST_MEMORY'.

IF sy-subrc = 0.
WRITE: / '从 ABAP 内存导入数据成功'.
WRITE: / '变量1:', lv_import_var1.
WRITE: / '变量2:', lv_import_var2.
WRITE: / '变量3:', lv_import_var3.
ELSE.
WRITE: / '导入失败'.
ENDIF.

" 释放 ABAP 内存
FREE MEMORY ID 'ZTEST_MEMORY'.

" 导出内表到 ABAP 内存
DATA: itab TYPE TABLE OF mara.
SELECT * FROM mara INTO TABLE itab UP TO 10 ROWS.
EXPORT itab TO MEMORY ID 'ZTEST_ITAB'.

" 从 ABAP 内存导入内表
DATA: itab_import TYPE TABLE OF mara.
IMPORT itab FROM MEMORY ID 'ZTEST_ITAB'.
WRITE: / '导入的内表行数:', lines( itab_import ).

" 注意事项:
" 1. EXPORT 和 IMPORT 语句中的变量名必须一致
" 2. ID 参数区分大小写
" 3. ABAP 内存只在当前会话中有效
" 4. 使用 FREE MEMORY 释放内存

SAP 和 ABAP 内存的区别?

  • 读取时使用方法不同
    SAP 内存使用 SET/GET parameters 方法。
    ABAP 内存使用 EXPORT 和 IMPORT 方法。
  • 共享范围不同
    SAP 内存可以被所有的主 Sesion 访问,内存数据可以在同一个 Session 中的不同程序或者不同 Session 之间共享数据。
    ABAP 只能在同个 Session 的不同程序之间共享數据。
  • 作用范围不同
    SAP 内存在整个终端 Session 时间内都有效。
    ABAP 内存只能在一个 Session 时间内有效。
  • 使用一般原则
    SAP 内存用于屏幕默认值输入。
    ABAP 内存用于模块之间传替数据。

示例代码:

" SAP 内存示例:使用 SET/GET parameters
" 设置 SAP 内存参数
SET PARAMETER ID 'MAT' FIELD '0000000001'.
SET PARAMETER ID 'WRK' FIELD '1000'.

" 获取 SAP 内存参数
DATA: lv_matnr TYPE matnr,
lv_werks TYPE werks_d.

GET PARAMETER ID 'MAT' FIELD lv_matnr.
GET PARAMETER ID 'WRK' FIELD lv_werks.

WRITE: / 'SAP 内存 - 物料:', lv_matnr.
WRITE: / 'SAP 内存 - 工厂:', lv_werks.

" SAP 内存特点:
" 1. 可以在不同 Session 之间共享
" 2. 在整个终端 Session 时间内有效
" 3. 用于屏幕默认值输入
" 4. 使用 SET/GET PARAMETER ID 语句

" ABAP 内存示例:使用 EXPORT/IMPORT
DATA: lv_var1 TYPE string VALUE 'Hello',
lv_var2 TYPE i VALUE 100.

" 导出到 ABAP 内存
EXPORT lv_var1 lv_var2 TO MEMORY ID 'ZTEST'.

" 从 ABAP 内存导入
DATA: lv_import_var1 TYPE string,
lv_import_var2 TYPE i.

IMPORT lv_var1 lv_var2 FROM MEMORY ID 'ZTEST'.

WRITE: / 'ABAP 内存 - 变量1:', lv_import_var1.
WRITE: / 'ABAP 内存 - 变量2:', lv_import_var2.

" 释放 ABAP 内存
FREE MEMORY ID 'ZTEST'.

" ABAP 内存特点:
" 1. 只能在同个 Session 的不同程序之间共享
" 2. 只在一个 Session 时间内有效
" 3. 用于模块之间传递数据
" 4. 使用 EXPORT/IMPORT 语句

" 区别总结:
" 1. SAP 内存:跨 Session,长期有效,用于屏幕默认值
" 2. ABAP 内存:同 Session,短期有效,用于程序间数据传递

应用技术

自定义搜索帮助有几种方式?如何实现?

  • SE11 创建搜索帮助。
  • 调用函数 F4IF_INT_TABLE_VALUE_REQUEST。
    在 POV 中或者 AT SELECTION-SCREEN ON VALUE-REQUEST FOR 中使用自定义的搜索帮助可以使用函数 “F4IF_INT_TABLE_VALUE_REQUEST”。

示例代码:

" 方式1:使用 SE11 创建搜索帮助
" 1. 事务代码 SE11 -> 搜索帮助 -> 创建
" 2. 定义选择方法(表或视图)
" 3. 定义参数(字段)
" 4. 激活搜索帮助

" 方式2:使用函数 F4IF_INT_TABLE_VALUE_REQUEST
DATA: itab_values TYPE TABLE OF string,
wa_value TYPE string.

" 准备搜索帮助数据
APPEND '0000000001' TO itab_values.
APPEND '0000000002' TO itab_values.
APPEND '0000000003' TO itab_values.

" 在选择屏幕中使用搜索帮助
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_matnr.
" 调用搜索帮助函数
CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
EXPORTING
retfield = 'MATNR'
value_org = 'S'
TABLES
value_tab = itab_values
EXCEPTIONS
parameter_error = 1
no_values_found = 2
OTHERS = 3.

IF sy-subrc = 0.
" 用户选择了值
READ TABLE itab_values INTO wa_value INDEX 1.
p_matnr = wa_value.
ENDIF.

" 在 POV(Process On Value-Request)中使用搜索帮助
PROCESS ON VALUE-REQUEST.
FIELD p_matnr MODULE f4_help.

MODULE f4_help INPUT.
" 准备更复杂的数据
DATA: BEGIN OF itab_help OCCURS 0,
matnr TYPE matnr,
maktx TYPE maktx,
END OF itab_help.

SELECT a~matnr b~maktx
INTO TABLE itab_help
FROM mara AS a
INNER JOIN makt AS b ON a~matnr = b~matnr
WHERE b~spras = sy-langu
UP TO 10 ROWS.

" 调用搜索帮助函数
CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
EXPORTING
retfield = 'MATNR'
value_org = 'S'
TABLES
value_tab = itab_help
EXCEPTIONS
parameter_error = 1
no_values_found = 2
OTHERS = 3.

IF sy-subrc = 0.
" 用户选择了值
READ TABLE itab_help INDEX 1.
p_matnr = itab_help-matnr.
ENDIF.
ENDMODULE.

怎么创建编码范围(Number Range)

首先使用事务代码 SNUM/SNRO 创建,在使用编码范围的编号时,需要首先将其锁定(函数:NUMBER_RANGE_ENQUEUE),然后取出编号(函数:NUMBER_GET_NEXT),最
后解锁(函数:NUMBER_RANGE_DEQUEUE)。

示例代码:

" 假设编码范围对象名为 ZNR_OBJECT
" 在 SNUM/SNRO 中创建编码范围对象后,使用以下代码获取编号

DATA: lv_number TYPE num10.

" 锁定编码范围
CALL FUNCTION 'NUMBER_RANGE_ENQUEUE'
EXPORTING
object = 'ZNR_OBJECT'
EXCEPTIONS
foreign_lock = 1
object_not_found = 2
system_failure = 3
OTHERS = 4.

IF sy-subrc = 0.
" 获取下一个编号
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01'
object = 'ZNR_OBJECT'
IMPORTING
number = lv_number
EXCEPTIONS
interval_not_found = 1
number_range_not_intern = 2
object_not_found = 3
quantity_is_0 = 4
quantity_is_not_1 = 5
interval_overflow = 6
buffer_overflow = 7
OTHERS = 8.

IF sy-subrc = 0.
WRITE: / '获取到的编号:', lv_number.
ENDIF.

" 解锁编码范围
CALL FUNCTION 'NUMBER_RANGE_DEQUEUE'
EXPORTING
object = 'ZNR_OBJECT'.
ENDIF.

函数中,如何给出错误消息?

有以下两种方式:

  • 在 TABLES 参数中增加返回表(Return Table),可以将执行过程中产生的错误消息返回,在函数外对返回表做处理。
  • 定义 EXCEPTION,当出现错误时,用 RASING 语句抛异常,RASING 语句会直接影响 SY-SUBRC 的值,函数外可以直接根据 SY-SUBRC 来做处理。

示例代码:

" 方式1:使用返回表
FUNCTION ztest_function.
TABLES
return STRUCTURE bapiret2.

" 业务逻辑
IF error_condition = 'X'.
" 添加错误消息到返回表
APPEND VALUE bapiret2(
type = 'E'
id = 'ZMSG'
number = '001'
message = '发生错误'
) TO return.
RETURN.
ENDIF.
ENDFUNCTION.

" 调用函数并处理返回表
DATA: lt_return TYPE TABLE OF bapiret2.

CALL FUNCTION 'ZTEST_FUNCTION'
TABLES
return = lt_return.

" 检查返回表
LOOP AT lt_return INTO DATA(ls_return) WHERE type = 'E'.
MESSAGE ls_return-message TYPE 'E'.
ENDLOOP.

" 方式2:使用异常
FUNCTION ztest_function2.
EXCEPTIONS
error_occurred = 1.

IF error_condition = 'X'.
RAISE error_occurred.
ENDIF.
ENDFUNCTION.

" 调用函数并处理异常
CALL FUNCTION 'ZTEST_FUNCTION2'
EXCEPTIONS
error_occurred = 1
OTHERS = 2.

IF sy-subrc <> 0.
MESSAGE '函数执行失败' TYPE 'E'.
ENDIF.

如何优化 ABAP 程序?

  • 可通过运行事务代码 SE30,运行待检查的程序,查看各操作的百分比。
  • 避免在循环语句中使用 SELECT 语句,而是通过 FOR ALL ENTRIES IN 语句抽取数据到内表中。
  • 通过运行事务代码 SLIN 进行扩展检查,根据 SAP 提供的反馈信息优化代码。

示例代码:

" 优化前:在循环中使用 SELECT(性能差)
LOOP AT itab INTO wa.
SELECT SINGLE * FROM mara INTO wa_mara
WHERE matnr = wa-matnr.
" 处理数据
ENDLOOP.

" 优化后:使用 FOR ALL ENTRIES(性能好)
SELECT * FROM mara INTO TABLE itab_mara
FOR ALL ENTRIES IN itab
WHERE matnr = itab-matnr.

LOOP AT itab INTO wa.
READ TABLE itab_mara INTO wa_mara
WITH KEY matnr = wa-matnr.
IF sy-subrc = 0.
" 处理数据
ENDIF.
ENDLOOP.

" 优化前:使用 SELECT *(性能差)
SELECT * FROM mara INTO TABLE itab_mara
WHERE matnr LIKE '000000000%'.

" 优化后:只选择需要的字段(性能好)
SELECT matnr mtart FROM mara INTO TABLE itab_mara
WHERE matnr LIKE '000000000%'.

" 优化前:使用嵌套循环(性能差)
LOOP AT itab1 INTO wa1.
LOOP AT itab2 INTO wa2 WHERE bukrs = wa1-bukrs.
" 处理数据
ENDLOOP.
ENDLOOP.

" 优化后:使用 SORTED TABLE 或 HASHED TABLE(性能好)
DATA: itab2_sorted TYPE SORTED TABLE OF ty_data
WITH UNIQUE KEY bukrs.
" 填充排序表
LOOP AT itab1 INTO wa1.
READ TABLE itab2_sorted INTO wa2
WITH TABLE KEY bukrs = wa1-bukrs.
IF sy-subrc = 0.
" 处理数据
ENDIF.
ENDLOOP.

" 使用 SE30 运行时分析
" 1. 事务代码 SE30
" 2. 输入程序名
" 3. 点击"执行"
" 4. 查看"命中列表"和"数据库"标签

" 使用 SLIN 扩展检查
" 1. 事务代码 SLIN
" 2. 输入程序名
" 3. 点击"执行"
" 4. 根据反馈信息优化代码

前导零的处理方式

CALL FUNCTION ‘CONVERSION_EXIT_ALPHA_INPUT’. “添加前导 0
CALL FUNCTION ‘CONVERSION_EXIT_ALPHA_OUTPUT’. “去掉前导 0
SHIFT 物料编码 LEFT DELETING LEADING ‘O. “去掉前导 0

示例代码:

" 添加前导零
DATA: lv_matnr TYPE matnr VALUE '12345'.

" 方式1:使用函数添加前导零
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
EXPORTING
input = lv_matnr
IMPORTING
output = lv_matnr.

WRITE: / '添加前导零后:', lv_matnr. " 输出: 00000000012345

" 方式2:使用字符串操作添加前导零
lv_matnr = '12345'.
lv_matnr = |{ lv_matnr ALPHA = IN }|.
WRITE: / '字符串操作添加前导零:', lv_matnr. " 输出: 00000000012345

" 去掉前导零
lv_matnr = '00000000012345'.

" 方式1:使用函数去掉前导零
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
EXPORTING
input = lv_matnr
IMPORTING
output = lv_matnr.

WRITE: / '去掉前导零后:', lv_matnr. " 输出: 12345

" 方式2:使用 SHIFT 去掉前导零
lv_matnr = '00000000012345'.
SHIFT lv_matnr LEFT DELETING LEADING '0'.
WRITE: / 'SHIFT 去掉前导零:', lv_matnr. " 输出: 12345

" 方式3:使用字符串操作去掉前导零
lv_matnr = '00000000012345'.
lv_matnr = |{ lv_matnr ALPHA = OUT }|.
WRITE: / '字符串操作去掉前导零:', lv_matnr. " 输出: 12345

" 注意事项:
" 1. ALPHA = IN 添加前导零
" 2. ALPHA = OUT 去掉前导零
" 3. 函数方式更通用,字符串操作方式更简洁
" 4. 去掉前导零后,变量长度会变短

怎样从文件服务器上读取文件?怎样写文件到文件服务器上?

从服务器上打开文件时,要确定文件的操作方式,调用语句:
OPEN DATASET dset FOR access IN mode [position] [os_addition] [error_handling].

从服务器上读取文件时,access 用 output 替换,再调用语句:
READ DATASET dset INTO dobj [MAXIMUM LENGTH mlen] [[ACTUAL] LENGTH alen].

写文件到服务器上 access 用 input 替换,再在调用语句:
TRANSFER dobj TO dset [LENGTH len] [NO END OF LINE].

示例代码:

" 写文件到服务器
DATA: lv_filename TYPE string VALUE '/tmp/test.txt',
lv_data TYPE string VALUE 'Hello World'.

OPEN DATASET lv_filename FOR OUTPUT IN TEXT MODE ENCODING DEFAULT.
IF sy-subrc = 0.
TRANSFER lv_data TO lv_filename.
CLOSE DATASET lv_filename.
WRITE: / '文件写入成功'.
ENDIF.

" 从服务器读取文件
DATA: lv_read_data TYPE string.

OPEN DATASET lv_filename FOR INPUT IN TEXT MODE ENCODING DEFAULT.
IF sy-subrc = 0.
READ DATASET lv_filename INTO lv_read_data.
CLOSE DATASET lv_filename.
WRITE: / '读取的内容:', lv_read_data.
ENDIF.

" 追加内容到文件
OPEN DATASET lv_filename FOR APPENDING IN TEXT MODE ENCODING DEFAULT.
IF sy-subrc = 0.
lv_data = 'New Line'.
TRANSFER lv_data TO lv_filename.
CLOSE DATASET lv_filename.
WRITE: / '内容追加成功'.
ENDIF.

" 删除文件
DELETE DATASET lv_filename.
IF sy-subrc = 0.
WRITE: / '文件删除成功'.
ENDIF.

在使用 ABAP 内存的程序间进行数据交换时用到的两个语句是什么?

EXPORT TO MEMORY ID 用于将数据复制到 ABAP 内存,IMPORT FROM MEMORY ID 用于将数据从 ABAP 内存复制到程序中。
在 ABAP 内存间进行交换的数据必须在两个程序中都进行声明,并包含同样的数据声明。

示例代码:

" 程序1:导出数据到 ABAP 内存
DATA: lv_var1 TYPE string VALUE 'Hello',
lv_var2 TYPE i VALUE 100.

" 导出到 ABAP 内存
EXPORT lv_var1 lv_var2 TO MEMORY ID 'ZTEST_MEMORY'.

" 程序2:从 ABAP 内存导入数据
DATA: lv_import_var1 TYPE string,
lv_import_var2 TYPE i.

" 从 ABAP 内存导入
IMPORT lv_var1 lv_var2 FROM MEMORY ID 'ZTEST_MEMORY'.

IF sy-subrc = 0.
WRITE: / '导入的变量1:', lv_var1.
WRITE: / '导入的变量2:', lv_var2.
ENDIF.

" 释放 ABAP 内存
FREE MEMORY ID 'ZTEST_MEMORY'.

" 导出内表到 ABAP 内存
DATA: itab TYPE TABLE OF mara.
SELECT * FROM mara INTO TABLE itab UP TO 10 ROWS.
EXPORT itab TO MEMORY ID 'ZTEST_ITAB'.

" 从 ABAP 内存导入内表
DATA: itab_import TYPE TABLE OF mara.
IMPORT itab FROM MEMORY ID 'ZTEST_ITAB'.
WRITE: / '导入的内表行数:', lines( itab_import ).

SELECT SINGLE 和 SELECT UP TO 1 ROWS 的区别是什么?

SELECT SINGLE WHERE 条件要确定唯一记录;
SELECT UP TO 1 ROWS 可以确定多条记录,只取第一条,一般用来进行存在性检查。

示例代码:

" SELECT SINGLE - 必须确定唯一记录
DATA: wa_mara TYPE mara.

SELECT SINGLE *
INTO wa_mara
FROM mara
WHERE matnr = '0000000001'.

IF sy-subrc = 0.
WRITE: / '物料描述:', wa_mara-mtart.
ENDIF.

" SELECT UP TO 1 ROWS - 可以确定多条记录,只取第一条
DATA: itab_mara TYPE TABLE OF mara.

SELECT *
INTO TABLE itab_mara
FROM mara
WHERE matnr LIKE '000000000%'
UP TO 1 ROWS.

IF sy-subrc = 0.
READ TABLE itab_mara INTO wa_mara INDEX 1.
WRITE: / '第一条物料:', wa_mara-matnr.
ENDIF.

" 区别说明:
" 1. SELECT SINGLE 用于确定唯一记录,如果有多条记录返回,结果不确定
" 2. SELECT UP TO 1 ROWS 用于存在性检查,可以配合 ORDER BY 使用
" 3. SELECT SINGLE 不能使用 ORDER BY
" 4. SELECT UP TO 1 ROWS 可以使用 ORDER BY 来确定返回哪一条记录

" 使用 ORDER BY 的例子
SELECT *
INTO TABLE itab_mara
FROM mara
WHERE matnr LIKE '000000000%'
ORDER BY matnr ASCENDING
UP TO 1 ROWS.

IF sy-subrc = 0.
READ TABLE itab_mara INTO wa_mara INDEX 1.
WRITE: / '最小的物料号:', wa_mara-matnr.
ENDIF.

清空内表命令 CLEAR、 REFRESH 和 FREE 的区别?

如果内表有表头行:
CLEAR ITAB 同时清空内表数据行和表头行的内容;
CLEAR ITAB[] 只清空内表本身,保留表头行的内;
REFRESH 只清空内表本身,保留表头行的内容。
如果内表没有表头行:
CLEAR 和 REFRESH 一样,清空内表。
CLEAR ITAB.: 带表头行时只清空表头行,不带表头行时清空整个内表。
CLEAR ITAB[].:只清空整个内表对象数据,不清空表头行。
REFRESH ITAB 或 REFRESH ITAB[].:只清空整个内表对象数据,不清空表头行。
FREE ITAB.或 FREE ITAB[].:只清空整个内表对象数据,不清空表头行,同时释放内存。

示例代码:

" 带表头行的内表
DATA: itab TYPE TABLE OF mara WITH HEADER LINE.

" 添加数据
SELECT * FROM mara INTO TABLE itab UP TO 5 ROWS.
WRITE: / '初始行数:', lines( itab ).
WRITE: / '表头行内容:', itab-matnr.

" CLEAR ITAB - 清空表头行和内表数据
CLEAR itab.
WRITE: / 'CLEAR 后行数:', lines( itab ). " 0
WRITE: / 'CLEAR 后表头:', itab-matnr. " 空

" 重新添加数据
SELECT * FROM mara INTO TABLE itab UP TO 5 ROWS.

" CLEAR ITAB[] - 只清空内表数据,保留表头行
CLEAR itab[].
WRITE: / 'CLEAR[] 后行数:', lines( itab ). " 0
WRITE: / 'CLEAR[] 后表头:', itab-matnr. " 有值

" 重新添加数据
SELECT * FROM mara INTO TABLE itab UP TO 5 ROWS.

" REFRESH ITAB - 只清空内表数据,保留表头行
REFRESH itab.
WRITE: / 'REFRESH 后行数:', lines( itab ). " 0
WRITE: / 'REFRESH 后表头:', itab-matnr. " 有值

" 重新添加数据
SELECT * FROM mara INTO TABLE itab UP TO 5 ROWS.

" FREE ITAB - 清空内表数据并释放内存
FREE itab.
WRITE: / 'FREE 后行数:', lines( itab ). " 0
WRITE: / 'FREE 后表头:', itab-matnr. " 有值

" 不带表头行的内表
DATA: itab2 TYPE TABLE OF mara.
SELECT * FROM mara INTO TABLE itab2 UP TO 5 ROWS.

" 对于不带表头行的内表,CLEAR 和 REFRESH 效果相同
CLEAR itab2.
WRITE: / 'CLEAR 后行数:', lines( itab2 ). " 0

SELECT * FROM mara INTO TABLE itab2 UP TO 5 ROWS.
REFRESH itab2.
WRITE: / 'REFRESH 后行数:', lines( itab2 ). " 0

SELECT * FROM mara INTO TABLE itab2 UP TO 5 ROWS.
FREE itab2.
WRITE: / 'FREE 后行数:', lines( itab2 ). " 0

FORM 中 USING、 CHANGING、 USING VALUE、 CHANGING VALUE 的区别?

  • USING、 CHANGING 引用传递,改变值。
  • USING VALUE 值传递,值不改变。
  • CHANGING VALUE 值传递,当正常结束后值改变。

示例代码:

" 测试 USING 和 CHANGING 的区别
DATA: lv_us TYPE i VALUE 10,
lv_ch TYPE i VALUE 20,
lv_usv TYPE i VALUE 30,
lv_chv TYPE i VALUE 40.

" 调用 FORM
PERFORM test_form USING lv_us lv_ch lv_usv lv_chv.

WRITE: / 'USING 后:', lv_us. " 改变 (引用传递)
WRITE: / 'CHANGING 后:', lv_ch. " 改变 (引用传递)
WRITE: / 'USING VALUE 后:', lv_usv. " 不改变 (值传递)
WRITE: / 'CHANGING VALUE 后:', lv_chv. " 改变 (值传递,正常结束)

" FORM 定义
FORM test_form USING p_us p_ch p_usv p_chv.
p_us = 100. " 修改引用传递的值
p_ch = 200. " 修改引用传递的值
p_usv = 300. " 修改值传递的值(不影响原变量)
p_chv = 400. " 修改值传递的值(正常结束后影响原变量)
ENDFORM.

" 输出结果:
" USING 后: 100 (引用传递,被修改)
" CHANGING 后: 200 (引用传递,被修改)
" USING VALUE 后: 30 (值传递,不改变)
" CHANGING VALUE 后: 40 (值传递,正常结束后改变)

" 注意:如果 FORM 中发生错误退出,CHANGING VALUE 不会改变原变量
FORM test_error_form CHANGING VALUE(p_chv) TYPE i.
p_chv = 999.
" 模拟错误退出
MESSAGE '错误' TYPE 'E'.
ENDFORM.

MESSAGE 参数最多几个占位符,占位符对应的消息文本长度最长?屏幕显示消息文本的长度最长?

最多占 4 个占位符,一个&为 50 个字节长,加上 MESSAGE 自身共有的长度为 240。
所有的消息都存储在系统数据库表 T100 中,该表包括四个字段:
SPRSL (Language Key):语言代码。
ARBGB (Message class):消息类,用于将消息按应用分类储存。
MSGNR (Message number):三个数字的消息序列号。
TEXT (Messege text): 消息文本,是显示在屏幕中的信息文字,最多可以有 72 个字符。
消息类型共六种:A(终止)、E(错误)、I(信息)、S(成功)、W(警告)、x(退出)。

示例代码:

" 消息占位符示例
DATA: lv_matnr TYPE matnr VALUE '0000000001',
lv_werks TYPE werks_d VALUE '1000'.

" 使用4个占位符
MESSAGE i001(zmsg) WITH lv_matnr lv_werks '第三参数' '第四参数'.

" 消息类 ZMSG 中的消息 001 定义为:
" 物料 & 在工厂 & 中不存在,原因:&,备注:&

" 消息文本长度限制:
" - 每个占位符 (&): 最多 50 字节
" - 消息总长度: 最多 240 字节
" - T100 表中的 TEXT 字段: 最多 72 字符

" 消息类型示例
MESSAGE '操作成功' TYPE 'S'. " 成功消息,在状态栏显示
MESSAGE '输入错误' TYPE 'E'. " 错误消息,在状态栏显示
MESSAGE '确认操作' TYPE 'I'. " 信息消息,弹出对话框
MESSAGE '警告信息' TYPE 'W'. " 警告消息,在状态栏显示
MESSAGE '程序异常' TYPE 'A'. " 终止消息,终止当前事务
MESSAGE '系统错误' TYPE 'X'. " 退出消息,进入 DUMP 界面

" 使用 DISPLAY LIKE 改变消息图标
MESSAGE '操作成功' TYPE 'S' DISPLAY LIKE 'E'. " 成功消息,但显示错误图标

" 在选择屏幕中使用消息
AT SELECTION-SCREEN.
IF p_matnr IS INITIAL.
MESSAGE '请输入物料号' TYPE 'E'.
ENDIF.

使用 FOR ALL ENTRIES 时的注意事项有哪些?

类似外连接的时候,用一个表的某一字段为条件,从另一数据库表中取得数据。
向 LOOP 套 SELECT 的时候用 2 个表,为了提升效率用 FOR ALLL ENTRIES.

  • 使用前要检查内表是否为空,否则会查询出全部数据。
  • 要尽可能多的用关键字,否则会把重复的删掉。
  • 此子句中不能使用 LIKE、 BETWEEN、 IN、 ORDER BY.

示例代码:

" 定义内表
DATA: itab_mara TYPE TABLE OF mara,
itab_makt TYPE TABLE OF makt,
wa_mara TYPE mara,
wa_makt TYPE makt.

" 首先查询物料主数据
SELECT * FROM mara INTO TABLE itab_mara
WHERE matnr LIKE '000000000%'.

" 注意事项1:检查内表是否为空
IF itab_mara IS NOT INITIAL.
" 使用 FOR ALL ENTRIES 查询物料描述
SELECT * FROM makt INTO TABLE itab_makt
FOR ALL ENTRIES IN itab_mara
WHERE matnr = itab_mara-matnr
AND spras = sy-langu.
ENDIF.

" 注意事项2:要尽可能多的用关键字
" 错误示例:只用部分关键字,会丢失重复数据
SELECT * FROM makt INTO TABLE itab_makt
FOR ALL ENTRIES IN itab_mara
WHERE matnr = itab_mara-matnr. " 可能有重复记录

" 正确示例:使用完整关键字
SELECT * FROM makt INTO TABLE itab_makt
FOR ALL ENTRIES IN itab_mara
WHERE matnr = itab_mara-matnr
AND spras = sy-langu. " 使用语言代码作为额外条件

" 注意事项3:不能使用 LIKE、BETWEEN、IN、ORDER BY
" 错误示例:
" SELECT * FROM makt INTO TABLE itab_makt
" FOR ALL ENTRIES IN itab_mara
" WHERE matnr LIKE itab_mara-matnr " 错误!不能使用 LIKE
" ORDER BY matnr. " 错误!不能使用 ORDER BY

" 正确示例:
SELECT * FROM makt INTO TABLE itab_makt
FOR ALL ENTRIES IN itab_mara
WHERE matnr = itab_mara-matnr
AND spras = sy-langu.

" 性能优化建议:
" 1. 尽量减少内表条目数
" 2. 使用完整关键字条件
" 3. 避免在循环中使用 FOR ALL ENTRIES
" 4. 考虑使用 INNER JOIN 替代(当条件允许时)

删除内表重复数据前应该做什么,如果要按照给定字段如何刪除?

删除前应做 SORT 排序,如果要按照给定字段进行删除则需要 COMPARING 语句指定字段。

示例代码:

" 定义内表
TYPES: BEGIN OF ty_data,
bukrs TYPE bukrs, " 公司代码
gjahr TYPE gjahr, " 会计年度
belnr TYPE belnr_d, " 凭证号
amount TYPE p LENGTH 8 DECIMALS 2,
END OF ty_data.

DATA: itab TYPE TABLE OF ty_data,
wa TYPE ty_data.

" 添加测试数据(包含重复数据)
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000001'. wa-amount = 100.
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000001'. wa-amount = 100. " 完全重复
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000002'. wa-amount = 200.
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000002'. wa-amount = 200. " 完全重复
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2025'. wa-belnr = '0000000003'. wa-amount = 300.
APPEND wa TO itab.

WRITE: / '删除前行数:', lines( itab ).

" 删除重复数据前必须排序
SORT itab.

" 删除完全重复的行
DELETE ADJACENT DUPLICATES FROM itab COMPARING ALL FIELDS.

WRITE: / '删除完全重复后行数:', lines( itab ).

" 按照给定字段删除重复数据
" 假设我们要按公司代码和会计年度删除重复数据
SORT itab BY bukrs gjahr.
DELETE ADJACENT DUPLICATES FROM itab COMPARING bukrs gjahr.

WRITE: / '按公司代码和年度删除后行数:', lines( itab ).

" 按照单个字段删除重复数据
SORT itab BY bukrs.
DELETE ADJACENT DUPLICATES FROM itab COMPARING bukrs.

WRITE: / '按公司代码删除后行数:', lines( itab ).

" 注意事项:
" 1. 删除前必须排序,否则只能删除相邻的重复数据
" 2. DELETE ADJACENT DUPLICATES 只能删除相邻的重复行
" 3. 如果需要删除所有重复数据,必须先排序
" 4. COMPARING 语句指定比较的字段,不指定则比较所有字段

哈希表和排序表的注意事项是什么?

排序表(SORTED TABLE) 与标准表相同,也具有一个逻辑索引,但其按关键字升序排序后再进行存储,其访问方式与标准表相同。
哈希表(HASHED TABLE) 没有索引,只能通过关键字来访问。系统用哈希算法管理表中的數据,因而其检索到一个数据行的时间与表的行数无关。

示例代码:

" 定义不同类型的内表
DATA: itab_standard TYPE TABLE OF mara, " 标准表
itab_sorted TYPE SORTED TABLE OF mara WITH UNIQUE KEY matnr, " 排序表
itab_hashed TYPE HASHED TABLE OF mara WITH UNIQUE KEY matnr, " 哈希表
wa TYPE mara.

" 添加数据
wa-matnr = '0000000003'. APPEND wa TO itab_standard. INSERT wa INTO TABLE itab_sorted. INSERT wa INTO TABLE itab_hashed.
wa-matnr = '0000000001'. APPEND wa TO itab_standard. INSERT wa INTO TABLE itab_sorted. INSERT wa INTO TABLE itab_hashed.
wa-matnr = '0000000002'. APPEND wa TO itab_standard. INSERT wa INTO TABLE itab_sorted. INSERT wa INTO TABLE itab_hashed.

" 标准表:按添加顺序存储
WRITE: / '标准表:'.
LOOP AT itab_standard INTO wa.
WRITE: / wa-matnr.
ENDLOOP.
" 输出: 0000000003, 0000000001, 0000000002

" 排序表:按关键字升序存储
WRITE: / '排序表:'.
LOOP AT itab_sorted INTO wa.
WRITE: / wa-matnr.
ENDLOOP.
" 输出: 0000000001, 0000000002, 0000000003

" 哈希表:没有索引,只能通过关键字访问
WRITE: / '哈希表:'.
" 可以通过 READ TABLE 访问
READ TABLE itab_hashed INTO wa WITH TABLE KEY matnr = '0000000002'.
IF sy-subrc = 0.
WRITE: / '找到物料:', wa-matnr.
ENDIF.

" 注意事项:
" 1. 排序表:有索引,可以按索引访问,按关键字排序
" 2. 哈希表:没有索引,只能通过关键字访问,访问速度快
" 3. 标准表:有索引,按添加顺序存储,可以使用 APPEND
" 4. 排序表:不能使用 APPEND,只能使用 INSERT
" 5. 哈希表:不能使用 APPEND,只能使用 INSERT
" 6. 排序表:插入和删除会导致索引重排,影响性能
" 7. 哈希表:适合频繁读取、很少插入/删除的场景
" 8. 排序表:适合需要排序、频繁读取的场景

怎样做带按钮的选择画面?

类似 CHECKBOX 和 RADIOBUTTON,可通过 PARAMETER 进行设置。

示例代码:

" 定义带按钮的选择屏幕
PARAMETERS: p_check AS CHECKBOX DEFAULT 'X',
p_radio1 RADIOBUTTON GROUP rad1,
p_radio2 RADIOBUTTON DEFAULT 'X' RADIOBUTTON GROUP rad1,
p_input TYPE c LENGTH 20.

" 添加自定义功能按钮
SELECTION-SCREEN FUNCTION KEY 1.
SELECTION-SCREEN FUNCTION KEY 2.

" 初始化按钮文本
INITIALIZATION.
%_p_check_%_app_%-text = '选择选项'.
%_p_radio1_%_app_%-text = '选项1'.
%_p_radio2_%_app_%-text = '选项2'.
sscrfields-functxt_01 = '按钮1'.
sscrfields-functxt_02 = '按钮2'.

" 处理按钮点击事件
AT SELECTION-SCREEN.
CASE sscrfields-ucomm.
WHEN 'FC01'.
MESSAGE '点击了按钮1' TYPE 'I'.
WHEN 'FC02'.
MESSAGE '点击了按钮2' TYPE 'I'.
ENDCASE.

START-OF-SELECTION.
IF p_check = 'X'.
WRITE: / '复选框被选中'.
ENDIF.
IF p_radio1 = 'X'.
WRITE: / '选择了选项1'.
ELSE.
WRITE: / '选择了选项2'.
ENDIF.

求和函数(COLLECT)的注意事项?

  • 所附加的内表必须具有扁平结构(组件中不可包含内表)。
  • 工作区必须与内表的行类型兼容(不仅仅是可相互转换)。
  • 所有的非表关键字段必须是数字类型。
  • 将新行附加至内表末尾,已存在相同表关键字值的行,不附加新行,而是将数字字段的值累计到已有数据行。

示例代码:

" 定义内表结构
TYPES: BEGIN OF ty_sum,
key_field TYPE c LENGTH 10,
amount TYPE p LENGTH 8 DECIMALS 2,
END OF ty_sum.

DATA: itab TYPE TABLE OF ty_sum,
wa TYPE ty_sum.

" 添加数据
wa-key_field = 'A'.
wa-amount = 100.
COLLECT wa INTO itab.

wa-key_field = 'B'.
wa-amount = 200.
COLLECT wa INTO itab.

wa-key_field = 'A'. " 相同关键字
wa-amount = 50.
COLLECT wa INTO itab. " amount 累加到150

" 输出结果
LOOP AT itab INTO wa.
WRITE: / wa-key_field, wa-amount.
ENDLOOP.
" 输出:
" A 150.00
" B 200.00

请列举 ABAP 程序的运行方式?它们一般表现为什么类型的程序?

可以分配事务代码并通过事务代码运行,用于完成一项系统功能,可以添加到最终用户的系统定制菜单中。
可执行程序(Report):模块池程序。
不能直接通过事务代码运行的程序:功能组、ABAP 类库、ABAP 接口库、子程序、类型组包含程序。

COLLECT 和 APPEND 的区别,以及 COLLECT 命令的限制是什么?

要逐行填充内表,可以使用 APPEND、 COLLECT 或 INSERT 语句。COLLECT 用于添加无相同表关键字的内表行,APPEND 可以添加相同表关键字的内表行。
COLLECT 命令的限制事项:

  • 要填充没有重复条目的内表,应该使用 COLLECT.
  • COLLECT 语句无法用于带深层结构的内表(组件中不可以包含内表)。
  • 为 COLLECT 指定的工作区域必须与内表的行类型兼容(不仅仅是可互相转换)。
  • 所有的非表关键字段必须是数字类型。

示例代码:

" 定义内表结构
TYPES: BEGIN OF ty_collect,
key_field TYPE c LENGTH 10,
amount TYPE p LENGTH 8 DECIMALS 2,
END OF ty_collect.

TYPES: BEGIN OF ty_append,
key_field TYPE c LENGTH 10,
amount TYPE p LENGTH 8 DECIMALS 2,
END OF ty_append.

DATA: itab_collect TYPE TABLE OF ty_collect,
itab_append TYPE TABLE OF ty_append,
wa_collect TYPE ty_collect,
wa_append TYPE ty_append.

" COLLECT 示例:自动累加相同关键字的数值字段
wa_collect-key_field = 'A'.
wa_collect-amount = 100.
COLLECT wa_collect INTO itab_collect.

wa_collect-key_field = 'B'.
wa_collect-amount = 200.
COLLECT wa_collect INTO itab_collect.

wa_collect-key_field = 'A'. " 相同关键字
wa_collect-amount = 50.
COLLECT wa_collect INTO itab_collect. " amount 累加到150

WRITE: / 'COLLECT 结果:'.
LOOP AT itab_collect INTO wa_collect.
WRITE: / wa_collect-key_field, wa_collect-amount.
ENDLOOP.
" 输出:
" A 150.00
" B 200.00

" APPEND 示例:直接添加行,允许重复
wa_append-key_field = 'A'.
wa_append-amount = 100.
APPEND wa_append TO itab_append.

wa_append-key_field = 'B'.
wa_append-amount = 200.
APPEND wa_append TO itab_append.

wa_append-key_field = 'A'. " 相同关键字
wa_append-amount = 50.
APPEND wa_append TO itab_append. " 直接添加,不累加

WRITE: / 'APPEND 结果:'.
LOOP AT itab_append INTO wa_append.
WRITE: / wa_append-key_field, wa_append-amount.
ENDLOOP.
" 输出:
" A 100.00
" B 200.00
" A 50.00

" COLLECT 限制示例:
" 1. 非关键字段必须是数字类型
" TYPES: BEGIN OF ty_invalid,
" key_field TYPE c LENGTH 10,
" text TYPE c LENGTH 20, " 非数字类型,不能用 COLLECT
" END OF ty_invalid.

" 2. 不能用于深层结构
" TYPES: BEGIN OF ty_deep,
" key_field TYPE c LENGTH 10,
" itab TYPE TABLE OF i, " 包含内表,不能用 COLLECT
" END OF ty_deep.

APPEND 与 INSERT 对内表操作的区别?

语法:
APPEND WA TO ITAB
INSERT WA INTO TABLE ITAB
前者不能用于 HASHED 表,后者不能用于 SORTED 表的 NON-UNIQUE 操作。

示例代码:

" 定义内表
DATA: itab_standard TYPE TABLE OF mara,
itab_sorted TYPE SORTED TABLE OF mara WITH UNIQUE KEY matnr,
itab_hashed TYPE HASHED TABLE OF mara WITH UNIQUE KEY matnr,
wa TYPE mara.

wa-matnr = '0000000001'.
APPEND wa TO itab_standard. " 标准表 - 正常
" APPEND wa TO itab_sorted. " 排序表 - 编译错误
" APPEND wa TO itab_hashed. " 哈希表 - 编译错误

INSERT wa INTO TABLE itab_standard. " 标准表 - 正常
INSERT wa INTO TABLE itab_sorted. " 排序表 - 正常
INSERT wa INTO TABLE itab_hashed. " 哈希表 - 正常

" 对于排序表,INSERT 会自动按关键字排序
wa-matnr = '0000000003'.
INSERT wa INTO TABLE itab_sorted.
wa-matnr = '0000000002'.
INSERT wa INTO TABLE itab_sorted.
" 结果自动按 matnr 排序: 0000000001, 0000000002, 0000000003

排序表还能插入和删除吗?

语法是允许的,但是规范里不推荐。因为会引起索引重排而导致的性能问题。

示例代码:

" 定义排序表
DATA: itab TYPE SORTED TABLE OF mara WITH UNIQUE KEY matnr,
wa TYPE mara.

" 插入数据
wa-matnr = '0000000003'.
INSERT wa INTO TABLE itab.

wa-matnr = '0000000001'.
INSERT wa INTO TABLE itab.

wa-matnr = '0000000002'.
INSERT wa INTO TABLE itab.

" 删除数据
DELETE itab WHERE matnr = '0000000002'.

" 注意:排序表的插入和删除会导致索引重排,影响性能
" 如果需要频繁插入/删除,建议使用标准表
" 排序表适合读取频繁、插入/删除较少的场景

" 查看结果
LOOP AT itab INTO wa.
WRITE: / wa-matnr.
ENDLOOP.
" 输出:
" 0000000001
" 0000000003

AT FIRST、 AT NEW、 AT END OF 有什么区别?

  • AT FIRST:当前循环为内表的第一行,即当第一次循环操作时,系统会执行控制语句块。
  • AT LAST:当前循环为内表的最后一行。
  • AT NEW f:行组的开头,行组指的是字段 f 和 f 前面(即左边)的全部字段,如果行组中的任何字段内容不同于上一行,执行语句块中的内容。
  • AT END OF f:行组的结尾,如果下一行行组中的任何字段内容不同于上一行,执行语句块中的内容。

示例代码:

" 定义内表
TYPES: BEGIN OF ty_data,
bukrs TYPE bukrs, " 公司代码
gjahr TYPE gjahr, " 会计年度
belnr TYPE belnr_d, " 凭证号
amount TYPE p LENGTH 8 DECIMALS 2,
END OF ty_data.

DATA: itab TYPE TABLE OF ty_data,
wa TYPE ty_data.

" 添加测试数据
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000001'. wa-amount = 100.
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000002'. wa-amount = 200.
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2025'. wa-belnr = '0000000003'. wa-amount = 300.
APPEND wa TO itab.
wa-bukrs = '2000'. wa-gjahr = '2024'. wa-belnr = '0000000004'. wa-amount = 400.
APPEND wa TO itab.

" 排序(AT 事件需要排序后的数据)
SORT itab BY bukrs gjahr.

" 使用 AT 事件
LOOP AT itab INTO wa.
AT FIRST.
WRITE: / '=== 开始处理 ==='.
ENDAT.

AT NEW bukrs.
WRITE: / '公司代码:', wa-bukrs.
ENDAT.

AT NEW gjahr.
WRITE: / ' 会计年度:', wa-gjahr.
ENDAT.

WRITE: / ' 凭证号:', wa-belnr, '金额:', wa-amount.

AT END OF gjahr.
WRITE: / ' 年度小计'.
ENDAT.

AT END OF bukrs.
WRITE: / '公司小计'.
ENDAT.

AT LAST.
WRITE: / '=== 处理结束 ==='.
ENDAT.
ENDLOOP.

AT NEW、 AT END OF 指定字段之后的字段將发生怎样的变化,应该注意什么?

在 AT/END AT 语句块内部,目标区城并没有用当前表格行进行填充,而是将进行下述操作:对于 AT FIRST 和 AT LAST,系统用“*” 填充所有标准关键字段内容:对于 AT NEW f 和 AT END OF f,系统用“*”填充目标区域中所有出现在指定字段 f 后面(右边)的标准关键字段。对于所有非标准关键字段,将进行初始化。

示例代码:

" 定义内表
TYPES: BEGIN OF ty_data,
bukrs TYPE bukrs, " 公司代码
gjahr TYPE gjahr, " 会计年度
belnr TYPE belnr_d, " 凭证号
amount TYPE p LENGTH 8 DECIMALS 2,
END OF ty_data.

DATA: itab TYPE TABLE OF ty_data,
wa TYPE ty_data.

" 添加测试数据
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000001'. wa-amount = 100.
APPEND wa TO itab.
wa-bukrs = '1000'. wa-gjahr = '2024'. wa-belnr = '0000000002'. wa-amount = 200.
APPEND wa TO itab.

SORT itab BY bukrs gjahr.

" 演示 AT NEW 中的字段变化
LOOP AT itab INTO wa.
AT NEW gjahr.
" 在 AT NEW gjahr 块中:
" - bukrs 字段有值(因为是行组的一部分)
" - gjahr 字段有值(指定字段)
" - belnr 字段为 '*' 或初始化(在指定字段之后)
" - amount 字段为 0(非关键字段初始化)
WRITE: / 'AT NEW gjahr:'.
WRITE: / ' bukrs:', wa-bukrs. " 有值
WRITE: / ' gjahr:', wa-gjahr. " 有值
WRITE: / ' belnr:', wa-belnr. " 为 '*' 或空
WRITE: / ' amount:', wa-amount. " 为 0
ENDAT.

" 在正常循环中,所有字段都有值
WRITE: / '正常循环:', wa-bukrs, wa-gjahr, wa-belnr, wa-amount.
ENDLOOP.

" 注意:如果需要在 AT 块中使用指定字段之后的值,需要重新读取
LOOP AT itab INTO wa.
AT NEW gjahr.
" 需要重新读取以获取完整数据
READ TABLE itab INTO wa WITH KEY bukrs = wa-bukrs
gjahr = wa-gjahr.
WRITE: / '重新读取后:', wa-belnr, wa-amount.
ENDAT.
ENDLOOP.

数据字典相关问题

激活锁对象时,产生的函数名字是什么?

首先要在 ABAP 字典中创建锁对象,才能在 ABAP 程序中设锁。创建锁对象时,系统会自动生成两个函数来进行锁管理。
用于设锁的函数为 ENQUEUE_<锁对象名>,它用于在锁表(Lock Table)中生成一个锁项(Lock Entry)。若设锁不成功的话,就会在 Return 中反映出来。
用于释放锁的函数为 DEQUEUE_<锁对象名>,它用于从锁表中删除一个锁项。在 ABAP 程序中,只需使用 “CALL FUNCITION..” 语句就可以调用它们。
这两个锁函数是在 SAP 系统的一个特殊工作进程中执行的,专门进行锁管理。它们运行在一个单独的服务器上,而该服务器专门用于维护整个 SAP 系统的主锁表(Central Lock Table)。
有两种锁类型:

  • 共享锁 (shared)——只读锁,该模式允许多个用户访问指定表行,但只能读访问。任何时候都不允许写访问。
  • 排他锁定 (exelusive,not cumulative)——该模式允许单个用户对指定表行进行读和访问,其他用户不能访问该行。
  • 扩展排他锁定 (exclusive,cumulative)——该模式避免具有读写访问权限的单个用户获得对相同表行集的进一步锁定,当使用递归例程更新时,该模式很有用。
    在事务 SM12 中查看锁表的情况。

示例代码:

" 假设锁对象名为 EZ_MARA
" 系统自动生成的函数:
" ENQUEUE_EZ_MARA - 设锁
" DEQUEUE_EZ_MARA - 释放锁

" 使用锁对象
DATA: lv_matnr TYPE matnr VALUE '0000000001'.

" 设锁
CALL FUNCTION 'ENQUEUE_EZ_MARA'
EXPORTING
matnr = lv_matnr
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.

IF sy-subrc = 0.
" 锁定成功,执行更新操作
UPDATE mara SET meins = 'KG'
WHERE matnr = lv_matnr.

" 释放锁
CALL FUNCTION 'DEQUEUE_EZ_MARA'
EXPORTING
matnr = lv_matnr.

COMMIT WORK.
ELSE.
MESSAGE '物料被其他用户锁定' TYPE 'E'.
ENDIF.

向 SAP 表中添加字段的方法是哪两种?

这两种方法是 Append 结构(Append Structure) 和自定义 Include (Customizing Include).
Append 结构是在向表尾添加字段时创建的,自定义 Include 由 SAP 开发人员指定,使用户可以创建新字段。

Doman、 Data Element 和 Table 之间的关系,为什么要分三层?

同样字段定义一个字段可以方便使用。
城(Doman)是定义数据元素的技术属性,具休包括数据字典中的基本内置数据类型,长度和小数位,进而确定该数据元素的数值范围。如果不同的数据元素属于相同的域(Doman),它们就有相同的属性。数据字典中,每一个数据元素必须属于一个域,以确定其基本性质,定义好数据元素可以用作结构或表类型中的字段组件或者数据库表的字段,因而这些组件字段的基本属性也都由某个域决定。
数据元素(Data Elements) 相当于定义在数据字典中的 ABAP 基本类型和引用类型。
表(Table)是一种数据库对象,数据字典中的表独立于数据库存在,一旦创建,则同样结构的数据库表实体也将在物理数据库中创建。

创建数据库表时应注意哪些事项?

  • 有 CURR 类型的字段必须有参照表和参照字段。
  • 建表时主键必须连续排序,中间不能有非主键存在。
  • 插入第一条数据时操作比较特殊

列举 ABAP 数据字典中的对象及作用有哪些?

  • Tables
  • Views
  • Data elements
  • Structure
  • Domains
  • Search Helps
  • Local Objects

CHECK TABLE 和 VALUE TABLE 各指的是什么?关系怎么样?

如果一个表字段定义了外部关键字,则该表称为外部关键字表,而对应的参照表则称为 CHECK TABLE。如果在数据字典中为一个表字段指定了约束表,则其输入帮助可以是该表中的关键字段值列表,或者是约束表中对应自动制定的输入帮助。

  • CHECK TABLE 用于字段级别的检查。
  • VALUE TABLE 用于域级别的检查。

数据库表维护有哪些内容?

  • 表字段。
    数据库表与结构类型、表类型的区别:后二者是单纯的数据类型,前者还包合其他相关设定,如外码、索引等。
  • 技术设定
    通过 Technical Settings 按钮维护,主要维护定义表中所存储数据的类型等信息。例如操作数据库表时是否需要进行缓冲(对不需经常更新的数据库表提高后续查询效率)、是否创建系统日志等。
  • 索引——仅包括部分字段的数据库表拷贝,通过 Indexes 按钮维护。
    1、主索引:只包含表关键字和指向整个数据条目的指针,系统自动生成。
    2、附属索引:根据其他字段生成索引,按指定方式排序,自行创建,协助主索引提高搜索效率。

报表相关问题

基本报表

报表事件

1)过程化事件有哪些?
INITIALIZATION.
START-OF-SELECTION.
END-OF-SELECTION.

2)选择屏幕事件有哪些?
AT SELECTION-SCREEN OUTPUT.
AT SELECTION-SCREEN.
AT SELECTION-SCREEN ON field.
AT SELECTION-SCREEN ON BLOCK block.
AT SELECTION-SCREEN ON RADIOBUTTON GROUP group1.
AT SELECTION-SCREEN ON seltab.
AT SELECTION-SCREEN ON END OF seltab.
AT SELECTION-SCREEN ON HELP-REQUEST FOR field.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR field.

3)在使用逻辑数据库时,如 HR 报表,涉及的逻辑数据库事件有哪些?
GET node.
GET node LATE.

4)报表操作事件有哪些?
TOP-OF-PAGE.
END-OF-PAGE.
AT-USER-COMMAND。

5)交互式报表事件有哪些?
AT USER-COMMAND.
AT LINE-SELECTION.
AT PF-FUNCTION KEY(设置功能键)。

6)按顺序举例说明各个 Report 事件及其作用?
LOAD-OF-PROGRAM:程序执行时,自动触发该 EVENT.
INITIALIZTION:对程序变量进行初始化。
AT SELECTION-SCREEN OUTPUT:选择屏幕 PBO 事件,显示选择屏幕之前触发。
AT SELECTION-SCREEN:选择屏幕 PAI 事件,用户在选择屏幕上执行某些功能。
START-OF-SELECTION: 选择开始事件。
END-OF-SELECTION:选择结束事件,一般在该事件中输出列表。
TOP-OF-PAGE:页眉控制。
END-OF-PAGE:页角控制。
AT LINE-SELECTION:行选择控制,用户选择某列表行时触发。
AT USER-COMMAND: 用户交互控制,用户选择某个 GUI 功能时触发。

报表选择画面

  1. 报表的选择画面上能否添加自定义工具栏?
    能够添加,通过语句 SELECTION-SCREEN FUNCTION KEY n (n 为 1 至 3,最多定义 5 个

2)如何对报表的选择画面元素进行分组?
在 PARAMETERS 或 SELECT-OPTIONS 后使用附加语句 “MODIF ID+组名”

3)如何更改屏幕各元素的状态?举例一些比较常用的屏幕属性。
在报表 AT SELECTION-SCREEN OUTPUT 事件中或 PBO ( PROCESS BEFORE OUTPUT) 中,利用如下代码:
LOOP AT SCREEN.
…MODIFY SCREEN.
ENDLOOP.

SCREEN-NAME: 画面元素的名称。
SCREEN-GROUP1 ~ SCREEN-GROUP4:对画面元素的分组。
SCREEN-INPUT:能否输入。
SCREEN-INVISIBLE:是否可见。
SCREEN-LENGTH:可见长度。
SCREEN-ACTIVE:是否是可用的状态。

4)在什么时候触发 TOP OF PAGE 语句?

  • 第一个 WRITE 命令。
  • 输出行数超过 LINE-COUNT 属性值。
  • NEW-PAGE 命令。

5)Report 的跳转语句是什么?PICK 是什么?怎么用?
该语句为 AT LINE-SELECTION。代码 PICK 为内部触发 AT LINE-SELECTION 事件。
在预定义界面中,将“编辑》选择”和分配到 PICK。设置功能键自动激活鼠标双击功能。

6)介绍一下下拉列表常用到的技术点?
下拉列表多用到 HIDE 指令、READ LINE 指令、事件 AT LINE-SELETION、 AT USER-COMMAND 及 AT PF
事件 AT LINE-SELECTTION 定义用户双击列表某行时触发的事件,
AT USER-COMMAND 定义用户按下某按钮,或者选择某一菜单项或输入命令后触发的事件及 AT PF用户按下功能触发的事件。

7)如果运行报表输出的程序,不想输出到屏幕上,而是直接打印应该怎么做?
在程序中利用代码 NEW-PAGE PRINT ON 将生成的列表直接发送到 SPOOL 系统进行打印,不在屏幕上显示。

ALV 报表

ALV 报表实现的流程是怎样的?

声明数据对象》定义选择画面》从数据库取数》处理数据》调用显示数据。

显示 ALV 常用的几个函数是什么?

REUSE ALVGRID_DISPLAY。
REUSE ALV_LIST_DISPLAY.
REUSE ALV_GRID
DISPLAY_LVC.

ALV 显示中的小计如何实现?

  • 使用 SUBTOT:
    ALV 可以实现自动小计,首先字段目录参数中添加 DO_SUM = X,然后传入排序表,可以在排序表中指定需要小计的列。

  • 使用 AT 语句:
    在 LOOP 循环中使用 AT END OF FIELD 逻辑。

函数 ALV 和面向对象 ALV 有什么不同?

函数 ALV 和 面向对象 ALV 都能够实现按钮自定义、数据修改、按钮处理自定义等操作。通常情況下函数 ALV 主要用于报表数据展示及简单交互;面向对象 ALV 主要用于 Dialog 程序开发,可以进行复杂的控制,比如单元格的修改控制(函数只能控制到列修改性)、自定义(F4)等。面向对象 ALV 可以根据容器排列很方便的定义布局,一个屏箱可以放多个 ALV,但是函数 ALV 只能一屏显示一个 ALV。

ALV 是什么,能简单介绍一下 ALV 的作用吗?

ALV (ABAP LIST VIEW) 是 ABAP 的报表显示工具,它把内表中的内容以设定的格式显示出来,附加一些功能,如下载为本地文件等。

SmartForm 报表

谈谈 SmartForm 中,Template 和 Table 表格的区别?

Template 是静态表格,单元格的输出是一定的。Table 是动态表格,包含在循环内,内表能够按条输出。

SmartForm 如何实现公司 LOGO 打印?

用 SE78 导入公司 L0G0,导入时只能导入位图和 GIF 文件。导入位图时,要注意用 256 色,否则会导致图片打印有问题。然后在 SmartForm 中添加图片即可。

SmartForm 中如何控制段落、单个字符的输出格式?

用 Styles 可以定义不同的段落、字符格式,在文本输出时可以指定格式。

什么是 SmartForm,简述其创建过程?

SmarForm 的基本思想是为了提高处理的效率,将数据筛选、整理过程与数据在 Form 中的应用处理及格式化过程相分离。
将数据库中的数据通过 SmatForms 格式化并输出的步菜如下:

  • 提取应用数据。
  • 定义 Form 格式及相关数据接口。
  • 将数据传输至 Form 中,并批量输出。

对话程序

事件

Dialog 中的事件有哪些?

PBO (PROCESS BEFORE OUTPUT)。
PAI (PROCESS AFTER INPUT)。
POV (PROCESS ON VALUE-REQUEST)。
POH (PROCESS ON HELP-REQUEST).

介绍一下 Dynpro 程序结构以及运行机制?

  • 逻辑上分为 PBO, PAI 以及 POV, POH
  • 建立程序时物理上 TOP, 001, 101, FO1.
  • 运行机制:TCODE->PBO->PAL……

解释一下 PBO, PAI 以及 POV,POH?

  • PBO: PROCESS BEFORE OUTPUT(显示前处理过程,在向用户显示屏幕 GUI 之前触发,一般做面面初始化和设定 Status、Title、动态设定画面属性和清空变量。
  • PAI: PROCESS AFTER INPUT(输入后处理过程)在用户进行某些屏幕行为后触发,对应一些用户操作、面面检查。
  • POV: PROCESS ON VALUE-REQUEST 对应功能键,用户选中画面上某个字段按键,类似检索帮助。
  • POH: PROCESS ON HELP-REQUEST 对应功能键

控件

如何在 Table Control 中实现选中一行或多行的效果?

将内表第一个字段定义为一个长度为 1,类型为 C 的变量,并在 Table Control 属性框中将该字段填写到选择字段属性中。

Dialog 开发的几个常用控件是什么?

子屏幕、文本、输入框、输出框、容器、表控制、按钮等。

简述表格控制和步循环的区别?

表格控制和步循环是用于屏幕表格显示的对象。表格控制只是增强了的步循环,它可以使用桌面应用程序中表格工具的“Look”和 “Feel”来显示数据。表格控制还提供使表格易于查看和使用的专用格式化功能。
步循环的一个特点是它们的表行能够在屏幕上跨越多行。相反,表格控制中的行总是单行,但可以很长(表格控制的行能够滚动)。
通常表格控制所提供的许多特征由系统的 SAPGUI 前端操纵,因此,不必在 ABAP/4 事务中编制任何特征。

语法

Dynpro 中画面调用的几种方式和各自的好处是什么?

  • SET SCREEN next_screen_number:为当前屏幕指定后续屏幕,不会自动返回当前屏幕。
  • CALL SCREEN screen_number 及 CALL TRANCTION tcode:挂起当前屏幕序列,进入新屏幕(序列)或事务。在新的屏幕序列中,通过语句 LEAVE SCREEN TO SCREEN 0 调用的屏幕就能够返回到挂起的屏幕序列上(返回到原程序)。
  • LEAVE SCREEN:如果未完成当前屏幕控制流就要转入后续屏幕,使用此语句。
  • LEAVE TO SCREEN screen_ number 及 LEAVE TO TRANCTION tcode:未完成当前屏幕就要转入后续屏幕,并且不返回原有程序。

举例说明 Dynpro 的动态属性有哪些?

  • NAME 屏幕字段名称。
  • GROUP1、 2、 3、4,屏幕字段属于哪个组。
  • ACTIVE 1 可用,0 不可用。
  • REQUIRED 1 必入力,O 非必入力。
  • INPUT 1 可入力,0 不可入力。
  • OUTPUT 1 可显示,0 不可显示。
  • INTENSIFIED 1 强调显示,0 非强调显示。
  • INVISIBLE1 为*,0 为输入即可见。
  • LENGTH 字段输出长度。
  • DISPLAY 3D 以三维框架显示字段。
  • VALUE HELP 有直询帮助的字段。

Dynpro 逻辑块中,Field 的作用是什么?

  • 用来传值。
  • 进行字段输入检查(防止所有控件都变灰)。

Field Module 和 On Value-Request 有什么作用?

在 Dynpro 程序中,控制屏幕控件是否输入修改值等操作,主要用于屏幕数据的检查。

批处理

原理

BDC 录屏的事务代码是什么?

SHDB。

操作步骤:

  1. 执行事务代码 SHDB 进入录屏界面
  2. 点击「新建录屏」按钮,输入录屏名称
  3. 输入要录制的事务代码,执行操作
  4. 系统自动记录屏幕字段和用户操作
  5. 保存录屏后可生成 BDC 程序模板

示例代码:通过 SHDB 录屏数据生成 BDC 程序

" SHDB 录屏后生成的代码结构示例(以创建物料主数据为例)
REPORT zbdctest.

DATA: lt_bdcdata TYPE TABLE OF bdcdata,
ls_bdcdata TYPE bdcdata,
lt_messtab TYPE TABLE OF bdcmsgcoll,
ls_messtab TYPE bdcmsgcoll.

" 填充 BDCDATA 内表
" 第一个屏幕
ls_bdcdata-program = 'SAPLMGMM'.
ls_bdcdata-dynpro = '0060'.
ls_bdcdata-dynbegin = 'X'.
APPEND ls_bdcdata TO lt_bdcdata.
CLEAR ls_bdcdata.

ls_bdcdata-fnam = 'RMMG1-MATNR'.
ls_bdcdata-fval = '000000000100000001'.
APPEND ls_bdcdata TO lt_bdcdata.
CLEAR ls_bdcdata.

ls_bdcdata-fnam = 'RMMG1-MBRSH'.
ls_bdcdata-fval = 'M'.
APPEND ls_bdcdata TO lt_bdcdata.
CLEAR ls_bdcdata.

" 执行 Call Transaction
CALL TRANSACTION 'MM01'
USING lt_bdcdata
MODE 'N' " N=后台不显示屏幕
UPDATE 'S' " S=同步更新
MESSAGES INTO lt_messtab.

" 检查结果
LOOP AT lt_messtab INTO ls_messtab.
WRITE: / ls_messtab-msgtyp, ls_messtab-msgid,
ls_messtab-msgnr, ls_messtab-msgv1.
ENDLOOP.

简述 BDC 与 BAPI 之间的区别?

都可以用作数据导入,但是 BDC 是完全模拟前台操作,而 BAPI 是 SAP 提供的标准 API,通过调用 BAPI 在后台生成相应数据,性能高于 BDC 导入方式。

对比总结:

特性 BDC BAPI
原理 模拟前台屏幕操作 调用标准 API 函数
性能 较低,逐屏模拟 较高,直接后台处理
数据校验 依赖屏幕校验逻辑 内置业务校验
适用场景 无标准 BAPI 时使用 优先使用
错误处理 通过 BDCMSGCOLLECT 通过 RETURN 参数

示例代码:使用 BAPI 创建物料主数据(对比 BDC)

" 使用 BAPI 创建物料(推荐方式)
DATA: ls_headdata TYPE bapimathead,
ls_clientdata TYPE bapimareb,
ls_return TYPE bapiret2.

ls_headdata-material = '000000000100000002'.
ls_headdata-ind_sector = 'M'.
ls_headdata-matl_type = 'FERT'.

ls_clientdata-base_uom = 'EA'.

CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
headdata = ls_headdata
clientdata = ls_clientdata
IMPORTING
return = ls_return.

IF ls_return-type = 'S'.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
WRITE: / '物料创建成功'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
WRITE: / '错误:', ls_return-message.
ENDIF.

BDC 录制的注意事项?

将要填写的字段全部输入,保证其能正确被录制下来,不需要填写的字段不要操作,以免录制后产生冗余字段而产生错误。

操作要点:

  1. 录制前先手动执行一遍事务,确认操作路径最短
  2. 只操作必要字段,不要点击多余按钮
  3. 录制完成后检查生成的 BDCDATA,删除冗余字段
  4. 注意表控件(Table Control)中行号的动态处理

示例代码:检查和清理录屏数据

" SHDB 录制后,在程序中动态处理表控件行号
" 表控件中的字段需要带行号,如:VBAP-MATNR(01)
DATA: lv_row TYPE n LENGTH 2.

DO 5 TIMES.
lv_row = sy-index.
" 动态拼接行号
CONCATENATE 'VBAP-MATNR(' lv_row ')' INTO ls_bdcdata-fnam.
ls_bdcdata-fval = '物料号'.
APPEND ls_bdcdata TO lt_bdcdata.
CLEAR ls_bdcdata.
ENDDO.

谈谈 BDC 的运行模式和更新模式?

运行模式包括:A.全屏幕显示;E.只有在发生错误时显示屏幕;N.不进行屏幕的显示将相关信息记录到信息内表里;P.不进行屏幕的显示,可进行 DEBUG 测试。
更新模式包括:A.异步更新;S.同步更新;L.本地更新。

示例代码:不同运行模式和更新模式的使用

" 运行模式示例
" MODE 'A' - 全屏幕显示(调试用)
CALL TRANSACTION 'MM01' USING lt_bdcdata MODE 'A' UPDATE 'S'.

" MODE 'E' - 仅出错时显示屏幕
CALL TRANSACTION 'MM01' USING lt_bdcdata MODE 'E' UPDATE 'S'.

" MODE 'N' - 不显示屏幕(生产环境推荐)
CALL TRANSACTION 'MM01' USING lt_bdcdata MODE 'N' UPDATE 'S'
MESSAGES INTO lt_messtab.

" 更新模式示例
" UPDATE 'S' - 同步更新(推荐,可立即获知结果)
CALL TRANSACTION 'MM01' USING lt_bdcdata MODE 'N' UPDATE 'S'
MESSAGES INTO lt_messtab.

" UPDATE 'A' - 异步更新(性能好,但无法立即获知结果)
CALL TRANSACTION 'MM01' USING lt_bdcdata MODE 'N' UPDATE 'A'.

" UPDATE 'L' - 本地更新(在同一应用服务器上执行)
CALL TRANSACTION 'MM01' USING lt_bdcdata MODE 'N' UPDATE 'L'.

BDC 执行出错时,为查看设置错误处,显示模式应怎样设置,A、E、N 分别代表什么含义?

可以设置 E.出错时转前台运行,有机会输入修正数据。
如果不想马上转前台用 Insert_Group 插入一个 Session。
A.全画面表示;
E.碰到错误时弹出;
N.不弹出错误画面,后台运行。

示例代码:BDC 错误处理和 Session 方式

" 方式1:Call Transaction + 错误处理
DATA: lt_messtab TYPE TABLE OF bdcmsgcoll.

CALL TRANSACTION 'MM01'
USING lt_bdcdata
MODE 'E' " 出错时弹出画面
UPDATE 'S'
MESSAGES INTO lt_messtab.

" 检查错误
LOOP AT lt_messtab INTO DATA(ls_msg) WHERE msgtyp CA 'EA'.
WRITE: / '错误:', ls_msg-msgid, ls_msg-msgnr,
ls_msg-msgv1, ls_msg-msgv2.
ENDLOOP.

" 方式2:Session 方式(推荐大批量数据)
" 创建 Session
CALL FUNCTION 'BDC_OPEN_GROUP'
EXPORTING
client = sy-mandt
group = 'ZSESSION_001'
keep = 'X'
user = sy-uname.

" 插入数据
CALL FUNCTION 'BDC_INSERT'
EXPORTING
tcode = 'MM01'
tp_dynpro = ''
TABLES
dynprotab = lt_bdcdata.

" 关闭 Session
CALL FUNCTION 'BDC_CLOSE_GROUP'.

" 在 SM35 中查看并执行 Session
" SUBMIT rsbdcsub AND RETURN
" WITH sesstab-group = 'ZSESSION_001'.

什么是 BDC(Batch Data Communication)?简单介绍其流程?BDC 调查有哪些方式?BDC 有几种,分别是什么?

BDC 是 SAP 常用的一种数据传输方法。用于一些数据量大,但是对速度又要求不高的数据传输。
其流程大致分为如下几步:

  • 提炼数据。
  • 把提炼的数据转换到 BDCDATA 内表中。
  • 根据 BDC 类型通过 Call Transaction 形式执行,或通过 Session 形式执行,如果 Session 执行成功,数据将被传送成功。
    调查 BDC 的事务代码:SHDB,查看 Session 执行结果的事务代码:SM35。

BDC 有两种方式:

  1. Call Transaction 方式 —— 立即执行,同步/异步更新
  2. Session 方式 —— 先创建 Session,再通过 SM35 执行

示例代码:完整 BDC 程序框架

REPORT zbdc_material_create.

TYPES: BEGIN OF ty_input,
matnr TYPE matnr,
maktx TYPE maktx,
meins TYPE meins,
END OF ty_input.

DATA: lt_input TYPE TABLE OF ty_input,
lt_bdc TYPE TABLE OF bdcdata,
lt_msg TYPE TABLE OF bdcmsgcoll,
lv_mode TYPE c VALUE 'N'.

" 1. 提炼数据(从文件或内表)
lt_input = VALUE #(
( matnr = '000000000100000001' maktx = '物料1' meins = 'EA' )
( matnr = '000000000100000002' maktx = '物料2' meins = 'KG' )
).

" 2. 逐条处理
LOOP AT lt_input INTO DATA(ls_input).
CLEAR: lt_bdc, lt_msg.

" 填充屏幕1
PERFORM bdc_dynpro USING 'SAPLMGMM' '0060'.
PERFORM bdc_field USING 'BDC_CURSOR' 'RMMG1-MATNR'.
PERFORM bdc_field USING 'RMMG1-MATNR' ls_input-matnr.
PERFORM bdc_field USING 'BDC_OKCODE' '/00'.

" 填充屏幕2
PERFORM bdc_dynpro USING 'SAPLMGMM' '0070'.
PERFORM bdc_field USING 'BDC_OKCODE' '/00'.

" 3. 执行
CALL TRANSACTION 'MM01'
USING lt_bdc
MODE lv_mode
UPDATE 'S'
MESSAGES INTO lt_msg.

" 4. 检查结果
READ TABLE lt_msg INTO DATA(ls_msg)
WITH KEY msgtyp = 'E'.
IF sy-subrc = 0.
WRITE: / '物料', ls_input-matnr, '创建失败:', ls_msg-msgv1.
ELSE.
WRITE: / '物料', ls_input-matnr, '创建成功'.
ENDIF.
ENDLOOP.

" BDC 辅助子程序
FORM bdc_dynpro USING p_program p_dynpro.
DATA ls_bdc TYPE bdcdata.
ls_bdc-program = p_program.
ls_bdc-dynpro = p_dynpro.
ls_bdc-dynbegin = 'X'.
APPEND ls_bdc TO lt_bdc.
ENDFORM.

FORM bdc_field USING p_fnam p_fval.
DATA ls_bdc TYPE bdcdata.
ls_bdc-fnam = p_fnam.
ls_bdc-fval = p_fval.
APPEND ls_bdc TO lt_bdc.
ENDFORM.

如果想把一个 BDC 的程序定义为零点开始启动应该怎么做?

通过计划后台作业(事务代码 SM36),定义 Job 并设定 Job 起始时间为零点。

操作步骤:

  1. 执行 SM36 进入作业定义界面
  2. 输入作业名称(如 ZBDC_JOB_001)
  3. 输入作业类和服务器
  4. 添加步骤,指定要执行的程序名
  5. 设定开始时间为零点
  6. 保存作业

示例代码:在程序中通过代码定义后台作业

" 使用 JOB_OPEN / JOB_SUBMIT / JOB_CLOSE 创建后台作业
DATA: lv_jobname TYPE tbtcjob-jobname VALUE 'ZBDC_NIGHTLY',
lv_jobcount TYPE tbtcjob-jobcount.

" 1. 打开作业
CALL FUNCTION 'JOB_OPEN'
EXPORTING
jobname = lv_jobname
IMPORTING
jobcount = lv_jobcount
EXCEPTIONS
OTHERS = 1.

IF sy-subrc <> 0.
WRITE: / '作业创建失败'.
RETURN.
ENDIF.

" 2. 提交程序到作业
SUBMIT zbdc_material_create
WITH p_file = '/data/input.csv'
VIA JOB lv_jobname NUMBER lv_jobcount
AND RETURN.

" 3. 关闭作业并设置零点启动
DATA: lv_startdate TYPE sy-datum,
lv_starttime TYPE sy-t.

lv_startdate = sy-datum + 1. " 明天
lv_starttime = '000000'. " 零点

CALL FUNCTION 'JOB_CLOSE'
EXPORTING
jobname = lv_jobname
jobcount = lv_jobcount
strtimmed = ' '
sdlstrtdt = lv_startdate
sdlstrttm = lv_starttime
EXCEPTIONS
OTHERS = 1.

IF sy-subrc = 0.
WRITE: / '作业', lv_jobname, '已设定为', lv_startdate, '零点启动'.
ENDIF.

语法

在 BDC 程序中,BDC 内表所用的构造名及其字段是什么?

BDC 内表所用的构造名为 BDCDATA,其中字段分别是:

  • PROGRAM:程序名(屏幕程序名)
  • DYNPRO:屏幕编号
  • DYNBEGIN:屏幕开始标志(’X’ 表示新屏幕开始)
  • FNAM:字段名
  • FVAL:字段值

示例代码:BDCDATA 结构详解

" BDCDATA 结构说明
DATA: lt_bdc TYPE TABLE OF bdcdata,
ls_bdc TYPE bdcdata.

" ---- 屏幕开始记录 ----
ls_bdc-program = 'SAPMF02K'. " 程序名(屏幕所属程序)
ls_bdc-dynpro = '0100'. " 屏幕编号
ls_bdc-dynbegin = 'X'. " 'X' 表示这是一个新屏幕的开始
APPEND ls_bdc TO lt_bdc.
CLEAR ls_bdc.

" ---- 字段赋值记录 ----
ls_bdc-fnam = 'BDC_CURSOR'. " 设置光标位置(可选)
ls_bdc-fval = 'RF02K-LIFNR'.
APPEND ls_bdc TO lt_bdc.
CLEAR ls_bdc.

ls_bdc-fnam = 'RF02K-LIFNR'. " 供应商编号字段
ls_bdc-fval = '0000001000'. " 字段值
APPEND ls_bdc TO lt_bdc.
CLEAR ls_bdc.

ls_bdc-fnam = 'RF02K-BUKRS'. " 公司代码字段
ls_bdc-fval = '1000'.
APPEND ls_bdc TO lt_bdc.
CLEAR ls_bdc.

ls_bdc-fnam = 'BDC_OKCODE'. " 执行的 OK Code(功能码)
ls_bdc-fval = '/00'. " /00 = 回车
APPEND ls_bdc TO lt_bdc.
CLEAR ls_bdc.

" 常用 BDC_OKCODE 值:
" /00 = 回车(Enter)
" /11 = 保存(F11)
" BACK = 返回
" /E = 取消
" ENTER = 回车

BDC Session 如何创建?在程序中如何处理?

BDC Session 创建过程如下:

  • CALL FUNCTION ‘BDC_OPEN_GROUP’
  • BDC 内表做成。
  • CALL FUNCTION BDC INSERT
  • CALL FUNCTION BDC CLOSE GROUP
    使用如下语句可以立即执行 BATCH INPUT SESSION。
    SUBMIT RSBDCBTC AND RETURN WITH OUBUF-ID = WK_QID.
    在 SM35 下查看 BATCH INPUT SESSION 执行结果。

示例代码:完整的 BDC Session 程序

REPORT zbdc_session_create.

DATA: lt_bdcdata TYPE TABLE OF bdcdata,
ls_bdcdata TYPE bdcdata,
lv_group TYPE apqi-groupid VALUE 'ZBDC_SESSION_001'.

" 1. 创建 Session(打开组)
CALL FUNCTION 'BDC_OPEN_GROUP'
EXPORTING
client = sy-mandt
group = lv_group
keep = 'X' " 'X'=执行后保留Session
user = sy-uname
EXCEPTIONS
client_invalid = 1
destination_invalid = 2
group_invalid = 3
group_is_locked = 4
holddate_invalid = 5
internal_error = 6
queue_error = 7
running = 8
system_lock_error = 9
user_invalid = 10
OTHERS = 11.

" 2. 填充 BDC 内表并插入 Session
" 循环处理多条数据
DO 3 TIMES.
CLEAR lt_bdcdata.

" 填充 BDC 数据(示例:创建供应商)
ls_bdcdata-program = 'SAPMF02K'.
ls_bdcdata-dynpro = '0100'.
ls_bdcdata-dynbegin = 'X'.
APPEND ls_bdcdata TO lt_bdcdata.

ls_bdcdata-fnam = 'RF02K-LIFNR'.
ls_bdcdata-fval = sy-index.
CONDENSE ls_bdcdata-fval NO-GAPS.
APPEND ls_bdcdata TO lt_bdcdata.

" 3. 插入数据到 Session
CALL FUNCTION 'BDC_INSERT'
EXPORTING
tcode = 'XK01' " 事务代码
TABLES
dynprotab = lt_bdcdata
EXCEPTIONS
OTHERS = 1.
ENDDO.

" 4. 关闭 Session
CALL FUNCTION 'BDC_CLOSE_GROUP'
EXCEPTIONS
OTHERS = 1.

WRITE: / 'Session', lv_group, '已创建,请在 SM35 中查看并执行'.

" 立即执行 Session 的方式:
" SUBMIT rsbdcsub AND RETURN
" WITH sesstab-group = lv_group.

BDC 时如何确定表控件里各行的数据?

用括号数字确定 BDC 输入行数。

示例代码:表控件(Table Control)行号处理

" 表控件中的字段需要在字段名后加行号,格式:FIELD_NAME(行号)
" 例如在采购订单中输入多行物料
DATA: lt_bdcdata TYPE TABLE OF bdcdata,
ls_bdcdata TYPE bdcdata,
lv_row TYPE n LENGTH 2.

" 第一个屏幕
ls_bdcdata-program = 'SAPMM06E'.
ls_bdcdata-dynpro = '0100'.
ls_bdcdata-dynbegin = 'X'.
APPEND ls_bdcdata TO lt_bdcdata.

" 设置表控件中的行数据
" 第1行
ls_bdcdata-fnam = 'EKPO-EMATN(01)'.
ls_bdcdata-fval = '000000000100000001'.
APPEND ls_bdcdata TO lt_bdcdata.

ls_bdcdata-fnam = 'EKPO-MENGE(01)'.
ls_bdcdata-fval = '100'.
APPEND ls_bdcdata TO lt_bdcdata.

ls_bdcdata-fnam = 'EKPO-MEINS(01)'.
ls_bdcdata-fval = 'EA'.
APPEND ls_bdcdata TO lt_bdcdata.

" 第2行
ls_bdcdata-fnam = 'EKPO-EMATN(02)'.
ls_bdcdata-fval = '000000000100000002'.
APPEND ls_bdcdata TO lt_bdcdata.

ls_bdcdata-fnam = 'EKPO-MENGE(02)'.
ls_bdcdata-fval = '200'.
APPEND ls_bdcdata TO lt_bdcdata.

ls_bdcdata-fnam = 'EKPO-MEINS(02)'.
ls_bdcdata-fval = 'KG'.
APPEND ls_bdcdata TO lt_bdcdata.

" 动态生成行号的技巧
DATA: lt_items TYPE TABLE OF ty_item, " 假设已填充数据
ls_item TYPE ty_item.

LOOP AT lt_items INTO ls_item.
lv_row = sy-tabix. " 当前行号

" 动态拼接字段名和行号
CONCATENATE 'EKPO-EMATN(' lv_row ')'
INTO ls_bdcdata-fnam.
ls_bdcdata-fval = ls_item-matnr.
APPEND ls_bdcdata TO lt_bdcdata.

CONCATENATE 'EKPO-MENGE(' lv_row ')'
INTO ls_bdcdata-fnam.
ls_bdcdata-fval = ls_item-menge.
APPEND ls_bdcdata TO lt_bdcdata.

" 当表控件超过一屏时,需要翻页
" 使用 BDC_OKCODE = 'P+' 翻到下一页
IF sy-tabix MOD 5 = 0. " 假设每屏显示5行
ls_bdcdata-fnam = 'BDC_OKCODE'.
ls_bdcdata-fval = 'P+'. " 下一页
APPEND ls_bdcdata TO lt_bdcdata.
ENDIF.
ENDLOOP.

增强

什么叫增强?

增强就是 ERP 系统中标准程序的出口,在该出口中由用户根据企业实际需求编写客户化逻辑代码。为什么会有出口呢?因为正常的业务系统不能满足实际需求,这时可以在出口中增加一些功能,来达到要求,这也就是为什么它还叫增强。在 SAP 中,出口就是标准程序最后要调用的一个函数或者功能模块,这个函数或功能模块的输入已经由 SAP 定义。自己写的 SAP 出口程序在系统升级的时候会被保留,而更改标准程序则会在 SAP 系统升级的时候被覆盖。

SAP 增强的四代技术:

代次 名称 特点
第一代 User Exit 函数出口,以 EXIT_ 开头
第二代 Customer Exit SMOD/CMOD 管理,包含函数/菜单/屏幕出口
第三代 BADI 面向对象,SE18/SE19 管理
第四代 Enhancement Spot 隐式增强,可插入任意位置

示例代码:第一代增强(User Exit)实现

" User Exit 示例:在销售订单保存前校验
" 出口函数名:EXIT_SAPLVKO1_001
" 包含程序:ZXSVO1U01(以 ZX 开头的 Include 程序)

" 在 ZXSVO1U01 中编写校验逻辑
" 示例:校验销售订单金额不超过 100 万
IF i_komk-netwr > 1000000.
" 抛出错误消息
MESSAGE e001(zmsg) WITH '订单金额' i_komk-netwr '超过限额'.
ENDIF.

如何查找 SAP 的第一类增强(User Exit)?

通过 SE37,以 EXIT* 开头,找到的函数大多是做系统预留的出口函数。前面说过,用户出口是标准程序留给用户的接口,标准程序通常不允许用户任意修改,如果修改需要申请 Access Key。而且修改标准程序可能导致的错误,ERP 公司通常是不负责的。在 SAP 中,自定义的程序通常以字母 Y 或 Z 开头,因此,出口函数中都预包含了一个 Z 开头的程序。

操作步骤:

  1. 执行 SE37,搜索函数名 EXIT*
  2. 找到相关出口函数后,双击查看其源代码
  3. 在源代码中找到以 ZX 开头的 Include 程序
  4. 使用 SE38 创建该 Include 程序并编写自定义逻辑

示例代码:查找并使用 User Exit

" 方法1:通过 SE37 查找
" 搜索 EXIT_SAPL* 或 EXIT_SAPMM*
" 例如 MM01 相关的增强函数:
" EXIT_SAPLMGMU_001 - 物料主数据检查

" 方法2:通过源代码搜索 CALL CUSTOMER-FUNCTION
" 在标准程序中搜索该语句可找到所有 User Exit 调用点

" 方法3:通过表 MODSAP 查找增强信息
SELECT * FROM modsap
WHERE name LIKE '%MM01%'.
WRITE: / modsap-name, modsap-member.
ENDSELECT.

" 示例:在 User Exit 中添加物料主数据校验
" 步骤1: SE37 找到 EXIT_SAPLMGMU_001
" 步骤2: 双击包含程序 ZXMGMU02
" 步骤3: 在 ZXMGMU02 中编写代码:
DATA: lv_matnr TYPE matnr.

" 读取当前物料号
IMPORT lv_matnr FROM MEMORY ID 'MAT'.

" 校验物料号前缀
IF lv_matnr(2) <> 'FG'.
MESSAGE e001(zmsg) WITH '物料号必须以FG开头'.
ENDIF.

如何查找 SAP 的第二类增强(Customer Exit)?其包含哪几方面?

客户自定义程序通过 Call Function 来调用 SAP 库中的一些系统函数,而用户增强的概念刚好与之相反,可以使用一个由 SAP 功能模块调用的功能模块出口。
可以在下面这几个层面对模块进行增强:在 ABAP 程序中的 Function Module Exit(E 类);在 GUI 接口中的 Menu Exit(C 类);在指定区域插入一个子窗口的 Screen Exit(S 类);在 ABAP 数据字典中表和结构的增强 Table Enhancement(T 类);还有在屏幕指定区域添加用户自定义功能的 Field Exit 和 Keyword Exit。

操作步骤:

  1. 执行 SMOD 查看增强组件
  2. 使用 CMOD 建立项目并实现增强
  3. 通过 Enhancement 查找相关增强点

示例代码:Customer Exit 的各种类型

" === E 类:Function Module Exit(函数出口)===
" 在 CMOD 中创建项目 Z_MY_ENHANCEMENT
" 添加增强组件 EXIT_SAPLV01Z_001

" 在包含程序中编写逻辑
DATA: ls_vbak TYPE vbak.
IMPORT ls_vbak FROM MEMORY ID 'VBAK'.

" 扩展销售订单抬头的自定义字段
IF sy-tcode = 'VA01'.
ls_vbak-zz_custom_field = '自定义值'.
ENDIF.


" === S 类:Screen Exit(屏幕出口)===
" 步骤1: CMOD 中添加增强组件
" 步骤2: 在增强的子屏幕中添加自定义字段
" 步骤3: 在 PBO/PAI 模块中编写逻辑

" 子屏幕 0100 的 PBO
MODULE status_0100 OUTPUT.
" 设置自定义字段的显示属性
LOOP AT SCREEN.
IF screen-name = 'Z_CUSTOM_FIELD'.
screen-input = 1. " 可输入
MODIFY SCREEN.
ENDIF.
ENDLOOP.
ENDMODULE.

" 子屏幕 0100 的 PAI
MODULE user_command_0100 INPUT.
" 校验自定义字段
IF z_custom_field IS INITIAL.
MESSAGE e001(zmsg) WITH '自定义字段不能为空'.
ENDIF.
ENDMODULE.


" === C 类:Menu Exit(菜单出口)===
" 在 CMOD 中定义功能代码 ZCUSTOM
" 在程序中处理自定义菜单功能
MODULE user_command INPUT.
CASE sy-ucomm.
WHEN 'ZCUSTOM'.
" 自定义菜单功能的处理逻辑
PERFORM custom_function.
ENDCASE.
ENDMODULE.

什么是 BADI?

即 Business Add-in,是基于 ABAP 对象的一种增强技术。SAP 预定了一些接口,供客户进行实现,完成增强。它是一种新的功能增强概念,使用 ABAP 对象技术。实现 BADI 要用到类、接口及方法等面向对象的概念。
在进行增强时,必须先定义 BADI,为 BADI 创建一个接口。接着创建一个适配器类(Adapter Class)来实现这个接口,然后创建这个适配器类的实例。

操作步骤:

  1. SE18 查找 BADI 接口定义
  2. SE19 创建 BADI 实现
  3. 在实现类中编写增强逻辑
  4. 激活 BADI 实现

示例代码:BADI 实现

" === 步骤1: SE18 查找 BADI 定义 ===
" 假设找到 BADI 定义:BADI_MATERIAL_CHECK
" 其接口 IF_BADI_MATERIAL_CHECK 包含方法:
" METHOD check_material
" IMPORTING iv_matnr TYPE matnr
" RAISING cx_check_error.

" === 步骤2: SE19 创建 BADI 实现 ===
" 实现名:Z_BADI_MATERIAL_CHECK_IMPL
" 关联接口:IF_BADI_MATERIAL_CHECK

" === 步骤3: 在实现类中编写代码 ===
" 类名:ZCL_BADI_MATERIAL_CHECK(自动生成)

CLASS zcl_badi_material_check DEFINITION
PUBLIC FINAL CREATE PUBLIC.

PUBLIC SECTION.
INTERFACES if_badi_material_check.
ENDCLASS.

CLASS zcl_badi_material_check IMPLEMENTATION.
METHOD if_badi_material_check~check_material.
" 自定义物料校验逻辑
DATA: lv_mara TYPE mara.

" 读取物料主数据
SELECT SINGLE * FROM mara INTO lv_mara
WHERE matnr = iv_matnr.

IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_check_error
EXPORTING
textid = cx_check_error=>not_found.
ENDIF.

" 校验物料状态
IF lv_mara-mstae = '01'. " 停用状态
RAISE EXCEPTION TYPE cx_check_error
EXPORTING
textid = cx_check_error=>inactive
mv_matnr = iv_matnr.
ENDIF.
ENDMETHOD.
ENDCLASS.

" === 步骤4: 在主程序中调用 BADI ===
DATA: lo_badi TYPE REF TO badi_material_check.

GET BADI lo_badi.

TRY.
CALL BADI lo_badi->check_material
EXPORTING iv_matnr = '0000000001'.
WRITE: / '物料校验通过'.
CATCH cx_check_error INTO DATA(lx_error).
WRITE: / '校验失败:', lx_error->get_text( ).
ENDTRY.

SAP 的第四类增强(Enhancement Spot)指什么?

可称为隐式增强,在代码中,可插入增强点对代码进行增强。Enhancement Spot 分为 ENHANCEMENT-POINT 与 ENHANCEMENT-SECTION,ENHANCEMENT-POINT 添加的新代码,能够与旧代码一起运行;ENHANCEMENT-SECTION 增加的新代码,将覆盖旧代码。

操作步骤:

  1. 打开标准程序(SE38/SE80)
  2. 在菜单中选择「编辑 → Enhancement Operations」
  3. 查看已有的隐式增强点
  4. 在增强点处编写自定义代码
  5. 激活增强

示例代码:Enhancement Spot 的两种类型

" === ENHANCEMENT-POINT(增强点)===
" 原代码中已定义的增强点,新代码与原代码共存
" 例如在标准程序 SAPLV01Z 的某个位置有增强点:

" 标准代码...
DATA: lv_netwr TYPE netwr.

ENHANCEMENT-POINT z_custom_point SPOTS z_custom_spot.
" ↑ 在此处插入自定义代码(新旧代码一起执行)

" 自定义增强代码(在 Enhancement Spot 中编写):
lv_netwr = lv_netwr * 1.1. " 增加 10% 附加费
WRITE: / '调整后金额:', lv_netwr.


" === ENHANCEMENT-SECTION(增强段)===
" 新代码将覆盖原代码
ENHANCEMENT-SECTION z_custom_section SPOTS z_custom_spot.
" 原始代码(将被覆盖)
lv_netwr = lv_netwr.
ENDENHANCEMENT-SECTION.

" 自定义增强代码(替换原始逻辑):
lv_netwr = lv_netwr * 0.95. " 给予 5% 折扣
WRITE: / '折扣后金额:', lv_netwr.


" === 查看所有增强点 ===
" 事务代码:SE80 → Enhancement Information System
" 或使用表 ENHSPOT 和 ENHSPOTOTX 查询
SELECT * FROM enhspot
WHERE enhspot LIKE 'Z%'.
WRITE: / enhspot-enhspot, enhspot-shorttext.
ENDSELECT.

如何建立增强?

  • 找到增强。
  • 建立增强项目。
  • 添加增强的组件添加进增强项目。
  • 编辑增强,添加自己的代码。
  • 激活增强代码。

示例代码:完整增强实施步骤(以 CMOD 为例)

" === 完整增强实施流程 ===

" 步骤1: 找到增强
" 方法A: 通过标准程序搜索 CALL CUSTOMER-FUNCTION
" 方法B: 通过 SMOD 搜索增强组件
" 方法C: 通过表 MODSAP 查询增强信息
SELECT * FROM modsap
WHERE member LIKE '%V45A%'." 查找销售订单相关增强

" 步骤2: CMOD 创建增强项目
" 执行 CMOD → 创建项目 → 输入项目名 Z_SALES_ENH
" 转到 → 分配增强组件 → 输入增强名

" 步骤3: 实现增强(以函数出口为例)
" 在包含程序中编写代码
DATA: lv_vgbel TYPE vgbel.

" 读取前凭证号
IMPORT lv_vgbel FROM MEMORY ID 'VGBEL'.

" 步骤4: 自定义逻辑
IF lv_vgbel IS NOT INITIAL.
" 校验前凭证是否存在
SELECT SINGLE vbeln FROM vbak
INTO @DATA(lv_exist)
WHERE vbeln = @lv_vgbel.
IF sy-subrc <> 0.
MESSAGE e001(zmsg) WITH '前凭证' lv_vgbel '不存在'.
ENDIF.
ENDIF.

" 步骤5: 激活增强
" CMOD → 选择项目 → 激活

与增强相关的事务代码有哪些?

  • 对于 Customer Exit:SMOD 查看增强组件,CMOD 建立项目后,实现增强。
  • 对于 BADI:SE18 查找接口,SE19 对接口进行实现。

示例代码:各增强类型对应的事务代码和表

" === 增强相关事务代码汇总 ===
" Customer Exit: SMOD(查看), CMOD(实现)
" BADI: SE18(定义), SE19(实现)
" Enhancement: SE80(Enhancement Info System)
" BTE: FIBF(BTE 配置)
" 代增强: SPRO(IMG 配置)

" === 通过表查询增强信息 ===
" 1. 查找 Customer Exit
SELECT * FROM modsap WHERE name LIKE '%VA01%'.
WRITE: / '增强:', modsap-name, '成员:', modsap-member.
ENDSELECT.

" 2. 查找 BADI 定义
SELECT * FROM sxc_attr WHERE exit_name LIKE 'BADI%'.
WRITE: / 'BADI:', sxc_attr-exit_name.
ENDSELECT.

" 3. 查找 BTE 事件
SELECT * FROM tbe01 WHERE event LIKE 'OPEN%'.
WRITE: / 'BTE事件:', tbe01-event, tbe01-appl.
ENDSELECT.

" 4. 查找隐式增强点
" 使用 SE80 → Enhancement Info System
" 或在程序中选择菜单 → Enhancement Operations → Show Enhancement Operations

如何进行数据库表字段的增强?Append 和 Include 的方式有何区别?

Append 结构(Append Structure)是在向表尾添加字段时创建的,自定义 Include(Customizing Include)由 SAP 开发人员指定,以使用户可以创建新字段。
它们之间的区别在于 Append 结构只能由该表使用,其他表不可使用;Include 能够插入任意一个结构或表,其中当插入表时,该表会自动转换为相同字段的结构。

操作步骤:

" === 方式1: Append Structure(推荐)===
" 步骤1: SE11 打开数据库表
" 步骤2: 菜单 → 附加结构 → 创建
" 步骤3: 输入 Append 名称(如 ZZ_KNA1_APPEND)
" 步骤4: 添加自定义字段
" 步骤5: 激活

" 示例:为 KNA1 表添加自定义字段
" 在 SE11 中打开表 KNA1 → 菜单 → Append Structure
" 添加字段:
" ZZ_CATEGORY TYPE CHAR10 " 客户分类
" ZZ_PRIORITY TYPE INT4 " 优先级
" ZZ_REMARK TYPE CHAR50 " 备注

" 使用自定义字段
SELECT kunnr, name1, zz_category, zz_priority
FROM kna1
WHERE zz_category = 'VIP'.

" === 方式2: Customizing Include ===
" 步骤1: SE11 打开表结构
" 步骤2: 查找 .INCLUDE 结构(如 CI_INCLUDE)
" 步骤3: 双击 Include 结构名
" 步骤4: 添加字段并激活

" 示例:表 VBAK 中已有 .INCLUDE 结构 CI_VBAK
" 在 CI_VBAK 中添加字段:
" ZZ_CHANNEL TYPE CHAR20 " 销售渠道
" ZZ_SOURCE TYPE CHAR30 " 订单来源

" 使用自定义字段
UPDATE vbak SET zz_channel = 'ONLINE'
zz_source = 'WEB'
WHERE vbeln = '0000000001'.

" === 区别总结 ===
" Append Structure:
" - 只能附加到一张表
" - 一个表可有多个 Append
" - 命名独立
" - 适合非 SAP 表或第三方表
"
" Customizing Include:
" - 可被多张表引用
" - 由 SAP 预留
" - 适合 SAP 标准表
" - 客户化配置后自动包含

实现功能模块出口时 SAP,应使用应用程序中的哪条语句?

某些 SAP 应用程序中存在功能模块出口,它使用户能够向 SAP 程序中添加一些功能。通过搜索 “CALL CUSTOMER-FUNCTION” 可以发现是否存在功能模块出口。

示例代码:查找和使用功能模块出口

" === 方法1: 在标准程序中搜索 CALL CUSTOMER-FUNCTION ===
" 使用 SE38 → 查找 → 输入 CALL CUSTOMER-FUNCTION
" 例如在 SAPLV01Z 中找到:
" CALL CUSTOMER-FUNCTION '001'
" EXPORTING
" I_VBAK = VBAK
" IMPORTING
" E_VBAK = VBAK.

" === 方法2: 通过程序名查找增强 ===
" 使用表 TRDIR 和 MODACT
SELECT * FROM modact
WHERE name IN ( 'SAPLV01Z', 'SAPLV45A' ).
WRITE: / '增强:', modact-name.
ENDSELECT.

" === 示例:在功能模块出口中添加校验 ===
" 出口函数: EXIT_SAPLV01Z_001
" 包含程序: ZXV01Z01

" 在 ZXV01Z01 中编写:
" 校验销售订单的客户信用
DATA: lv_kunnr TYPE kunnr.

IMPORT lv_kunnr FROM MEMORY ID 'KUNNR'.

SELECT SINGLE kunnr FROM kna1
INTO @DATA(lv_check)
WHERE kunnr = @lv_kunnr
AND zz_credit_block = 'X'. " 假设有信用冻结字段

IF sy-subrc = 0.
MESSAGE e001(zmsg) WITH '客户' lv_kunnr '已被信用冻结'.
ENDIF.

什么叫 BTE(Business Transaction Event),BTE 的两种类型?

BTE 可以通过函数的模式附加一个组件到 SAP 系统中,从本质上讲,BTE 也是预定义的访问接口。
BTE 的两种类型:

  • Publish and Subscribe Interface:通过事件驱动的模式从 SAP 提供的参数触发一个或多个事件(函数),但是这些事件不会返回信息给 SAP,这种模式不影响 SAP 的正常业务流程。
  • Process Interface:属于 SAP 控制业务流程的一部分,这个接口可能对应多个事件,但是每次运行的时候只能根据需要选择运行其中的一个事件,SAP 供给参数接口,并且返回参数给 SAP,这种模式影响了 SAP 的正常业务流程。

操作步骤:

  1. FIBF 查看 BTE 配置
  2. 查找相关事件编号
  3. 创建自定义函数模块
  4. 在 FIBF 中分配函数到事件

示例代码:BTE 实现

" === BTE 实现步骤 ===
" 1. FIBF → 设置 → 产品 → 客户产品 → 创建
" 2. FIBF → 设置 → P/S 函数模块/进程 → 分配函数到事件

" === 示例1: Publish and Subscribe(只读,不影响业务)===
" 事件 00001250: 付款建议创建后触发
" 创建自定义函数 Z_BTE_00001250

FUNCTION z_bte_00001250.
*" PUBLISH AND SUBSCRIBE - 不返回值给 SAP
IMPORTING
i_fipos TYPE fipos " 付款建议号
i_lifnr TYPE lifnr. " 供应商号

" 自定义逻辑:记录日志
DATA: ls_log TYPE zbtelog.
ls_log-event = '00001250'.
ls_log-doc_no = i_fipos.
ls_log-partner = i_lifnr.
ls_log-erdat = sy-datum.
ls_log-erzet = sy-uzeit.
INSERT zbtelog FROM ls_log.

ENDFUNCTION.

" === 示例2: Process Interface(可修改业务数据)===
" 事件 00002040: 采购订单创建前触发
" 创建自定义函数 Z_BTE_00002040

FUNCTION z_bte_00002040.
*" PROCESS INTERFACE - 可返回修改后的数据
IMPORTING
i_ekko TYPE ekko " 采购订单抬头
CHANGING
c_ekpo TYPE ekpo_t. " 采购订单行项目(可修改)

" 自定义逻辑:自动填充默认数据
LOOP AT c_ekpo ASSIGNING FIELD-SYMBOL(<fs_ekpo>).
IF <fs_ekpo>-werks IS INITIAL.
<fs_ekpo>-werks = '1000'. " 默认工厂
ENDIF.
ENDLOOP.

ENDFUNCTION.

接口

SAP 有哪些外部接口及其内容?

SAP 接口模式主要有 RFC、BAPI、IDOC。

  • RFC:RFC 是 SAP 系统和其他(SAP 或者非 SAP)系统间的一个重要而常用的双向接口技术,也被视为 SAP 与外部通信的基本协议。简单地说,RFC 过程就是系统调用当前系统外的程序模块,从而实现某个功能,而且调用系统和被调用系统中至少有一个必须是 SAP ABAP 系统。

  • BAPI:BAPI 实质上是一种特殊的、支持远程调用的业务对象方法,并通过远程支持的功能模块实现。BAPI 支持同步与异步的数据通信过程,典型的 BAPI 功能包括业务对象的创建、属性的读取与更新操作。

  • IDOC:IDOC 是 SAP 系统之间或 SAP 系统与外部系统之间电子数据交换的标准数据格式。通过 IDOC 接口可以实现 SAP 系统之间以及 SAP 系统与其他系统之间的数据交换。

示例代码:三种接口的基本调用方式

" === RFC 调用示例 ===
" 同步调用远程函数
CALL FUNCTION 'Z_REMOTE_FUNC'
DESTINATION 'RFC_DEST' " SM59 中配置的 RFC 目标
EXPORTING
iv_input = 'DATA'
IMPORTING
ev_output = lv_result
EXCEPTIONS
system_failure = 1
communication_failure = 2.

" === BAPI 调用示例 ===
" 调用标准 BAPI 创建销售订单
DATA: ls_header TYPE bapisdhead1,
lt_items TYPE TABLE OF bapisditem,
lt_return TYPE TABLE OF bapiret2.

ls_header-doc_type = 'TA'.
ls_header-sales_org = '1000'.

CALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2'
EXPORTING
order_header_in = ls_header
TABLES
order_items_in = lt_items
return = lt_return.

" === IDOC 创建示例 ===
" 通过 IDOC 发送数据到外部系统
DATA: ls_edidc TYPE edidc,
lt_edidd TYPE TABLE OF edidd.

ls_edidc-idoctp = 'ZMSG_TYPE01'. " IDOC 类型
ls_edidc-mestyp = 'ZMSG_TYPE'. " 消息类型
ls_edidc-rcvprt = 'LS'. " 接收方类型
ls_edidc-rcvprn = 'RECEIVER_SYS'. " 接收方

" 创建 IDOC
CALL FUNCTION 'MASTER_IDOC_DISTRIBUTE'
EXPORTING
master_idoc_control = ls_edidc
TABLES
communication_idoc_control = lt_edidc
master_idoc_data = lt_edidd.

简述 ALE/EDI 接口类型概念及应用?

ALE:用于同一个企业中不同 SAP 系统之间的数据交换,通过 IDOC 格式的数据创建分布式的系统。
EDI(Electronic Data Exchange,电子数据交换):用于实现不同企业间的电子数据交换,是各应用系统间的结构化电子数据交换标准。EDI 中的 IDOC 符合 EDI 标准,SAP 格式的数据通过 EDI 子系统转换为 EDI 标准,然后与业务伙伴进行数据交换。

示例代码:ALE 配置和 IDOC 数据发送

" === ALE 分发模型配置 ===
" 步骤1: BD54 创建逻辑系统
" 步骤2: SCC4 将逻辑系统分配给 Client
" 步骤3: SM59 创建 RFC 连接
" 步骤4: BD64 创建分发模型
" 步骤5: BD71 分发模型

" === 通过 ALE 发送主数据 ===
" 使用 BAPI 进行 ALE 分发
DATA: ls_master_data TYPE bapimaster_data,
lt_return TYPE TABLE OF bapiret2.

CALL FUNCTION 'BAPI_MATERIAL_SAVEREPLICA'
EXPORTING
headdata = ls_headdata
TABLES
return = lt_return.

" === IDOC 手动创建和发送 ===
DATA: ls_edidc TYPE edidc,
ls_edidd TYPE edidd,
lt_edidd TYPE TABLE OF edidd.

" 控制记录
ls_edidc-idoctp = 'MATMAS05'. " 物料主数据 IDOC 类型
ls_edidc-mestyp = 'MATMAS'. " 消息类型
ls_edidc-rcvprt = 'LS'. " 伙伴类型
ls_edidc-rcvprn = 'T90CLNT090'. " 接收方逻辑系统
ls_edidc-sndprt = 'LS'.
ls_edidc-sndprn = 'T90CLNT000'. " 发送方逻辑系统

" 数据记录 - E1MARA 段(物料通用数据)
ls_edidd-segnam = 'E1MARA'.
ls_edidd-sdata = '0000000001M EA '. " 物料号、物料类型、基本单位
APPEND ls_edidd TO lt_edidd.

" 发送 IDOC
CALL FUNCTION 'MASTER_IDOC_DISTRIBUTE'
EXPORTING
master_idoc_control = ls_edidc
TABLES
communication_idoc_control = lt_edidc_out
master_idoc_data = lt_edidd.

COMMIT WORK.

什么是 RFC,有哪些通信模式?

RFC 是 Remote Function Call 的简称,是 SAP 系统和其他系统之间重要而常用的双向接口技术,它包含同步 RFC、异步 RFC、事务性 RFC、队列 RFC 和并行 RFC。

示例代码:五种 RFC 通信模式

" === 1. 同步 RFC(sRFC)===
" 调用方等待被调用方返回结果
CALL FUNCTION 'Z_RFC_FUNCTION'
DESTINATION 'RFC_DEST'
EXPORTING
iv_input = lv_input
IMPORTING
ev_output = lv_output
EXCEPTIONS
system_failure = 1 MESSAGE lv_msg
communication_failure = 2 MESSAGE lv_msg.

" === 2. 异步 RFC(aRFC)===
" 调用方不等待结果,通过回调接收
CALL FUNCTION 'Z_RFC_FUNCTION'
DESTINATION 'RFC_DEST'
STARTING NEW TASK 'TASK_001'
PERFORMING callback_on_complete ON END OF TASK
EXPORTING
iv_input = lv_input.

" 回调子程序
FORM callback_on_complete USING taskname.
RECEIVE RESULTS FROM FUNCTION 'Z_RFC_FUNCTION'
IMPORTING
ev_output = lv_output.
WRITE: / '异步结果:', lv_output.
ENDFORM.

" === 3. 事务性 RFC(tRFC)===
" 在 COMMIT WORK 时批量执行
CALL FUNCTION 'Z_RFC_FUNCTION'
IN BACKGROUND TASK
DESTINATION 'RFC_DEST'
EXPORTING
iv_input = lv_input.

" 触发执行
COMMIT WORK.

" === 4. 队列 RFC(qRFC)===
" 按队列顺序执行,保证处理顺序
CALL FUNCTION 'Z_RFC_FUNCTION'
IN BACKGROUND TASK
DESTINATION 'RFC_DEST'
EXPORTING
iv_input = lv_input.

" 入队
CALL FUNCTION 'TRFC_SET_QUEUE_NAME'
EXPORTING
qname = 'Z_QUEUE_001'.

COMMIT WORK.

" === 5. 并行 RFC(pRFC)===
" 多个 RFC 并行执行,提高性能
DO 4 TIMES.
CALL FUNCTION 'Z_RFC_FUNCTION'
DESTINATION IN GROUP 'PARALLEL_GROUP'
STARTING NEW TASK |TASK_{ sy-index }|
PERFORMING callback_on_complete ON END OF TASK
EXPORTING
iv_input = lv_input.
ENDDO.

" 等待所有并行任务完成
WAIT UNTIL gv_complete_count = 4 UP TO 60 SECONDS.

RFC 中涉及的常用事务代码有哪些?

SM51:查看当前 SAP 系统中活动的应用服务器,包括 Server_Names,Hostname 和 Type(服务类型)。
SM49:查看、设定并执行外部命令。
SM59:配置 RFC 连接,其中相同类型的 RFC 目标被组合至同一个目录。这些远程目标的定义将保存在表 RFCDES 中。

示例代码:通过 RFC 相关事务代码查询信息

" === SM59 配置 RFC 连接 ===
" 步骤1: SM59 → 创建 → 选择连接类型
" - 类型 3: ABAP 系统(SAP 到 SAP)
" - 类型 T: 外部程序(TCP/IP)
" - 类型 H: HTTP 连接
" - 类型 L: 逻辑目标
" 步骤2: 输入目标主机、系统编号、登录信息
" 步骤3: 测试连接

" === 通过表查询 RFC 配置信息 ===
" 读取 RFC 目标配置
SELECT * FROM rfcdes
WHERE rfcdest LIKE 'Z%'.
WRITE: / rfcdes-rfcdest, rfcdes-host, rfcdes-sysnum.
ENDSELECT.

" 查看当前活动的应用服务器(对应 SM51)
SELECT * FROM mserv_list INTO @DATA(ls_server).
WRITE: / ls_server-name, ls_server-host, ls_server-servtype.
ENDSELECT.

" === SM49 执行外部命令 ===
" 在 SAP 中执行操作系统命令
DATA: lv_command TYPE sxpgcolist-name VALUE 'ZUNIX_CMD',
lt_output TYPE TABLE OF btcxpm.

CALL FUNCTION 'SXPG_COMMAND_EXECUTE'
EXPORTING
commandname = lv_command
additional_parameters = 'ls -la /tmp'
TABLES
exec_protocol = lt_output
EXCEPTIONS
OTHERS = 1.

LOOP AT lt_output INTO DATA(ls_output).
WRITE: / ls_output-message.
ENDLOOP.

根据调用的不同,RFC 接口提供了什么样的服务?

  • ABAP 程序的调用接口:任何 ABAP 程序都可以使用 CALL FUNCTION…DESTINATION 预计调用远程函数。
  • 非 SAP ABAP 程序的调用接口:SAP 系统为外部程序提供 RFC-supported interface 和 GUI-supported interface。

示例代码:ABAP 程序调用远程函数

" === ABAP 程序通过 RFC 调用远程函数 ===
DATA: lv_matnr TYPE matnr VALUE '0000000001',
ls_mara TYPE mara,
lv_subrc TYPE sy-subrc.

CALL FUNCTION 'Z_GET_MATERIAL_DATA'
DESTINATION 'SAP_P01' " 远程系统
EXPORTING
iv_matnr = lv_matnr
IMPORTING
es_mara = ls_mara
EXCEPTIONS
not_found = 1
system_failure = 2
communication_failure = 3
OTHERS = 4.

lv_subrc = sy-subrc.
IF lv_subrc = 0.
WRITE: / '物料描述:', ls_mara-matnr, ls_mara-mtart.
ELSE.
WRITE: / '调用失败,错误码:', lv_subrc.
ENDIF.

" === 非 ABAP 程序调用 SAP RFC ===
" Java 程序使用 JCo 连接 SAP:
" JCoDestination dest = JCoDestinationManager.getDestination("SAP_P01");
" JCoFunction function = dest.getRepository().getFunction("Z_GET_MATERIAL_DATA");
" function.getImportParameterList().setValue("IV_MATNR", "0000000001");
" function.execute(dest);
" String matnr = function.getExportParameterList().getString("ES_MARA");

RFC 接口的具体功能包括哪些?

  • 登录并退出远程系统,并执行权限检查。
  • 调用并管理远程系统会话所需的通信例程。
  • 将参数转换成远程系统所需的格式。
  • 处理通信过程中的错误。

示例代码:RFC 连接管理和错误处理

" === RFC 连接的完整生命周期 ===

" 1. 登录远程系统(自动完成,通过 SM59 配置的登录信息)
" 2. 执行权限检查
AUTHORITY-CHECK OBJECT 'S_RFC'
ID 'RFC_TYPE' FIELD 'FUNC'
ID 'RFC_NAME' FIELD 'Z_REMOTE_FUNC'
ID 'ACTVT' FIELD '16'. " 16=执行

IF sy-subrc <> 0.
WRITE: / '无权限调用远程函数'.
RETURN.
ENDIF.

" 3. 调用远程函数并处理通信错误
CALL FUNCTION 'Z_REMOTE_FUNC'
DESTINATION 'RFC_DEST'
EXPORTING
iv_input = lv_input
IMPORTING
ev_output = lv_output
EXCEPTIONS
system_failure = 1
communication_failure = 2
resource_failure = 3
OTHERS = 4.

CASE sy-subrc.
WHEN 0.
WRITE: / '调用成功:', lv_output.
WHEN 1.
WRITE: / '系统错误,远程系统不可用'.
WHEN 2.
WRITE: / '通信错误,网络连接中断'.
WHEN 3.
WRITE: / '资源不足,连接数已满'.
WHEN OTHERS.
WRITE: / '未知错误'.
ENDCASE.

在通过 CALL FUNCTION 语句进行远程功能调用的基本模式有哪些?

  • CALL FUNCTION - DESTINATION:以同步 RFC 方式实现的远程函数调用。
  • CALL FUNCTION - STARTING NEW TASK:以异步 RFC 方式实现的远程函数调用。
  • CALL FUNCTION - IN BACKGROUND TASK:以事务性 RFC 方式实现的远程函数调用。

示例代码:三种 CALL FUNCTION 模式对比

" === 模式1: 同步 RFC - 立即执行并等待结果 ===
CALL FUNCTION 'BAPI_SALESORDER_GETLIST'
DESTINATION 'SAP_P01'
EXPORTING
customer_number = '0000001000'
TABLES
sales_orders = lt_orders
EXCEPTIONS
OTHERS = 1.

" 调用后立即获得结果
LOOP AT lt_orders INTO DATA(ls_order).
WRITE: / ls_order-vbeln, ls_order-netwr.
ENDLOOP.


" === 模式2: 异步 RFC - 不等待,回调接收结果 ===
DATA: gv_result TYPE string.

CALL FUNCTION 'Z_LONG_RUNNING_FUNC'
DESTINATION 'SAP_P01'
STARTING NEW TASK 'ASYNC_001'
PERFORMING receive_result ON END OF TASK
EXPORTING
iv_input = 'DATA'.

" 可以继续执行其他逻辑
WRITE: / '异步调用已发起,继续执行...'.

" 结果在回调子程序中处理
FORM receive_result USING taskname.
DATA: lv_result TYPE string.
RECEIVE RESULTS FROM FUNCTION 'Z_LONG_RUNNING_FUNC'
IMPORTING
ev_output = lv_result.
WRITE: / '异步结果:', lv_result.
ENDFORM.


" === 模式3: 事务性 RFC - COMMIT WORK 时执行 ===
" 适合数据分发场景
CALL FUNCTION 'Z_SEND_MASTER_DATA'
IN BACKGROUND TASK
DESTINATION 'SAP_P01'
EXPORTING
iv_matnr = '0000000001'.

CALL FUNCTION 'Z_SEND_MASTER_DATA'
IN BACKGROUND TASK
DESTINATION 'SAP_P01'
EXPORTING
iv_matnr = '0000000002'.

" COMMIT WORK 时批量执行
COMMIT WORK.

怎么创建一个支持远程调用的 RFC?

在 SAP 系统中,RFC 的创建方式与普通函数模块类似,只是编写函数模块时需要在 Attribute 选项卡中将 Processing Type 选项设为 Remote-Enable Module,在传入、传出参数那里需要选择参数为 Passing Value。

操作步骤:

  1. SE37 创建函数模块
  2. Attribute 选项卡中 Processing Type 选择「Remote-Enabled Module」
  3. 定义参数时选中 Pass Value 选项
  4. 编写函数逻辑
  5. 激活函数

示例代码:创建 RFC 函数模块

" === 在 SE37 中创建 RFC 函数 ===
" 函数名:Z_RFC_GET_MATERIAL
" 函数组:Z_MATERIAL_GRP

FUNCTION z_rfc_get_material.
*"----------------------------------------------------------------------
*" RFC 函数:远程获取物料数据
*" 属性:Remote-Enabled Module
*"----------------------------------------------------------------------

IMPORTING
VALUE(iv_matnr) TYPE matnr " Pass Value 选中
EXPORTING
VALUE(es_mara) TYPE mara " Pass Value 选中
VALUE(ev_subrc) TYPE sy-subrc
EXCEPTIONS
not_found.

" 函数逻辑
SELECT SINGLE * FROM mara INTO es_mara
WHERE matnr = iv_matnr.

IF sy-subrc <> 0.
RAISE not_found.
ENDIF.

ev_subrc = sy-subrc.

ENDFUNCTION.

" === 调用该 RFC ===
CALL FUNCTION 'Z_RFC_GET_MATERIAL'
DESTINATION 'LOCAL'
EXPORTING
iv_matnr = '0000000001'
IMPORTING
es_mara = ls_mara
ev_subrc = lv_subrc
EXCEPTIONS
not_found = 1
OTHERS = 2.

怎么调用一个 SAP 标准 RFC?

在 SAP 中,功能模块的调用通过 CALL FUNCTION 语句实现。远程功能作为普通函数模块调用的扩展,可通过在 CALL FUNCTION 语句中添加 DESTINATION 子句完成,其语法格式与普通调用完全相同。

示例代码:调用 SAP 标准 RFC 函数

" === 调用标准 RFC 函数 ===
" 例如调用远程系统的 RFC_READ_TABLE
DATA: lt_fields TYPE TABLE OF rfc_db_fld,
lt_data TYPE TABLE of tab512,
ls_fields TYPE rfc_db_fld.

ls_fields-fieldname = 'MATNR'.
APPEND ls_fields TO lt_fields.
ls_fields-fieldname = 'MTART'.
APPEND ls_fields TO lt_fields.

CALL FUNCTION 'RFC_READ_TABLE'
DESTINATION 'SAP_P01'
EXPORTING
query_table = 'MARA'
rowcount = 10
TABLES
fields = lt_fields
data = lt_data
EXCEPTIONS
OTHERS = 1.

LOOP AT lt_data INTO DATA(ls_data).
WRITE: / ls_data.
ENDLOOP.

" === 本地调用(不指定 DESTINATION)===
" 如果是本地系统内的函数,可省略 DESTINATION
CALL FUNCTION 'Z_LOCAL_FUNCTION'
EXPORTING
iv_input = 'DATA'
IMPORTING
ev_output = lv_result.

怎样建立 RFC 程序?RFC 程序传递的参数是传递值还是引用?如何建立函数组?

在 SE37 中建立函数模块的时候,Attributes 选项卡中选中 Remote Function。RFC 程序传递的参数都是值传递(选中 PassValue 选项)。
建立函数组可进入 SE80,选中屏幕左边的 Repository Browser 选项,在第一个输入框中选中函数组,在第二个输入框中输入需建立的函数组名,再按回车键就可以了。
或者从 SE37 进入,在菜单上选中 goto → function group → create group。

示例代码:创建函数组和 RFC 函数

" === 步骤1: SE80 创建函数组 ===
" SE80 → Repository Browser → Function Group → 右键创建
" 函数组名:Z_RFC_GROUP_001
" 描述:RFC 接口函数组

" === 步骤2: 创建主程序(自动生成)===
" 函数组的主程序 SAPLZ_RFC_GROUP_001
" 包含文件:
" LZ_RFC_GROUP_001TOP - 全局数据声明
" LZ_RFC_GROUP_001UXX - 函数模块 Include
" LZ_RFC_GROUP_001F01 - 子程序(可选)

" === 步骤3: 在 TOP Include 中声明全局数据 ===
" LZ_RFC_GROUP_001TOP
DATA: gv_initialized TYPE abap_bool VALUE abap_false.

" === 步骤4: 创建 RFC 函数模块 ===
" SE37 → 创建 → 函数组:Z_RFC_GROUP_001
" Processing Type: Remote-Enabled Module

FUNCTION z_rfc_process_data.
*"----------------------------------------------------------------------
*" 参数必须选中 Pass Value(值传递)
*"----------------------------------------------------------------------

IMPORTING
VALUE(iv_matnr) TYPE matnr " 值传递,不可修改
VALUE(iv_werks) TYPE werks_d " 值传递
EXPORTING
VALUE(es_result) TYPE zresult_s " 值传递
CHANGING
VALUE(cv_flag) TYPE c " 值传递
EXCEPTIONS
invalid_input.

" 初始化检查
IF gv_initialized = abap_false.
gv_initialized = abap_true.
ENDIF.

" 业务逻辑
IF iv_matnr IS INITIAL OR iv_werks IS INITIAL.
RAISE invalid_input.
ENDIF.

" 处理并返回结果
SELECT SINGLE maktx FROM makt INTO es_result-description
WHERE matnr = iv_matnr AND spras = sy-langu.

cv_flag = 'X'.

ENDFUNCTION.

怎么维护 DESTINATION(远程目标)?

SM59 中包含链接和登录远程系统所需的全部参数信息。还可以在远程调用时直接指定当前系统的应用服务器作为 RFC 目标。通过 SM51 查看当前 SAP 系统中活动的应用服务器。

示例代码:RFC 目标配置和动态指定

" === SM59 配置步骤 ===
" 1. SM59 → 创建
" 2. 连接类型选择:
" - 3 = ABAP 系统(SAP ↔ SAP)
" - T = TCP/IP(调用外部程序)
" - H = HTTP/HTTPS
" 3. 填写目标主机、系统编号、Client
" 4. 填写登录用户和密码
" 5. 测试连接

" === 动态指定 RFC 目标 ===
" 方法1: 直接指定应用服务器
DATA: lv_server TYPE msname2.

SELECT SINGLE name FROM mserv_list INTO lv_server
WHERE host = 'sapapp01'.

CALL FUNCTION 'Z_REMOTE_FUNC'
DESTINATION lv_server
EXPORTING
iv_input = 'DATA'.

" 方法2: 从表 RFCDES 读取配置
SELECT SINGLE rfcdest FROM rfcdes
INTO @DATA(lv_dest)
WHERE host = @lv_server
AND systyp = '3'.

" 方法3: 使用逻辑目标(SM59 中创建)
CALL FUNCTION 'Z_REMOTE_FUNC'
DESTINATION 'LOGICAL_DEST' " 逻辑目标,自动路由
EXPORTING
iv_input = 'DATA'.

什么是 BAPI?你使用过哪些 BAPI 实现什么功能?

BAPI 业务应用程序接口(Business Application Programming Interface)是面向对象程序设计方法中的一组程序接口。它允许程序员通过 SAP 将第三方软件整合成 R/3 专有产品。

常用 BAPI 及功能:

BAPI 功能
BAPI_SALESORDER_CREATEFROMDAT2 创建销售订单
BAPI_GOODSMVT_CREATE 物料凭证过账
BAPI_ACC_DOCUMENT_POST 会计凭证过账
BAPI_MATERIAL_SAVEDATA 创建/修改物料主数据
BAPI_CUSTOMER_CREATEFROMDATA1 创建客户主数据
BAPI_PO_CREATE1 创建采购订单

示例代码:常用 BAPI 调用示例

" === BAPI 创建销售订单 ===
DATA: ls_header TYPE bapisdhead1,
lt_items TYPE TABLE OF bapisditem,
ls_item TYPE bapisditem,
lt_partners TYPE TABLE OF bapipartnr,
ls_partner TYPE bapipartnr,
lt_return TYPE TABLE OF bapiret2,
lv_vbeln TYPE vbeln_va.

" 订单抬头
ls_header-doc_type = 'TA'. " 订单类型
ls_header-sales_org = '1000'. " 销售组织
ls_header-distr_chan = '10'. " 分销渠道
ls_header-division = '00'. " 产品组

" 售达方
ls_partner-partn_role = 'AG'.
ls_partner-partn_numb = '0000001000'.
APPEND ls_partner TO lt_partners.

" 行项目
ls_item-itm_number = '000010'.
ls_item-material = '000000000100000001'.
ls_item-target_qty = '100'.
ls_item-target_qu = 'EA'.
ls_item-plant = '1000'.
APPEND ls_item TO lt_items.

" 调用 BAPI
CALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2'
EXPORTING
order_header_in = ls_header
IMPORTING
salesdocument = lv_vbeln
TABLES
return = lt_return
order_items_in = lt_items
order_partners = lt_partners.

" 检查结果
READ TABLE lt_return TRANSPORTING NO FIELDS
WITH KEY type = 'E'.
IF sy-subrc <> 0.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING wait = 'X'.
WRITE: / '销售订单创建成功:', lv_vbeln.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
LOOP AT lt_return INTO DATA(ls_ret) WHERE type CA 'EA'.
WRITE: / ls_ret-type, ls_ret-message.
ENDLOOP.
ENDIF.

什么是业务对象类型?它包含哪些组件?

业务对象类型是业务对象的定义和描述,面向对象构架的实现基础,也就是 SAP 系统中的类,它封装了业务功能和数据。它包含接口、关键字段、属性、方法和事件 5 个业务组件。

示例代码:通过 BOR(Business Object Repository)操作业务对象

" === 使用 SWO1 查看业务对象 ===
" SWO1 → 输入对象类型 → 显示
" 例如:BUS2012(采购订单)

" === 通过代码操作业务对象 ===
DATA: lo_object TYPE REF TO borobject,
lv_key TYPE swo_typeid.

lv_key = '4500000001'. " 采购订单号

" 创建业务对象实例
CREATE OBJECT lo_object
TYPE ('BUS2012') " 采购订单业务对象类型
EXPORTING
key = lv_key.

" 调用业务对象的方法
" 获取属性
DATA: lv_status TYPE string.
lo_object->get_property(
EXPORTING property = 'STATUS'
IMPORTING value = lv_status
).

" 执行方法
lo_object->execute_method(
EXPORTING method = 'RELEASE' " 审批
).

如何创建一个 BAPI?

  • 定义 BAPI Structure(Structure 不能在 BAPI 中重复使用,因为一旦 BAPI 被释放,其 Structure 将被冻结)。
  • 创建 FUNCTION MODULE(每个 BAPI 必须有自己的 Function Group,Function Group 属性必须为 RFC)。
  • 创建 Business Object(SWO1)。
  • 使用 BAPI WIZARD 创建 API Method(这样 BAPI 可以被外部程序调用)。
  • 释放 BAPI Function Module,释放 Business Object Type,释放 BAPI 作为 BOR 的一种 Method。

示例代码:创建自定义 BAPI 的完整步骤

" === 步骤1: SE11 创建 BAPI 结构 ===
" 结构名:ZBAPI_MATERIAL_EXT
" 字段:
" MATERIAL TYPE MATNR " 物料号
" PLANT TYPE WERKS_D " 工厂
" CATEGORY TYPE CHAR10 " 分类
" PRIORITY TYPE INT4 " 优先级

" === 步骤2: SE37 创建函数模块 ===
" 函数名:ZBAPI_MATERIAL_GETEXT
" 函数组:Z_BAPI_MATERIAL(属性:RFC)

FUNCTION zbapi_material_getext.
*"----------------------------------------------------------------------
*" BAPI: 获取物料扩展信息
*" 属性: Remote-Enabled Module
*"----------------------------------------------------------------------

IMPORTING
VALUE(iv_matnr) TYPE matnr
VALUE(iv_werks) TYPE werks_d
EXPORTING
VALUE(es_data) TYPE zbapi_material_ext
TABLES
return TYPE bapiret2
EXCEPTIONS
not_found.

" 校验输入参数
IF iv_matnr IS INITIAL.
APPEND VALUE bapiret2(
type = 'E'
id = 'ZMSG'
number = '001'
message = '物料号不能为空'
) TO return.
RETURN.
ENDIF.

" 读取数据
SELECT SINGLE matnr FROM mara
INTO @DATA(lv_check)
WHERE matnr = @iv_matnr.
IF sy-subrc <> 0.
RAISE not_found.
ENDIF.

" 填充返回结构
es_data-material = iv_matnr.
es_data-plant = iv_werks.

" 成功消息
APPEND VALUE bapiret2(
type = 'S'
id = 'ZMSG'
number = '003'
message = '数据获取成功'
) TO return.

ENDFUNCTION.

" === 步骤3: SWO1 创建业务对象 ===
" 对象类型:ZBUS_MATERIAL
" 接口:IF_MATERIAL_EXT
" 方法:GetExtendedData(关联 BAPI 函数)

" === 步骤4: 释放 BAPI ===
" SWO1 → 菜单 → 设置 → 发布 → Business Object
" SE37 → 函数模块 → 菜单 → 发布 → BAPI

编写 BAPI 的注意事项有哪些?

BAPI 不能包括 Call Transaction 或 Submit Report 那些会跳转到别的程序的语句;BAPI 的结构中不能使用 Append 或 Include,并且每个 BAPI 的结构都必须新建;BAPI 中不能使用 Dialog,如需进行文件选择或用户交互的也不能在 RFC 中出现。

示例代码:BAPI 开发规范和错误处理模式

" === BAPI 标准开发模式 ===

FUNCTION zbapi_create_record.
*"----------------------------------------------------------------------
*" BAPI: 创建记录
*"----------------------------------------------------------------------

IMPORTING
VALUE(is_data) TYPE zbapi_record_s
EXPORTING
VALUE(ev_id) TYPE zrecord_id
TABLES
return TYPE bapiret2.

DATA: lv_subrc TYPE sy-subrc.

" ---- 原则1: 输入校验 ----
IF is_data-field1 IS INITIAL.
APPEND VALUE #( type = 'E' id = 'ZMSG' number = '001'
message_v1 = '字段1不能为空' ) TO return.
RETURN.
ENDIF.

" ---- 原则2: 不使用 CALL TRANSACTION / SUBMIT ----
" ❌ 错误示范:
" CALL TRANSACTION 'SE38'. " BAPI 中禁止!
" SUBMIT zreport. " BAPI 中禁止!

" ---- 原则3: 不使用 Dialog ----
" ❌ 错误示范:
" CALL FUNCTION 'POPUP_TO_CONFIRM'. " BAPI 中禁止!
" WRITE: / '请输入数据'. " BAPI 中禁止!

" ---- 原则4: 使用标准错误处理模式 ----
" 执行业务逻辑
PERFORM create_record USING is_data
CHANGING ev_id lv_subrc.

IF lv_subrc <> 0.
APPEND VALUE #( type = 'E' id = 'ZMSG' number = '002'
message_v1 = '创建失败'
message_v2 = |SUBRC={ lv_subrc }| ) TO return.
RETURN.
ENDIF.

" 成功
APPEND VALUE #( type = 'S' id = 'ZMSG' number = '003'
message_v1 = '创建成功'
message_v2 = |ID={ ev_id }| ) TO return.

ENDFUNCTION.

谈谈与 BAPI 相关的事务代码。

SWO1 业务对象创建器。
SWO2 业务对象浏览器。
SWO3 业务对象仓库浏览器。
BAPI 对象浏览器。

示例代码:使用 BAPI 浏览器查找和测试 BAPI

" === BAPI 相关事务代码汇总 ===
" SWO1 - 创建和编辑业务对象
" SWO2 - 浏览业务对象
" SWO3 - 业务对象仓库浏览器
" BAPI - BAPI 浏览器(旧版)
" BAPI Explorer (SE37 中菜单 → 工具 → BAPI)

" === 通过代码查找 BAPI ===
" 方法1: 通过函数名搜索
SELECT * FROM tfdir
WHERE funcname LIKE 'BAPI%'.
WRITE: / tfdir-funcname.
ENDSELECT.

" 方法2: 通过业务对象查找关联 BAPI
SELECT * FROM swotdv
WHERE otype = 'BUS2012'. " 采购订单
WRITE: / swotdv-method, swotdv-funcname.
ENDSELECT.

" 方法3: 通过表 TBAT 查找 BAPI 关联
SELECT * FROM bapireturn
WHERE bapi_name LIKE 'BAPI_SALESORDER%'.
ENDSELECT.

RFC 和 BAPI 的区别?

  • BAPI 和 RFC 不是同一个层次上的概念。BAPI 是 SAP 提供的基于业务对象的函数,关键是它们处理的对象是 R/3 的业务相关 Business Object,比如单据类销售订单、组织、公司等,它们是一系列实体。RFC 则是一种系统间通讯的方式(Remote Function Call),一个 BAPI 函数往往是一个 RFC 函数。
  • BAPI 是 SAP 里一个很好的思想,把业务对象都对象化了。

示例代码:RFC 与 BAPI 的对比使用

" === 使用 RFC(通用远程调用)===
" 适合:系统间通信、数据传输、自定义接口
DATA: lt_raw_data TYPE TABLE OF zraw_s.

CALL FUNCTION 'Z_RFC_GET_RAW_DATA'
DESTINATION 'EXTERNAL_SYS'
EXPORTING
iv_query = 'SELECT * FROM TABLE'
IMPORTING
et_data = lt_raw_data.


" === 使用 BAPI(业务对象操作)===
" 适合:SAP 标准业务操作、主数据维护、单据处理
DATA: ls_header TYPE bapivbkdk,
lt_return TYPE TABLE OF bapiret2.

" BAPI 封装了完整的业务逻辑和校验
CALL FUNCTION 'BAPI_BILLINGDOC_CREATEMULTIPLE'
EXPORTING
billingdatain = ls_header
TABLES
return = lt_return.

" BAPI 必须配合 COMMIT/ROLLBACK 使用
READ TABLE lt_return TRANSPORTING NO FIELDS
WITH KEY type = 'E'.
IF sy-subrc = 0.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING wait = 'X'.
ENDIF.

本地文件上传为 PDF、Excel 文件使用的函数是什么?

  • KCD_EXCEL_OLE_TO_INT_CONVERT - 上传 EXCEL 文件。
  • CONVERT_OTFSPOOLJOB_2_PDF - Converts a OTF spool to PDF.
  • CONVERT_ABAPSPOOLJOB_2_PDF - Converts ABAP spool output to PDF.

示例代码:文件转换函数的使用

" === Excel 文件上传 ===
DATA: lt_raw TYPE TABLE OF alsmex_tabline,
lt_data TYPE TABLE OF zexcel_s.

" 1. 读取本地 Excel 文件
CALL FUNCTION 'ALSM_EXCEL_TO_INTERNAL_TABLE'
EXPORTING
filename = 'C:\data\input.xlsx'
i_begin_col = 1
i_begin_row = 1
i_end_col = 10
i_end_row = 1000
TABLES
intern = lt_raw
EXCEPTIONS
OTHERS = 1.

" 2. 处理上传数据
LOOP AT lt_raw INTO DATA(ls_raw).
CASE ls_raw-col.
WHEN 1. ls_data-field1 = ls_raw-value.
WHEN 2. ls_data-field2 = ls_raw-value.
ENDCASE.
AT END OF row.
APPEND ls_data TO lt_data.
CLEAR ls_data.
ENDAT.
ENDLOOP.


" === PDF 文件生成 ===
" 1. 生成 Spool
SUBMIT zreport TO SAP-SPOOL
SPOOL PARAMETERS pri_params
WITHOUT SPOOL DYNPRO.

" 2. 转换 Spool 为 PDF
DATA: lv_pdf TYPE xstring,
lv_spoolid TYPE tsp01-rqident.

CALL FUNCTION 'CONVERT_ABAPSPOOLJOB_2_PDF'
EXPORTING
src_spoolid = lv_spoolid
IMPORTING
pdf_byte = lv_pdf
EXCEPTIONS
OTHERS = 1.

" 3. 下载 PDF 文件
CALL FUNCTION 'GUI_DOWNLOAD'
EXPORTING
filename = 'C:\output\report.pdf'
filetype = 'BIN'
TABLES
data_tab = lt_pdf_content.

FTP 文件的上传、下载操作是什么?

FTP 程序大致流程为:

  • FTP_CONNECT - 打开并登录 FTP 服务器的连接。
  • FTP_COMMAND - 在 FTP 服务器上执行一个命令。
  • FTP_DISCONNECT - 关闭指向 FTP 服务器的连接。

示例代码:FTP 文件上传下载完整程序

REPORT zftp_example.

DATA: lv_handle TYPE i,
lv_cmd TYPE string,
lt_result TYPE TABLE OF string.

" 1. 连接 FTP 服务器
CALL FUNCTION 'FTP_CONNECT'
EXPORTING
user = 'ftpuser'
password = 'ftppass'
host = '192.168.1.100'
rfc_dest = 'SAPFTP' " SM59 中配置的 FTP 目标
IMPORTING
handle = lv_handle
EXCEPTIONS
OTHERS = 1.

IF sy-subrc <> 0.
WRITE: / 'FTP 连接失败'.
RETURN.
ENDIF.

" 2. 切换目录
CALL FUNCTION 'FTP_COMMAND'
EXPORTING
handle = lv_handle
command = 'cd /upload'
TABLES
data = lt_result.

" 3. 上传文件
" 先将内表数据转为字符串
DATA: lt_lines TYPE TABLE OF string.
APPEND 'HEADER1,HEADER2,HEADER3' TO lt_lines.
APPEND 'DATA1,DATA2,DATA3' TO lt_lines.

CALL FUNCTION 'FTP_COMMAND'
EXPORTING
handle = lv_handle
command = 'put localfile.csv'
TABLES
data = lt_result.

" 4. 下载文件
CLEAR lt_result.
CALL FUNCTION 'FTP_COMMAND'
EXPORTING
handle = lv_handle
command = 'get remotefile.csv'
TABLES
data = lt_result.

" 5. 断开连接
CALL FUNCTION 'FTP_DISCONNECT'
EXPORTING
handle = lv_handle.

WRITE: / 'FTP 操作完成'.

本地文件上传为 CSV 文件的函数?

  • TEXT_CONVERT_CSV_TO_SAP:导入 CSV 数据到 SAP 系统。
  • GUI_UPLOAD:文件上传。

示例代码:CSV 文件上传和解析

" === 方式1: GUI_UPLOAD 上传文件 ===
DATA: lt_raw TYPE TABLE OF string,
lv_file TYPE string VALUE 'C:\data\input.csv'.

CALL FUNCTION 'GUI_UPLOAD'
EXPORTING
filename = lv_file
filetype = 'ASC' " 文本文件
has_field_separator = ',' " CSV 分隔符
TABLES
data_tab = lt_raw
EXCEPTIONS
file_open_error = 1
OTHERS = 2.

" 解析 CSV 数据
DATA: lt_data TYPE TABLE OF zcsv_s,
ls_data TYPE zcsv_s.

LOOP AT lt_raw INTO DATA(lv_line).
" 按逗号分割
SPLIT lv_line AT ',' INTO
ls_data-field1
ls_data-field2
ls_data-field3.
APPEND ls_data TO lt_data.
ENDLOOP.


" === 方式2: TEXT_CONVERT_CSV_TO_SAP ===
DATA: lt_csv_data TYPE TABLE OF zcsv_s.

CALL FUNCTION 'TEXT_CONVERT_CSV_TO_SAP'
EXPORTING
field_seperator = ','
input_filename = lv_file
TABLES
datatab = lt_csv_data
EXCEPTIONS
conversion_failed = 1
OTHERS = 2.


" === 方式3: 读取应用服务器文件 ===
DATA: lv_line TYPE string.

OPEN DATASET '/data/input.csv' FOR INPUT
IN TEXT MODE ENCODING DEFAULT.

DO.
READ DATASET '/data/input.csv' INTO lv_line.
IF sy-subrc <> 0. EXIT. ENDIF.

SPLIT lv_line AT ',' INTO
ls_data-field1
ls_data-field2
ls_data-field3.
APPEND ls_data TO lt_data.
ENDDO.

CLOSE DATASET '/data/input.csv'.

SAP 文件操作都有哪几种?分别有什么操作?版本有什么限制?

SAP 文件操作的有以下两种文件:

  • 顺次文件(服务器文件)。
  • 本地文件(客户端文件)。
    顺次文件的操作包括:打开、读取、写入、关闭、删除。
    本地文件的操作:上传、下载。4.7 版本以后使用 GUI_DOWNLOAD / GUI_UPLOAD;4.6 版本以前使用 WS_DOWNLOAD / WS_UPLOAD / DOWNLOAD / UPLOAD。

示例代码:服务器文件操作和本地文件操作

" === 服务器文件操作(应用服务器)===

" 1. 打开文件(写入)
OPEN DATASET '/data/output.txt' FOR OUTPUT
IN TEXT MODE ENCODING DEFAULT.

" 2. 写入数据
DATA: lv_line TYPE string.
lv_line = '第一行数据'.
TRANSFER lv_line TO '/data/output.txt'.

lv_line = '第二行数据'.
TRANSFER lv_line TO '/data/output.txt'.

" 3. 关闭文件
CLOSE DATASET '/data/output.txt'.


" 4. 打开文件(读取)
OPEN DATASET '/data/output.txt' FOR INPUT
IN TEXT MODE ENCODING DEFAULT.

" 5. 读取数据
DO.
READ DATASET '/data/output.txt' INTO lv_line.
IF sy-subrc <> 0. EXIT. ENDIF.
WRITE: / lv_line.
ENDDO.

CLOSE DATASET '/data/output.txt'.


" 6. 删除文件
DELETE DATASET '/data/old_file.txt'.


" === 本地文件操作(客户端)===

" 上传(4.7+ 版本)
DATA: lt_file TYPE TABLE OF string.
CALL FUNCTION 'GUI_UPLOAD'
EXPORTING
filename = 'C:\data\input.txt'
filetype = 'ASC'
TABLES
data_tab = lt_file.

" 下载(4.7+ 版本)
DATA: lt_output TYPE TABLE OF string.
APPEND '输出数据行1' TO lt_output.
APPEND '输出数据行2' TO lt_output.

CALL FUNCTION 'GUI_DOWNLOAD'
EXPORTING
filename = 'C:\data\output.txt'
filetype = 'ASC'
TABLES
data_tab = lt_output.

在文件处理时,文件中出现乱码,应该从哪方面考虑?

OpenDataset 时指定字符编码。

示例代码:正确处理文件字符编码

" === 乱码问题的常见原因和解决方案 ===

" 原因1: 未指定编码(默认使用系统编码,可能不匹配)
" 错误方式:
OPEN DATASET '/data/file.txt' FOR INPUT IN TEXT MODE.

" 正确方式:指定 UTF-8 编码
OPEN DATASET '/data/file.txt' FOR INPUT
IN TEXT MODE ENCODING UTF-8.

" 原因2: 中文文件需要 GB2312 编码
OPEN DATASET '/data/chinese.txt' FOR INPUT
IN TEXT MODE ENCODING GB2312.

" 原因3: 二进制文件被当作文本读取
" 图片、PDF 等文件应使用二进制模式
OPEN DATASET '/data/report.pdf' FOR INPUT
IN BINARY MODE.

DATA: lv_xstr TYPE xstring.
READ DATASET '/data/report.pdf' INTO lv_xstr.
CLOSE DATASET '/data/report.pdf'.


" 原因4: 写入时编码不一致
" 写入 UTF-8 文件
OPEN DATASET '/data/output.txt' FOR OUTPUT
IN TEXT MODE ENCODING UTF-8 WITH BYTE-ORDER MARK.

DATA: lv_text TYPE string.
lv_text = '中文数据'.
TRANSFER lv_text TO '/data/output.txt'.
CLOSE DATASET '/data/output.txt'.


" 原因5: GUI_UPLOAD 编码问题
DATA: lt_data TYPE TABLE OF string.
CALL FUNCTION 'GUI_UPLOAD'
EXPORTING
filename = 'C:\data\chinese.txt'
filetype = 'ASC'
TABLES
data_tab = lt_data.

" 如果上传后乱码,尝试指定代码页
CALL FUNCTION 'GUI_UPLOAD'
EXPORTING
filename = 'C:\data\chinese.txt'
filetype = 'ASC'
codepage = '8400' " UTF-8 代码页
TABLES
data_tab = lt_data.

简单描述 Web Service 的创建过程?

发布大体步骤:SE37 向导发布 Web Service,SOAMANAGER 配置端口。
调用大体步骤:SE80 创建代理类,LOCONFIG 创建逻辑端口,SE38 程序中调用,创建代理类对象,调用对象的方法。

示例代码:Web Service 发布和调用

" === 发布 Web Service ===
" 步骤1: SE37 → 函数模块 → 菜单 → 实用程序 → 创建 Web Service
" 步骤2: 向导中选择服务类型和端点
" 步骤3: SOAMANAGER 配置
" - 创建服务组
" - 绑定服务
" - 配置认证方式

" === 调用 Web Service ===
" 步骤1: SE80 → 创建代理类(从 WSDL 生成)
" 步骤2: SOAMANAGER 创建逻辑端口

" 代码调用
DATA: lo_proxy TYPE REF TO zco_my_service,
lo_input TYPE zmy_request,
lo_output TYPE zmy_response.

" 创建代理对象
CREATE OBJECT lo_proxy
EXPORTING
logical_port_name = 'ZLP_MY_SERVICE'.

" 设置请求参数
lo_input-username = 'testuser'.

" 调用 Web Service
TRY.
lo_proxy->my_operation(
EXPORTING input = lo_input
IMPORTING output = lo_output
).
WRITE: / '结果:', lo_output-result.
CATCH cx_ai_system_fault INTO DATA(lx_error).
WRITE: / '调用错误:', lx_error->get_text( ).
CATCH zcx_my_exception INTO DATA(lx_biz).
WRITE: / '业务错误:', lx_biz->get_text( ).
ENDTRY.

现阶段接口开发使用的技术?

企业服务总线(ESB:Enterprise Service Bus)+ WebService。

示例代码:ESB 集成模式

" === ESB 集成架构 ===
" SAP ↔ ESB(中间件)↔ 外部系统

" 1. SAP 作为服务提供方
" - 创建 RFC/BAPI
" - 发布为 Web Service
" - ESB 消费该 Web Service

" 2. SAP 作为服务消费方
" - ESB 发布 Web Service
" - SAP 创建代理类调用

" 3. 通过 IDOC + ESB 实现异步集成
" - SAP 发送 IDOC
" - ESB 监听 IDOC 端口
" - ESB 转换数据格式后发送到目标系统


" === RESTful API 调用(新版本 SAP 支持)===
" 使用 CL_HTTP_CLIENT 调用 REST API
DATA: lo_http_client TYPE REF TO if_http_client,
lv_url TYPE string VALUE 'https://api.example.com/data',
lv_response TYPE string.

cl_http_client=>create_by_url(
EXPORTING url = lv_url
IMPORTING client = lo_http_client
).

lo_http_client->request->set_method( if_http_request=>co_request_method_post ).
lo_http_client->request->set_cdata( '{"key":"value"}' ).
lo_http_client->request->set_header_field(
name = 'Content-Type'
value = 'application/json'
).

lo_http_client->send( ).
lo_http_client->receive( ).

lv_response = lo_http_client->response->get_cdata( ).
WRITE: / 'API 响应:', lv_response.

为一个远程调用函数(Remote-Enabled Module)创建 Web Service 都有什么相关的事务代码?

创建 Web Service:SE37/SE80。

示例代码:通过 SOAMANAGER 配置 Web Service

" === Web Service 创建和配置步骤 ===

" 步骤1: SE37 创建 RFC 函数
FUNCTION zws_get_customer_data.
*" Remote-Enabled Module
IMPORTING
VALUE(iv_kunnr) TYPE kunnr
EXPORTING
VALUE(es_kna1) TYPE kna1.

SELECT SINGLE * FROM kna1 INTO es_kna1
WHERE kunnr = iv_kunnr.
ENDFUNCTION.

" 步骤2: SE37 发布为 Web Service
" SE37 → 输入函数名 → 菜单 → 实用程序 → 更多 → 创建 Web Service
" 选择 Wizard 类型
" 输入服务名和描述

" 步骤3: SOAMANAGER 配置
" SOAMANAGER → 服务配置
" 创建服务发布 → 选择刚创建的 Web Service
" 配置端点 URL 和认证方式

" 步骤4: 测试 Web Service
" SOAMANAGER → Web Service 测试工具
" 或使用浏览器访问 WSDL URL:
" http://host:port/sap/bc/srt/wsdl/bndg_.../zws_get_customer_data?sap-client=100

在企业信息系统结构内企业服务总线是如何使用的?

企业服务总线来管理 ERP 和其他接口交互的信息,可以查看监听管理双发系统的交互信息。

示例代码:ESB 集成场景设计

" === 典型 ESB 集成场景 ===

" 场景1: SAP → ESB → CRM(主数据同步)
" SAP 通过 ALE/IDOC 发送客户主数据
" ESB 接收 IDOC → 转换格式 → 调用 CRM API

" 场景2: 电商平台 → EBS → SAP(订单同步)
" 电商平台发送 REST API 请求到 ESB
" ESB 转换为 BAPI 调用 → SAP 创建销售订单

" 场景3: SAP → ESB → 银行(付款接口)
" SAP 发送付款请求到 ESB
" ESB 加密 → 调用银行接口 → 返回结果

" === SAP 侧的监控代码 ===
" 通过 SM58 监控 tRFC 调用
" 通过 SM21 查看系统日志中的接口错误
" 通过 BD87 监控 IDOC 处理状态

" 查询 IDOC 处理状态
SELECT * FROM edidc
WHERE credat = @sy-datum
AND status IN ('51', '56'). " 错误状态
WRITE: / 'IDOC:', edidc-docnum,
'状态:', edidc-status,
'消息类型:', edidc-mestyp.
ENDSELECT.

数据库知识

ABAP 数据字典有哪些对象或元素?

Data element、Domain、structure、lock object、views。

示例代码:数据字典对象的创建和使用

" === 数据字典对象层次关系 ===
" Domain → Data Element → Table Field
" Structure → 表结构
" Lock Object → 锁定对象
" Views → 视图

" === 1. Domain(域)- 定义数据的技术属性 ===
" SE11 → 创建 → Domain
" 域名:ZMATNR_TYPE
" 数据类型:CHAR
" 长度:18
" 转换规则:MATN1(前导零转换)

" === 2. Data Element(数据元素)- 定义字段的业务含义 ===
" SE11 → 创建 → Data Element
" 名称:ZMATNR_EXT
" 域:ZMATNR_TYPE
" 短描述:物料编号(扩展)

" === 3. Structure(结构)===
TYPES: BEGIN OF ty_material,
matnr TYPE matnr, " 物料号
maktx TYPE maktx, " 物料描述
mtart TYPE mtart, " 物料类型
meins TYPE meins, " 基本单位
END OF ty_material.

" === 4. Lock Object(锁定对象)===
" SE11 → 创建 → Lock Object:EZ_MARA
" 主表:MARA,锁定模式:E(独占锁定)
" 激活后自动生成函数:ENQUEUE_EZ_MARA / DEQUEUE_EZ_MARA

" 加锁
CALL FUNCTION 'ENQUEUE_EZ_MARA'
EXPORTING
matnr = '0000000001'
EXCEPTIONS
foreign_lock = 1
system_failure = 2.

" 解锁
CALL FUNCTION 'DEQUEUE_EZ_MARA'
EXPORTING
matnr = '0000000001'.

" === 5. Views(视图)===
" SE11 → 创建 → View
" 视图类型:Database View / Help View / Maintenance View / Projection View
" Database View:关联多表查询
" Maintenance View:可维护的视图(SM30)

数据库提交确认和数据库回滚取消语句?

COMMIT WORK。
ROLLBACK WORK。

示例代码:数据库提交和回滚的正确使用

" === COMMIT WORK - 确认数据库操作 ===
" 适用于:INSERT、UPDATE、DELETE、MODIFY 之后

" 向数据库插入数据
INSERT zcustom_table FROM TABLE lt_data.

IF sy-subrc = 0.
" 提交事务,数据正式写入数据库
COMMIT WORK.
WRITE: / '数据提交成功,共', sy-dbcnt, '条'.
ELSE.
" 回滚事务,撤销所有未提交的操作
ROLLBACK WORK.
WRITE: / '数据提交失败,已回滚'.
ENDIF.


" === 带 WAIT 的 COMMIT ===
" COMMIT WORK AND WAIT = 同步提交
" 等待数据库操作完成后再继续
INSERT zcustom_table FROM TABLE lt_data.
COMMIT WORK AND WAIT. " 确保数据已写入数据库后才继续


" === 在 BAPI 中的使用 ===
" BAPI 不自动提交,需要显式调用
CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
headdata = ls_headdata
IMPORTING
return = ls_return.

IF ls_return-type <> 'E'.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'. " 同步等待
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ENDIF.


" === 注意事项 ===
" 1. COMMIT WORK 会触发:
" - 数据库提交
" - tRFC 任务执行
" - 更新任务(CALL FUNCTION IN UPDATE TASK)执行
" - 事件触发
" 2. 不要在 LOOP 中使用 COMMIT WORK(性能差)
" 3. CALL FUNCTION IN UPDATE TASK 会在 COMMIT 时执行

什么是 LUW?

LUW 称为逻辑工作单位,同一个逻辑工作单位共享一段内存,将操作放置在同一个 LUW 中可以控制事务的同时提交和回滚。

示例代码:LUW 的理解和使用

" === LUW 概念 ===
" 一个 LUW 从上次 COMMIT/ROLLBACK 开始
" 到下次 COMMIT/ROLLBACK 结束
" 同一 LUW 中的所有操作要么全部成功,要么全部失败

" === 示例:银行转账(同一 LUW 中的操作)===
" 场景:从账户 A 转 1000 元到账户 B

DATA: lv_amount TYPE p VALUE 1000.

" 步骤1: 扣除账户 A 的余额(在同一个 LUW 中)
UPDATE zaccount SET balance = balance - lv_amount
WHERE account_id = 'A001'.

IF sy-subrc <> 0.
ROLLBACK WORK.
WRITE: / '账户 A 扣款失败,事务回滚'.
RETURN.
ENDIF.

" 步骤2: 增加账户 B 的余额(在同一个 LUW 中)
UPDATE zaccount SET balance = balance + lv_amount
WHERE account_id = 'B001'.

IF sy-subrc <> 0.
ROLLBACK WORK. " 步骤1 也被回滚
WRITE: / '账户 B 入账失败,事务回滚'.
RETURN.
ENDIF.

" 步骤3: 提交整个 LUW
COMMIT WORK. " 步骤1 和步骤2 同时提交
WRITE: / '转账成功'.


" === 更新任务中的 LUW ===
" 使用 CALL FUNCTION IN UPDATE TASK 将操作放入更新 LUW
" 更新函数在 COMMIT WORK 时执行
CALL FUNCTION 'Z_UPDATE_TASK'
IN UPDATE TASK
EXPORTING
iv_data = 'VALUE'.

" 触发更新任务执行
COMMIT WORK.

简述 Modify、Insert、Update 对数据库表做操作时的影响。

  • Modify 操作数据库时,可以使用 From 内表或者工作区来进行多条或单条的更新,要求内表或工作区跟数据库表的结构一致。当数据库表中存在重复记录时,执行更新操作,更新的值为内表或工作区的值;当数据库表中不存在记录时,执行插入操作,插入的值为内表或工作区的值。
  • Insert 操作数据库时,可以用 From 内表或者工作区进行多条或单条的插入,要求内表或工作区跟数据库表的结构一致。当数据库中不存在重复记录时,执行插入操作,插入的值为内表或工作区的值;如果已经存在重复记录,会出现更新异常。
  • Update 操作数据库时,可以直接 set 来进行单值更新,可以用 From Table 和工作区进行批量更新,要求内表和工作区跟数据库表的结构一致。当存在记录时执行更新操作;当不存在记录时,没有数据会被更新,同时也不会产生异常,sy-subrc=0。

示例代码:三种操作的对比

DATA: ls_mara TYPE mara,
lt_mara TYPE TABLE OF mara.

" === INSERT - 插入(记录必须不存在)===
ls_mara-matnr = 'TEST001'.
ls_mara-mtart = 'FERT'.
ls_mara-meins = 'EA'.

INSERT mara FROM ls_mara.
IF sy-subrc = 0.
COMMIT WORK.
WRITE: / '插入成功'.
ELSE.
ROLLBACK WORK.
WRITE: / '插入失败(记录已存在)'.
ENDIF.

" 批量插入
INSERT mara FROM TABLE lt_mara.
IF sy-subrc = 0.
COMMIT WORK.
WRITE: / '批量插入成功,共', sy-dbcnt, '条'.
ENDIF.


" === UPDATE - 更新(记录必须存在)===
" 方式1: SET 语法
UPDATE mara SET mtart = 'HALB'
meins = 'KG'
WHERE matnr = 'TEST001'.

IF sy-subrc = 0.
COMMIT WORK.
WRITE: / '更新成功'.
ENDIF.

" 方式2: FROM 工作区(按主键更新)
ls_mara-matnr = 'TEST001'.
ls_mara-mtart = 'ROH'.
UPDATE mara FROM ls_mara.
COMMIT WORK.

" 注意:UPDATE 不存在的记录不会报错
UPDATE mara SET mtart = 'FERT'
WHERE matnr = 'NOT_EXIST'.
" sy-subrc = 0,但 sy-dbcnt = 0


" === MODIFY - 修改(存在则更新,不存在则插入)===
" 方式1: FROM 工作区
ls_mara-matnr = 'TEST002'.
ls_mara-mtart = 'FERT'.
ls_mara-meins = 'EA'.
MODIFY mara FROM ls_mara.
IF sy-subrc = 0.
COMMIT WORK.
WRITE: / '修改成功(插入或更新)'.
ENDIF.

" 方式2: FROM TABLE(批量)
MODIFY mara FROM TABLE lt_mara.
COMMIT WORK.

" MODIFY = INSERT + UPDATE 的组合
" 适合:不确定记录是否存在的情况
" 注意:性能比 INSERT 或 UPDATE 差


" === 三种操作对比总结 ===
" INSERT : 记录不存在 → 插入;记录已存在 → 异常
" UPDATE : 记录存在 → 更新;记录不存在 → 不操作(sy-subrc=0)
" MODIFY : 记录存在 → 更新;记录不存在 → 插入

描述域、数据元素、表字段之间的关系。

域为数据字典中最小的单元,数据元素是基于域进行定义的,表字段则是通过数据元素进行定义的。

示例代码:三层结构的创建和关系

" === 层次关系 ===
" Domain(域)→ Data Element(数据元素)→ Table Field(表字段)
" 定义技术属性 定义业务含义 实际使用

" === 创建示例 ===
" 1. 创建 Domain
" SE11 → Domain → ZCUST_STATUS
" 数据类型:CHAR,长度:1
" 固定值:A=活跃, I=不活跃, B=冻结

" 2. 创建 Data Element
" SE11 → Data Element → ZCUST_STATUS_DE
" 关联域:ZCUST_STATUS
" 字段标签:客户状态

" 3. 在表中使用
" SE11 → 表 ZCUSTOMER
" 字段 STATUS TYPE ZCUST_STATUS_DE

" === 为什么要分三层? ===
" 1. 复用性:一个域可以被多个数据元素引用
" 例如:域 ZCHAR10 可用于物料号、订单号等
" 2. 一致性:修改域的技术属性,所有引用自动更新
" 3. 业务语义:数据元素封装了字段的业务含义和帮助文本

" === 查询三者关系 ===
SELECT dd03l~fieldname,
dd04l~rollname, " Data Element
dd04l~domname " Domain
FROM dd03l
LEFT JOIN dd04l ON dd03l~rollname = dd04l~rollname
WHERE dd03l~tabname = 'MARA'
INTO TABLE @DATA(lt_fields).

LOOP AT lt_fields INTO DATA(ls_field).
WRITE: / ls_field-fieldname,
ls_field-rollname,
ls_field-domname.
ENDLOOP.

数据字典有几种缓冲方式,适用范围是什么?

  • Full Buffer(全缓冲),一个表的数据要么全在内存中要么全不在。当表记录很少,访问非常频繁且很少进行修改时使用。
  • Generic Area Buffer(常规区域缓冲),语言代码相关的表是最常使用的情况,如文本描述表,缓存满足在 Generic Keys 中定义的字段的值的记录。
  • Single-Record Buffer(单记录缓冲),数据库表记录比较多,并且经常都是读取一条记录,如 select single。

示例代码:缓冲方式的选择和配置

" === 缓冲配置(SE11 → 表 → 技术设置)===

" 1. Full Buffer(全缓冲)
" 适用表:T000(Client 表)、T001(公司代码表)
" 特点:表数据完全加载到内存
" 适合:数据量小(<100条)、频繁读取、极少修改
" 示例:配置表、文本表

" 2. Generic Area Buffer(通用区域缓冲)
" 适用表:MAKT(物料文本)、KNA1(客户主数据)
" 特点:按通用键值缓存
" 配置:Generic Key Fields = SPRAS(语言代码)
" 适合:按特定字段查询的表

" 3. Single-Record Buffer(单记录缓冲)
" 适用表:MARA(物料主数据)、VBAK(销售订单抬头)
" 特点:仅缓存访问过的单条记录
" 适合:数据量大、通常用 SELECT SINGLE 访问


" === 缓冲相关注意事项 ===
" 1. 使用 SELECT SINGLE 时自动使用缓冲
SELECT SINGLE * FROM mara INTO ls_mara
WHERE matnr = '0000000001'.
" 如果表启用了 Single-Record Buffer,此查询从缓冲读取

" 2. 更新缓冲表时需注意一致性
UPDATE mara SET mtart = 'FERT'
WHERE matnr = '0000000001'.
COMMIT WORK.
" 更新后缓冲会自动刷新(同应用服务器)
" 其他应用服务器的缓冲可能延迟刷新

" 3. 绕过缓冲直接读取数据库
SELECT SINGLE * FROM mara INTO ls_mara
WHERE matnr = '0000000001'
%_HINTS ORACLE 'INDEX(MARA MATNR)'.

" 4. 清除缓冲(事务码:ST02 → Buffer → Reset)
" 或使用:CALL FUNCTION 'BUFFER_RESET_ALL'.

ABAP 数据表的主索引是什么?索引的好处与坏处有哪些?建立索引的注意事项有哪些?

数据表的主键即是表的主索引;好的索引能加快数据读取的速度但会增加更新数据库表的时间;建立次级索引时应尽量选取那些查询条件经常使用到的字段。

示例代码:索引的创建和使用

" === 主索引(Primary Index)===
" 由表的主键字段自动创建
" 例如 MARA 表:主键 = MATNR → 主索引包含 MATNR
" 查询时使用主键字段可直接利用主索引
SELECT SINGLE * FROM mara INTO ls_mara
WHERE matnr = '0000000001'. " 使用主索引,最快


" === 次级索引(Secondary Index)===
" SE11 → 表 → Indexes → 创建
" 例如:为 VBAK 表创建次级索引
" 索引名:ZVBAK_IDX01
" 字段:VBELN(已含在主索引)、ERDAT(创建日期)、ERNAM(创建者)

" 使用次级索引的查询
SELECT * FROM vbak INTO TABLE lt_vbak
WHERE erdat = '20240101'
AND ernam = 'ADMIN'.
" 系统自动选择最优索引


" === 索引注意事项 ===
" 1. 建立索引的原则
" - 选择查询条件中频繁使用的字段
" - 选择高选择性的字段(不同值多)
" - 避免在频繁更新的表上建立过多索引

" 2. 查看查询使用了哪个索引
" ST05 → SQL Trace → 查看执行计划
" SE30 → Runtime Analysis

" 3. 索引字段顺序很重要
" 将等值条件字段放在前面,范围条件字段放在后面
" 例如索引 (BUKRS, GJAHR, BELNR)
" WHERE bukrs = '1000' AND gjahr = '2024' ← 好
" WHERE gjahr = '2024' AND bukrs = '1000' ← 也好(优化器自动调整)
" WHERE belnr = '001' ← 不能使用此索引(跳过了前导字段)

" 4. 查看表的索引信息
SELECT * FROM dd12l
WHERE sqltab = 'VBAK'
AND indexname <> 'VBAK~0'. " 排除主索引
WRITE: / '索引:', dd12l-indexname,
'字段:', dd12l-seckeyname.
ENDSELECT.

" 5. 避免索引失效的写法
" ❌ 在索引字段上使用函数
" WHERE UPPER(matnr) = 'TEST' ← 索引失效
" ✅ 正确写法
" WHERE matnr = 'TEST' ← 使用索引

" ❌ LIKE 以通配符开头
" WHERE matnr LIKE '%001' ← 索引失效
" ✅ 正确写法
" WHERE matnr LIKE '00000000%' ← 使用索引

ABAP 透明表有哪几种数据类(Data Class)?对数据的存储有什么影响?

数据类主要分为以下三类:

  • 主数据类:经常读取,很少修改的数据。
  • 业务数据类:经常更新的数据。
  • 组织数据类:系统初始化时存在的数据,也很少修改。
    数据类决定了数据实际存放的物理区域。

示例代码:数据类的理解和配置

" === 数据类(Data Class)在 SE11 中配置 ===
" 表 → 技术设置 → Data Class

" 1. 主数据类(Master Data)
" APPL0 - 主数据(如 MARA、KNA1、LFA1)
" 特点:读多写少,数据相对稳定
" 存储区域:专用主数据表空间

" 2. 业务数据类(Transaction Data)
" APPL1 - 业务数据(如 VBAK、BKPF、EKKO)
" 特点:频繁更新,数据量大
" 存储区域:专用业务数据表空间

" 3. 组织数据类(Organization Data)
" APPL2 - 组织数据(如 T001、T001W)
" 特点:系统初始化时创建,很少修改
" 存储区域:专用组织数据表空间

" 4. 系统数据类
" MASTER - 系统主数据
" CUSTOMIZING - 定制化数据
" USER - 用户数据


" === 查询表的数据类 ===
SELECT tabname, contflag, tabclass
FROM dd02l
WHERE tabname IN ('MARA', 'VBAK', 'T001', 'BKPF')
INTO TABLE @DATA(lt_tables).

LOOP AT lt_tables INTO DATA(ls_table).
WRITE: / ls_table-tabname,
'类型:', ls_table-tabclass,
'数据类:', ls_table-contflag.
ENDLOOP.

" === 数据类对性能的影响 ===
" 1. 不同数据类存放在不同的表空间/磁盘区域
" 2. 主数据类通常有更好的缓冲支持
" 3. 业务数据类需要考虑分区策略
" 4. 选择正确的数据类有助于数据库优化器的工作

SAP 中有几种表,他们的区别是什么?

Transparent table(透明表),Pooled table(共享表)和 Cluster table(簇表)。
透明表是和 DB 层的物理表(Physical Table)对应的,后两者则是不对应到 DB 表的。比如 TBSL 就是一个共享表,在 DB 层找不到此表。
然后多个簇表或共享表组成一个表簇(table cluster)或表池(Table Pool),表簇和表池是 DB 层的一个物理表。

示例代码:三种表类型的查询和使用

" === 1. 透明表(Transparent Table)===
" 特点:与数据库物理表一一对应
" 例子:MARA、VBAK、BKPF、KNA1
" 可以直接用 SQL 语句操作
SELECT * FROM mara INTO TABLE lt_mara UP TO 10 ROWS.

" 查看透明表的数据库表名
SELECT tabname, sqltab FROM dd02l
WHERE tabname IN ('MARA', 'VBAK', 'T001')
AND as4local = 'A'
INTO TABLE @DATA(lt_info).
" MARA → MARA(透明表名=数据库表名)


" === 2. 共享表(Pooled Table)===
" 特点:多个共享表组合成一个表池(Table Pool)
" 例子:TBSL、T000、T001W(某些配置表)
" 在 DB 层,多个共享表存储在一个物理表中
" 查询方式与透明表相同
SELECT * FROM tbsl INTO TABLE lt_tbsl.

" 查看共享表信息
SELECT tabname, tabclass FROM dd02l
WHERE tabclass = 'POOL'
AND as4local = 'A'
INTO TABLE @DATA(lt_pooled).


" === 3. 簇表(Cluster Table)===
" 特点:多个簇表组合成一个表簇(Table Cluster)
" 例子:BSEG(会计凭证行项目)、KONV(定价条件)
" 簇表不能直接用 JOIN 操作
" 查询时需要指定完整的主键
SELECT * FROM bseg INTO TABLE lt_bseg
WHERE bukrs = '1000'
AND belnr = '0000000001'
AND gjahr = '2024'.

" 查看簇表信息
SELECT tabname, tabclass FROM dd02l
WHERE tabclass = 'CLUSTER'
AND as4local = 'A'
INTO TABLE @DATA(lt_cluster).


" === 三种表的对比 ===
" 特性 透明表 共享表 簇表
" DB物理表 一对一 多对一 多对一
" SQL操作 完全支持 完全支持 有限制
" JOIN 支持 支持 不支持
" 访问性能 高 中 低
" 典型例子 MARA TBSL BSEG

什么是簇表(cluster table)?举出知道的簇表。

簇表是逻辑上有关联的几个表,在定义的时候分配给一个表簇,如 Bseg。

示例代码:簇表的特性和操作

" === 簇表特性 ===
" 1. 多个簇表共享一个物理表(表簇/Cluster)
" 2. 簇表的数据以压缩形式存储
" 3. 访问簇表时需要完整的主键

" === 常见簇表 ===
" BSEG - 会计凭证行项目(簇表,簇名:RFBLG)
" BSAD - 已清账客户行项目
" BSAK - 已清账供应商行项目
" BSAS - 已清账总账行项目
" KONV - 定价条件(簇表,簇名:KONVC)
" COSP - 成本总计(计划)


" === 簇表操作注意事项 ===
" 1. 不能对簇表使用 JOIN
" ❌ 错误:SELECT * FROM bseg JOIN bkpf ON ...
" ✅ 正确:先查 BKPF,再用条件查 BSEG

" 2. 簇表查询必须包含完整主键
" ❌ 效率低:
SELECT * FROM bseg INTO TABLE lt_bseg
WHERE bukrs = '1000'.
" 没有指定凭证号和年度,需要全表扫描

" ✅ 效率高:
SELECT * FROM bseg INTO TABLE lt_bseg
WHERE bukrs = '1000'
AND belnr = '0000000001'
AND gjahr = '2024'.
" 完整主键,直接定位

" 3. 簇表不能使用某些 SQL 功能
" ❌ SELECT DISTINCT(不支持)
" ❌ GROUP BY(不支持)
" ❌ 聚合函数 SUM/MAX/MIN(不支持)

" 4. 建议使用索引表替代簇表查询
" 对于 BSEG,可以使用 BSIK/BSAK/BSID/BSAD 等索引表
SELECT * FROM bsik INTO TABLE lt_bsik
WHERE bukrs = '1000'
AND lifnr = '0000001000'.
" BSIK 是透明表,支持 JOIN 和聚合

找数据库表有哪些常用的方法?

  • 单击画面上需要查找的字段,单击(F1)键,在弹出画面中的技术信息中查找。
  • 通过 ST05 进行数据库操作的跟踪,对于在前台界面进行数据的新增或更新,在数据库中都会有所体现。
  • 通过事务 SE80 → Repository Information System → ABAP 字典 → 数据库表格中,对某个字段进行查询。

示例代码:通过代码查找数据库表和字段

" === 方法1: F1 技术信息 ===
" 在任何 SAP 屏幕上,点击字段 → F1 → 技术信息
" 可看到:表名、字段名、数据元素、域

" === 方法2: ST05 SQL 跟踪 ===
" ST05 → Activate Trace → 执行前台操作 → Deactivate Trace → Display Trace
" 可以看到所有 SQL 语句,包括表名和条件

" === 方法3: 通过代码搜索表和字段 ===

" 搜索包含特定字段的表
SELECT tabname, fieldname FROM dd03l
WHERE fieldname = 'ZZCUSTOM'
AND as4local = 'A'
INTO TABLE @DATA(lt_tables).
LOOP AT lt_tables INTO DATA(ls_t).
WRITE: / '表:', ls_t-tabname, '字段:', ls_t-fieldname.
ENDLOOP.

" 搜索表名包含关键字的表
SELECT tabname, ddtext FROM dd02t
WHERE tabname LIKE 'Z%'
AND ddlanguage = '1'
AND as4local = 'A'
INTO TABLE @DATA(lt_custom_tables).

" 搜索字段包含关键字的字段
SELECT tabname, fieldname, ddtext FROM dd03t
WHERE ddtext LIKE '%客户分类%'
AND ddlanguage = '1'
INTO TABLE @DATA(lt_fields).


" === 方法4: 通过表关系查找 ===
" 查找外键关系
SELECT tabname, fieldname, checktable
FROM dd05l
WHERE checktable = 'KNA1'
AND as4local = 'A'
INTO TABLE @DATA(lt_fk).
" 找到所有引用 KNA1 表的外键字段


" === 方法5: 常用的 SAP 标准表 ===
" 物料相关:MARA(通用)、MARC(工厂)、MARD(库存地点)
" 销售相关:VBAK(订单抬头)、VBAP(行项目)、VBEP(计划行)
" 采购相关:EKKO(PO抬头)、EKPO(行项目)
" 财务相关:BKPF(凭证抬头)、BSEG(行项目)
" 客户/供应商:KNA1(客户)、LFA1(供应商)

如何提高程序效率?

使用 OPEN SQL 时注意以下原则:

  • 尽可能减少满足条件的数据条目数量。
  • 减少数据的传输量,以减少网络流量。
  • 减少访问的数据库表量。
  • 减少查询难度,可以通过整理选择标准来实现。
  • 减少数据库负载。

示例代码:程序优化的具体实践

" === 优化原则1: 不要在 LOOP 中访问数据库 ===
" ❌ 错误方式:N+1 查询问题
LOOP AT lt_vbak INTO ls_vbak.
SELECT SINGLE maktx FROM makt INTO lv_maktx
WHERE matnr = ls_vbak-matnr AND spras = sy-langu.
ENDLOOP.
" 问题:每行数据都执行一次数据库查询

" ✅ 正确方式:批量读取到内表,用 READ TABLE
SELECT matnr, maktx FROM makt
FOR ALL ENTRIES IN lt_vbak
WHERE matnr = lt_vbak-matnr
AND spras = sy-langu
INTO TABLE @DATA(lt_makt).

SORT lt_makt BY matnr.
LOOP AT lt_vbak INTO ls_vbak.
READ TABLE lt_makt INTO DATA(ls_makt)
WITH KEY matnr = ls_vbak-matnr
BINARY SEARCH.
IF sy-subrc = 0.
ls_vbak-maktx = ls_makt-maktx.
ENDIF.
ENDLOOP.


" === 优化原则2: 只选择需要的字段 ===
" ❌ 错误方式
SELECT * FROM mara INTO TABLE lt_mara
WHERE mtart = 'FERT'.

" ✅ 正确方式
SELECT matnr, mtart, meins FROM mara
INTO TABLE @DATA(lt_mara2)
WHERE mtart = 'FERT'.


" === 优化原则3: WHERE 条件优化 ===
" ❌ 条件顺序不合理
SELECT * FROM vbak INTO TABLE lt_vbak
WHERE vbeln LIKE '10%'
AND erdat = '20240101'
AND vkorg = '1000'.

" ✅ 将主键和高选择性条件放在前面
SELECT vbeln, erdat, vkorg FROM vbak
INTO TABLE @lt_vbak
WHERE vbeln LIKE '10%' " 主键前缀
AND vkorg = '1000' " 等值条件
AND erdat = '20240101'. " 等值条件


" === 优化原则4: 使用聚合函数代替逐行计算 ===
" ❌ 错误方式
LOOP AT lt_bseg INTO ls_bseg.
lv_total = lv_total + ls_bseg-dmbtr.
ENDLOOP.

" ✅ 正确方式
SELECT SUM( dmbtr ) FROM bseg INTO lv_total
WHERE bukrs = '1000'
AND gjahr = '2024'.


" === 优化原则5: 避免 SELECT...ENDSELECT ===
" ❌ 逐行读取(性能差)
SELECT * FROM mara INTO ls_mara WHERE mtart = 'FERT'.
" 处理每行
WRITE: / ls_mara-matnr.
ENDSELECT.

" ✅ 一次性读取到内表
SELECT * FROM mara INTO TABLE lt_mara WHERE mtart = 'FERT'.
LOOP AT lt_mara INTO ls_mara.
WRITE: / ls_mara-matnr.
ENDLOOP.


" === 优化原则6: 使用 SORT + BINARY SEARCH 代替线性查找 ===
" ❌ 线性查找 O(n)
READ TABLE lt_makt INTO ls_makt
WITH KEY matnr = '0000000001'.

" ✅ 二分查找 O(log n)
SORT lt_makt BY matnr.
READ TABLE lt_makt INTO ls_makt
WITH KEY matnr = '0000000001'
BINARY SEARCH.


" === 优化原则7: 使用 FOR ALL ENTRIES ===
" 注意:内表不能为空!
IF lt_vbak IS NOT INITIAL.
SELECT matnr, maktx FROM makt
FOR ALL ENTRIES IN lt_vbak
WHERE matnr = lt_vbak-matnr
AND spras = sy-langu
INTO TABLE @lt_makt.
ENDIF.

表内联(外联)限制条件有哪些?

  • LEFT OUTER JOIN 限制条件:在 ON 附加项中,只能使用 “=” 操作符,且必须至少有一个条件是对主选择表和结合表中的字段进行比较。
  • INNER JOIN 限制条件:不同逻辑条件之间只能通过 AND 连接。
  • LEFT OUTER JOIN 与 INNER JOIN 的主要区别是 LEFT OUTER JOIN 中对于主选择表的数据,即使在结合数据表中结合条件字段值不存在,也将该数据行选出,结合表中不存在的字段保持空白。

示例代码:JOIN 的正确使用和限制

" === INNER JOIN(内连接)===
" 只返回两表中都匹配的数据
SELECT a~vbeln, a~erdat, b~posnr, b~matnr
FROM vbak AS a
INNER JOIN vbap AS b ON a~vbeln = b~vbeln
INTO TABLE @DATA(lt_inner)
WHERE a~erdat = '20240101'
AND a~vkorg = '1000'.

" 限制1: 不同条件只能用 AND 连接
" ❌ 错误
" INNER JOIN vbap AS b ON a~vbeln = b~vbeln OR a~auart = b~auart
" ✅ 正确
" INNER JOIN vbap AS b ON a~vbeln = b~vbeln

" 限制2: 每个条件必须包含主表字段
" ❌ 错误
" INNER JOIN vbap AS b ON b~posnr = '000010'
" ✅ 正确
" INNER JOIN vbap AS b ON a~vbeln = b~vbeln AND b~posnr = '000010'


" === LEFT OUTER JOIN(左外连接)===
" 返回左表所有数据,右表不匹配的字段为空
SELECT a~vbeln, a~erdat, b~posnr, b~matnr
FROM vbak AS a
LEFT OUTER JOIN vbap AS b ON a~vbeln = b~vbeln
INTO TABLE @DATA(lt_left)
WHERE a~erdat = '20240101'.

" 限制1: ON 中只能使用 = 操作符
" ❌ 错误
" LEFT OUTER JOIN vbap AS b ON a~vbeln <> b~vbeln
" ✅ 正确
" LEFT OUTER JOIN vbap AS b ON a~vbeln = b~vbeln

" 限制2: WHERE 中不能对右表字段做过滤
" ❌ 错误(会过滤掉左表不匹配的行)
" WHERE b~matnr = '0000000001'
" ✅ 正确(将右表条件放在 ON 中)
SELECT a~vbeln, b~matnr
FROM vbak AS a
LEFT OUTER JOIN vbap AS b
ON a~vbeln = b~vbeln AND b~matnr = '0000000001'
INTO TABLE @DATA(lt_left2).


" === 多表连接 ===
" 最多可连接 10 张表(SAP 限制)
SELECT a~vbeln, a~erdat,
b~posnr, b~matnr,
c~maktx
FROM vbak AS a
INNER JOIN vbap AS b ON a~vbeln = b~vbeln
LEFT OUTER JOIN makt AS c ON b~matnr = c~matnr
AND c~spras = @sy-langu
INTO TABLE @DATA(lt_multi)
WHERE a~erdat = '20240101'.

" === 建议:超过3张表时考虑使用视图或拆分查询 ===

UPDATE、DELETE 数据库时的注意事项?

  • DELETE 语句用以删除数据表里记录。注意:删除记录并不能释放 ORACLE 里被占用的数据块表空间,它只把那些被删除的数据块标成 unused。如果确实要删除一个大表里的全部记录,可以用 TRUNCATE 命令,TRUNCATE TABLE 表名,且此操作不可逆。
  • UPDATE 语句用以修改数据表里记录。注意:以上 SQL 语句对表都加上了行级锁,确认完成后,必须加上事务处理结束的命令 COMMIT 才能正式生效。

示例代码:UPDATE 和 DELETE 的正确使用

" === DELETE 操作 ===

" 1. 按条件删除
DELETE FROM zcustom_table WHERE status = 'O'.
IF sy-subrc = 0.
COMMIT WORK.
WRITE: / '删除', sy-dbcnt, '条记录'.
ENDIF.

" 2. 按工作区删除(按主键)
DATA ls_data TYPE zcustom_table.
ls_data-id = '001'.
DELETE zcustom_table FROM ls_data.
IF sy-subrc = 0.
COMMIT WORK.
ENDIF.

" 3. 按内表批量删除
DELETE zcustom_table FROM TABLE lt_data.
IF sy-subrc = 0.
COMMIT WORK.
ENDIF.

" 4. 注意事项
" ⚠️ 内表为空时会删除所有数据!
IF lt_data IS INITIAL.
WRITE: / '警告:内表为空,跳过删除'.
RETURN.
ENDIF.
DELETE zcustom_table FROM TABLE lt_data.


" === UPDATE 操作 ===

" 1. SET 语法(按条件更新)
UPDATE zcustom_table SET status = 'C'
chgdat = sy-datum
WHERE status = 'O'.
IF sy-subrc = 0.
COMMIT WORK.
WRITE: / '更新', sy-dbcnt, '条记录'.
ENDIF.

" 2. FROM 工作区(按主键更新)
ls_data-id = '001'.
ls_data-status = 'C'.
UPDATE zcustom_table FROM ls_data.
COMMIT WORK.

" 3. FROM TABLE(批量更新)
UPDATE zcustom_table FROM TABLE lt_data.
COMMIT WORK.

" 4. 更新不存在的记录
UPDATE zcustom_table SET status = 'X'
WHERE id = 'NOT_EXIST'.
" sy-subrc = 0, sy-dbcnt = 0 → 不报错但无数据被更新


" === TRUNCATE(清空大表)===
" 注意:不可逆操作!
" 在 SAP 中通过 Native SQL 执行
EXEC SQL.
TRUNCATE TABLE ZCUSTOM_TABLE
ENDEXEC.


" === 最佳实践 ===
" 1. DELETE/UPDATE 后必须 COMMIT 或 ROLLBACK
" 2. 大量操作时分批处理(每 1000 条 COMMIT 一次)
" 3. 生产环境操作前先 SELECT 确认影响范围
" 4. 使用数据库锁防止并发问题

在程序中使用逻辑数据库的两种方法是什么?

在程序中使用逻辑数据库有两种方法,通常是通过 GET 事件或者功能模块进行调用,如 NODES node。

示例代码:逻辑数据库的使用

" === 方法1: GET 事件 ===
" 在报表程序声明中指定逻辑数据库
REPORT zldbp_test DATABASE DBM LDB_VERSION '1.0'.

" 声明节点
NODES: kna1, knb1.

" GET 事件处理
GET kna1.
WRITE: / '客户:', kna1-kunnr, kna1-name1.

GET knb1.
WRITE: / ' 公司代码:', knb1-bukrs, knb1-akont.

" 选择屏幕自动包含逻辑数据库的标准选择条件
" GET kna1 会在读取 KNA1 表每行数据时触发


" === 方法2: 功能模块调用 ===
" 使用 LDB_PROCESS 函数调用逻辑数据库
DATA: lt_fields TYPE TABLE OF rsds_fields,
lt_ranges TYPE TABLE OF rsds_trange.

CALL FUNCTION 'LDB_PROCESS'
EXPORTING
ldbname = 'F1S' " 逻辑数据库名
TABLES
callback = lt_callback
EXCEPTIONS
ldb_not_installed = 1
ldb_wrong_ldb_type = 2
ldb_no_selections = 3
OTHERS = 4.


" === 逻辑数据库优点 ===
" 1. 预设的选择屏幕(标准选择条件自动包含)
" 2. 自动处理数据表之间的外键关系
" 3. 内置权限检查
" 4. 数据读取按层次结构进行

" === 逻辑数据库维护 ===
" SE36 或 SLDB - 创建和维护逻辑数据库
" SE36 → 输入逻辑数据库名 → 创建/修改

" === 查看系统中的逻辑数据库 ===
SELECT * FROM ldbdir
WHERE ldbname LIKE 'F%'
INTO TABLE @DATA(lt_ldb).
LOOP AT lt_ldb INTO DATA(ls_ldb).
WRITE: / ls_ldb-ldbname, ls_ldb-descr.
ENDLOOP.

ABAP 声明结构体和内表

TYPES:BEGIN OF iline,  "创建类型包含三个字段  TYPES:定义类型  DATA:创建的对象
first_name(30) TYPE c,
last_name(30) TYPE c,
age TYPE i,
END OF iline.
DATA: wa_line TYPE iline. " 参照类型创建结构体
DATA: wa_tline LIKE wa_line. "参照结构体生成结构体 只能用LIKE不能用TYPE
DATA: pa_tline TYPE range_werks.. "但是 参照SAP标准的结构体生成结构体时,能用TYPE
DATA: ta_tline LIKE range_werks.. "但是 参照SAP标准的结构体生成结构体时,能用TYPE


DATA: itab TYPE iline OCCURS 0 WITH HEADER LINE. "参照结构创建内表有header line

DATA: jtab TYPE iline OCCURS 0. "参照某个结构创建无 HEADER LINE的內表
DATA: wa_jtab LIKE LINE OF itab. "定义内表的表头

DATA: p_tab LIKE TABLE OF wa_line. " 可以参照结构体创建无 HEADER LINE 的内表 不能用 TYPE
DATA: lt_werks TYPE TABLE OF range_werks. " 但是TYPE 可以参照SAP标准的结构体创建无 HEADER LINE 的内表,

DATA: BEGIN OF otab OCCURS 0, "以此种方式自定义的结构默认带HEADER LINE的内表
first_name(30) TYPE c,
last_name(30) TYPE c,
age TYPE i,
END OF otab.

DATA: it_0006 TYPE TABLE OF pa0006. "没有 HEADER LINE 的内表
DATA: pt_0006 TYPE TABLE OF pa0006 WITH HEADER LINE. "有 HEADER LINE 的内表

DATA: it_0007 LIKE TABLE OF pa0007. "没有 HEADER LINE 的内表
DATA: pt_0007 LIKE TABLE OF pa0007 WITH HEADER LINE. "有 HEADER LINE 的内表

嵌套结构体类型利用 INCLUDE STRUCTRER 语句,可以把 DATA 语句定义的结构体或者 ABAP 数据字典存在的结构体包含到嵌套结构体中

*声明结构体类型
TYPES:BEGIN OF T_PEOPLE,
NAME TYPE C LENGTH 20,
COUNTRY TYPE C LENGTH 15,
CITY TYPE C LENGTH 10,
END OF T_PEOPLE.

*定义结构体变量
DATA GS_PEOPLE TYPE T_PEOPLE.
GS_PEOPLE-NAME = 'NAME'.
GS_PEOPLE-COUNTRY = 'COUNTRY'.
GS_PEOPLE-CITY = 'Beijing'.

*定义嵌套结构体变量
DATA:BEGIN OF GS_PEOPLE1.
INCLUDE STRUCTURE GS_PEOPLE AS PEO.
DATA:PHONE TYPE C LENGTH 10,
END OF GS_PEOPLE1.

GS_PEOPLE1-PEO-NAME = '123'.
WRITE GS_PEOPLE1-PEO-NAME.

*定义包含结构体变量
DATA:BEGIN OF GS_PEOPLE2.
INCLUDE STRUCTURE GS_PEOPLE .
DATA:PHONE TYPE C LENGTH 10,
END OF GS_PEOPLE2.
GS_PEOPLE2-NAME = '456'.
WRITE GS_PEOPLE2-NAME.

DATA:BEGIN OF lt_itab OCCURS 0.
INCLUDE STRUCTURE zbapi_dhbilling_list.
DATA:vgbel TYPE vbrp-vgbel,
vgpos TYPE vbrp-vgpos,
END OF lt_itab.

————————————————
版权声明:本文为 CSDN 博主「诸秋白」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_36910300/article/details/120632412

常用系统变量

SY-ABCDE 常量,A-Z 字母表

SY-BATCH 后台的程序运行

SY-COLNO:当前选定列的列号

SY-CPAGE 列表的当前显示页

SY-CUCOL 屏幕,PAI 的水平光标位置

SY-CUROW 选择某行时光标列位置

sy-datar 在 PAI 中,如果屏幕上至少有一个输入字段的值被用户或其他数据传输所更改,
          则为“X”,否则为初始值。

sy-datlo 用户的当前日期

SY-DATUM 服务器日期

SY-DYNNR 当前屏幕的编号

SY-INDEX 循环的次数(DO While)

SY-LANGU 当前登录语言

SY-LILLI:选择某行时光标行位置

SY-LINCT 当前报表长度 (REPROT 语句中设定的 LINE-COUNT)

SY-LINNO 当前选定行的行号

SY-LINSZ 当前报表宽度(REPROT 语句中设定的 LINE-SIZE)

SY-LISEL 选择行的内容,长度为 255

SY-LISTI 上一个列表的索引

SY-LOOPC:当前表格控件在屏幕中的总行数(Table Control)

SY-LSIND:当前列表索引,第一级列表为 1

SY-MSGID 消息, 消息类

SY-MSGTY 消息,消息类型

SY-MSGNO 消息, 消息编号

SY-MSGV1 消息,消息变量 1

SY-MSGV2 消息,消息变量 2

SY-MSGV3 消息,消息变量 3

SY-MSGV4 消息,消息变量 4

SY-PAGNO 当前页号

SY-SCOLS 当前窗口的列表栏目数

SY-SLSET 选择屏幕的变式名称

SY-SROWS 当前窗口的列表行数

SY-STARO 真实行号

SY-STEPL 返回当前操作的屏幕行号(Table Control)

SY-SUBRC 执行状态

SY-TABIX 当前处理的内表的 Index

SY-TCODE 当前的事务代码

SY-UCOMM PAI 所出发的功能代码

SY-ULINE 水平线

SY-UNAME 用户名

SY-UZEIT 服务器时间

SY-VLINE 垂直线

sy-mandt 当前客户端

================= FUNCTION MODULE (或 RFC 中) SY-SUBRC 的含义 ================

使用 SELECT 语句选择查询:
SY-SUBRC = 0: 至少有一行数据,当 ENDSELECT 语句执行完,SY-DBCNT 中保存着记录的个数。
SY-SUBRC = 4: 没有数据。
SY-SUBRC = 8: 只有使用“SELECT SINGLE FOR UPDATE”时才会有,
              表示: WHERE 条件指定的记录不止一行,结果是没有记录被选中。

使用 INSERT 语句,向表中插入一行,必须注意 INSERT 的顺序与表中字段的顺序一致:
SY-SUBRC = 0: 插入成功,SY-DBCNT 包含了插入的行数,0 或 1。
SY-SUBRC = 4: 由于有相同的 KEY 存在,所以插入失败。

使用 LOOP 语句来遍历一个内表:
SY-SUBRC = 0: 循环至少被执行一次。
SY-SUBRC = 4: 循环没有被执行,可能是没有数据,也可能是没有符合条件的记录。

使用 DELETE 语句来删除一条记录:
SY-SUBRC = 0: 找到一行并删除之,如果该表有不唯一主键,也就是有多条重复的记录,则只删除第一条记录。
SY-SUBRC = 4: 没有找到符合条件的记录,也没有删除。

使用 UPDATE 语句来更新一条记录:
SY-SUBRC = 0: 找到记录并更新,(如果有多条记录呢?)
SY-SUBRC = 4: 没有找到符合条件的记录,也没有更新。

常用代码

循环 LOOP AT 的使用

LOOP AT GT_AFVV.
    ON CHANGE OF GT_AFVV-AUFPL.
      CLEAR G_AFKO.
      SELECT SINGLE \*
        INTO G_AFKO
        FROM AFKO
       WHERE AUFPL = GT_AFVV-AUFPL.
    ENDON.
ENDLOOP.

SELECT INTO 语句

SELECT SINGLE 
       MATNR 
       MAKTX 
  INTO (WA_OUT-IDNRK, WA_OUT-OJTXP
  FROM MAKT 
 WHERE SPRAS = SY-LANGU 
   AND MATNR = S_MATNR-LOW.

SELECT SINGLE \* FROM setleaf WHERE setname = @gs_data-wbsgroup INTO @DATA(it_item1).
IF sy-subrc = 0.
ENDIF.

定义常量变量

CONSTANTS GV_FORMNAME TYPE STRING VALUE ‘ZPP_CP_N’ .

ABAP 程序参考菜单

ZPLM_ROUTING_PRINT 
MAIN1

定义选择屏幕字段变量

RANGES: S_MATNR FOR MARC-MATNR.

定义常量变量

CONSTANTS GV_FORMNAME(20) TYPE C VALUE ‘ZPP_CP_N’.

权限检查

AUTHORITY-CHECK OBJECT ‘ZPLMWERKS’ FOR USER ‘123456'
                    ID 'WERKS' FIELD P_WERKS
                    ID 'ACTVT' FIELD '03'.

IF NOT SY-SUBRC IS INITIAL.
  MESSAGE '对该工厂没有操作权限' TYPE 'E'.
  STOP.
ENDIF.

ALV 编辑字段

ALV 字段:W_FCAT-EDIT = ‘X’.
用户命令:

"保存修改后的内容
DATA: LR_GRID TYPE REF TO CL_GUI_ALV_GRID.

"刷新 ALV 格式
CALL FUNCTION 'GET_GLOBALS_FROM_SLVC_FULLSCR'
  IMPORTING
    E_GRID = LR_GRID.
CALL METHOD LR_GRID->CHECK_CHANGED_DATA.
  RS_SELFIELD-REFRESH = 'X' .

项目维护

http://www.cnblogs.com/landina/articles/1131746.html

选择字段赋值

S_MATNR-SIGN   = 'I'.
S_MATNR-OPTION = 'EQ' .
S_MATNR-LOW    = WA_MARC-MATNR.
APPEND S_MATNR.

MOVE-CORRESPONDING

MOVE-CORRESPONDING 用于将一个结构中的数据赋予另一个结构中的对应字段,只能存一行数据。
所以使用时应该是如下形式:

TABLES: spfli.

DATA : yp_wa LIKE spfli ,
yp_tab LIKE TABLE OF spfli .

TYPES : BEGIN OF sp ,
sel TYPE c .
INCLUDE STRUCTURE spfli .
TYPES : END OF sp .

DATA : wa TYPE sp ,
tab TYPE TABLE OF sp .

LOOP AT yp_tab INTO yp_wa .
MOVE-CORRESPONDING yp_wa TO wa .
APPEND wa TO tab .
ENDLOOP .

其中 yptab / tab 为两个自定义内表,yp_wa 中只有字段名相同的才可以被赋予 wa 的工作区中。
————————————————
版权声明:本文为 CSDN 博主「心丨悦
蜗牛在裸奔」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qinyuezhan/article/details/39546147

LOOP AT A.
MOVE-CORRESPONDING A TO B.
APPEND B.
ENDLOOP A.

其中 A\B 为两个自定义内表,A 中只有字段名相同的才可以被赋予 B 的表头中。

TYPES:
BEGIN OF flight,
carrid TYPE spfli-carrid,
connid TYPE spfli-connid,
cityfrom TYPE spfli-cityfrom,
cityto TYPE spfli-cityto,
END OF flight.
DATA
flights TYPE SORTED TABLE OF flight WITH UNIQUE KEY carrid connid.

SELECT *
FROM spfli
INTO TABLE @DATA(spfli_tab).

MOVE-CORRESPONDING spfli_tab TO flights.

cl_demo_output=>display( flights ).

CONCATENATE

DATA: t1 TYPE c LENGTH 10 VALUE 'We',
t2 TYPE c LENGTH 10 VALUE 'have',
t3 TYPE c LENGTH 10 VALUE 'all',
t4 TYPE c LENGTH 10 VALUE 'the',
t5 TYPE c LENGTH 10 VALUE 'time',
t6 TYPE c LENGTH 10 VALUE 'in',
t7 TYPE c LENGTH 10 VALUE 'the',
t8 TYPE c LENGTH 10 VALUE 'world'.

CONCATENATE t1 t2 t3 t4 t5 t6 t7 t8
INTO DATA(result).
CONCATENATE t1 t2 t3 t4 t5 t6 t7 t8
INTO result SEPARATED BY space.

常用函数

更具姓名获取工厂信息

"定义变量
DATA: LV_WERKS   TYPE WERKS_D.

"获取用户工厂信息
CALL FUNCTION 'ZPLM_AUTH_PLANTFILL'
  EXPORTING
    UNAME            = SY-UNAME
  IMPORTING
    WERKS            = LV_WERKS.

确认执行

CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
  EXPORTING
    WAIT = 'X'.

回滚操作

CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'
  IMPORTING
    RETURN = LS_RETURN.

负号前置

CALL FUNCTION ‘CLOI_PUT_SIGN_IN_FRONT’

单位转换

CALL FUNCTIO'RSW_UNIT_CONVERSION'
  EXPORTING
    I_SOURCE_UNIT               =
    I_TARGET_UNIT               =
    I_INPUT_VALUE               =
  IMPORTING
    E_RESULT_VALUE              =
  EXCEPTIONS
    CONVERSION_FAILED           = 1
    SOURCE_UNIT_NOT_FOUND       = 2
    TARGET_UNIT_NOT_FOUND       = 3
    OTHERS                      = 4.

获取工艺路线

CALL FUNCTION 'CARO_ROUTING_READ'
   EXPORTING
     DATE_FROM               =   
     PM_DATE
     DATE_TO                  ='99991231'
     PLNTY                    = 'N'
     PLNNR                    = V_PLNNR
     PLNAL                    = V_PLNAL

- MATNR                     =
*   BUFFER_DEL_FLG            = 'X'
*   DELETE_ALL_CAL_FLG        = 'X'
*   ADAPT_FLG                 = 'X'
*   IV_CREATE_ADD_CHANGE      = ' '
   TABLES
*   TSK_TAB                   =
*   SEQ_TAB                   =
     OPR_TAB                  = OPR_TAB
*   PHASE_TAB                 =
*   SUBOPR_TAB                =
*   REL_TAB                   =
*   COM_TAB                   =
*   REFERR_TAB                =
*   REFMIS_TAB                =
   EXCEPTIONS
     NOT_FOUND                = 1
     REF_NOT_EXP              = 2
     NOT_VALID                = 3
     OTHERS                   = 4.

文档下载

CALL FUNCTION ‘ZSCMS_DOC_READ’

获取工艺路线

CALL FUNCTION ‘CARO_ROUTING_READ’

获取 PRT

CALL FUNCTION ‘CF_DB_PLFH_READ’

获取对象-描述

CALL FUNCTION 'SO_OBJECT_READ'
  EXPORTING
    FOLDER_ID                 = FOLDER_ID
    OBJECT_ID                 = OBJECT_ID
  TABLES
    OBJCONT                   = OBJCONT
  EXCEPTIONS
    ACTIVE_USER_NOT_EXIST       = 1
    COMMUNICATION_FAILURE       = 2
    COMPONENT_NOT_AVAILABLE     = 3
    FOLDER_NOT_EXIST            = 4
    FOLDER_NO_AUTHORIZATION     = 5
    OBJECT_NOT_EXIST            = 6
    OBJECT_NO_AUTHORIZATION     = 7
    OPERATION_NO_AUTHORIZATION  = 8
    OWNER_NOT_EXIST             = 9
    PARAMETER_ERROR             = 10
    SUBSTITUTE_NOT_ACTIVE       = 11
    SUBSTITUTE_NOT_DEFINED      = 12
    SYSTEM_FAILURE              = 13
    X_ERROR                     = 14
    OTHERS                      = 15.

获取长文本

CALL FUNCTION 'READ_TEXT'
  EXPORTING
    ID            = 'AEOI'
    LANGUAGE      = SY-LANGU
    NAME          = LV_NAME
    OBJECT        = 'CHANGE'
  TABLES
    LINES         = LT_LINES.

打印 SmartForm

CALL FUNCTION 'SSF_FUNCTION_MODULE_NAME'
    EXPORTING
      FORMNAME           = 'ZPP_REC_LD'
    IMPORTING
      FM_NAME            = FM_NAME
    EXCEPTIONS
      NO_FORM            = 1
      NO_FUNCTION_MODULE = 2
      OTHERS             = 3.

CALL FUNCTION FM_NAME
    TABLES
      IT_DATA         = IT_OUT1
    EXCEPTIONS
      FOMATTING_ERROR = 1
      INTERNAL_ERROR  = 2
      SEND_ERROR      = 3
      USER_CANCLED    = 4
      OTHERS          = 5.

SO_NAME_CONVERT

获取用户名

CALL FUNCTION ‘BAPI_USER_GETLIST’

CM_BT_PLMZ_COPY 复制组件分配

BAPI_GOODSMVT_CREATE

BAPI_GOODSMVT_CANCEL 
       T158G

SMARTFORMS 打印

"打印输出
  CALL FUNCTIO 'SSF_FUNCTION_MODULE_NAME'
    EXPORTING
      FORMNAME             = 'ZPP_SF_PS'
    IMPORTING
      FM_NAME              = FM_NAME
    EXCEPTIONS
      NO_FORM              = 1
      NO_FUNCTION_MODULE   = 2
      OTHERS               = 3.

CALL FUNCTION FM_NAME
    EXPORTING
      STR_PS_HEADER = GS_PS_HEADER
    TABLES
      IT_PS_ITEMS = GT_PS_ITEMS
    EXCEPTIONS
      FORMATTING_ERROR = 1
      INTERNAL_ERROR = 2
      SEND_ERROR = 3
      USER_CANCELED = 4
      OTHERS = 5 .

获取内表的行数

1、循环内表,累加(可加條件過濾)
LOOP AT it_itab where XXXX.
g_lines2 = g_lines2 + 1.
ENDLOOP.

2.使用函数来计算内部表行数

g_lines3 = lines(it_itab ). 使用函数来计算内部表行数。

3.获得内部表的属性,将内部表行数赋值给 g_lines1 , g_lines1 为 I 型变量。

DESCRIBE TABLE it_itab LINES g_lines1 .

DESCRIBE TABLE lt_mat LINES lv_cont.
”这行的意思是 计算内表 lt_mat 的行数 ,将行数放到变量 lv_cont 里。
SELECT carrid FROM scarr INTO TABLE @DATA(lt_tab).

OLD:
DESCRIBE TABLE lt_tab LINES DATA(LV_TABIX).

NEW:
DATA(LV_TABIX) = LINES( lt_tab ).

上传文件到 PDF

"定义输出内表,字段必须是字符串类型
  TYPES: BEGIN OF TY_FILE,
           PROJN(24)   TYPE C,
           CJ10001(18) TYPE C,
           CJ10004(18) TYPE C,  
         END OF TY_FILE.

DATA: WA_FILE TYPE TY_FILE,
        IT_FILE TYPE STANDARD TABLE OF TY_FILE.

"定义 FTP 变量
  DATA: L_USER(30)       TYPE C VALUE 'SAPUSER01',
        L_PWD(30)        TYPE C VALUE '12345678' ,
        L_HOST(64)       TYPE C VALUE '10.56.3.11' ,

L_SLEN           TYPE I,
        L_KEY            TYPE I VALUE 26101957,
        L_HDL            TYPE I,
        L_DEST           TYPE RFCDES-RFCDEST VALUE 'SAPFTPA',
        L_HEADERFILE(40) TYPE C,
        L_ITEMFILE(40)   TYPE C.

"定义变量
  DATA: IT_RESULT  TYPE TABLE OF TEXT WITH HEADER LINE,

L_FILENAME TYPE FILENAME-FILEEXTERN,
        L_MSGTXT   TYPE BAPIRET2-MESSAGE,

L_SEP      TYPE C VALUE CL_ABAP_CHAR_UTILITIES=>HORIZONTAL_TAB,
        L_CMD(100) TYPE C.

"将数据保存到输出内表中
  LOOP AT ITAB_ITEMS.

MOVE-CORRESPONDING ITAB_ITEMS TO WA_FILE.

"转化 WBS
    CALL FUNCTION 'CONVERSION_EXIT_ABPSP_OUTPUT'
      EXPORTING
        INPUT  = WA_FILE-PROJN
      IMPORTING
        OUTPUT = WA_FILE-PROJN.

APPEND WA_FILE TO IT_FILE.

CLEAR: WA_FILE.
  ENDLOOP.

"获取逻辑文件路径名
  L_FILENAME = 'Z_DATA_ECC_TRANSFER' .

"从逻辑文件名转化为物理路径名
  CALL FUNCTION 'FILE_GET_NAME'
    EXPORTING
      CLIENT           = SY-MANDT
      LOGICAL_FILENAME = L_FILENAME
    IMPORTING
      FILE_NAME        = L_FILENAME.

CONCATENATE L_FILENAME 'ZFI_ZCO30_FUND' INTO L_FILENAME.

"如果文件已经存在(关键字为 FOR INPUT),则将原来的文件删除
  OPEN DATASET L_FILENAME FOR INPUT IN TEXT MODE ENCODING NON-UNICODE WITH WINDOWS LINEFEED.
  IF SY-SUBRC = 0.
    DELETE DATASET L_FILENAME.
  ENDIF.

"创建新的文件,关键字 FOR APPENDING
  OPEN DATASET L_FILENAME FOR APPENDING IN TEXT MODE ENCODING NON-UNICODE WITH WINDOWS LINEFEED.
  IF SY-SUBRC = 0.

LOOP AT IT_FILE INTO WA_FILE.

CONCATENATE WA_FILE-PROJN
                  WA_FILE-CJ10001
                  WA_FILE-CJ10004
             INTO WA_FILE SEPARATED BY L_SEP.

TRANSFER WA_FILE TO L_FILENAME.

CLEAR: WA_FILE.
    ENDLOOP.
  ENDIF.
  CLOSE DATASET L_FILENAME.

"打开 FTP 服务器
  L_SLEN = STRLEN( L_PWD ).
  CALL FUNCTION 'HTTP_SCRAMBLE'
    EXPORTING
      SOURCE      = L_PWD
      SOURCELEN   = L_SLEN
      KEY         = L_KEY
    IMPORTING
      DESTINATION = L_PWD.

"打开 FTP 连接
  CALL FUNCTION 'FTP_CONNECT'
    EXPORTING
      USER            = L_USER
      PASSWORD        = L_PWD
      HOST            = L_HOST
      RFC_DESTINATION = L_DEST
    IMPORTING
      HANDLE          = L_HDL.

"判断是否连通
  CHECK L_HDL > 0.

"字符类型
  REFRESH IT_RESULT.

CALL FUNCTION 'FTP_COMMAND'
    EXPORTING
      HANDLE        = L_HDL
      COMMAND       = 'ascii'     "一定要小写
    TABLES
      DATA          = IT_RESULT
    EXCEPTIONS
      TCPIP_ERROR   = 1
      COMMAND_ERROR = 2
      DATA_ERROR    = 3.

IF SY-SUBRC EQ 0.
    WRITE: / 'SET ASCII MODE' .
    SKIP 1.
  ELSE.
    CALL FUNCTION 'MESSAGE_TEXT_BUILD'
      EXPORTING
        MSGID               = SY-MSGID
        MSGNR               = SY-MSGNO
        MSGV1               = SY-MSGV1
        MSGV2               = SY-MSGV2
        MSGV3               = SY-MSGV3
        MSGV4               = SY-MSGV4
      IMPORTING
        MESSAGE_TEXT_OUTPUT = L_MSGTXT.
    WRITE: / 'SET ASCII MODE FAILED' COLOR COL_NEGATIVE INTENSIFIED OFF.
    WRITE: / L_MSGTXT COLOR COL_NEGATIVE INTENSIFIED OFF.
  ENDIF.

"等待时间
  WAIT UP TO 10 SECONDS.

"产生文件
  CLEAR L_CMD.
  CONCATENATE 'put' L_FILENAME  'ZFI_ZCO30_FUND.TXT' INTO L_CMD SEPARATED BY SPACE.

CALL FUNCTION 'FTP_COMMAND'
    EXPORTING
      HANDLE        = L_HDL
      COMMAND       = L_CMD
    TABLES
      DATA          = IT_RESULT
    EXCEPTIONS
      COMMAND_ERROR = 1
      TCPIP_ERROR   = 2.

IF SY-SUBRC EQ 0.
    WRITE L_CMD.
    SKIP 1.
  ELSE.

CALL FUNCTION 'MESSAGE_TEXT_BUILD'
      EXPORTING
        MSGID               = SY-MSGID
        MSGNR               = SY-MSGNO
        MSGV1               = SY-MSGV1
        MSGV2               = SY-MSGV2
        MSGV3               = SY-MSGV3
        MSGV4               = SY-MSGV4
      IMPORTING
        MESSAGE_TEXT_OUTPUT = L_MSGTXT.
    WRITE: / L_CMD, 'TO ' , L_HOST, ' FAILED' COLOR COL_NEGATIVE INTENSIFIED OFF.
    WRITE: / L_MSGTXT COLOR COL_NEGATIVE INTENSIFIED OFF.
  ENDIF.

"断开 FTP 连接服务器
  CALL FUNCTION 'FTP_DISCONNECT'
    EXPORTING
      HANDLE = L_HDL.

CALL FUNCTION 'RFC_CONNECTION_CLOSE'
    EXPORTING
      DESTINATION = L_DEST
    EXCEPTIONS
      OTHERS      = 1.

SAP 前缀补零去零函数

SAP 中有很多 Char 类型的字段,如果都是数字,且不满该字段长度,则会自动在前缀补足零,然后保存在 Table 中,而在显示的时候却不显示这些前缀零。比如料号字段,长度是 18 位,如果一个料号都是数字 824500089012,因为不满 18 位的长度,其在存入 Table 之前会在前缀补上零,直到补足 18 位。
但是,在一些报表和 RFC 中,这些动作却需要代码来完成。不过,还好 SAP 系统中提供了一些现成的函数供使用。
Function group: ALFA (ALPHAconversion)
Function Module:CONVERSION_EXIT_ALPHA_INPUT (前缀补零)
Function Module:CONVERSION_EXIT_ALPHA_OUTPUT’(前缀去零)

lv_kunnr = |{ kunnr ALPHA = IN }|. "加前导零
*加上 p_in 的前导零
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
EXPORTING
input = p_in
IMPORTING
output = p_in.
*去除 p_out 的前导零
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
EXPORTING
input = p_out
IMPORTING
output = p_out.

Function group: OMCV (MaterialNumber Conversion)
Function Module:CONVERSION_EXIT_MATN1_INPUT (料号前缀补零)
Function Module:CONVERSION_EXIT_MATN1_OUTPUT(料号前缀去零)

CALL FUNCTION 'CONVERSION_EXIT_MATN1_INPUT'   "料号前缀补零
EXPORTING
input = i_matnr
IMPORTING
output = o_matnr.
CALL FUNCTION 'CONVERSION_EXIT_MATN1_OUTPUT' "料号前缀去零
EXPORTING
input = i_matnr
IMPORTING
output = o_matnr.

SORT 语法小记

1. SORT  BY (‘aaa’) (‘bbb’)  (‘AAA’) DESCENDING (‘BBB’) DESCENDING. 
  Table   按  (‘aaa’) (‘bbb’)  升序,按   (‘AAA’) (‘BBB’)  降序. 
2.SORT  DESCENDING BY (‘aaa’) (‘bbb’)  (‘AAA’) (‘BBB’).  
  Table  按  (‘aaa’) (‘bbb’)  (‘AAA’) (‘BBB’) 所有字段降序.
3.SORT  BY (‘aaa’) (‘bbb’)  (‘AAA’) (‘BBB’).  
  默认 Table   按  (‘aaa’) (‘bbb’)  (‘AAA’) (‘BBB’) 升序.

SORT 在用于给内表排序时,后面可以用 ASCENDING 和 DESCENDING 进行升序和降序排列,但是这其中用法很多,经过尝试后总结如下:

1.SORT LT_TAB BY WERKS LGORT EMAIL.
正常排序并使用默认 ASCENDING。
2.SORT LT_TAB BY WERKS LGORT EMAIL DESCENDING.
前两个字段默认升序排列,EMAIL 字段为降序排列。
3.SORT LT_TAB BY WERKS LGORT DESCENDING EMAIL.
第一第三字段为默认升序,第二字段为降序。由此可知写在字段后面的 ASC 和 DES 只能控制这一个字段。
4.SORT LT_TAB DESCENDING BY WERKS LGORT EMAIL.
写在表名后面的 ASC 和 DEC 可以控制所有字段的排序。
5.SORT LT_TAB ASCENDING BY WERKS DESCENDING LGORT EMAIL.
此时第一个字段降序,其他为升序。
6.SORT LT_TAB DESCENDING BY WERKS LGORT ASCENDING EMAIL.
此时整体为降序,但第二个字段为升序。
总结,内表后的 DEC 会控制整个表的排序,如果此时某字段后面有 DEC 或者 ASC,则在整个内表排序完成之后再进行字段排序。

FIELD-SYMBOLS

https://blog.csdn.net/champaignwolf/article/details/51208881

CONVERT_DATE_TO_INTERNAL

CALL FUNCTION 'CONVERT_DATE_TO_INTERNAL'
EXPORTING
date_external = lt_char_of_batch-atwtb
IMPORTING
date_internal = itmslb-ersda
EXCEPTIONS
date_external_is_invalid = 1
OTHERS = 2.

ABAP LOOP AT 内表循环事件块

https://blog.csdn.net/tao273849019/article/details/109713868

读取销售订单长文本(Read_text)

http://www.baidusap.com/abap/function/281

RANGE 内表

RANGES 语句:用与选择表相同的结构创建内表,可使用 RANGES 语句,如下所示:
语法:RANGES FOR .
该语句创建选择表,该表参考数据库表的列或内部字段。选择表必须在程序中填充。不必在程序中再用 TABLES 语句声明数据库表。
RANGES 语句是下列语句的短格式:

DATA: BEGIN OF <seltab> OCCURS 10,
SIGN(1),
OPTION(2)
LOW LIKE <f>,
HIGH LIKE <f>,
END OF <seltab>.

用 RANGES 语句创建的选择表不是选择屏幕的一部分,它们在程序中不能用于数据传递,
该程序由下列语句启动:SUBMIT WITH IN

.
可以在调用程序中用 RANGES 创建。

用 RANGES 创建的内表与选择表结构相同,但功能不同。注意:sign 和 option 的值必须是大写
下面介绍以下 range 中四个参数的具体取值:
SIGN 可以取以下两种值:INCLUED 和 EXCLUDE
sign = ‘I’ ‘I’ 表示包含:LOW 、HIGH 的值
sign = ‘E’ ‘E’ 表示排除:LOW、HIGH 的值

OPTION 运算符可以取以下值:
‘EQ’ , ‘NE’ “等于,不等于 Equal ,Not Equal
‘GT’ , ‘LT’ “大于,小于 Greater Than ,Less Than
‘GE’ , ‘LE’ “大于等于,小于等于 Greater Than or Equal ,Less Than or Equal
‘CP’ , ‘NP’ “参考判断格式,如”ABC*“,NP 排除参考格式
‘BT’ , ‘NB’ “位于,不位于,功能与 Between 和 Not Between 一样。

LOW 是指当前值,或区间最小值(操作符是范围的话)
LOW:LOW 的数据类型与数据库表的列类型相同,该表与选择标准相连接。
如果 Option 运算符不为 ‘BT’ 或 ‘NB’,LOW 的内容定义单值选择,它与 OPTION 中的运算符相结合,为数据库选择指定了条件。
如果 Option 运算符为 ‘BT’ 或 ‘NB’,并且 HIGH 不为空, LOW 和 HIGH 中的内容为间隔选择指定上界和下界,与 OPTION 中的运算符相结合,该间隔为数据库选择指定了条件。
HIGH 是指区间最大值
如果 Option 运算符为 ‘BT’ 或 ‘NB’,HIGH 的值才会生效,运算符不是 Between 和 Not Between 时,high 有值也不会参与计算

字符串拼接的三种方法

定义:将字符串 A、B 拼接给 字符串 C
DATA: A TYPE CHAR12 VALUE ‘A’.
DATA: B TYPE CHAR12 VALUE ‘B’.
DATA: C TYPE CHAR12.

方法如下:
方法一: CONCATENATE A B INTO C .
方法二: C = A && B.
方法三: C = |{ A }{ B }|.

显示:
START-OF-SELECTION.
WRITE: / C.
————————————————
版权声明:本文为 CSDN 博主「AllenFlyer」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_49707237/article/details/125164813

ABAP 运算符

abs(n):绝对值
sign(n):符号值
ceil(n):向下取整
floor(n):向上取整
trunc(n):只取整数部分
frac(n):只取小数部分
strlen(n):取长度
lines(arg):取行数

内表汇总数据的方式

内表统计执行数据统计大致有下述几种方式:
COLLECT 语句实现
AT NEW 语句实现
LOOP AT GROUP 语句实现
READ 哈希表语句实现. SELECT SUM 也能汇总内表

*&---------------------------------------------------------------------*
*& Report ZTS_ITAB_SUM
*&---------------------------------------------------------------------*
*&测试内表统计的几种方式
*&---------------------------------------------------------------------*
REPORT zts_itab_sum.


DATA: BEGIN OF lw_data,
matnr TYPE matnr,
werks TYPE werks_d,
menge TYPE menge_d,
END OF lw_data.
DATA: lt_data LIKE TABLE OF lw_data.
DATA: BEGIN OF lw_sum,
matnr TYPE matnr,
werks TYPE werks_d,
menge TYPE menge_d,
END OF lw_sum.
DATA: lt_sum LIKE TABLE OF lw_sum.


START-OF-SELECTION.
*准备数据
lt_data = VALUE #(
( matnr = 'M1' werks = 'W1' menge = 10 )
( matnr = 'M2' werks = 'W1' menge = 20 )
( matnr = 'M1' werks = 'W2' menge = 30 )
( matnr = 'M2' werks = 'W2' menge = 40 )
).
*方式一:通过COLLECT 语句汇总.
**该语句的局限性: 目标内表中所有字段区分未关键字和指标(数字型).系统底层会创建临时哈希表,解决汇总的性能问题
**该方式另外的局限是: 不参与统计的字段必须清空.
REFRESH lt_sum.
LOOP AT lt_data INTO lw_data.
CLEAR lw_sum.
lw_sum-matnr = lw_data-matnr.
lw_sum-werks = ''. "不能附加第一行或最后一行地点
lw_sum-menge = lw_data-menge.
COLLECT lw_sum INTO lt_sum.
ENDLOOP.


*方式二: 通过AT NEW 语句实现
**该语句的局限性: 参与统计的字段必须是内表前面的字段,并且需要实现排序
*如果统计结果中的地点需要放第一行或最后一行的地点, 可以通过特定逻辑实现
DATA: lw_data_help LIKE lw_data.
REFRESH lt_sum.
SORT lt_data BY matnr werks.
LOOP AT lt_data INTO lw_data.
lw_data_help = lw_data.
AT NEW matnr.
CLEAR lw_sum.
lw_sum-matnr = lw_data-matnr.
lw_sum-werks = lw_data_help-werks. "使用第一个地点放到统计表中
ENDAT.
lw_sum-menge = lw_sum-menge + lw_data-menge.
AT END OF matnr.
lw_sum-werks = lw_data_help-werks. "使用最后一个地点放到统计表中
APPEND lw_sum TO lt_sum.
ENDAT.
ENDLOOP.


*方式三: 通过LOOP AT GROUP 实现
*该语句无需排序,字段无需作为内表前面的字段, 但是性能比AT NEW 方式差.且无关字段只能使用第一行的
* LOOP AT LT_DATA INTO DATA(LW_GP) GROUP BY ( MATNR = LW_GP-MATNR WERKS = LW_GP-WERKS ). "多关键字情况
REFRESH lt_sum.
LOOP AT lt_data INTO DATA(lw_gp) GROUP BY ( matnr = lw_gp-matnr ).
CLEAR lw_sum.
lw_sum-matnr = lw_gp-matnr.
lw_sum-werks = lw_gp-werks. "只能使用第一个地点放到统计表中
LOOP AT GROUP lw_gp INTO lw_data.
lw_sum-menge = lw_sum-menge + lw_data-menge.
ENDLOOP.
APPEND lw_sum TO lt_sum.
ENDLOOP.


*方式四: 通过READ 哈希表实现
DATA: lt_sum_hs LIKE HASHED TABLE OF lw_sum WITH UNIQUE KEY matnr .
refresh lt_sum_hs.
LOOP AT lt_data INTO lw_data.
READ TABLE lt_sum_hs ASSIGNING FIELD-SYMBOL(<fs_sum>) WITH TABLE KEY matnr = lw_data-matnr.
IF sy-subrc <> 0.
CLEAR lw_sum.
lw_sum-matnr = lw_data-matnr.
lw_sum-werks = lw_data-werks. "使用第一行地点放到汇总表中
INSERT lw_sum INTO TABLE lt_sum_hs ASSIGNING <fs_sum>.
ENDIF.
IF <fs_sum> IS ASSIGNED.
<fs_sum>-menge = <fs_sum>-menge + lw_data-menge.
<fs_sum>-werks = lw_data-werks. "使用最后一行地点放到汇总表.
ENDIF.
ENDLOOP.

https://blog.csdn.net/syjf1976/article/details/118216146

SMARTFORMS 数量金额输出格式设置说明

&field()& 设置输出长度.
&field(*)& 如果该字段类型是 abap 数据字典里定义的类型,系统将按照字典定义的长度设置输出长度
&field(S)& 禁止输出符号位
&field(<)& 符号位显示在数据的左边
&field(.)& 设置显示小数的位数
&field(E)& 设置为科学标示法
&field(T)& 禁止千分位的显示(适用于: DEC, CURR, INT 和 QUAN 几种数据类型).
&field(Z)& 禁止数字前导 0 的显示
&field(I)& 禁止显示空值
&field(K)& 禁止类型系统按数据字典定义的转换函数进行输出转换
&field(R)& 右对齐(只有在定义了输出长度时才有效)
&field(F)& 用指定的字符替换左边的空格.
&field(L)& 将日期转换为本地显示格式,使用 JDAT 指定的格式
&field(C)& 该设置效果和 ABAP 的 CONDENSE 语句相同.

CLEAR/REFRESH/FREE 用法

REFRESH .
该语句将内表重置为填充它以前的状态。这意味着表格将不包含任何行。如果使用没有表格工作区域的内表,可以使用 CLEAR 语句代替 REFRESH 语句
1、Deletes all table lines.
2、Storage space is not released.
3、Paging is released.
4、Header line remains unchanged.

CLEAR.
如果使用有表头行的内表,CLEAR 仅清除表格工作区域。要重置整个内表而不清除表格工作区域,使用 REFRESH 语句或 CLEAR 语句 CLEAR[].内表名称之后的方括号指内表体。使用 REFRESH 或 CLEAR 初始化内表后,系统保持在内存中保留的空间 。可以用 FREE 语句释放内存
1、Initialises the header line.
2、Internal table lines remain unchanged.

FREE.
也可以使用 FREE 语句重置内表并直接释放其内存, 而不必先使用 REFRESH 或 CLEAR。 与 REFRESH 一样,FREE 在表格体上 而不在表格工作区域上工作。
在 FREE 语句之后可以再次定位内表。这样系统就再次保留内存空间。

1、Deletes all table lines.
2、Storage space is released.
3、Header line remains unchanged.

DATA: BEGIN OF LINE,
COL1,
COL2,
END OF LINE.
DATA ITAB LIKE LINE OCCURS 10.
LINE-COL1 = 'A'.
LINE-COL2 = 'B'.
APPEND LINE TO ITAB.
REFRESH ITAB.
IF ITAB IS INITIAL.
WRITE 'ITAB is empty'.
FREE ITAB.
ENDIF.

其输出为:
ITAB is empty.
在该程序中 ,先对内表 ITAB 进行填充,然后再用 REFRESH 初始化。在 IF 语句中,用 带 IS INITIAL 参数的逻辑 表达式检查 ITAB 是否为空。 如果空着,则释放内存 。

内表:如果使用有表头行的内表,CLEAR 仅清除表格工作区域。例如 clear gs-school 清除工作区。
要重置整个内表而不清除表格工作区域,使用 REFRESH 语句或 CLEAR 语句 CLEAR [].
REFRESH 加不加中括号都是只清内表,另外 REFRESH 是专为清内表的,不能清基本类型变量,但 CLEAR 可以清基本类型变量。例如 clear gs-school[] 等价于 refresh gs-school 等价于 refresh gs-school[]。

CLEAR、REFRESH 语句都不会释放掉内表所占用的空间。
如果想初始化内表的同时还要释放所占用的空间,请使用:FREE . 比如 free gs-school 可以释放内表空间。

DELETE ADJACENT DUPLICATES FROM

DELETE ADJACENT DUPLICATES FROM 的功能是删除邻近、相同的行,如果是为了把内表 itab 里部相同的行去掉,则应该对内表先按某个字段排序。
排序也是有讲究的,排序语句使用 SORT itab BY comp1, comp2,…, compi ASCENDING|DESCENDING
通常我们会按一个字段进行排序,但是这有有可能相同的行排序不到一块去:
SORT itab_sf01 BY bukrs.
由于排序时只按 BUKRS 字段,所以第 1 行和第 3 行、第 2 行和第 4 行内容相同,但没有相邻,所以执行 DLETE ADJACENT DUPLICATES FROM itab_sf01 也没有达到预期目的。

解决方案:把 SORT 语句改成 SORT itab_sf01 BY bukrs belnr gjahr augdt.即可。
————————————————
版权声明:本文为 CSDN 博主「JellyLin_SAP」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jellylin_sap/article/details/7599043

delete adjacent duplicates from itab 和 delete adjacent duplicates from itab comparing all fields 区别:
前者相邻两行数据,如果除金额字段以外的其他字段都相同,则去重复删除其中一行;
后者相邻两行数据,如果所有对应的字段都相同(包含金额字段),则去重复删除其中一行。

解决方案:要对全部字段进行比较,如果全部字段相同则删除,即
DELETE ADJACENT DUPLICATE FROM itab_sf02 COMPARING ALL FIELDS.

注意:
如果没有 COMPARING 选项,则标准关键字段 的内容必须相同(默认的内表关键字有 F、I 和 P 型),金额型不算,所以除金额型外其他相同的都被咔嚓(删除)了。COLLECT 语句也有类似的功能,除了金额型字段外其他的字段内容相同,则去掉相同行并把相同行的金额值累加到留下来的一行。

内表修改 MODIFY 和 MODIFY table 的区别

MODIFY table gt_intable FROM gs_intable.
MODIFY gt_intable FROM gs_intable.

对于内表修改命令,MODIFY 与 MODIFY table 的区别:
MODIFY table :
1、是出现在 LOOP 外对内表的修改;
2、有一个前提是该内表的定义一定要有主键,如果没有,该语句不能成功。
3、排序表和散列表的主表键是只读的,不能修改。
4、sy-tabix 系统变量无关。
5、利用工作区只能修改一行数据。MODIFY table gt_intable FROM gs_intable.

MODIFY :
1、没有主键的内表可以使用该语句。MODIFY gt_intable FROM gs_intable
2、如果不带 WHERE 条件,只能出现在 LOOP 中。
3、带 WHERE 条件,在 LOOP 外可以处理满足条件的多条数据。
MODIFY gt_intable FROM gs_intable TRANSPORTING a WHERE id = 10.

上面的办法太麻烦,还有很多限制,好吧,用 FS 来修改内表,又快又简单:

LOOP AT gt_alv ASSIGNING FIELD-SYMBOL(<fs_alv>) WHERE SELXXX eq 'X' .
<fs_alv>-LES_DEL = 'X'."内表更新
UPDATE ZTSD0010 SET LES_DEL = 'X' WHERE ID = <fs_alv>-ID. "透明表更新
ENDLOOP.

COMMIT WORK AND WAIT.

调试例子代码:

REPORT ZJAMES020.

TYPES: BEGIN OF ty_intable,
id TYPE i ,
a(10),
END OF ty_intable.

DATA gt_intable TYPE TABLE OF ty_intable." WITH NON-UNIQUE KEY id.
DATA gs_intable TYPE ty_intable.

DO 500 TIMES.

gs_intable-id = 10."gs_intable-id + 1.
gs_intable-a = 'something'.

APPEND gs_intable TO gt_intable.

ENDDO.

- _LOOP AT gt_intable INTO gs_intable._
-
- IF gs_intable-id = 10.
-
- gs_intable-a = 'xxxxx'.
-
- " MODIFY table gt_intable FROM gs_intable.
-
- ENDIF.\*
- \*ENDLOOP.

gs_intable-id = 10.
gs_intable-a = 'xxxxx'.

MODIFY gt_intable FROM gs_intable TRANSPORTING a WHERE id = 10.
"MODIFY gt_intable FROM gs_intable.

cl_demo_output=>display( gt_intable ).

系统变量 SY-INDEX 与 SY-TABIX

系统变量 SY-INDEX 与 SY-TABIX 是用来记录循环的次数或者索引的,不同的是 SY-INDEX 在 DO….ENDDO 中是有效的,而 SY-TABIX 在 LOOP….ENDLOOP 中及 READ 内表时获取相应的索引。

ABAP 中字符大小写转换问题

  • 字符串的大小写转换
    DATA lv_text TYPE string value Barbcbdbarb.
    “to upper
    TRANSLATE lv_text TO UPPER CASE.

“to lower
TRANSLATE lv_text TO LOWER CASE.

“to upper
lv_text = to_upper( lv_text ).

“to lower
lv_text = to_lower( lv_text ).

  • 编辑器中的大小写转换
    SE80 中大小写转换的快捷键

To Lower Case: Ctrl + L
To Upper Case: Ctrl + U

ABAP Development Tool 中的大小写转换快捷键

To Lower Case: Ctrl + Shift + Y
To Upper Case: Ctrl + Shift + X

  • Pretty Printer 中的大小写设置

  • Data Element 中的大小设置
    默认情况下,Char 类型的 Domain 会是大写的,如需要让其允许小写,勾选 Lowercase。

  • 调试器中的大小写转换
    例如在 SE37 中,默认情况下 Case-sensitive 是没有勾选的,此时输入的小写字符会自动变成大写;如需要输入小写,请勾选 case-sensitive。

————————————————
版权声明:本文为 CSDN 博主「SAP-nkGavin」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/nkGavinGuo/article/details/109749139

SELECT-OPTIONS

https://www.cnblogs.com/jiangzhengjun/p/4265187.html

权限对象

https://blog.csdn.net/reagon2008/article/details/134523403

用户拥有某种功能的权限,用户、角色、权限对象。
用户可以有多种角色,角色可以有多种权限对象。
整体步骤:
SU20: 维护权限字段
SU21: 创建权限对象,分配权限字段
SU24: 关联程序和自建权限对象(标准 TCODE 会默认存在标准权限对象)
PFCG: 创建角色
SU01: 给用户分配角色。

1.SU20 创建权限字段
2.SU21 创建权限对象。

  • 首先创建对象类,输入对象类名称、文本,点击保存
  • x 找到之前创建的对象类,可以鼠标右键创建权限对象。
    TABLES scarr.
    DATA gs_scarr TYPE scarr.
    DATA gt_scarr TYPE STANDARD TABLE OF scarr.
    SELECT-OPTIONS s_carrid FOR scarr-carrid .

START-OF-SELECTION.
分两种情况讨论:
1)s_carrid 有值,需要将不符合权限的删除掉
  IF s_carrid IS NOT INITIAL.
    LOOP AT s_carrid.
      AUTHORITY-CHECK OBJECT ‘ZCARRID’
               ID ‘CARRID’ FIELD s_carrid-low.
      IF sy-subrc <> 0.
        DELETE s_carrid.
      ENDIF.
    ENDLOOP.
  ENDIF.

2)s_carrid 为空。
  SELECT * INTO TABLE gt_scarr FROM scarr WHERE carrid IN s_carrid.
  LOOP AT gt_scarr INTO gs_scarr.
    IF s_carrid IS INITIAL.
      AUTHORITY-CHECK OBJECT ‘ZCARRID’
               ID ‘CARRID’ FIELD gs_scarr-carrid.
      IF sy-subrc <> 0.
* Implement a suitable exception handling here
      ELSE.
        WRITE : / gs_scarr.
      ENDIF.
    ELSE.
      WRITE : / gs_scarr.
    ENDIF.
  ENDLOOP.

COMMIT WORK && COMMIT WORK AND WAIT && ROLLBACK WORK

首先说明一点:更新是异步的,更新是由 SAP 中 UPD1 和 UPD2 两个进程执行的:
UPD1 先执行,主要是用于数据库表的更新,比如说写表
UPD2 后执行,主要是用于一些数据的收集统计等的更新,比如说为 BW 收集一下数据等等
同时这个是可以在 SM13 看到的,如果看到日志中 UPD1 成功,UPD2 失败,如有 BW 的取数,那肯定会丢数据的。

所以说 COMMIT WORK 是异步的,为了使其同步,所有就有了 COMMIT WORK AND WAIT.
对于 COMMIT WORK AND WAIT 来说,是可以判断是否更新成功的,SY-SUBRC NE 0 就是有问题的。

上面是最简单的区别,再有:
由于系统中 COMMIT WORK 的数量是有限的,比如说我们系统是 4000 个,同时只能提交 4000 个更新进程,对于 COMMIT WORK 来说,执行完就释放了,可以为后面的 COMMIT WLRK 继续使用;而对于 COMMIT WORK AND WAIT 是要执行完才释放的,所以对于没有必要同步更新的,也尽快使用异步,使得资源得到释放。

有时,在继续处理前需要确认对数据库表所作的更改。另一方面,在 永久存储之前可能需要 取消一些对 数据库表所作的更改。
要确认对数 据库表的更 改,请使用 COMMIT WORK 语句。要在 永久存储之 前取消一些 更改,请使 用 ROLLBACK WORK 语句。
这些语句在 对话编程( 即 SAP 事务、数据 库事务、打 开或关闭屏 幕等等,有 关对话编程 的概述,参 见编写 ABAP/4 事务)中起着 十分重要的 作用。在本 主题中,您 将了解到如 何在 ABAP/4 报表中使用 COMMIT WORK 和 ROLLBACK WORK( 参见编写 ABAP/4 报表)。

在 ABAP/4 报表中,可 能有几个作 业联系在一 起形成作业 的逻辑单元 (LUW) 。通常,可 能要处理 LUW 中的所有动
作,或什么 也不要处理 。假设 LUW 中包含了将 五行插入到 数据库表中 的进程。如 果事务成功 ,所有五行 都将存储在 数据库表中 (这包含一 个更新请求 和一个数据 库事务)。 在显示新屏 幕之前,数 据库事务将 自动结束( ABAP/4 报表的结束 )。在 ABAP/4 报表中不能 以任何方式 影响该进程

如果要保证 数据库中当 前所作的更 改立即被确 认,那么, 就必须使用 COMMIT WORK 语句结束 LUW。COMMIT WORK 在程序代码 中标记了 LUW 结束并启动 更新任务( 参见 COMMIT WORK 处理 )。在 COMMIT WORK 语句以后, 对数据库所 作的所有更 改都不能再 取消。
但是,如果 在 LUW 中出现了错 误,就必须 取消已经执 行的部分。 这意味着当 前没有任何 插入的行能 永久地保存 在数据库中 。要撤销当 前 LUW 对数据库的 更改,请使 用 ROLLBACK WORK, 它将取消前 一次数据库 提交后的所 有更改。

要确认对数 据库表的更 改并使它们 不能再取消 ,请按如下 格式使用 COMMIT WORK 语句:

COMMIT WORK [AND WAIT].
如果使用 AND WAIT 选项,那么 在程序继续 执行以前, 它要等到更 新任务的结 束。如果更 新是成功的 ,SY-SUBRC
就设置为 0。如果 SY-SUBRC 返回一个非 零值,就没 有成功的存 储所作的更 改。
要在保存之 前取消对数 据库表所作 的更改,请 按下列形式 使用 ROLLBACK WORK 语句:

ROLLBACK WORK.
如果对更改 的取消是成 功的,SY-SUBRC 就设置为 0。如果 SY-SUBRC 返回一个非 零值,就没 有成功地取 消所作的更 改
使用 COMMIT WORK 和 ROLLBACK WORK 语句的结果 是将丢失所 有的数据库 光标。因此 ,在 SELECT
循环中或在 处理 SQL 语句之前不 允许使用这 些语句。

Do varying

除了在上述情况下使用,还经常在财务报表开发时累加名称类似的变量,这些变量名是有序的,即存在数字规律,比如 HSL01,HSL02,HSL03 或 WORD01,WORD02 等等的情况,DO-VARYING 语法可以很好的对数据进行累加。

例如:需要将 FAGLFLEXT 表中的 hsl01,hsl02…hsl16 累加,对应财务 1-16 账期每月发生额。

https://cxymm.net/article/yannickdann/115396015

自动计算税金 CALCULATE_TAX_FROM_GROSSAMOUNT

在 F-02 录入会计凭证时候,使用 bapi:BAPI_ACC_DOCUMENT_POST,如果要求导入的数据里增加税码 TAX_CODE
那么如何根据税码计算税金呢:
使用函数:CALCULATE_TAX_FROM_GROSSAMOUNT
————————————————
版权声明:本文为 CSDN 博主「weixin_43885987」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43885987/article/details/113522888

AT NEW、AT LAST、AT FIRST、AT END OF 使用

使用场景:需要统计求和
其实在 loop…endloop 中间,有个 AT ,<…>,ENDAT 循环. 其中的包括: FRIST, LAST, NEW , END OF.这些可以用来进行内表中按某个字段进行分组统计.
FIRST 内表的第一行
LAST 内表的最后一 行
NEW 行组的开头,与 字段 和 剩余字段中 的内容相同
END Of 行组的结尾,与 字段 和 剩余字段中 的内容相同

AT - ENDAT 块中的语句块使用这些行条件代表预定义的控制结构。用户可以使用它们处理内表中的控制断点,而不必使用编程分支和循环中所述的控制语句自己编程。
在 AT - ENDAT 语句块中,工作区域没有用当前表格行进行填充。初始化所有不是标准关键字部件的字段( 参见标识表格行 )。对于行 条件 FIRST 和 LAST, 系统用星号 () 改写所有标 准关键字段 。对于行条件 NEW 和 END OF ,系统用星号 () 改写所有出现在工作区域中指定字段右边的标准关键字段。 用户可根据自己的需求 在 AT - ENDAT 语句块中填 充工作区域 。

1、AT New 事件触发说明
如 AT New f.
f 是内表的一个字段,当 f 字段或者 f 字段左边的字段内容发生变化是该事件后面的语句都会执行。
使用 at new, at first, at last, at end of 的时候要注意:
loop 的时候不能加条件;at 和 endat 之间不能使用 loop into 的 working area。
手动实现 atnew, at end of 的时候,需要注意,容易出错,尤其是在 at end of 的时候。

TYPES: BEGIN OF companies_type,
name(30),
product(20),
sales TYPE i,
END OF companies_type.

DATA: companies TYPE STANDARD TABLE OF companies_type WITH NON-UNIQUE DEFAULT KEY INITIAL SIZE 20,
wa_companies TYPE companies_type.

LOOP AT companies INTO wa_companies.
AT NEW product.
NEW-PAGE.
WRITE: / wa_companies-name, wa_companies-product.
ENDAT.
WRITE: / wa_companies-product, wa_companies-sales.
AT END OF name.
SUM.
WRITE: / wa_companies-name, wa_companies-sales.
ENDAT.
ENDLOOP.

2、使用 AT NEW f. 和 .AT END OF f 时需要注意:

  • f 必须是内表的第一个字段
  • 内表中 f 之后的字段的值都会变成 *
DATA: BEGIN OF th_lifnr,
lifnr TYPE lfa1-lifnr,
item TYPE c,
END OF th_lifnr.

DATA: td_lifnr LIKE TABLE OF th_lifnr.

SELECT lifnr FROM ekko INTO TABLE td_lifnr
WHERE ebeln > 4500006374 AND ebeln < 4500006390.

SORT td_lifnr ASCENDING BY lifnr.

LOOP AT td_lifnr INTO th_lifnr.
AT NEW lifnr.
WRITE:/10 th_lifnr-lifnr.
WRITE:/ 'the next is NEW lifnr.'.
ENDAT.
ENDLOOP.

DATA: wa_data      TYPE ty_data,
lt_data TYPE TABLE OF ty_data,
lt_data_out1 TYPE TABLE OF ty_data,
lt_data_out2 TYPE TABLE OF ty_data,
lt_data_out3 TYPE TABLE OF ty_data.

lt_data = VALUE #(
( znum = 'CC' zcount = 1 matnr = '58M2221110' kwmeng = '100.000' zchar = 'JJ' )
( znum = 'CC' zcount = 2 matnr = '58M2221110' kwmeng = '200.000' zchar = 'JJ' )
( znum = 'CC' zcount = 1 matnr = '58M2221110' kwmeng = '300.000' zchar = 'JJ' )
( znum = 'CC' zcount = 2 matnr = '58M2221110' kwmeng = '400.000' zchar = 'JC' )
( znum = 'CC' zcount = 1 matnr = '58M2221110' kwmeng = '100.000' zchar = 'JC' )
( znum = 'CC' zcount = 2 matnr = '58M2221111' kwmeng = '200.000' zchar = 'JC' )
( znum = 'CC' zcount = 1 matnr = '58M2221111' kwmeng = '300.000' zchar = 'JJ' )
( znum = 'CC' zcount = 2 matnr = '58M2221111' kwmeng = '400.000' zchar = 'JJ' ) ).

SORT lt_data BY znum zcount matnr DESCENDING.

LOOP AT lt_data INTO wa_data.
AT NEW matnr.
APPEND wa_data TO lt_data_out1.
ENDAT.
ENDLOOP.

LOOP AT lt_data INTO wa_data WHERE znum = 'CC' AND matnr = '58M2221110'.
AT NEW matnr.
APPEND wa_data TO lt_data_out2.
ENDAT.
ENDLOOP.

LOOP AT lt_data INTO wa_data WHERE znum = 'CC' AND zchar = 'JJ'.
AT NEW matnr.
APPEND wa_data TO lt_data_out3.
ENDAT.
ENDLOOP.

cl_demo_output=>display( lt_data_out1 ).
cl_demo_output=>display( lt_data_out2 ).
cl_demo_output=>display( lt_data_out3 ).

lt_data_out1:
ZNUM MATNER KWMENG ZCHAR
CC 58M2221110 0 *****
CC 58M2221111 0 *****

————————————————
版权声明:本文为 CSDN 博主「酒剑仙 abc」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fengxin_/article/details/10701990

AT NEW F.
代码段..
ENDAT.
F 是内表的一个字段,当内表中 F 字段以及 F 左边的任何字段内容发生变化时,执行其中的代码段。

AT END OF F.
代码段..
ENDAT.
当 F 字段以及 F 左边字段内容即将发生变化时,执行代码段。

AT FIRST.
代码段..
ENDAT.
内表的第一行时,执行。

AT LAST.
代码段..
ENDAT.
内表的最后一行时执行。

使用条件:
一般在 loop 中使用;
要提前对内表进行排序 sort;
Loop 不能加 where 条件;
在 at 和 endat 之间不能再使用 loop 语法。

ABAP 指针的 6 种用法

用指针的方式计算科目余额,对该科目的 10 个条目数,每个条目 16 个记账期间求和

*&---------------------------------------------------------------------*
*& Report ZLQT98
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT ZLQT98.
DATA LV_NUMB TYPE I .
"指针的6种用法
SELECT RYEAR ,RACCT , RBUKRS , TSLVT ,
TSL01 ,TSL02 , TSL03 , TSL04 ,
TSL05 ,TSL06 , TSL07 , TSL08 ,
TSL09 ,TSL10 , TSL11 , TSL12 ,
TSL13 ,TSL14 , TSL15 , TSL16
FROM FAGLFLEXT
WHERE RBUKRS = '1000'
AND RYEAR = '2020'
AND RACCT = '8035000001'
INTO TABLE @DATA(LT_FAG)
UP TO 10 ROWS.
""""""如果没有取到数据先加数据,加数据这段也使用指针,可以先看后面的代码再看这段
IF LT_FAG[] IS INITIAL. "
DO 10 TIMES.
APPEND INITIAL LINE TO LT_FAG ASSIGNING FIELD-SYMBOL(<FS_FAG>) .
DO 20 TIMES.
ASSIGN COMPONENT SY-INDEX OF STRUCTURE <FS_FAG> TO FIELD-SYMBOL(<FS_VIUE>) .
CASE SY-INDEX.
WHEN 1.
<FS_VIUE> = '2020'.
WHEN 2.
<FS_VIUE> = '8035000001'.
WHEN 3.
<FS_VIUE> = '1000'.
WHEN 4.
<FS_VIUE> = '0'.
WHEN OTHERS.
<FS_VIUE> = <FS_VIUE> + LV_NUMB .
ENDCASE.
LV_NUMB = LV_NUMB + 1 .
ENDDO.
ENDDO.
ENDIF.
"""""""

"1.READ语法中分配指针
READ TABLE LT_FAG ASSIGNING FIELD-SYMBOL(<FS_FAG1>) INDEX 1.

"2.静态分配。
ASSIGN <FS_FAG1>-TSL01 TO FIELD-SYMBOL(<FS_TSL01>) .

DATA LV_NUM TYPE N LENGTH 2 VALUE 1.
DATA LV_CHA TYPE C LENGTH 20 VALUE '<FS_FAG2>-TSL' .
DATA LV_CHA2 TYPE C LENGTH 20 VALUE 'TSL' .
DATA LV_SUM TYPE DMBTR .
"3.LOOP循环里面分配指针。
LOOP AT LT_FAG ASSIGNING FIELD-SYMBOL(<FS_FAG2>).

"4.1.动态分配,循环累计工作区中的 HSL01-HSL16 。将字符串组成字段名直接分配给指针。(字符串中带工作区类型)
DO 16 TIMES.
DATA(LV_FIED) = LV_CHA && LV_NUM .
ASSIGN (LV_FIED) TO FIELD-SYMBOL(<FS_TSL>) .
IF <FS_TSL> <> 0.
LV_SUM = LV_SUM + <FS_TSL> .
ENDIF.
LV_NUM = LV_NUM + 1 .
ENDDO.

"4.2.动态分配,循环累计工作区中的 HSL01-HSL16 。将字符串组成字段名直接分配给指针。(字符串中不带工作区类型)
CLEAR : LV_SUM .
LV_NUM = 1 .
DO 16 TIMES.
DATA(LV_FIED2) = LV_CHA2 && LV_NUM .
ASSIGN COMPONENT LV_FIED2 OF STRUCTURE <FS_FAG2> TO FIELD-SYMBOL(<FS_TSL2>) .
IF <FS_TSL2> <> 0.
LV_SUM = LV_SUM + <FS_TSL2> .
ENDIF.
LV_NUM = LV_NUM + 1 .
ENDDO.


"5.动态分配,循环累计工作区中的 HSL01-HSL16 。将工作区的第几个字段分配给指针。
CLEAR : LV_NUM ,LV_SUM .
DO 16 TIMES.
LV_NUM = 5 ."从第五个字段开始累加
ASSIGN COMPONENT LV_NUM OF STRUCTURE <FS_FAG2> TO FIELD-SYMBOL(<FS_TSL3>) .
IF <FS_TSL2> <> 0.
LV_SUM = LV_SUM + <FS_TSL3> .
ENDIF.
LV_NUM = LV_NUM + 1 .
ENDDO.
"注意事项:指针分配结束后应该判断是否被分配了,此处DEMO做了省略"

ENDLOOP.

循环累计 faglflext-HSLXX 值

DATA:lv_kjqj        TYPE i VALUE 10,
ls_faglflext TYPE faglflext,
lt_faglflext TYPE STANDARD TABLE OF faglflext,
lt_faglflext_1 TYPE TABLE OF faglflext,
lv_str TYPE string,
lv_sum TYPE acdoca-hsl.
FIELD-SYMBOLS:<fs>.
" 折扣后营收占比
SELECT hsl01,
hsl02,
hsl03,
hsl04,
hsl05,
hsl06,
hsl07,
hsl08,
hsl09,
hsl10,
hsl11,
hsl12,
hsl13,
hsl14,
hsl15,
hsl16
FROM faglflext
INTO CORRESPONDING FIELDS OF TABLE @lt_faglflext
WHERE ryear = 2022
AND rbukrs = '3001'
AND racct = '6001010000' AND rldnr = '0L'.

LOOP AT lt_faglflext INTO ls_faglflext.
COLLECT ls_faglflext INTO lt_faglflext_1.
CLEAR:ls_faglflext.
ENDLOOP.
LOOP AT lt_faglflext_1 INTO DATA(ls_faglflext_1).
lv_str = 'LS_FAGLFLEXT_1-HSL' && lv_kjqj.
ASSIGN (lv_str) TO <fs>.
lv_sum = <fs>.
ENDLOOP.

通过列行配置表动态分配

DATA:gt_fieldcat TYPE lvc_t_fcat,
gs_fieldcat TYPE lvc_s_fcat.

FORM frm_fill_fieldcat USING p_col p_fieldname p_text.
APPEND INITIAL LINE TO gt_fieldcat ASSIGNING FIELD-SYMBOL(<fs_field>).
IF <fs_field> IS ASSIGNED.
<fs_field>-col_pos = p_col."输出列
<fs_field>-fieldname = p_fieldname."内部表字段的字段名称
<fs_field>-scrtext_m = p_text."字段标签
IF p_fieldname = 'COL1'.
<fs_field>-outputlen = '40'."宽度
ENDIF.
"列字段为行数的时候,宽度调整
IF p_fieldname = 'COL2'.
<fs_field>-outputlen = '3'."宽度
ENDIF.
IF p_fieldname <> 'COL1' AND p_fieldname <> 'COL2'.
<fs_field>-outputlen = '20'.
ENDIF.
ENDIF.
ENDFORM.

FORM frm_get_data .
DATA:lv_comps TYPE string, "内部表字段的字段名称
lv_text TYPE string. "字段标签
DATA:lv_index TYPE sy-tabix.

DATA:lv_hsl TYPE fins_vhcur12."金额

DATA:lt_acdoca TYPE TABLE OF acdoca,
LS_acdoca TYPE acdoca.

DATA:lt_SETLEAF TYPE TABLE OF setleaf,
ls_SETLEAF TYPE setleaf.

DATA:lt_where TYPE TABLE OF string."动态SQL,动态条件
"去重用于取行项目
DATA: lt_data TYPE TABLE OF ty_data,
ls_data TYPE ty_data.

RANGES: lt_poper FOR acdoca-poper."期间
RANGES: lt_PRCTR FOR acdoca-prctr."利润中心
RANGES: lt_racct FOR acdoca-racct. "科目
RANGES: lt_rfarea FOR acdoca-rfarea. "职能范围
RANGES: LT_rbukrs FOR acdoca-rbukrs."公司代码

"查行配置表
SELECT
a~zrowdes"行项目描述
a~zfsvsno "报表版本
a~zsortno "排序码
a~zrowid "行项目ID
a~zrowsubid "行项目索引
a~zcatg "项目类型
a~racct_start "起始科目号
a~RACCT_end "终止科目号
a~rfarea "功能范围
a~zfrm "计算公式
a~drcrk_s "借方
a~drcrk_h "贷方
a~zoppo "取反
a~rbukrs "公司
a~prctr "利润中心
a~setname "利润中心组
INTO CORRESPONDING FIELDS OF TABLE gt_data
FROM ztfi010 AS a
WHERE a~zfsvsno = p_fsvsno."版本号
IF sy-subrc <> 0.
MESSAGE '该版本无行配置数据,请检查行配置!' TYPE 'E'.
LEAVE LIST-PROCESSING.
ENDIF.
CLEAR:gs_data.

SORT gt_data BY zsortno.
"查列配置表
SELECT * FROM ztfi011 INTO TABLE @DATA(lt_ztfi011) WHERE zfsvsno = @p_fsvsno.
IF sy-subrc <> 0.
MESSAGE '该版本无列配置数据,请检查列配置!' TYPE 'E'.
LEAVE LIST-PROCESSING.
ENDIF.
"创建列
PERFORM frm_fill_fieldcat USING 1 'COL1' '项 目'. "动态列表创建项目描述列
PERFORM frm_fill_fieldcat USING 2 'COL2' '行数'. "动态列表创建行数列
SORT lt_ztfi011 BY zsortno.
LOOP AT lt_ztfi011 INTO DATA(ls_ztfi011).
lv_comps = |COL{ sy-tabix + 2 }|."内部表字段的字段名称,由于创建2个列,所以+2
lv_text = ls_ztfi011-zvoldes."字段标签
lv_index = sy-tabix + 2.
PERFORM frm_fill_fieldcat USING lv_index lv_comps lv_text.
ENDLOOP.


"列排序
SORT gt_fieldcat BY col_pos.
" 根据列创建动态内表
CALL METHOD cl_alv_table_create=>create_dynamic_table
EXPORTING
it_fieldcatalog = gt_fieldcat
IMPORTING
ep_table = lr_table.

" 将表对象分配给指针
ASSIGN lr_table->* TO <fs_table>.

" 根据表对象创建行对象
CREATE DATA lr_line LIKE LINE OF <fs_table>.

" 将行对象分配给指针
ASSIGN lr_line->* TO <fs_line>.

"取配置表数据至lt_data
lt_data = gt_data.

"删除排序码重复项(前台只展示 相同排序码的一条数据),用于行数创建和逻辑取值
DELETE ADJACENT DUPLICATES FROM lt_data COMPARING zsortno.

"初始化行
DO lines( lt_data ) TIMES.
APPEND INITIAL LINE TO <fs_table> .
ENDDO.

CLEAR:lv_index,gs_data,ls_ztfi011,ls_data.

"标准项指针赋值
LOOP AT <fs_table> ASSIGNING FIELD-SYMBOL(<a>).

READ TABLE lt_data INTO ls_data INDEX sy-tabix. "取相同行的值
IF <a> IS ASSIGNED .
"列循环赋值
DO lines( lt_ztfi011 ) + 2 TIMES .
DATA(lv_comp) = |COL{ sy-index }|.
"用指针指向这个字段
ASSIGN COMPONENT lv_comp OF STRUCTURE <a> TO FIELD-SYMBOL(<fs_comp>).
IF sy-subrc = 0.

"根据列字段名称进行赋值
IF lv_comp = 'COL1'.
<fs_comp> = ls_data-zrowdes.
ELSEIF lv_comp = 'COL2'.
<fs_comp> = sy-tabix.
ELSEIF lv_comp <> 'COL1' AND lv_comp <> 'COL2' AND ls_data-zcatg = '标准项'.
READ TABLE lt_ztfi011 INTO ls_ztfi011 INDEX sy-index - 2.

IF ls_data-rfarea IS NOT INITIAL.
lt_rfarea = VALUE #( sign = 'I'
option = 'EQ'
low = ls_data-rfarea ).
APPEND lt_rfarea.
ENDIF.
"公司代码配置查询赋值
IF ls_data-rbukrs IN s_rbukrs.
lt_rbukrs = VALUE #( sign = 'I'
option = 'EQ'
low = ls_data-rbukrs ).
APPEND lt_rbukrs.
ENDIF.

LOOP AT s_prctr.
lt_prctr = VALUE #( sign = s_prctr-sign
option = s_prctr-option
low = s_prctr-low
high = s_prctr-high ).
APPEND lt_prctr.
ENDLOOP.

"期间

LOOP AT s_poper.
IF s_poper-option = 'EQ'.
DATA(lv_lowpoper) = s_poper-low.
ELSEIF s_poper-option = 'BT'.
lv_lowpoper = s_poper-high.
ENDIF.
ENDLOOP.

APPEND
'gjahr = @lv_jgahr' &&"会计年度
' AND poper IN @lt_poper' &&"期间(全年度)
' AND prctr IN @lt_prctr' && "利润中心
' AND rbukrs IN @lt_rbukrs' && "公司代码
' AND rfarea IN @lt_rfarea' && "职能范围
' AND racct IN @lt_racct' &&"科目范围取值
' AND ( drcrk = @ls_data-drcrk_h OR drcrk = @ls_data-drcrk_s )'"借贷方
TO lt_where.

"#转换为空格
LOOP AT lt_where INTO DATA(ls_where).
REPLACE ALL OCCURRENCES OF '#' IN ls_where WITH space.
MODIFY lt_where FROM ls_where.
ENDLOOP.

SELECT hsl
INTO CORRESPONDING FIELDS OF TABLE @lt_acdoca
FROM acdoca
WHERE (lt_where).

"根据公司科目号排序
SORT lt_acdoca BY rbukrs racct.

LOOP AT lt_acdoca INTO ls_acdoca.
lv_hsl = lv_hsl + ls_acdoca-hsl.
ENDLOOP.
IF ls_data-zoppo = 'X'."取反
lv_hsl = lv_hsl * -1.
ENDIF.


"将金额\行id\列号插入表中,用于公式计算
gs_price-hsl = lv_hsl."金额
gs_price-zrowid = ls_data-zrowid."行id
gs_price-comp = lv_comp."列号
APPEND gs_price TO gt_price.

<fs_comp> = lv_hsl.
CLEAR:ls_ztfi011,lv_hsl,ls_acdoca,lt_where,lt_poper[],lt_poper,lt_PRCTR[],lt_PRCTR,lt_racct[],lt_racct,gs_price,lt_rfarea[],lt_rfarea,lt_rbukrs[],lt_rbukrs.

ENDIF.
ENDIF.
ENDDO .
ENDIF .
CLEAR:ls_data.
ENDLOOP .
ENDFORM.

COND/SWITCH 的小坑

ABAP 提供了新语法 COND/SWITCH,可以用来给变量赋值,代替 if 以及 case when,使得代码更加简洁。一般情况下,在我们主观意愿上来说,以下代码,看似是等价的。

IF lv_str IS INITIAL.
lv_str = 'new value'.
ENDIF.
lv_str = COND #( WHEN lv_str IS INITIAL THEN 'new value' ).

但是其实不是,因为 COND when 只是定义了 lv_str 为空时候的赋值,那么变量本身就有值呢?答案是,会给一个空值。
至此,我们就发现了一个小 BUG,所以新语法转化为旧的语法,是这样的,

IF lv_str IS INITIAL.
lv_str = 'new value'.
ELSE.
lv_str = ''.
ENDIF.

这显然会与我们的初衷背道而驰,解决的方式也很简单,那就是定义好 COND 的所有条件。
lv_str = COND #( WHEN lv_str IS INITIAL THEN ‘new value’ ELSE lv_str ).
这样一来,我们便不会丢失原始的值了。SWITCH 的语法和 COND 道理相同。

"New
lv_str = SWITCH #( n WHEN 1 THEN 'A'
WHEN 2 THEN 'B' ).

"Old
CASE n.
WHEN 1. lv_str = 'A'.
WHEN 2. lv_str = 'B'.
WHEN OTHERS. lv_str = ''.
ENDCASE.

这是一个比较小的点,但是往往这些不引人注目的点,会带来较大的 BUG。

ABAP 函数 EVAL_FORMULA 计算公式

ABAP 实现动态的表达式计算,可以使用函数 EVAL_FORMULA 实现,参考代码在下面;同时也可以考虑用类 CL_JAVA_SCRIPT 调用 js 功能实现。

report ypwk_formula.

data: dataf type f,
datap type lbkum.

parameters: formula(40) default 'A*B*C*D',
a_val type lbkum default 3,
b_val type lbkum default 1,
c_val type lbkum default 2,
d_val type lbkum default 4.

start-of-selection.

call function 'CHECK_FORMULA'
exporting
formula = formula
program = sy-repid
routine = 'SUB_CHECK_FORMULA'
* UNIT_OF_MEASURE = ' '
* IMPORTING
* FUNCNAME =
* MESSAGE =
* POS =
* SUBRC =
exceptions
error_in_formula = 1
missing_parameter = 2
others = 3.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.

call function 'EVAL_FORMULA'
exporting
formula = formula
program = sy-repid
routine = 'SUB_GET_VALUE'
importing
value = dataf
exceptions
division_by_zero = 1
exp_error = 2
formula_table_not_valid = 3
invalid_expression = 4
invalid_value = 5
log_error = 6
parameter_error = 7
sqrt_error = 8
units_not_valid = 9
missing_parameter = 10
others = 11.

if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
else.

move dataf to datap.
write:/ formula, datap.
endif.

form sub_check_formula using value(name)
CHANGING value(subrc).
subrc = 0.

ENDFORM.


form sub_get_value using value(name)
changing value(value)
value(subrc).
subrc = 0.
value = 0.
case name.
when 'A'.
value = a_val.
when 'B'.
value = b_val.
when 'C'.
value = c_val.
when 'D'.
value = d_val.
endcase.
endform. "VAR_GET

DATA: dataf TYPE f,
datap TYPE fins_vhcur12. "计算公式的金额

CALL FUNCTION 'EVAL_FORMULA'
EXPORTING
formula = lv_formula
program = sy-repid
routine = 'SUB_GET_VALUE'
IMPORTING
value = dataf
EXCEPTIONS
division_by_zero = 1
exp_error = 2
formula_table_not_valid = 3
invalid_expression = 4
invalid_value = 5
log_error = 6
parameter_error = 7
sqrt_error = 8
units_not_valid = 9
missing_parameter = 10
OTHERS = 11.
IF sy-subrc <> 0.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ELSE.
MOVE dataf TO datap.
ENDIF.

FORM sub_get_value USING VALUE(name)
CHANGING VALUE(value)
VALUE(subrc).
subrc = 0.
value = 0.
LOOP AT gt_formula INTO gs_formula.
CASE name.
WHEN gs_formula-zrowid.
value = gs_formula-hsl.
ENDCASE.
CLEAR:gs_formula.
ENDLOOP.
ENDFORM.

ABAP 常规检查

检查数字

方法 1: CATS_NUMERIC_INPUT_CHECK
方法 2: NUMERIC_CHECK
方法 3: 用 catch 命令自己做

data  c(10) type c.
data i type p.
c = '123X'.

catch system-exceptions convt_no_number = 1.
i = c.
endcatch.

if sy-subrc = 1.
write:/ 'not number'.
else.
write:/ 'number'.
endif.

1.如果返回X则是 否则不是
cl_abap_matcher=>matches(
pattern = '^(-?[1-9]\d*(\.\d*[1-9])?)|(-?0\.\d*[1-9])$'
text = 'XXX' )

2.定义数字变量进行赋值,如果不是则会异常
DATA l_num TYPE p LENGTH 15 DECIMALS 2.

CATCH SYSTEM-EXCEPTIONS convt_no_number = 8.
l_num = pu_num.
ENDCATCH.

IF sy-subrc = 8.

ENDIF.


检查中文

1.如果 flag = 1 则是英文,否则是中文
CALL FUNCTION ‘NLS_VISUAL_CHARLEN’
EXPORTING
str = c1
IMPORTING
len_visual = flag.

ABAP 如何得到完整的错误消息

场景描述:BDC 或者 BAPI 返回的系统消息需要组成完整的文本,显示出来供查错误原因.
方法一,用 message into,语法如下:

MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
INTO DATA(mtext)
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4

方法二,调用函数 MESSAGE_TEXT_BUILD()
作用:将系统消息装换成一个字符串,消息转为文本函数。

data:v_mess(60).

*&---错误消息处理
clear:v_mess.
loop at i_messtab where msgtyp = 'E' or msgtyp = 'A'.
call function 'MESSAGE_TEXT_BUILD'
exporting
msgid = i_messtab-msgid
msgnr = i_messtab-msgnr
msgv1 = i_messtab-msgv1
msgv2 = i_messtab-msgv2
msgv3 = i_messtab-msgv3
msgv4 = i_messtab-msgv4
importing
message_text_output = v_mess.
endloop.

表维护生成器 SM30 表维护

屏幕-》布局修改字段文本
https://blog.csdn.net/qq_40977705/article/details/109763552

在表维护程序 SM30 的使用过程中,经常需要自动填充和修改一些值,比较常规的做法是修改屏幕逻辑流,但是如果重新激活了 SM30,那么逻辑流就要重写,相对比较麻烦,之前接触过表维护程序中的‘事件’,经过研究得出以下方法:
事件编号 事件功能
01 保存前修改数据
05 创建数据时修改
21 退出单元格编辑时填充隐藏字段
————————————————
版权声明:本文为 CSDN 博主「长沙铁道学院-康帅傅」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012232542/article/details/90925845

SM30 表维护自动更新值

背景:SM30 在维护自建表中,经常需要自动填充和修改一些值,比如创建者、创建日期、创建时间、修改者、修改日期、修改时间等。
实现:这些是通过 SE54 中的“事件(EVENT)”来实现的。
实例:SM30 自动填充创建者、创建日期、创建时间、修改者、修改日期、修改时间

FORM FORM_INIT.

ZTFI014-ZNAME = SY-UNAME.
ZTFI014-ZDATE = SY-DATUM.
ZTFI014-ZTIME = SY-UZEIT.

ENDFORM.

FORM FORM_UPDATE.

IF X_HEADER-MAINTVIEW = 'ZTFI014'.

FIELD-SYMBOLS: <F> TYPE ZTFI014.
ASSIGN <TABLE1> TO <F>.

<F>-ZNAME = SY-UNAME.
<F>-ZDATE = SY-DATUM.
<F>-ZTIME = SY-UZEIT.

ENDIF.
UNASSIGN <F>.

ENDFORM.

https://blog.csdn.net/weixin_41641081/article/details/102663146

SM30 添加导入 excel 的功能按钮

SM30 维护试图时可以导入 excel 数据
https://blog.csdn.net/sinat_38119716/article/details/120330051

LOOP GROUP BY

LOOP AT gt_list INTO DATA(ls_ekko)
GROUP BY ( ebeln = ls_ekko-ebeln ) ASCENDING ASSIGNING FIELD-SYMBOL(<group>)."按括号里的条件进行分组
LOOP AT GROUP <group> ASSIGNING FIELD-SYMBOL(<ls_member>). "按分组后进行组员的访问
lv_count = lv_count + <ls_member>-EFFWR. "这里就是对同一组的进行某个字段的汇总
ENDLOOP.
<ls_member>-totol = lv_count.
CLEAR: lv_count.
ENDLOOP.

内外码转换 ALPHA = IN

ls_accountgl-gl_account      = |{  ls_item-hkont  ALPHA = IN }|."总账科目
ls_accountgl-costcenter = |{ ls_item-kostl ALPHA = IN }|."成本中心
ls_accountgl-profit_ctr = |{ ls_item-prctr ALPHA = IN }|."利润中心
ls_accountgl-orderid = |{ ls_item-aufnr ALPHA = IN }|."内部订单

DDIF_DTEL_GET、DDIF_FIELDINFO_GET 取 DATA ELEMENT、表的文本描述

DDIF_DTEL_GET 取 DATA ELEMENT 文本描述
DATA ELEMENT 的文本描述存于 DD04V 表中,在取得时可以从 DB 表中取 or 使用下面的 Function。
项目中为了在出力帐票时取得文本描述,使用此标准 Function 自定义了一个共通的 Function;传入 Data Element 名称,传出文本描述。

DDIF_FIELDINFO_GET 取表文本描述
此 Function 与上面的使用效果类似,既可以取得单一 DATA ELEMENT,也可以取得整张表所有 DATA_ELEMENT 的文本描述。传出构造对应的项目名也一致。

CALL FUNCTION 'DDIF_DTEL_GET'
EXPORTING
NAME = G_WRK_NAME "DATA ELEMENT名称
STATE = 'A'
LANGU = SY-LANGU
IMPORTING
DD04V_WA = G_REC_DD04V "取得的TEXT构造
EXCEPTIONS
ILLEGAL_INPUT = 1
OTHERS = 2.


CALL FUNCTION 'DDIF_FIELDINFO_GET'
EXPORTING
tabname = pv_tabname "表名
TABLES
dfies_tab = pt_tab
EXCEPTIONS
not_found = 1
internal_error = 2
OTHERS = 3.

原文链接:https://blog.csdn.net/nancyln9711/article/details/108520236

税码获取税率

CALL FUNCTION 'GET_TAX_PERCENTAGE'
EXPORTING
aland = 'CN' "国家语言
datab = sy-datum "当前时间
mwskz = <f>-zmwskz "税码
txjcd = ' '
TABLES
t_ftaxp = t_ftaxp.

"读取获取出来的数据
READ TABLE t_ftaxp INTO t_ftaxp WITH KEY kschl = 'MWAS'.
IF sy-subrc EQ 0.
lv_mwskz = t_ftaxp-kbetr / 10."税率
CONCATENATE lv_mwskz '%' INTO <f>-ztax.
ENDIF.

取采购订单含税金额

需求 MB5S 增加含税单价、含税金额、税率、税额:
含税单价:根据 ebeln 采购单号找到凭证条件编号(EKKO-KNUMV),根据凭证条件编号找到 PRCD_ELEMENTS 表中的记录;
其中 PRCD_ELEMENTS-KSCHL == PB00 的记录中 KBETR 代表含税单价,KWERT 代表含税金额;
其中 PRCD_ELEMENTS-KSCHL == ZTAX 的记录中 KBETR 代表税率,KWERT 代表税额;

参考:https://www.cnblogs.com/rainysblog/p/10949888.html

锁对象及使用方法

https://blog.csdn.net/stone0823/article/details/117920412

批导字符串转换为数值

批导的时候,数值类型的数据会自动变大,把批导结构中的数值字段定义为字符型,解决了这个问题。但是在做计算的时候要转换回数值类型。
用到以下函数:

DATA: LV_DCPFM TYPE USR01-DCPFM.
DATA: LV_STR TYPE C LENGTH 18.
DATA: LV_OUT TYPE P DECIMALS 2.

SELECT SINGLE DCPFM
INTO LV_DCPFM
FROM USR01
WHERE BNAME EQ SY-UNAME.

CALL FUNCTION 'UNITS_STRING_CONVERT'
EXPORTING
UNITS_STRING = LV_STR "字符类型的数值数据
DCPFM = LV_DCPFM "USR01表中当前用户的配置
* MLLN = 'M'
* TSND = 'T'
IMPORTING
UNITS = LV_OUT "数值类型的字段
EXCEPTIONS
INVALID_TYPE = 1
OTHERS = 2.

RFC_CVI_EI_INBOUND_MAIN BP 主数据创建/修改示例

TAXTYPE 自动”CN0”转换成”CN5”问题
RFC_CVI_EI_INBOUND_MAIN 创建后 CN0 默认转换为 CN5,可用 BAPI_BUPA_TAX_CHANGE 强制改成 CN0

https://blog.csdn.net/qq_30797051/article/details/124347700

对于前台标识页签下的税号相关数据,需要采用 BAPI_BUPA_TAX_ADD 进行维护类别、税号数据;
对于前台控制页签下的税分类相关数据,需要用 BAPI_BUTX_FRG0010_ADD 才能添加进去税分类国家/地区 税收类型 税收组数据
https://blog.csdn.net/z_x_xing*/article/details/90079579

SAP 金额等负号提前问题

第三种:
这种方法相对于前两种方法的优点是,一.类型还是金额等数字类型,负号实现前置;二.几乎不用写很多代码,且可以保留千分位;三.适用于多个这样的字段修改需求。
实现步骤如下:
1.SE37,按下图命名规范创建 FUNCTION,输入函数组和描述,然后输入 import 和 export 参数,如图
添加代码:

FUNCTION conversion_exit_z001_output.
*"----------------------------------------------------------------------
*"*"本地接口:
*" IMPORTING
*" REFERENCE(INPUT)
*" EXPORTING
*" REFERENCE(OUTPUT)
*"----------------------------------------------------------------------

DATA: output1(20),
output2(20),
outnum(16) TYPE p DECIMALS 2.

IF input IS NOT INITIAL AND input NA '%'.
outnum = input.
IF input >= 0.
WRITE outnum TO output1.
ELSE.
outnum = outnum * ( -1 ).
WRITE outnum TO output1.
CONCATENATE '-' output1 INTO output1.
ENDIF.
ELSE.
* CLEAR output1.
output1 = input.
ENDIF.
CONDENSE output1 NO-GAPS.
WRITE output1 TO output2 RIGHT-JUSTIFIED.
output = output2.
CLEAR : output2.
ENDFUNCTION.

保存,激活。
最后,在对应的 alv 设置 fieldcat 时针对设置金额等数字类型的字段添加代码:固定写成’ZXXX’形式。
WE_FIELDCAT-EDIT_MASK = ‘==Z001’.

版权声明:本文为 CSDN 博主「逆风的鱼」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011576750/article/details/48029589

SAP 号码范围(number range)的使用

事物代码:SNRO
编号长度域就填创建的域,但是一定要先激活。指定这个编号范围对象 ZDIAODUDAN 的长度,我们定义域长度为 10,因此这个编号范围对象生成的编号长度也是 10 位。

无翻滚间隔是指如果编号达到最大,是否自动从头开始,勾上就不从头开始,而在获取编号的时候提示出错。我们这里应该打勾。

编号范围处理属于翻译问题,实际就是编号范围的事务码,如果指定这个事务码,就可以不需要通过 SNUM 或者 SNRO 的主屏幕然后输入编号范围对象的名字来维护这个编号范围,而可以输入事务码直接进入它的号码范围维护屏幕。

警告百分比是指如果编号使用到一定比例,就发出系统警告,提示管理人员增大范围或者进行历史数据归档,避免编号全部用完后影响正常业务,这里我们填写 95,就是 95%的时候提示。

主内存缓冲是指系统为了提高性能而预先取出一些编号放在应用服务器上,当程序取用编号时直接从应用服务器获取,而无需再读取数据库了。通常,如果对号码的连续性要求不高的情况下可以使用这种方式,而如果是财务凭证等要求连续的编号,则应该把主内存缓冲关闭,方法是通过菜单“编辑-设置缓存-无缓存”。

*&---SNRO取值编号
clear : lv_nr1,lv_num1.
lv_nr1 = '01'.
call function 'NUMBER_RANGE_ENQUEUE'
exporting
object = 'ZFI016A1'
exceptions
foreign_lock = 1
object_not_found = 2
system_failure = 3
others = 4.
if sy-subrc = 0. "如果号码范围存在
call function 'NUMBER_GET_NEXT' "将号码累加,获得下一个流水号
exporting
nr_range_nr = lv_nr1
object = 'ZFI016A1'
importing
number = lv_num1
exceptions
interval_not_found = 1
number_range_not_intern = 2
object_not_found = 3
quantity_is_0 = 4
quantity_is_not_1 = 5
interval_overflow = 6
buffer_overflow = 7
others = 8.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
else.
call function 'NUMBER_RANGE_DEQUEUE'
exporting
object = 'ZFI016A1'
exceptions
object_not_found = 1
others = 2.
endif.
endif.

ABAP CDS VIEW WITH ASSOCIATION

ASSOCIATION 是一种通过 Join 条件从多个表中获取数据的连接,但这些是“按需连接”,也就是说,它们只会在用户访问需要关联表的所需数据时被触发
由于 ASSOCIATION 是按需触发,当 Join 较多实际访问时用户只访问单个或者少数 Join 时,相比较传统可以高效率地完成 SQL
ASSOCIATION 的关联基数:
[ 0 … 1 ] = 最多关联一个(at most one)
[ 1 … 1 ] = 只能一一对应关联(exactly one)
[ 0 … n ] = 关联任意数量(any number)
[ 1 … n ] = 至少关联一个(at least one)
[ 1 … 1 ] [ 1 … n ]保证了关联的主表始终至少有一条记录

获取 select 某个条件下的条目数

(1)第一板斧:系统变量 sy-dbcnt

DATA: L_COUNT TYPE I.
SELECT EQUI~EQUNR
EQKT~EQKTX
ILOA~SWERK
INTO TABLE GT_EQUI
FROM EQUI
LEFT JOIN EQKT ON EQUI~EQUNR = EQKT~EQUNR AND SPRAS = SY-LANGU
LEFT JOIN EQUZ ON EQUI~EQUNR = EQUZ~EQUNR AND EQUZ~DATBI = '99991231'
INNER JOIN ILOA ON EQUZ~ILOAN = ILOA~ILOAN
WHERE ILOA~SWERK IN S_SWERK
AND EQUI~ERDAT IN S_ERDAT .

IF SY-SUBRC = 0.
L_COUNT = SY-DBCNT.
ENDIF.

(2)第二板斧:select count (*)

DATA: L_COUNT TYPE I.
SELECT COUNT( * )
INTO L_COUNT
FROM EQUI
LEFT JOIN EQKT ON EQUI~EQUNR = EQKT~EQUNR AND SPRAS = SY-LANGU
LEFT JOIN EQUZ ON EQUI~EQUNR = EQUZ~EQUNR AND EQUZ~DATBI = '99991231'
INNER JOIN ILOA ON EQUZ~ILOAN = ILOA~ILOAN
WHERE ILOA~SWERK IN S_SWERK
AND EQUI~ERDAT IN S_ERDAT .

IF SY-SUBRC = 0.

ENDIF.

(3)第三板斧:describe table XXX lines XXX

DATA: L_COUNT TYPE I.
SELECT EQUI~EQUNR
EQKT~EQKTX
ILOA~SWERK
INTO TABLE GT_EQUI
FROM EQUI
LEFT JOIN EQKT ON EQUI~EQUNR = EQKT~EQUNR AND SPRAS = SY-LANGU
LEFT JOIN EQUZ ON EQUI~EQUNR = EQUZ~EQUNR AND EQUZ~DATBI = '99991231'
INNER JOIN ILOA ON EQUZ~ILOAN = ILOA~ILOAN
WHERE ILOA~SWERK IN S_SWERK
AND EQUI~ERDAT IN S_ERDAT .

IF SY-SUBRC = 0.
* L_COUNT = SY-DBCNT.
DESCRIBE TABLE GT_EQUI LINES L_COUNT.
ENDIF.

SAP ABAP 日期,时间 相关函数

https://www.cnblogs.com/mingdashu/p/5099283.html

使用函数 RP_CALC_DATE_IN_INTERVAL 进行日期的年月日的加减

DATA: days   TYPE i,
months TYPE i,
years TYPE i.

CALL FUNCTION 'FIMA_DAYS_AND_MONTHS_AND_YEARS'
EXPORTING
i_date_from = '20130101'
i_date_to = '20141001'
i_flg_separate = ' ' "是否单独计算 若有标识,则返回两日期相差的天数
IMPORTING
e_days = days "返回天数:638
e_months = months "返回月数:21
e_years = years. "返回年数:2

* 单独计算
CALL FUNCTION 'FIMA_DAYS_AND_MONTHS_AND_YEARS'
EXPORTING
i_date_from = '20130101'
i_date_to = '20141001'
i_flg_separate = 'X' "标识 'X'
IMPORTING
e_days = days "返回天数:0
e_months = months "返回月数:9
e_years = years. "返回年数:1

DATA LV_DATE type DATS.
CALL FUNCTION 'RP_CALC_DATE_IN_INTERVAL'
EXPORTING
DATE = LV_IN_DATE
DAYS = '00'
MONTHS = '01'
SIGNUM = '+'
YEARS = '00'
IMPORTING
CALC_DATE = LV_DATE.

ABAP 中 JSON 格式的转换与解析

使用 cl_trex_json_serializer 类来将结构转为 JSON 格式,不建议使用:

DATA:
serializer TYPE REF TO cl_trex_json_serializer,
lv_json TYPE string.

DATA: BEGIN OF ls_kv,
key1 TYPE string,
value TYPE string,
END OF ls_kv.

DATA: lt_kv LIKE HASHED TABLE OF ls_kv WITH UNIQUE KEY key1.

ls_kv-key1 = 'key'.
ls_kv-value = '值'.
INSERT ls_kv INTO TABLE lt_kv.

CREATE OBJECT serializer
EXPORTING
data = ls_kv.

serializer->serialize( ).

lv_json = serializer->get_data( ).

WRITE / lv_json.

使用 cl_trex_json_deserializer 来实现 JSON 的解析:

DATA: deserializer TYPE REF TO cl_trex_json_deserializer.
CLEAR ls_kv.

CREATE OBJECT deserializer.

deserializer->deserialize(
EXPORTING json = lv_json
IMPORTING abap = ls_kv ).

WRITE: / ls_kv-key1, / ,ls_kv-value.

这两个对象返回的的 JSON 是{key: “value”},而规范的 JSON 应该是{“key”: “value”}的格式。如果使用它们来解析{“key”: “value”}的话,会因为双引号的问题导致出现异常,解析失败。在这种情况下,推荐使用/UI2/CL_JSON:

types: begin of t_record,
key1 type string,
key2 type string,
end of t_record.

data: json type string.
data: record type t_record.

json = '{"key1": "VALUE1", "key2": "VALUE2"}'.

/ui2/cl_json=>deserialize( exporting json = json changing data = record ).

Message(消息)

https://blog.csdn.net/weixin_42959207/article/details/90476922

类型定义 INCLUDE

TYPES: BEGIN OF ty_alv,
mark TYPE char1.
INCLUDE TYPE ztfi040.
TYPES END OF ty_alv.
DATA: gt_alv TYPE STANDARD TABLE OF ty_alv,
gs_alv TYPE ty_alv.

SAP 业务顾问也会的增强——标准字段文本修改的 4 种方法

Copy PR header text to PO header

  METHOD if_ex_me_process_po_cust~process_header.
*取值采购申请抬头文本填充采购订单抬头文本 EL-ZHOUZHOU 20230203
DATA: lt_lines TYPE TABLE OF tline,
ls_lines TYPE tline,
l_tdname TYPE tdobname.
DATA:lt_purchase TYPE purchase_order_items,
ls_purchase TYPE purchase_order_item,
ls_mepoitem TYPE mepoitem.
DATA: lt_textlines TYPE mmpur_t_textlines,
ls_textlines TYPE mmpur_textlines.
CLEAR: lt_lines[],ls_lines,l_tdname,lt_purchase[],ls_purchase,ls_mepoitem,
lt_textlines[],ls_textlines.
lt_purchase = im_header->get_items( ).
LOOP AT lt_purchase INTO ls_purchase.
ls_mepoitem = ls_purchase-item->get_data( ).
IF ls_mepoitem-banfn IS NOT INITIAL..
EXIT.
ENDIF.
ENDLOOP.

"取值采购申请抬头文本
l_tdname = ls_mepoitem-banfn.
CALL FUNCTION 'READ_TEXT'
EXPORTING
client = sy-mandt
id = 'B01'
language = sy-langu
name = l_tdname
object = 'EBANH'
TABLES
lines = lt_lines
EXCEPTIONS
id = 1
language = 2
name = 3
not_found = 4
object = 5
reference_check = 6
wrong_access_to_archive = 7
OTHERS = 8.

IF lt_lines IS NOT INITIAL.
LOOP AT lt_lines INTO ls_lines.
ls_textlines-tdobject = 'EKKO'.
ls_textlines-tdid = 'F01'.
ls_textlines-tdformat = ls_lines-tdformat.
ls_textlines-tdline = ls_lines-tdline.
APPEND ls_textlines TO lt_textlines.
CLEAR: ls_lines,ls_textlines.
ENDLOOP.

"设置抬头文本
CALL METHOD im_header->if_longtexts_mm~set_text
EXPORTING
im_tdid = 'F01'
im_textlines = lt_textlines.
ENDIF.
ENDMETHOD.

https://blog.csdn.net/sapliumeng/article/details/109652281

批量删除销售订单

1、SO 没有创建过任何后续凭证时,可以直接删除:VA02-》输入单号-》进入后选择菜单栏“销售订单”-》删除。
2、如果创建过后续凭证,即使全部冲销删除 DN 也是不能删除的,只能对 SO 进行 rejected。
不清楚你是现在是什么情况,如果你是第一种情况,那么你需要通过 LSMW 自己创建批处理工具。
当然即使是第一种情况,你同样可以采用 reject SO 的方式来取消 SO。

这里提供一个批量 reject SO 的方法:
1、执行 Tcode MASS。
2、选择对象类型 BUS2032。
3、选择 MASSVBAP 后执行。
4、输入你要批量处理的订单号,执行。
5、点击“选择字段”,选择拒绝原因,后面的操作与 XD99,批量修改客户一样。

SAPGUI_PROGRESS_INDICATOR

DATA: lv_lines(10),                            "行数
lv_lines1 TYPE i, "行数
lv_group TYPE i, "组
lv_tabix(10), "循环标号
lv_pecnt TYPE p LENGTH 6 DECIMALS 2, "百分比
lv_pecet(6), "百分数
lv_text(40). "消息
CLEAR: lv_lines,lv_tabix,lv_pecnt,lv_pecet,lv_text.

lv_tabix = lv_tabix + 1. "当前处理的行号
lv_pecnt = lv_tabix / lv_lines * 100."已处理百分比
lv_pecet = lv_pecnt.
"---拼接显示消息
CONCATENATE '已完成' lv_pecet '%,共' lv_tabix '条/' lv_lines '条' INTO lv_text.
CONDENSE lv_text. "整理格式,去掉多余空格
CALL FUNCTION 'SAPGUI_PROGRESS_INDICATOR' "调用进度条函数,传入百分比和消息
EXPORTING
percentage = lv_pecnt
text = lv_text.
CLEAR: lv_pecnt,lv_pecet,lv_text.
DATA: g_lines1 TYPE i, "采购订单创建行数
g_lines2 TYPE i,
g_lines3 TYPE i, "总数据行数
lv_str TYPE string,
lv_per TYPE i,
lv_str = '共有' && g_lines3 && '条数据需要处理,已处理' &&
g_lines2 && '条,请勿关闭操作'.

IF g_lines2 <> g_lines3.
CALL FUNCTION 'SAPGUI_PROGRESS_INDICATOR'
EXPORTING
percentage = 10
text = lv_str.
ELSE.
CALL FUNCTION 'SAPGUI_PROGRESS_INDICATOR'
EXPORTING
percentage = 100
text = lv_str.
ENDIF.

SAP 中去除末尾符号或开头字符

当遇到字符串拼接通过符号串联起来的时候,未尾会多出一个符号,此时需要将这个符号给删除,则需要采用
方法 shift_right( val = ‘’ sub =‘’)
开头采用的是:shift_left( val = sub =’”).

DATA:zkqjg TYPE C.
CONCATENATE '(全天);' zkqjg INTO zkqjg .
zkqjg = shift_right( val = zkqjg sub = ';').

SAP 增强实施禁止指定 TCODE 中 ALV 右键导出 Excel 文件

SE24 增强类:CL_CTMENU 方法:HIDE_FUNCTIONS 或 DISABLE_FUNCTIONS

https://blog.csdn.net/qq_30797051/article/details/122820144

屏幕字段搜索帮助 F4IF_INT_TABLE_VALUE_REQUEST

https://blog.csdn.net/wangjolly/article/details/80075531

AT SELECTION-SCREEN ON VALUE-REQUEST FOR s_name-low.
PERFORM frm_funcname_f4 USING 'FUNCNAME' 'S_NAME-LOW'.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR s_name-high.
PERFORM frm_funcname_f4 USING 'FUNCNAME' 'S_NAME-HIGH'.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_para.
CLEAR: dyfields[], dyfields.
CALL FUNCTION 'DYNP_VALUES_READ'
EXPORTING
dyname = sy-cprog
dynumb = sy-dynnr
request = 'A'
TABLES
dynpfields = dyfields.
READ TABLE dyfields WITH KEY fieldname = 'P_NAME'.
IF dyfields-fieldvalue IS INITIAL.
MESSAGE s000(oo) DISPLAY LIKE 'E' WITH '先填写函数名'.
EXIT.
ENDIF.
TRANSLATE dyfields-fieldvalue TO UPPER CASE.
CLEAR it_para[].
SELECT parameter INTO TABLE it_para
FROM fupararef
WHERE funcname = dyfields-fieldvalue AND
paramtype <> 'T' AND
r3state = 'A'.
* ORDER BY paramtype DESCENDING pposition.
SELECT parameter APPENDING TABLE it_para
FROM fupararef
WHERE funcname = dyfields-fieldvalue AND
paramtype = 'T' AND
r3state = 'A'.
* ORDER BY paramtype pposition.
CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
EXPORTING
retfield = 'PARA'
dynpprog = sy-repid
dynpnr = sy-dynnr
dynprofield = 'P_PARA'
value_org = 'S'
TABLES
value_tab = it_para
EXCEPTIONS
parameter_error = 1
no_values_found = 2
OTHERS = 3.

*&---------------------------------------------------------------------*
*& Form frm_funcname_f4
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& --> p1 text
*& <-- p2 text
*&---------------------------------------------------------------------*
FORM frm_funcname_f4 USING VALUE(pv_retfield) VALUE(pv_dynprofield).
SELECT a~funcname,b~stext INTO TABLE @DATA(lt_zfunc) FROM tfdir AS a
LEFT JOIN tftit AS b ON b~funcname = a~funcname AND b~spras = @sy-langu
WHERE a~funcname LIKE '%ZFM%' .
SORT lt_zfunc BY funcname.
CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
EXPORTING
retfield = pv_retfield
dynpprog = sy-repid
dynpnr = sy-dynnr
dynprofield = pv_dynprofield
value_org = 'S'
TABLES
value_tab = lt_zfunc
EXCEPTIONS
parameter_error = 1
no_values_found = 2
OTHERS = 3.
IF sy-subrc <> 0.
* Implement suitable error handling here
ENDIF.
ENDFORM.

FORM frm_f4_ny.
DATA: BEGIN OF mf_dynpfields OCCURS 1.
INCLUDE STRUCTURE dynpread.
DATA: END OF mf_dynpfields.
DATA: mf_returncode LIKE sy-subrc,
mf_monat LIKE isellist-month,
mf_hlp_repid LIKE sy-repid.

GET CURSOR FIELD mf_dynpfields-fieldname.
APPEND mf_dynpfields.
mf_hlp_repid = sy-repid.

DO 2 TIMES.

CALL FUNCTION 'DYNP_VALUES_READ'
EXPORTING
dyname = mf_hlp_repid
dynumb = sy-dynnr
TABLES
dynpfields = mf_dynpfields
EXCEPTIONS
invalid_abapworkarea = 01
invalid_dynprofield = 02
invalid_dynproname = 03
invalid_dynpronummer = 04
invalid_request = 05
no_fielddescription = 06
undefind_error = 07.

IF sy-subrc = 3.
mf_hlp_repid = 'SAPLALDB'.
ELSE.
READ TABLE mf_dynpfields INDEX 1.
TRANSLATE mf_dynpfields-fieldvalue USING '_ '.
EXIT.
ENDIF.

ENDDO.

IF sy-subrc = 0.
CALL FUNCTION 'CONVERSION_EXIT_PERI_INPUT'
EXPORTING
input = mf_dynpfields-fieldvalue
IMPORTING
output = mf_monat
EXCEPTIONS
error_message = 1.

IF mf_monat IS INITIAL.
mf_monat = sy-datlo(6).
ENDIF.

CALL FUNCTION 'POPUP_TO_SELECT_MONTH'
EXPORTING
actual_month = mf_monat
IMPORTING
selected_month = mf_monat
return_code = mf_returncode
EXCEPTIONS
factory_calendar_not_found = 01
holiday_calendar_not_found = 02
month_not_found = 03.

IF sy-subrc = 0 AND mf_returncode = 0.
CALL FUNCTION 'CONVERSION_EXIT_PERI_OUTPUT'
EXPORTING
input = mf_monat
IMPORTING
output = mf_dynpfields-fieldvalue.
mf_dynpfields-fieldvalue = mf_dynpfields-fieldvalue+0(5) && '0' && mf_dynpfields-fieldvalue+5(2).
COLLECT mf_dynpfields.
CALL FUNCTION 'DYNP_VALUES_UPDATE'
EXPORTING
dyname = mf_hlp_repid
dynumb = sy-dynnr
TABLES
dynpfields = mf_dynpfields
EXCEPTIONS
invalid_abapworkarea = 01
invalid_dynprofield = 02
invalid_dynproname = 03
invalid_dynpronummer = 04
invalid_request = 05
no_fielddescription = 06
undefind_error = 07.
ENDIF.
ENDIF.
ENDFORM.

single 与 up to 1 rows

使用 single : 在 where 语句中如果 使用了全部的 key 字段 可以使用 single
格式: SELECT single Field_Name_List
FROM Table_Name
WHERE Condition_List

使用 up to 1 rows : 在 where 语句中如果 使用了部分或没有使用 key 字段 ,则只能使用 up to 1 rows
格式 : SELECT Field_Name_List
FROM Table_Name
up to 1 rows
WHERE Condition_List
ENDSELECT

例题 : 表 kssk,其中 MANDT、OBJEK、MAFID、KLART、CLINT、ADZHL 为 key 字段。
使用 single : SELECT single CLINT
INTO KSSK-CLINT
FROM KSSK
WHERE OBJEK EQ INOD-CUOBJ
AND MAFID EQ C_O_SIGN
AND KLART EQ C_KLART

使用 up to 1 rows :
SELECT CLINT INTO KSSK-CLINT
FROM KSSK
up to 1 rows
WHERE OBJEK EQ INOD-CUOBJ
AND MAFID EQ C_O_SIGN
AND KLART EQ C_KLART
ENDSELECT
在使用 single 时是错误的,而使用 up to 1 rows 是正确的。
因为 single,在 where 子句中要使用所有 key 字段的作为条件才能使用 single。
而 up to 1 rows 则使用了部分或没有使用 key 字段都可以使用。
single 取第一个符合条件的数据,不需要继续再查找下去。
up to 1 rows 先查找所有数据,再取一条符合条件的数据。
up to n rows 可以取 n 条数据

已释放(发布)的请求号取消释放

https://blog.csdn.net/XLevon/article/details/127265747

这里可以通过把释放的请求号取消释放来实现,如果请求号里只是对程序做修改可以这么做,但是对于有新增表、元素、结构等等在这里不建议这么做。
取消释放的思路为修改表,把表的释放状态改为可修改的状态:
方式一:SE38 执行 RDDIT076 程序   输入请求号
方式二:直接改表 E070。第一种方式也是直接改表 E070。

update E070 set TRSTATUS = ‘D’ where TRKORR = ‘ED1K913540’ –请求号
delete from E070A where TRKORR = ‘ED1K913540’ and ATTRIBUTE = ‘EXPTIMESTAMP’ –过期时间

副本请求

SAP 副本传输的主要原理是:仅释放你传输请求的子请求,然后把你的这个子请求,包到另外一个主请求下。这个主请求,也就是我们所说的副本请求了。然后,我们把这个副本请求进行传输至目标系统。这样就在不释放主请求的情况下,把修改内容直接传输至这目标系统。就是副本传输的主要原理。
事务码:SE09

  1. 点击显示按钮
  2. 点击创建请求按钮
  3. 选择副本传输,点击对勾按钮
  4. 输入请求描述和目标系统,点击保存按钮
  5. 查看创建好的副本传输请求
  6. 拷贝修改程序对应的请求号下的任务号
  7. 选中副本请求号,点击包含对象按钮,将上一步拷贝的任务号添加进来;这个时候就会将任务号下面的所有对象都会拷贝到副本传输的请求号下面
  8. 最后选中副本请求号,点击释放按钮就可以了,然后再去测试机用事务码 STMS 进行接收过去就可以了

LOOP GROUP BY

https://blog.csdn.net/lmf496891416/article/details/111317377

1.格式:

LOOP AT 内表 INTO DATA(工作区) GROUP BY ( 字段 = 工作区-字段
size = GROUP SIZE “分组组数
index = GROUP INDEX ) “分组的组序号
ASCENDING ASSIGNING FIELD-SYMBOL(<组>).

LOOP AT GROUP <组> ASSIGNING FIELD-SYMBOL(<指针>).
“对指针进行操作,和普通的 loop 循环相似
ENDLOOP.
ENDLOOP.

2.简而言之:就是第一个 loop 循环是把内表进行分组,第二 loop 循环是把组里的数据循环读出来进行修改判断等操作。

3.可以替代 AT END OF /AT NEW OF。

4.具体实例-复制就能用

TYPES: BEGIN OF ty_employee,
name TYPE char30,
role TYPE char30,
age TYPE i,
sex TYPE char10,
END OF ty_employee,
ty_employee_t TYPE STANDARD TABLE OF ty_employee WITH KEY name.
"对内表进行赋值"
DATA(gt_employee) = VALUE ty_employee_t(
( name = 'John' role = 'ABAP guru' age = 34 sex = 'MAN' )
( name = 'Alice' role = 'FI Consultant' age = 42 sex = 'WOMAN' )
( name = 'Barry' role = 'ABAP guru' age = 54 sex = 'WOMAN' )
( name = 'Mary' role = 'FI Consultant' age = 37 sex = 'WOMAN' )
( name = 'Arthur' role = 'ABAP guru' age = 34 sex = 'MAN' )
( name = 'Mandy' role = 'SD Consultant' age = 64 sex = 'MAN' ) ).

DATA: gv_tot_age TYPE i,
gv_avg_age TYPE decfloat34.
"通过角色进行分组"
LOOP AT gt_employee INTO DATA(ls_employee) GROUP BY ( role = ls_employee-role
size = GROUP SIZE "分组组数"
index = GROUP INDEX ) "分组的组序号"
ASCENDING ASSIGNING FIELD-SYMBOL(<group>).

CLEAR: gv_tot_age.
WRITE: / | 组序号: { <group>-index } 角色: { <group>-role WIDTH = 15 }|
& | 这个组有几条数据: { <group>-size } |.
"loop循环组,将组里的数据读出来"
LOOP AT GROUP <group> ASSIGNING FIELD-SYMBOL(<ls_member>).
gv_tot_age = gv_tot_age + <ls_member>-age.
WRITE: /13 <ls_member>-name .
ENDLOOP.

gv_avg_age = gv_tot_age / <group>-size.
WRITE: / |组平均年龄: { gv_avg_age }|.
SKIP.
ENDLOOP.
LOOP AT gt_list INTO DATA(ls_ekko)
GROUP BY ( ebeln = ls_ekko-ebeln ) ASCENDING ASSIGNING FIELD-SYMBOL(<group>)."按括号里的条件进行分组
LOOP AT GROUP <group> ASSIGNING FIELD-SYMBOL(<ls_member>). "按分组后进行组员的访问
lv_count = lv_count + <ls_member>-effwr. "这里就是对同一组的进行某个字段的汇总
ENDLOOP.
<ls_member>-totol = lv_count.
CLEAR: lv_count.
ENDLOOP.

DELETE gt_list WHERE totol IS INITIAL.

REDUCE 汇总内表数字型字段的和

https://blog.csdn.net/weixin_44911062/article/details/108650067

REDUCE 语句相当于是一个 LOOP,对内表的数据进行处理,累加汇总在一个字段内,并不会提高运行效率,在数据量小的时候使用,大数据量的累计还是推荐 GROUP BY。

DATA:BEGIN OF ls_data,
a TYPE char1,
b TYPE numc2,
END OF ls_data,
lt_data LIKE TABLE OF ls_data.

lt_data = VALUE #( ( a = 'A' b = 10 )
( a = 'B' b = 30 )
( a = 'C' b = 50 ) ) .

DATA(lv_sum) = REDUCE #( INIT sum TYPE menge_d"定义求和的字段SUM类型QUAN,名字随便
FOR ls_wa IN lt_data "创建一个工作区ls_wa对应内表
WHERE ( a = 'A' OR a = 'B' ) "汇总如果有条件,可以添加汇总条件
NEXT sum = sum + ls_wa-b )."把值和工作区wa取的值相加
WRITE lv_sum.

转换例程

客户、供应商:CONVERSION_EXIT_ALPHA_INPUT/OUTPUT;

lv_kunnr = |{ kunnr ALPHA = IN }|. "加前导零

物料:CONVERSION_EXIT_MATN1_INPUT/OUTPUT;

"给物料添加前导0"
CALL FUNCTION 'CONVERSION_EXIT_MATN1_INPUT'
EXPORTING
input = pv_matnr
IMPORTING
output = pv_matnr
EXCEPTIONS
length_error = 1
OTHERS = 2.
IF sy-subrc <> 0.
ENDIF.

物料单位:

CALL FUNCTION 'CONVERSION_EXIT_CUNIT_INPUT'
EXPORTING
input = gs_upload-bstme
language = sy-langu
IMPORTING
output = gs_upload-bstme.

SUBMIT

https://www.cnblogs.com/hhelibeb/p/5802398.html

SUBMIT {rep|(name)}
[USING SELECTION-SCREEN dynnr]
[VIA SELECTION-SCREEN]
[USING SELECTION-SET variant]
[USING SELECTION-SETS OF PROGRAM prog]
[WITH SELECTION-TABLE rspar]
{ [WITH sel1 { {{EQ|NE|CP|NP|GE|LT|LE|GT} dobj [SIGN sign]}
| {[NOT] BETWEEN dobj1 AND dobj2 [SIGN sign]}
| {IN rtab} }]
[WITH sel2 { {{EQ|NE|CP|NP|GE|LT|LE|GT} dobj [SIGN sign]}
| {[NOT] BETWEEN dobj1 AND dobj2 [SIGN sign]}
| {IN rtab} }]
... }
[WITH FREE SELECTIONS texpr]
[LINE-SIZE width]
[LINE-COUNT page_lines]
{ [EXPORTING LIST TO MEMORY]
| [TO SAP-SPOOL SPOOL PARAMETERS pri_params
[ARCHIVE PARAMETERS arc_params]
WITHOUT SPOOL DYNPRO] }
[[USER user] VIA JOB job NUMBER n]
[AND RETURN].

{rep|(name)}:使用 rep 静态地指定一个程序,或者使用 name 动态地指定
USING SELECTION-SCREEN dynnr:指定要访问的选择屏幕 dynnr,如不使用该条目,系统调用标准选择屏幕。
VIA SELECTION-SCREEN:显示被调用的选择屏幕。如不使用该条目,选择屏幕过程(selection screen processing)将会在后台发生。
USING SELECTION-SET variant:选择屏幕使用变式
USING SELECTION-SETS OF PROGRAM prog:指定使用的变式的程序
WITH SELECTION-TABLE rspar:选择屏幕使用内表 rspar 中的值,内表的行结构为 RSPARAMS 或者 RSPARAMSL_255

REPORT ztestcalling.

CONSTANTS: g_memory1(30) VALUE 'JACKYTESTA'."定义MEMORY ID
CONSTANTS: g_memory2(30) VALUE 'JACKYTESTB'.
DATA: val_return_from_accessed1 TYPE i,
val_return_from_accessed2 TYPE i.
DATA: selectable LIKE TABLE OF rsparams. "选择参数内表
DATA: selectline LIKE LINE OF selectable.

SELECTION-SCREEN BEGIN OF BLOCK blk.
PARAMETERS: p_value TYPE i.
SELECTION-SCREEN END OF BLOCK blk.

INITIALIZATION.
START-OF-SELECTION.
selectline-selname = 'P_VALUE'. "选择屏幕字段名
selectline-kind = 'P'.       "选择类型为单选,如果是多选则为S
selectline-low = p_value.      "选择字段的值
APPEND selectline TO selectable.

SUBMIT ztestACCESSED1 WITH SELECTION-TABLE selectable AND RETURN.
IMPORT val TO val_return_from_accessed1 FROM MEMORY ID g_memory1. "取出ABAP内存中的数据
FREE MEMORY ID g_memory1.               "释放MEMORY ID

SUBMIT ztestACCESSED2 WITH SELECTION-TABLE selectable AND RETURN.
IMPORT val TO val_return_from_accessed2 FROM MEMORY ID g_memory2.
FREE MEMORY ID g_memory2.

SKIP.

WRITE: '程序ACCESSED1的运行结果是', p_value, '+ 1 =', val_return_from_accessed1 .
WRITE: /'程序ACCESSED2的运行结果是', p_value, '* 3 =', val_return_from_accessed2 .

REPORT ztestACCESSED1.

DATA val TYPE i.
CONSTANTS: g_memory1(30) VALUE 'JACKYTESTA'.

SELECTION-SCREEN BEGIN OF BLOCK blk1.
PARAMETERS: p_value TYPE i.
SELECTION-SCREEN END OF BLOCK blk1.

INITIALIZATION.
START-OF-SELECTION.
val = p_value + 1.
IF sy-calld = 'X'.
EXPORT val FROM val TO MEMORY ID g_memory1.
ELSE.
WRITE val.
ENDIF.

根据 HTTP 地址将电子发票的 PDF 文件下载到本地

function zfm_if_demo_get_pdf.
*"----------------------------------------------------------------------
*"*"本地接口:
*"----------------------------------------------------------------------

data:lv_response_data type string,
lt_mfv type zif_t_http_mfv with header line,
lv_url type string.

data:lv_xstring type xstring,
lt_solix type solix_tab with header line,
lv_file_name type string,
lv_file_path type string,
lv_full_name type string,
lv_file_length type i,
lv_max_length type i,
lv_actual_length type i,
lv_window_title type string,
lv_default_file_name type string,
lv_prompt_on_overwrite type abap_bool,
lv_path type string,
lv_user_action type i.

perform:frm_add_mfv using 'SET_HEADER_FIELD' '~request_method' 'GET' '' changing lt_mfv[],
frm_add_mfv using 'SET_HEADER_FIELD' '~server_protocol' 'HTTP/1.1' '' changing lt_mfv[],"可不传
frm_add_mfv using 'SET_HEADER_FIELD' 'Accept' '*/*' '' changing lt_mfv[],"可不传
frm_add_mfv using 'SET_HEADER_FIELD' 'Content-Type' 'application/pdf' '' changing lt_mfv[]."可不传

lv_url = 'http://dzfp.ycansoft.com:........./2021/09/15/04400200041127629533.pdf'."这个放你自己的地址

call function 'ZFM_IF_OUTBOUND_HTTP' “这里我有公共封装,你可以自己写http调用,就是常规的调用。
exporting
iv_url = lv_url
importing
ev_response_data = lv_response_data
changing
ct_mfv = lt_mfv[].

lv_xstring = lv_response_data.

call function 'SCMS_XSTRING_TO_BINARY'
exporting
buffer = lv_xstring
* APPEND_TO_TABLE = ' '
importing
output_length = lv_file_length
tables
binary_tab = lt_solix.

lv_window_title = '选择文件夹'.
lv_default_file_name = '电子发票' && sy-datum && sy-uzeit.
lv_prompt_on_overwrite = abap_true.

clear:lv_file_name,lv_path,lv_file_path,lv_user_action.
call method cl_gui_frontend_services=>file_save_dialog
exporting
window_title = lv_window_title
default_extension = 'pdf'
default_file_name = lv_default_file_name
prompt_on_overwrite = lv_prompt_on_overwrite
changing
filename = lv_file_name
path = lv_path
fullpath = lv_file_path
user_action = lv_user_action
exceptions
cntl_error = 1
error_no_gui = 2
not_supported_by_gui = 3
invalid_default_file_name = 4
others = 5.
if sy-subrc <> 0.
message id sy-msgid type 'S' number sy-msgno display like 'E'
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
return.
endif.

if lv_user_action <> cl_gui_frontend_services=>action_ok.
return.
endif.

call method cl_gui_frontend_services=>gui_download
exporting
bin_filesize = lv_file_length
filename = lv_file_path
filetype = 'BIN'
changing
data_tab = lt_solix[]
exceptions
file_write_error = 1
no_batch = 2
gui_refuse_filetransfer = 3
invalid_type = 4
no_authority = 5
unknown_error = 6
header_not_allowed = 7
separator_not_allowed = 8
filesize_not_allowed = 9
header_too_long = 10
dp_error_create = 11
dp_error_send = 12
dp_error_write = 13
unknown_dp_error = 14
access_denied = 15
dp_out_of_memory = 16
disk_full = 17
dp_timeout = 18
file_not_found = 19
dataprovider_exception = 20
control_flush_error = 21
not_supported_by_gui = 22
error_no_gui = 23
others = 24.
if sy-subrc <> 0.
message id sy-msgid type 'S' number sy-msgno display like 'E'
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
return.
else.
message '文件保存成功' type 'S'.
endif.

endfunction.

ABAP and Excel

SAP OLE

1.ole 中如何保存和退出。 call method of sheetname ‘saveas’ exporting #1 = filepath #2 =1. call method of applicationname ‘quit’.

2.给 sheet 重命名。 call method of sheetname ‘name’ = ‘sheetname’.

3.创建 application. call method of XXX ‘excel.application’.

4.设置 XXX 的显示模式。 set property of XXX ‘visible’ = 1. 前台运行。为 0 时表示为后台运行。

5.创建 workbook. CALL METHOD OF EXCEL ‘WORKBOOKS’ = WORKBOOK . call method of workbook ‘ADD’.

6.在一个 workbook 中添加一个 worksheet. CALL METHOD OF applicationname ‘sheets’ = worksheet. call method of worksheet ‘Add’ .

7.给单元格赋值。 CaLL METHOD OF EXCEL ‘CELLS’ = CELL EXPORTING #1 = 2 #2 = 2. Set PROPERTY OF CELL ‘value’ = xxxx.

8.指定要被操作的 sheet. CALL METHOD OF applicationname ‘Worksheets’ = SHEET EXPORTING #1 = ‘sheet3’. 这里 sheet3 为要操作的 sheet 的名字。 call method of sheet ‘Activate ‘.

9.指定操作的单元格的范围。 CALL METHOD OF applicationname ‘Range’ = range EXPORTING #1 = ‘B2’ #2 = ‘c2’.

  1. 如何操作范围内的单元格。 call method of range ‘select ‘. range 也为已经定义好的 ole2_object. Set PROPERTY OF range ‘MergeCells’ = 0 . 合并单元格,0 时不合并,1 则合并。与前面一起使用 Set PROPERTY OF range ‘HorizontalAlignment’ = 10 . Set PROPERTY OF range ‘VerticalAlignment’ = -4108 .

11.单元格内部属性的操作. call method of cell ‘INTERIOR’ = int. set property of int ‘ColorIndex’ = color. 颜色 set property of int ‘Pattern’ = pattern.

12.单元格内字体的操作. call method of CELL ‘FONT’ = font. set property of font ‘BOLD’ = bold. set property of font ‘SIZE’ = size.

13.边框的操作. call method of CELL ‘BORDERS’ = BORDERS EXPORTING #1 = ‘1’. 1-left 2-right 3-top 4-bottom set property of borders ‘Linestyle’ = plinestyle . set property of borders ‘Weight’ = pweight . free object borders. 在此之前应该指定 range.

14.复制与粘贴. call method of sheet ‘copy’. call method of sheet ‘paste’.

15.一点注意。 在操作 sheet 时,默认为上次操作的 sheet.如果想更换,参考 8。

16.一个问题。有时输入数据如 111111111111111111,会显示为 1E+17。 解决办法: CALL METHOD OF h_excel ‘COLUMNS’ = columnObj EXPORTING #1 = 6. “the column number SET PROPERTY OF columnObj ‘ColumnWidth’ = 10. SET PROPERTY OF columnObj ‘NumberFormat’ = ‘@’.

17.打开一个 workbook. call METHOD OF workbook ‘Open’ EXPORTING #1 = filename+path.

  1. 所有的操作方法都可以在 sell—-表 oleload 中 查询到。

  2. 执行宏。 CALL METHOD OF EXCEL ‘RUN’ EXPORTING #1 = ‘ZMACRO2’.

20.清除 range 内容 CALL METHOD OF EXCEL ‘Range’ = RANGE EXPORTING #1 = tar_cell #2 = tar_cell. call METHOD of RANGE ‘ClearContents’.

21.合并单元格 CALL METHOD OF EXCEL ‘Range’ = RANGE EXPORTING #1 = sor_cell #2 = tar_cell. SET PROPERTY OF RANGE ‘MergeCells’ = 1.

22.缩小字体填充 SET PROPERTY OF range ‘ShrinkToFit’ = 0 . note: 为 0 时取消缩小字体填充,为 1 时设置缩小字体填充。

note: 1.与 ole 相关的关键字存储在表 oleload 中。

*--DATA DEFINITION OF OLE
DATA: gs_excel TYPE ole2_object,
gs_wbooklist TYPE ole2_object,
gs_application TYPE ole2_object,
gs_wbook TYPE ole2_object,
gs_activesheet TYPE ole2_object,
gs_sheets TYPE ole2_object,
gs_newsheet TYPE ole2_object,
gs_cell1 TYPE ole2_object,
gs_cell2 TYPE ole2_object,
gs_cells TYPE ole2_object,
gs_range TYPE ole2_object,
gs_font TYPE ole2_object,
gs_interior TYPE ole2_object,
gs_columns TYPE ole2_object.
DATA: v_filename TYPE string,
v_filename1 TYPE string.
DATA: gv_lines TYPE i.

DATA gv_sheet_name(20) TYPE c .
DATA gv_linno TYPE i . "LINE NUMBER
DATA gv_colno TYPE i . "COLUMN NUMBER

FORM frm_export_excel .
DATA: lv_flag.
"根据销售区域去重
DATA(lt_out) = gt_out[].
SORT lt_out BY bzirk.
DELETE ADJACENT DUPLICATES FROM lt_out COMPARING bzirk.

CLEAR: v_filename,lv_flag,gv_lines.
DESCRIBE TABLE gt_fieldcat LINES gv_lines.
IF lt_out[] IS NOT INITIAL.
CALL METHOD cl_gui_frontend_services=>directory_browse
CHANGING
selected_folder = v_filename.

IF v_filename IS INITIAL.
lv_flag = 'X'.
ELSE.
CONCATENATE v_filename '/' '销售发货明细表' '--' sy-datum '.xls'
INTO v_filename.
ENDIF.
ENDIF.

IF lv_flag = ''.
"循环销售区域,每个生成相应的SHEET
LOOP AT lt_out INTO DATA(ls_out).
*--FORMING SHEET NAME
IF ls_out-bzirk_txt IS NOT INITIAL.
gv_sheet_name = ls_out-bzirk_txt.
ELSE.
gv_sheet_name = '销售地区为空'.
ENDIF.

*--FOR THE FIRST LOOP, EXCEL IS INITIATED AND ONE NEW SHEET IS ADDED
IF sy-tabix = 1.
CREATE OBJECT gs_excel 'EXCEL.APPLICATION'.
SET PROPERTY OF gs_excel 'VISIBLE' = 0 .
GET PROPERTY OF gs_excel 'WORKBOOKS' = gs_wbooklist .
GET PROPERTY OF gs_wbooklist 'APPLICATION' = gs_application .
SET PROPERTY OF gs_application 'SHEETSINNEWWORKBOOK' = 1 .
CALL METHOD OF gs_wbooklist 'ADD' = gs_wbook.
GET PROPERTY OF gs_application 'ACTIVESHEET' = gs_activesheet .
SET PROPERTY OF gs_activesheet 'NAME' = gv_sheet_name .
ELSE.
GET PROPERTY OF gs_wbook 'Sheets' = gs_sheets .
CALL METHOD OF gs_sheets 'Add' = gs_newsheet.
SET PROPERTY OF gs_newsheet 'Name' = gv_sheet_name .
ENDIF.

CLEAR gv_colno.
LOOP AT gt_fieldcat INTO gs_fieldcat.
gv_colno = gv_colno + 1. "

*--TITLE
*--SETTING TITLE DATA
CALL METHOD OF gs_excel 'CELLS' = gs_cell1
EXPORTING
#1 = 1 "行
#2 = gv_colno. "列

SET PROPERTY OF gs_cell1 'VALUE' = gs_fieldcat-scrtext_l .

*--FORMATTING
GET PROPERTY OF gs_cell1 'FONT' = gs_font .
SET PROPERTY OF gs_font 'BOLD' = 1 .
GET PROPERTY OF gs_cell1 'INTERIOR' = gs_interior .
SET PROPERTY OF gs_interior 'COLORINDEX' = 15 .

CLEAR: gs_fieldcat.
FREE OBJECT: gs_interior,gs_cell1,gs_font.
ENDLOOP.

*--BORDERING TITLE DATA AREA
"行
CALL METHOD OF gs_excel 'CELLS' = gs_cell1
EXPORTING
#1 = 1
#2 = 1.
CALL METHOD OF gs_excel 'CELLS' = gs_cell2
EXPORTING
#1 = 1
#2 = gv_lines. "字段数量
CALL METHOD OF gs_excel 'RANGE' = gs_cells
EXPORTING
#1 = gs_cell1
#2 = gs_cell2.
CALL METHOD OF gs_cells 'SELECT'.
CALL METHOD OF gs_cells 'INTERIOR' = gs_interior.
SET PROPERTY OF gs_interior 'COLORINDEX' = 6.

FREE OBJECT: gs_interior,gs_cells,gs_cell2,gs_cell1.

*** 后续行数据开始写入
CLEAR: gv_linno.
gv_linno = 1.
LOOP AT gt_out INTO gs_out WHERE bzirk = ls_out-bzirk.
gv_linno = gv_linno + 1.
PERFORM frm_insert_data USING gs_out.
CLEAR: gs_out.
ENDLOOP.

"优化列宽
CALL METHOD OF gs_excel 'COLUMNS' = gs_columns.
CALL METHOD OF gs_columns 'AUTOFIT'.

*--ACTIVATE CURRENT SHEET
CALL METHOD OF gs_excel 'WORKSHEETS' = gs_activesheet
EXPORTING
#1 = gv_sheet_name.
CALL METHOD OF gs_activesheet 'ACTIVATE'.

CLEAR ls_out.
ENDLOOP.

*** 保存 ***
GET PROPERTY OF gs_excel 'ACTIVEWORKBOOK' = gs_wbook.

CALL METHOD OF gs_wbook 'SAVEAS'
EXPORTING
#1 = v_filename
#2 = 1.

"关闭EXCEL
CALL METHOD OF gs_wbook 'CLOSE'.
CALL METHOD OF gs_excel 'QUIT'.

*--DEALLOCATING MEMORY
FREE: gs_excel, gs_wbooklist, gs_application, gs_wbook,
gs_activesheet,gs_sheets, gs_newsheet, gs_cell1,
gs_cell2, gs_cells, gs_range, gs_font, gs_interior,
gs_columns.

MESSAGE s208(00) WITH '已导出成功!'.
ELSE.
MESSAGE s208(00) WITH '已取消文件导出!' DISPLAY LIKE 'E'.
ENDIF.
ENDFORM.


FORM frm_insert_data USING ps_out LIKE gs_out.
DEFINE zset_data.
SET PROPERTY OF gs_cell1 'VALUE' = &1 .
END-OF-DEFINITION.
CLEAR: gv_colno.
FIELD-SYMBOLS: <fs>.

LOOP AT gt_fieldcat INTO gs_fieldcat.
gv_colno = gv_colno + 1.
CALL METHOD OF gs_excel 'CELLS' = gs_cell1
EXPORTING
#1 = gv_linno "行
#2 = gv_colno. "列

ASSIGN COMPONENT gs_fieldcat-fieldname OF STRUCTURE ps_out TO <fs>.
IF <fs> IS ASSIGNED AND <fs> IS NOT INITIAL.
SET PROPERTY OF gs_cell1 'VALUE' = <fs> .
ELSE.
SET PROPERTY OF gs_cell1 'VALUE' = ' ' .
ENDIF.
ENDLOOP.
ENDFORM.

OLE 下载数据到 EXCEL 模板及速度优化

https://blog.csdn.net/weixin_41641081/article/details/95036315

Microsoft Excel 在剪贴板上有大量信息。是否保留其内容,以便此后粘贴到其他程序中?

1.问题描述
弹窗文本在剪贴板上有大量信息。是否保留其内容,以便此后粘贴到其他程序中?
·要保存剪贴板上的信息,请单击”是”按钮。
·要删除其中的信息以释放内存,请单击”否”按钮。

2.原因分析
技术原因
批导/上载程序使用了 ole2 技术,如 ABAP 经典 Excel 处理函数 ALSM_EXCEL_TO_INTERNAL_TABLE ,因为 ole2 技术是通过粘贴板 clipboard_import 和 clipboard_export 来实现数据复制的,所以如果在批导/上载前,存在大量数据的复制(CTRL + C)就会触发这个提示。

3.解决办法
几种非技术解决办法
(1)任意复制一个单元格,然后关闭 Excel ,执行批导/上载
注意事项:如果批导/上载仍不成功,需要新开一个 SAP 窗口,关闭原 SAP 窗口,我猜这应该是原始 SAP 窗口还记录着这个信息,盲猜和 SAP GUI 选项的内容相关。
(2)关闭 Excel 剪贴板收集功能
(3)重启电脑,重启大法好

技术解决办法
(1)通过 Application.CutCopyMode = False 直接关闭剪贴板,把复制的信息从剪贴板中清空

————————————————
版权声明:本文为 CSDN 博主「李 BIN 的笔记」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/libin961797440/article/details/128542120

ZALSM_EXCEL_TO_INTERNAL_TABLE 完整实施的源代码、附件、说明

源代码缺少了 Sheet Name 读取的部分;
实现中有不完美的地方,如 ZALSMEX_TABLINE 结构组件类型未使用数据元素;
其他不影响,可实施后直接使用;

FUNCTION ZALSM_EXCEL_TO_INTERNAL_TABLE .
*"----------------------------------------------------------------------
*"*"本地接口:
*" IMPORTING
*" VALUE(FILENAME) LIKE RLGRAP-FILENAME
*" VALUE(I_BEGIN_COL) TYPE I
*" VALUE(I_BEGIN_ROW) TYPE I
*" VALUE(I_END_COL) TYPE I
*" VALUE(I_END_ROW) TYPE I
*" TABLES
*" INTERN STRUCTURE ZALSMEX_TABLINE
*" EXCEPTIONS
*" INCONSISTENT_PARAMETERS
*" UPLOAD_OLE
*"----------------------------------------------------------------------
TYPE-POOLS: OLE2.
TYPES: TY_D_ITABVALUE TYPE ZALSMEX_TABLINE-VALUE,
TY_T_ITAB TYPE ZALSMEX_TABLINE OCCURS 0,
BEGIN OF TY_S_SENDERLINE,
LINE(4096) TYPE C,
END OF TY_S_SENDERLINE,
TY_T_SENDER TYPE TY_S_SENDERLINE OCCURS 0.
CONSTANTS: GC_ESC VALUE '"'.

DATA: EXCEL_TAB TYPE TY_T_SENDER.
DATA: LD_SEPARATOR TYPE C.
DATA: APPLICATION TYPE OLE2_OBJECT,
WORKBOOK TYPE OLE2_OBJECT,
RANGE TYPE OLE2_OBJECT,
WORKSHEET TYPE OLE2_OBJECT.
DATA: H_CELL TYPE OLE2_OBJECT,
H_CELL1 TYPE OLE2_OBJECT.
DATA:
LD_RC TYPE I.


* Rückgabewert der Methode "clipboard_export "

* Makro für Fehlerbehandlung der Methods
DEFINE M_MESSAGE.
CASE SY-SUBRC.
WHEN 0.
WHEN 1.
MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
WHEN OTHERS. RAISE UPLOAD_OLE.
ENDCASE.
END-OF-DEFINITION.


* check parameters
IF I_BEGIN_ROW > I_END_ROW. RAISE INCONSISTENT_PARAMETERS. ENDIF.
IF I_BEGIN_COL > I_END_COL. RAISE INCONSISTENT_PARAMETERS. ENDIF.

* Get TAB-sign for separation of fields
CLASS CL_ABAP_CHAR_UTILITIES DEFINITION LOAD.
LD_SEPARATOR = CL_ABAP_CHAR_UTILITIES=>HORIZONTAL_TAB.

* open file in Excel
IF APPLICATION-HEADER = SPACE OR APPLICATION-HANDLE = -1.
CREATE OBJECT APPLICATION 'Excel.Application'.
M_MESSAGE.
ENDIF.
CALL METHOD OF APPLICATION 'Workbooks' = WORKBOOK.
M_MESSAGE.
CALL METHOD OF WORKBOOK 'Open' EXPORTING #1 = FILENAME.
M_MESSAGE.
* set property of application 'Visible' = 1.
* m_message.

IF SHEET_NAME = SPACE.
GET PROPERTY OF APPLICATION 'ACTIVESHEET' = WORKSHEET.
M_MESSAGE.
ELSE.
CALL METHOD OF APPLICATION 'WORKSHEETS' = WORKSHEET.
EXPORTING #1 = SHEET_NAME.

CALL METHOD OF WORKSHEET 'Activate'.
M_MESSAGE.
ENDIF.

* mark whole spread sheet
CALL METHOD OF WORKSHEET 'Cells' = H_CELL
EXPORTING #1 = I_BEGIN_ROW #2 = I_BEGIN_COL.
M_MESSAGE.
CALL METHOD OF WORKSHEET 'Cells' = H_CELL1
EXPORTING #1 = I_END_ROW #2 = I_END_COL.
M_MESSAGE.

CALL METHOD OF WORKSHEET 'RANGE' = RANGE
EXPORTING #1 = H_CELL #2 = H_CELL1.
M_MESSAGE.
CALL METHOD OF RANGE 'SELECT'.
M_MESSAGE.

* copy marked area (whole spread sheet) into Clippboard
CALL METHOD OF RANGE 'COPY'.
M_MESSAGE.

* read clipboard into ABAP
CALL METHOD CL_GUI_FRONTEND_SERVICES=>CLIPBOARD_IMPORT
IMPORTING
DATA = EXCEL_TAB
EXCEPTIONS
CNTL_ERROR = 1
* ERROR_NO_GUI = 2
* NOT_SUPPORTED_BY_GUI = 3
OTHERS = 4.
IF SY-SUBRC <> 0.
MESSAGE A037(ALSMEX).
ENDIF.

PERFORM SEPARATED_TO_INTERN_CONVERT TABLES EXCEL_TAB INTERN
USING LD_SEPARATOR.

* clear clipboard
REFRESH EXCEL_TAB.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>CLIPBOARD_EXPORT
IMPORTING
DATA = EXCEL_TAB
CHANGING
RC = LD_RC
EXCEPTIONS
CNTL_ERROR = 1
* ERROR_NO_GUI = 2
* NOT_SUPPORTED_BY_GUI = 3
OTHERS = 4.

* quit Excel and free ABAP Object - unfortunately, this does not kill
* the Excel process
CALL METHOD OF APPLICATION 'QUIT'.
M_MESSAGE.

* >>>>> Begin of change note 575877
* to kill the Excel process it's necessary to free all used objects
FREE OBJECT H_CELL. M_MESSAGE.
FREE OBJECT H_CELL1. M_MESSAGE.
FREE OBJECT RANGE. M_MESSAGE.
FREE OBJECT WORKSHEET. M_MESSAGE.
FREE OBJECT WORKBOOK. M_MESSAGE.
FREE OBJECT APPLICATION. M_MESSAGE.
* <<<<< End of change note 575877
ENDFUNCTION.

*&---------------------------------------------------------------------*
*& Form separated_to_intern_convert
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
* -->I_TAB text
* -->I_INTERN text
* -->I_SEPARATOR text
*----------------------------------------------------------------------*
TYPES: TY_D_ITABVALUE TYPE ZALSMEX_TABLINE-VALUE,
* internal table containing the excel data
TY_T_ITAB TYPE ZALSMEX_TABLINE OCCURS 0,

* line type of sender table
BEGIN OF TY_S_SENDERLINE,
LINE(4096) TYPE C,
END OF TY_S_SENDERLINE,
* sender table
TY_T_SENDER TYPE TY_S_SENDERLINE OCCURS 0.

CONSTANTS: GC_ESC VALUE '"'.

*&---------------------------------------------------------------------*
*& Form SEPARATED_TO_INTERN_CONVERT
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
* -->I_TAB text
* -->I_INTERN text
* -->I_SEPARATOR text
*----------------------------------------------------------------------*
FORM SEPARATED_TO_INTERN_CONVERT TABLES I_TAB TYPE TY_T_SENDER
I_INTERN TYPE TY_T_ITAB
USING I_SEPARATOR TYPE C.
DATA: L_SIC_TABIX LIKE SY-TABIX,
L_SIC_COL TYPE KCD_EX_COL.
DATA: L_FDPOS LIKE SY-FDPOS.
REFRESH I_INTERN.

LOOP AT I_TAB.
L_SIC_TABIX = SY-TABIX.
L_SIC_COL = 0.
WHILE I_TAB CA I_SEPARATOR.
L_FDPOS = SY-FDPOS.
L_SIC_COL = L_SIC_COL + 1.
PERFORM LINE_TO_CELL_SEPARAT TABLES I_INTERN
USING I_TAB L_SIC_TABIX L_SIC_COL
I_SEPARATOR L_FDPOS.
ENDWHILE.
IF I_TAB <> SPACE.
CLEAR I_INTERN.
I_INTERN-ROW = L_SIC_TABIX.
I_INTERN-COL = L_SIC_COL + 1.
I_INTERN-VALUE = I_TAB.
APPEND I_INTERN.
ENDIF.
ENDLOOP.
ENDFORM. " SEPARATED_TO_INTERN_CONVERT
*---------------------------------------------------------------------*
FORM LINE_TO_CELL_SEPARAT TABLES I_INTERN TYPE TY_T_ITAB
USING I_LINE
I_ROW LIKE SY-TABIX
CH_CELL_COL TYPE KCD_EX_COL
I_SEPARATOR TYPE C
I_FDPOS LIKE SY-FDPOS.
DATA: L_STRING TYPE TY_S_SENDERLINE.
DATA L_SIC_INT TYPE I.

CLEAR I_INTERN.
L_SIC_INT = I_FDPOS.
I_INTERN-ROW = I_ROW.
L_STRING = I_LINE.
I_INTERN-COL = CH_CELL_COL.
* csv Dateien mit separator in Zelle: --> ;"abc;cd";
IF ( I_SEPARATOR = ';' OR I_SEPARATOR = ',' ) AND
L_STRING(1) = GC_ESC.
PERFORM LINE_TO_CELL_ESC_SEP USING L_STRING
L_SIC_INT
I_SEPARATOR
I_INTERN-VALUE.
ELSE.
IF L_SIC_INT > 0.
I_INTERN-VALUE = I_LINE(L_SIC_INT).
ENDIF.
ENDIF.
IF L_SIC_INT > 0.
APPEND I_INTERN.
ENDIF.
L_SIC_INT = L_SIC_INT + 1.
I_LINE = I_LINE+L_SIC_INT.
ENDFORM. "line_to_cell_separat

*---------------------------------------------------------------------*
FORM LINE_TO_CELL_ESC_SEP USING I_STRING
I_SIC_INT TYPE I
I_SEPARATOR TYPE C
I_INTERN_VALUE TYPE TY_D_ITABVALUE.
DATA: L_INT TYPE I,
L_CELL_END(2).
FIELD-SYMBOLS: <L_CELL>.
L_CELL_END = GC_ESC.
L_CELL_END+1 = I_SEPARATOR .

IF I_STRING CS GC_ESC.
I_STRING = I_STRING+1.
IF I_STRING CS L_CELL_END.
L_INT = SY-FDPOS.
ASSIGN I_STRING(L_INT) TO <L_CELL>.
I_INTERN_VALUE = <L_CELL>.
L_INT = L_INT + 2.
I_SIC_INT = L_INT.
I_STRING = I_STRING+L_INT.
ELSEIF I_STRING CS GC_ESC.
* letzte Celle
L_INT = SY-FDPOS.
ASSIGN I_STRING(L_INT) TO <L_CELL>.
I_INTERN_VALUE = <L_CELL>.
L_INT = L_INT + 1.
I_SIC_INT = L_INT.
I_STRING = I_STRING+L_INT.
L_INT = STRLEN( I_STRING ).
IF L_INT > 0 . MESSAGE X001(KX) . ENDIF.
ELSE.
MESSAGE X001(KX) . "was ist mit csv-Format
ENDIF.
ENDIF.

ENDFORM. "line_to_cell_esc_sep

增强后的数据结构 ZALSMEX_TABLINE
这是图片

使用 ALSM_EXCEL_TO_INTERNAL_TABLE 批导/上载大数据量 Excel

实现原理:
分批读取 Excel 数据;
通过减少 Excel 打开关闭次数以减少时间花费(仅需一次打开关闭);
基于 ZALSM EXCEL TO INTERNAL TABLE 封装服务级函数 ZALSM EXCEL2ABAP
封装服务级函数 ZALSM EXCEL2ABAP 实施过程
一般实施:非实施过程
实施过程 1:复制 ZALSM EXCEL TO INTERNAL TABLE 为 ZALSM EXCEL2ABAP
实施过程 2:取消 ZALSM EXCEL2ABAP 的 RFC 属性。
实施过程 3:将仍会使用的参数重新局部定义
实施过程 4:将 ZFM AT ALSM EXCEL2ABAP 的参数复制为此函数参数。
实施过程 5:在打开和关闭 Excel 之前循环执行复制粘贴(注意参数替换)。
说明:注意:此函数为 ZFM AT ALSM EXCEL2ABAP 的进阶版。说明:进阶内容:当分批量处理时无需重复打开和关闭 Excel

call function 'ZALSM_EXCEL2ABAP'
exporting
iv_fname = p_impfn
iv_sname = gt_mv23-sheet
iv_brow = gt_mv23-brow
iv_erow = gt_mv23-erow
iv_bcol = gt_mv23-bcol
iv_ecol = gt_mv23-ecol
iv_batch = 10000
iv_offset = 0
tables
et_data = <gt_main>
et_mesg = lt_mesg.

这是图片

ABAP 新语法 INNER JOIN 内表 新特性详解

系统环境要求:INNER J0IN 内表是 ABAP 7.50+ 新特性。
什么时候需要使用 INNER JOIN 代替 FOR ALL ENTRIES IN ?
连接内表,并且使用 聚合函数 时,例如 SUM( ),AVG( )。

report ytest_008.

data:lt_key type table of mard with header line. "可以WITH HEADER LINE

if lt_key[] is not initial. "需要判断是否空表
select
matnr, "如果使用了@声明变量,则需要逗号分隔,否则不用
werks,
lgort,
labst
into table @data(lt_mard) "结果表lt_mard基于select字段自动去重
from mard
for all entries in @lt_key "lt_key如果基于连接字段有重复,对结果没有影响
where matnr = @lt_key-matnr
and werks = @lt_key-werks
and lgort = @lt_key-lgort.
endif.

INNER JOIN

report ytest_007.

data:lt_key type table of mard with empty key. "尽量指定关键字,不能WITH HEADER LINE

if lt_key[] is not initial. "不需要判断连接表非空,判断不判断结果一样的,我还是喜欢判断,利用IF-ENDIF的结构化提高代码可读性
sort lt_key by matnr werks.
delete adjacent duplicates from lt_key comparing matnr werks. "需要对lt_key的真正关键字去重,否则可能结果表重复或聚合结果错误

select
a~matnr, "需要逗号分隔
a~werks,
sum( a~labst ) as labst
from mard as a
inner join @lt_key as b on a~matnr = b~matnr "需要@声明变量 如果lt_key基于连接字段有重复,对结果有影响
and a~werks = b~werks "内表与数据库表连接字段长度可以不一致
group by a~matnr,a~werks
into table @data(lt_mard_sum). "需要放在select语句结束句,结果表lt_mard_sum不会基于select字段自动去重
endif.

开发注意事项

激活警告消息
消息: The primary key of “LT KEY” is ignored because it is generic or is the standard key.
原因:未明确表 LT_KEY 的关键字。
解决:
(1)指定空关键字 with empty key
data:lt key type table of mard with empty key.
(2)指定正确的关键字 with key…
data:lt key type table of mard with key mandt matnr werks。

一个 SELECT 语句中不能 INNER JOIN 两个内表,INNER JOIN ITAB 前, ITAB 需要去重;

M_TABLES System View 监控数据表行计数和存储大小

https://blogs.sap.com/2017/12/22/another-approach-to-get-the-number-of-records-stored-in-a-table-in-hana-db/

RADIOSCREEN

REPORT zjcb_radioscreen.

TABLES: sscrfields,pa0001.
TYPE-POOLS: slis.

SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001 .
SELECT-OPTIONS:
s_bukrs FOR pa0001-bukrs MODIF ID m1,
s_pernr FOR pa0001-pernr MODIF ID m1,
s_ename FOR pa0001-ename MODIF ID m1.

PARAMETERS: p_file LIKE rlgrap-filename MODIF ID m2.
SELECTION-SCREEN END OF BLOCK b1.


SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-002.
PARAMETERS: rb1 RADIOBUTTON GROUP g1 DEFAULT 'X' USER-COMMAND u1,
rb2 RADIOBUTTON GROUP g1,
rb3 RADIOBUTTON GROUP g1
.
SELECTION-SCREEN END OF BLOCK b2.

INITIALIZATION.

*&---------------------------------------------------------------------*
*& SELECTION-SCREEN OUTPUT
*&---------------------------------------------------------------------*
AT SELECTION-SCREEN OUTPUT.
LOOP AT SCREEN.
IF rb2 EQ 'X'.
IF screen-group1 EQ 'M1'.
screen-active = '0'. "隐藏
ELSE.
screen-active = '1'. "显示
ENDIF.

ELSEIF rb1 EQ 'X' OR rb3 EQ 'X'.
IF screen-group1 EQ 'M2'.
screen-active = '0'.
ELSE.
screen-active = '1'.
ENDIF.
ENDIF.

MODIFY SCREEN.
ENDLOOP.

SAP 内存与 ABAP 内存

先弄清 External Session 和 Internal Session 区别:
External Session:
当 user 登陆 SAP 后, 就开启了一个 userlogon session。当 user 退出所有 sap window, 就结束了这个 user logon session。一个 userlogon session 最多有 6 个 external session(即 main session,对应有 6 个窗口).

Internal Session:
在一个 external session 内调用其它程序将会生成新的 internal session, 比如 call FM, call transaction 或是 submit*** and return 等方式。 当关掉这个 external session 时,也就结束了 external session 下的所有 internal session。一个 External session 最多有 20 个 Internal session.
对于上面这几个 session 之间的关系可以这么简单概括:User session > external session > internal session。

SAP memory 类似于全局变量,用于 Externalsession 间的通信,不同的 externalsession 都可以访问,当退出 userlogon session 时将清空 SAPmemory(不同 user 对应的 SAPmemory 不同)。
ABAP memory 类似于局部变量,用于同一 externalsession 下 internalsession 间的通信,不同的 externalsession 有不同的 ABAPmemory,同一个 externalsession 里的 internalsession 只能访问相应 session 的 ABAP memory。

原文链接:https://blog.csdn.net/SAPmatinal/article/details/105706290

区别与联系

https://www.cnblogs.com/BinGeneral/p/18078759

SAP 内存(SAP Memory)和 ABAP 内存(ABAP Memory),两者都可用于编程中的数据传递,将数据通过 ID 进行绑定,之后在其他程序中通过 ID 获取。

语法区别:
1、SAP 内存通过 SET/GET PARAMETER,来抛接内存值,如:
SET PARAMETER ID ‘MAT’ FIELD p_matnr.
GET PARAMETER ID ‘MAT’ FIELD p_matnr.

2、ABAP 内存通过 EXPORT/IMPORT,来抛接内存值,如:
EXPORT p_matnr TO MEMORY ID ‘MAT’.
IMPORT p_matnr FROM MEMORY ID ‘MAT’.
FREE MEMORY ID ‘MAT’.

作用范围区别:
在介绍作用范围区别之前,先引入三个概念,User Session、External Session 和 Internal Session。
当用户登录 SAP 就生成了 User Session。
打开一个窗口界面,就产生了一个 External Session,窗口关闭,则 External Session 释放。
SAP NW 7.0 之前的版本,一次最多打开 6 个窗口,也就是 6 个 External Session。之后的版本最多打开 16 个窗口。
在一个窗口内,通过 CALL TRANSACTION,SUBMIT 等方式调用程序,则会自动打开 Internal Session,此处会根据调用打开多个 Internal Session。
当窗口关闭时,则释放该 External Session 下的所有 Internal Session。
用户退出登录,则关闭所有 External Session 和 Internal Session。

理解了这三个概念,现在回到主题,上文的 ABAP 内存是在 Internal Session 之间进行数据传递,而 SAP 内存是在 External Session 之间进行数据传递,当然也可以传数据到 Internal Session 中。
通俗的讲,ABAP 内存是在同一个窗口内运行的程序之间,进行数据传递。而 SAP 内存可以在多个窗口之间进行数据传递。

标准 SAP 内存 ID:
在 SAP 中,定义了很多 SAP 内存 ID,存放在 TPARA 表中,通常也与数据元素绑定:

DATA:lv_matnr TYPE mara-matnr.
lv_matnr = '2000000585'.
SET PARAMETER ID 'MAT' FIELD lv_matnr.
CALL TRANSACTION 'MM03'." AND SKIP FIRST SCREEN.

AMDP-ABAP Managed Database Procedures

ABAP 管理数据库过程是 SAP HANA 上的 ABAP 开发环境中用于 ABAP 代码优化的推荐模式之一。
AMDP 允许您作为 ABAP 开发人员直接在 ABAP 中编写数据库过程。特殊的 ABAP 类(所谓的 AMDP 类)可以包含用于在 SAP HANA DB 层中生成 DB 过程的嵌入式代码(SQLScript)。AMDP 的基本思想是在 AS ABAP 服务器内管理 SAP HANA 过程及其生命周期。为了允许在 ABAP 层内本地使用 SAP HANA 功能, SAP HANA 数据库过程语言 SQLScript 已集成到 ABAP 堆栈中。AMDP 在 ABAP 类方法(所谓的 AMDP 方法)中实现,这些方法充当 SQLScript 代码的容器。

这是图片
在 SAP HANA 中,有多种 “代码到数据” 技术可用于在数据库层实现数据密集型计算。SAP HANA 使用 SAP HANA 视图或存储过程在数据库层执行数据密集型计算。之后,这些成果会通过多种 “代码到数据” 技术在 ABAP 应用程序中得到利用。
ABAP 管理数据库过程(AMDP)是实现代码下推功能的一种推荐技术。该框架采用自上而下的方法在 ABAP 环境中创建和管理数据库存储过程。ABAP 管理数据库过程框架确保了 AMDP 存储过程从创建、更改、激活到传输的整个生命周期都由 ABAP 运行时环境在应用层完成。作为一名开发人员,你将在一个 AMDP 类的 AMDP 方法实现中编写存储过程。与传统的 ABAP 方法不同,AMDP 方法是一种独特的方法,它实现特定于数据库的编程语言,如 SQLScript、原生 SQL 以及 L(SAP 内部使用)。关键字 “LANGUAGE” 指定了用于实现存储过程的特定于数据库的语言。

CLASS ztest_amdp DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .

PUBLIC SECTION.
INTERFACES if_amdp_marker_hdb.
CLASS-DATA: scarr_tab TYPE ty_scarr.
CLASS-METHODS:
select_scarr IMPORTING VALUE(clnt) TYPE mandt
EXPORTING VALUE(scarr_tab) TYPE ty_scarr,

get_zywjkmx EXPORTING VALUE(et_zywjkmx) TYPE ztt_zywjkmx,

get_zywjkmx_v EXPORTING VALUE(et_zywjkmx) TYPE ztt_zywjkmx.


PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.

CLASS ztest_amdp IMPLEMENTATION.
METHOD select_scarr
BY DATABASE PROCEDURE FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING scarr.
scarr_tab =
SELECT *
FROM "SCARR"
WHERE mandt = clnt
ORDER BY carrid;
ENDMETHOD.

METHOD get_zywjkmx
BY DATABASE PROCEDURE FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY.
et_zywjkmx =
SELECT zid,zfylx,ZSCENECODE FROM "ZMSL"."ZMSL.SOURCE_TABLE::ZYWJKMX";

ENDMETHOD.

METHOD get_zywjkmx_v
BY DATABASE PROCEDURE FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY.
et_zywjkmx =
SELECT zid,zfylx,'sss' as ZSCENECODE FROM _SYS_BIC."ZMSL.DATA_PROCESS/ZYCDZMX"
where zfylx = 'TBJJT';

ENDMETHOD.

ENDCLASS.

*&---------------------------------------------------------------------*
*& Report zamdp_test
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zamdp_test.

DATA: result TYPE ty_scarr,
gt_zywjkmx TYPE ztt_zywjkmx,
gt_zywjkmx_v TYPE ztt_zywjkmx.

START-OF-SELECTION.


NEW ztest_amdp( )->select_scarr(
EXPORTING clnt = sy-mandt
IMPORTING scarr_tab = result
).


IF NOT cl_abap_dbfeatures=>use_features(
EXPORTING requested_features = VALUE #(
( cl_abap_dbfeatures=>call_amdp_method )
( cl_abap_dbfeatures=>amdp_table_function ) ) ).
cl_demo_output=>display( 'SYSTEM DOES NOT SUPPORT AMDP. PLEASE USE ANOTHER WAYS' ).
ELSE.

* ztest_amdp=>get_zywjkmx(
* IMPORTING et_zywjkmx = gt_zywjkmx ).

ztest_amdp=>get_zywjkmx_v(
IMPORTING et_zywjkmx = gt_zywjkmx_v ).

cl_demo_output=>display( gt_zywjkmx_v ).

ENDIF.