1.1. 基本数据类型 ... 1

1.1.1. P 类型 ( 压缩型 ) 数据 ... 1

1.2. TYPE LIKE . 2

1.3. DESCRIBE . 3

1.4. 字符串表达式 ... 3

1.5. Data element Domain . 4

1.6. 词典预定义类型与 ABAP 类型映射 ... 5

1.7. 字符串处理 ... 7

1.7.1. count match 结合 ... 7

1.7.2. FIND …SUBMATCHES . 8

1.7.3. FIND …RESULTS  itab . 8

1.7.4. 正则式类 ... 9

1.7.4.1. matches match . 9

1.7.4.2. contains . 10

1.7.4.3. find_all 10

1.7.4.4. find_next . 11

1.7.4.5. get_length get_offset get_submatch . 11

1.7.4.6. replace_all 12

1.8. CLEAR REFRESH FREE . 12

1.9. ABAP 程序中的局部与全局变量 ... 12

1.10. Form Function . 13

1.10.1. FORM .. 13

1.10.2. FUNCTION .. 15

1.10.2.1. Function Group 结构 ... 15

1.10.2.2. Function 参数传值、传址 ... 18

1.11. 字段符号 FIELD-SYMBOLS . 20

1.11.1. ASSIGN 隐式强转 ... 21

1.11.2. ASSIGN 显示强转 ... 21

1.11.3. ASSIGN 动态分配 ... 21

1.11.4. UNASSIGN CLEAR . 21

1.12. 数据引用、对象引用 ... 21

1.12.1. 数据引用 Data References . 21

1.12.2. 对象引用 Object references . 22

1.12.3. GET REFERENCE OF 获取变量 / 对象 / 常量地址 ... 22

1.13. 动态语句 ... 22

1.13.1. 内表动态访问 ... 22

1.13.2. 动态类型 ... 23

1.13.3. 动态 SQL . 23

1.13.4. 动态调用类的方法 ... 23

1.13.5. ASSIGN 动态分配 ... 23

1.13.5.1. 动态访问类的属性成员 ... 24

1.14. 反射 ... 24

1.14.1. TYPE HANDLE . 24

1.14.2. 动态创建数据 Data 或对象 Object . 25

1.14.3. 动态创建基本类型变量、结构、内表 ... 25

1.14.4. 类对象反射 ... 26

2. 面向对象 ... 27

2.1. 类与接口定义 ... 27

2.1.1. components . 27

2.2. 类定义、实现 ... 27

2.3. 接口定义、实现 ... 27

2.4. 类、接口继承 ... 28

2.5. 向下强转型 ?= . 28

2.6. 方法 ... 28

2.6.1. parameters . 29

2.6.2. PREFERRED PARAMETER 首选参数 ... 29

2.6.3. 普通调用 ... 29

2.6.4. 简单调用 ... 30

2.6.5. 函数方法 ... 30

2.7. me super . 30

2.8. 事件 ... 30

2.8.1. 事件定义 ... 30

2.8.2. 事件触发 ... 31

2.8.3. 事件处理器 Event Handler . 31

2.8.4. 注册事件处理器 ... 32

2.8.5. 示例 ... 32

3. 内表 ... 33

3.1. LOOP AT 循环内表 ... 33

3.1.1. SUM .. 34

3.1.2. AT... ENDAT . 34

3.1.3. 自已实现 AT... ENDAT . 37

3.2. LOOP AT 中修改当前内表行 ... 39

3.2.1. 循环中修改索引表 ... 39

3.2.2. 循环中修改 HASH ... 40

3.3. 第二索引 ... 40

3.3.1. 使用第二索引 ... 41

3.3.2. 示例 ... 41

3.4. 适合所有类型的内表操作 ... 42

3.5. 适合索引内表操作 ... 43

4. OPEN SQL . 43

4.1. SELECT INSERT UPDATE DELETE MODIFY . 43

4.2. 条件操作符 ... 44

4.3. RANG 条件内表 ... 44

4.4. FOR ALL ENTRIES . 45

4.5. INNER JOIN LEFT OUTER JOIN 使用限制 ... 46

4.6. 动态 SQL . 46

4.7. 子查询 ... 47

4.7.1. = <> < <= > >= 子查询 ... 47

4.7.1.1. ALL ANY SOME . 48

4.7.2. [NOT] IN 子查询 ... 48

4.7.3. [NOT] EXISTS 子查询 ... 48

4.7.4. 相关子查询 ... 48

4.8. 统计函数 ... 48

4.9. 分组过滤 ... 48

4.10. 游标 ... 49

4.11. 三种缓存 ... 49

4.12. Native SQL . 50

4.12.1. 查询 ... 50

4.12.2. 存储过程 ... 50

4.12.3. 游标 ... 50

4.13. SAP ... 51

5. SAP/DB LUW .. 51

5.1. DB LUW .. 51

5.1.1. 显式提交 ... 52

5.1.2. 隐式提交 ... 52

5.1.3. 显示回滚 ... 52

5.1.4. 隐式回滚 ... 52

5.2. SAP LUW .. 53

5.2.1. SAP LUW 的绑定方式 ... 54

5.2.1.1. Function . 54

5.2.1.2. subroutine . 55

5.2.2. 开启新的 SAP LUW .. 55

5.2.3. 同步或异步更新(提交) ... 55

5.2.4. 本地、非本地方式提交 ... 55

6. 逻辑数据库 ... 56

6.1. 组成 ... 56

6.2. 结构 ... 56

6.3. 选择屏幕( Selections ... 57

6.3.1. PARAMETERS 屏幕参数扩充 ... 58

6.3.2. SELECTION-SCREEN 格式化屏幕 ... 58

6.3.3. DYNAMIC SELECTIONS 动态选择条件 ... 58

6.3.3.1. DYN_SEL . 60

6.3.3.1.1. RSDS_TYPE-CLAUSES . 60

6.3.3.1.2. RSDS_TYPE-TRANGE . 61

6.3.4. FIELD SELECTION 动态选择字段 ... 62

6.3.4.1. SELECT_FIELDS . 63

6.4. 数据库程序中重要 FORM .. 65

6.5. LDB 选择屏幕:静 ( ) 态选择屏幕、动态选择视图 ... 66

7. ALV . 70

7.1. Layout 重要字段 ... 70

7.2. FIELDCATALOG 重要字段 ... 70

7.3. 指定双击触发的 FunCode . 71

7.4. 相关函数 ... 71

7.5. 重要参数接口 ... 71

7.6. 让预置按钮回调 I_CALLBACK_USER_COMMAND .. 72

7.7. 颜色 ... 72

7.8. 可编辑 ... 72

7.9. 单元格数据修改后立即自动刷新 ... 73

7.10. 数据有效性验证事件: data_changed . 73

7.11. 金额、数字类型输入问题 ... 74

7.12. 排序、分类汇总 ... 74

7.13. 可打印的表头输出 ... 75

7.14. 布局变式读取、切换、根据布局格式导出数据 ... 75

7.15. 动态内表 ... 76

8. OO ALV . 77

8.1. 相关类 ... 77

8.2. 控制区域、容器、 Grid 关系 ... 77

8.3. CL_GUI_ALV_GRID 重要方法 ... 77

8.4. set_table_for_first_dispaly() 方法重要参数 ... 77

8.5. 事件绑定、触发、回调处理 ... 77

8.6. CL_GUI_DOCKING_CONTAINER 容器 ... 78

8.7. 覆盖(拦截)预设按钮的功能 FunCode BEFORE_USER_COMMAND .. 78

8.8. 数据改变事件 data_changed data_changed_finished . 79

8.9. 单元格可编辑 ... 79

9. 问题 ... 79

9.1. ALV 自带导出文件时字段数据末尾被截断问题 ... 79

9.2. Smartform Template 无法显示减号后面内容 ... 80

9.3. Smartform 金额或者数量字段显示不出来 ... 80

9.4. 更新数据库表时, 工作区或 内表的结构需参考数据库表来定义 ... 80

9.5. DELETE ADJACENT DUPLICATES… 去重复 ... 80

9.6. Text 使用 Excel 打开乱码问题 ... 80

9.7. VBFA EKPO 联合查询问题 ... 81

10. 技巧 ... 81

10.1. READ TABLE...WITH KEY 可使用 OR 条件或其他非“ = ”操作符 ... 81

10.2. SELECT SINGLE ... WHERE ... 无法排序问题 ... 82

10.3. 当心 Where 后的条件内表为空时 ... 82

10.4. 快速查找 SO 所对应的交货单 DN PO .. 82

10.5. X 类型的 C 类型视图 ... 82

10.6. 字符串连接: && 替代 CONCATENATE . 83

10.7. Variant 变式中动态日期 ... 83

11. 优化 ... 84

11.1. 数据库 ... 84

11.2. 程序 ... 86

12. 屏幕 ... 88

12.1. AT SELECTION-SCREEN PAI AT USER-COMMAND 触发时机 ... 88

12.2. SELECTION-SCREEN 格式化屏幕、激活预设按钮 ... 88

12.3. PARAMETERS . 88

12.4. SELECT-OPTIONS . 89

12.4.1. 输入 ABAP 程序默认值时,需要加上“ =” . 89

12.4.2. 选择条件内表多条件组合规则 ... 89

12.4.3. 使用 SELECT-OPTIONS 替代 PARAMETERS . 90

12.5. 各种屏幕元素演示 ... 91

12.6. 按钮、单选复选框、下拉框的 FunCode . 91

12.6.1. 选择屏幕中的按钮 ... 92

12.6.2. 选择屏幕中的单选 / 复选按钮:点击时显示、隐藏其他屏幕元素 ... 92

12.6.3. 选择屏幕中下拉列表: AS LISTBOX . 93

12.7. 屏幕流逻辑 ... 93

12.7.1. FIELD .. 93

12.7.2. MODULE . 94

12.7.3. ON INPUT ON CHAIN-INPUT 区别 ... 94

12.8. EXIT-COMMAND .. 95

12.8.1. MODULE <mod> AT EXIT-COMMAND .. 95

12.8.2. AT SELECTION-SCREEN ON EXIT-COMMAND .. 95

12.9. OK_CODE . 95

12.9.1. ok_code 使用前需拷贝 ... 95

12.10. Search help F4 ... 95

12.10.1. VALUE CHECK fixed Values Value Table . 95

12.10.2. 检查表 Check Table --- Value Table . 96

12.10.3. SE11 检查表与搜索帮助关系 ... 96

12.10.4. F4 搜索帮助联动的决定因素 ... 98

12.11. 搜索帮助参数说明 ... 100

12.12. F4IF_SHLP_EXIT_EXAMPLE 帮助出口 ... 102

12.12.1. 修改数据源 ... 102

12.12.2. 删除重复 ... 103

12.13. 搜索帮助优先级 ... 103

12.14. 搜索帮助创建函数 ... 103

12.15. POV 事件里读取屏幕字段中的值函数 ... 104

12.16. 动态修改屏幕 ... 104

12.17. 子屏幕 ... 105

12.18. 屏幕跳转 ... 106

12.18.1. CALL SCREEN 误用 ... 106

12.18.2. CALL SCREEN/SET SCREEN/LEAVE TO SCREEN 区别 ... 107

12.19. 修改标准选择屏幕的 GUI Status . 107

12.20. 事件分类 ... 107

12.20.1. 报表事件 ... 107

12.20.2. 选择屏幕事件 ... 107

12.20.3. 逻辑数据库事件 ... 108

12.20.4. 列表事件 ... 108

12.20.5. 事件流图 ... 109

12.21. 事件终止 ... 110

12.21.1. RETURN .. 110

12.21.2. STOP . 110

12.21.3. EXIT . 110

12.21.4. CHECK . 110

12.21.5. LEAVE . 111

12.21.5.1. REJECT . 111

13. 列表屏幕 ... 111

13.1. 标准 LIST . 112

13.2. 自定义 LIST . 112

13.3. LIST 事件 ... 113

13.4. Detail Lists 创建 ... 113

13.5. 标准的 List Status . 113

13.6. 列表屏幕上的数据与程序间的传递 ... 114

13.6.1. SY-LISEL . 114

13.6.2. HIDE . 114

13.6.3. READ LINE . 114

13.7. Screen Processing 屏幕处理切换到 Lists 列表输出 ... 115

13.8. LIST 打印输出 ... 115

14. Messages . 115

14.1. 00 消息 ID 中的通用消息 ... 115

14.2. 消息常量 ... 116

14.3. 静态指定 ... 116

14.4. 动态指定 ... 116

14.5. 消息拼接 MESSAGE …INTO .. 116

14.6. 修改消息显示性为 …DISPLAY LIKE… .. 116

14.7. RAISING <exc> :消息以异常形式抛出 ... 116

14.8. CALL FUNCTION…EXCEPTIONS . 117

14.8.1. error_message = n_error 捕获消息 ... 118

14.9. 各种消息的显示及处理 ... 118

14.10. 异常处理 ... 119

14.10.1. RAISE [EXCEPTION]… 触发异常 ... 119

14.10.1.1. 触发类异常 ... 119

14.10.1.2. RESUMABLE 选项 ... 120

14.10.2. 捕获异常 ... 121

14.10.2.1. 类异常捕获 TRY…CATCH .. 121

14.10.2.2. 老式方式捕获 runtime errors( 运行时异常 ) 121

14.10.3. 向上抛出异常 ... 121

14.10.4. 类异常 ... 122

15. 数据格式化、转换 ... 123

15.1. 数据输入输出转换 ... 123

15.1.1. 输出时自动转换 ... 123

15.1.2. 输入时自动转换 ... 124

15.1.3. 通过转换规则输入输出函数手动转换 ... 124

15.2. 数量小位数格式化 ... 125

15.2.1. 案例 ... 126

15.3. 单位换算: UNIT_CONVERSION_SIMPLE . 128

15.4. 货币格式化 ... 129

15.4.1. 从表中读取日元并正确的格式化输出 ... 130

15.4.2. SAP 货币转换因子 ... 131

15.4.3. 货币内外格式转换 ... 133

16. 业务 ... 134

16.1. 表、业务流程 ... 134

16.2. MM .. 138

16.2.1. 常用表 ... 138

16.2.2. 库存 ... 139

16.2.3. 物料凭证 ... 139

16.3. SD .. 139

16.3.1. ... 139

16.3.2. 定价过程 ... 141

16.3.2.1. 条件技术七要素 ... 141

16.3.2.2. 条件表 V/03 V/04 V/05 . 142

16.3.2.3. 存取顺序 V/07 . 142

16.3.2.4. 条件类型 V/06 . 142

16.3.2.5. 定价过程 V/08 与确定 OVKK . 143

16.3.2.6. VK11 :价格主数据维护 ... 146

16.3.2.7. 定价计算: KONV . 147

16.3.2.7.1. 条件类型的计算公式 ... 147

16.3.2.8. 定价过程示例 ... 148

16.3.2.9. 销售订单中的定价示例 ... 148

16.3.2.10. 定价通信表 KOMK KOMP . 151

16.3.3. 销售相关的凭证类型、类型 ... 151

16.4. 业务概念 ... 154

16.4.1. 售达方、送达方、开票方、付款方 ... 154

16.4.2. 进项税、销项税 ... 154

16.4.3. 订单日期、凭证日期、过账日期 ... 155

16.5. 业务知识 ... 155

16.5.1. 客户联系人相关信息 ... 155

16.5.2. 销售订单合作伙伴功能 ... 156

17. 增强 ... 157

17.1. 第一代:基于源码增强(子过程 subroutine ... 157

17.2. 第二代:基于函数出口增强( Function ... 157

17.2.1. 示例:采购订单屏幕增强 ... 159

17.2.1.1. 定义全局变量 ... 161

17.2.1.2. 子屏幕 ... 161

17.2.1.3. 屏幕与业务表数据间传递 ... 162

17.2.1.4. 相关函数说明 ... 163

17.2.2. 如何快速找到增强 ... 163

17.3. 第三代:基于类的增强( BADI ... 165

17.3.1. 新式 BADI 创建 ... 166

17.3.1.1. 定义 ... 166

17.3.1.2. 实现 ... 168

17.3.1.3. 过滤器 ... 170

17.3.1.3.1. 调用 ... 171

17.3.1.4. 多个 BADI/ Enhancement 实现时究竟调谁 ... 172

17.3.2. 经典 BADI 创建 ... 173

17.3.2.1. Filter-Depend. 过滤器 ... 174

17.3.2.1.1. 调用 ... 175

17.3.2.2. 通过经典 BADI 扩展自定义程序(菜单、屏幕、功能) ... 176

17.3.3. 示例:通过 BADI 实现采购订单屏幕增强 ... 179

17.4. 第四代: Enhancement-Point . 179

17.4.1. 为自己程序创建显示增强 ... 180

17.4.2. 隐式与显示增强 ... 182

18. 数据批量维护 ... 182

18.1. BDC SM35 SHDB ... 182

18.2. LSMW .. 184

18.3. 业务对象和 BAPI 184

18.3.1. SAP 业务对象( SWO1 ... 184

18.3.1.1. 业务对象类型的组成 ... 185

18.3.1.2. 业务对象( BO )设计 ... 185

18.3.1.2.1. 创建业务表 ... 185

18.3.1.2.2. 创建业务对象类型 ... 186

18.3.1.2.3. 添加(继承)接口 ... 186

18.3.1.2.4. 添加关键字段 Key . 187

18.3.1.2.5. 添加属性 ... 187

18.3.1.2.6. 通过报表程序来实现业务对象的方法 ... 189

18.3.1.2.6.1. 报表程序 ... 189

18.3.1.2.6.2. 重定义接口与方法实现 ... 190

18.3.1.2.6.3. 测试 ... 191

18.3.1.2.7. 通过 BAPI 函数来实现业务对象方法 ... 192

18.3.1.2.7.1. 创建 BAPI 参数结构 ... 192

18.3.1.2.7.2. 创建 BAPI 函数、 BAPI 调用返回 RETURN 结果处理 ... 193

18.3.1.2.7.3. BAPI 函数绑定到相应的业务方法 ... 195

18.3.2. BAPI 197

18.3.2.1. BAPI 浏览器 ... 197

18.3.2.2. SE37 查找: BAPI 函数的命名规则 ... 198

18.3.2.3. 查找某事务码所对应的 BAPI 198

18.3.2.4. 常用 BAPI 函数 ... 199

18.3.2.5. 调用 BAPI 199

18.3.2.5.1. BAPI 事务处理 ... 200

18.3.2.5.2. 外部系统( Java )调用 BAPI 函数 ... 201

18.3.2.5.2.1. 直连、连接池 ... 201

18.3.2.5.2.2. 访问结构 ... 202

18.3.2.5.2.3. 访问表 (Table) 203

18.3.2.5.2.4. Java 多线程调用有 / 无状态 RFM .. 204

18.3.2.5.3. ABAP 访问 Java 服务 ... 204

18.3.2.5.4. ABAP 创建远程目标 ... 204

18.3.2.5.5. 连接异常 registrationnot allowed . 205

18.3.2.5.6. 带状态访问 ... 206

18.4. IDoc . 206

18.4.1. 数据段类型和数据段定义( WE31 ... 206

18.4.2. IDoc 定义( WE30 ... 207

18.4.3. 自定义 IDoc 发送与接收实例 ... 208

18.4.3.1. 发送端 800 outbound )配置 ... 208

1 、创建 segment WE31 ... 208

2 、创建 IDOC Type WE30 ... 209

3 、创建 Message Type WE81 ... 210

4 、关联 Message Type IDOC Type WE82 ... 210

5 、创建接收端 RFC Destination SM59 ... 210

6 、创建到收端的端口( WE21 ... 211

7 、创建发送端 Logical System 并分配( SALE ... 211

8 、创建接收端 Logical System SALE ... 212

9 、创建接收端合作和伴配置文件 Partner profile WE20 ... 212

10 、通过 ABAP 程序发送 IDOC . 213

18.4.3.2. 接收端 810 Inbound )配置 ... 216

1 、创建发送端 RFC Destination SM59 ... 216

2 、创建发送端的端口( WE21 ... 217

3 、将接收端 Logical System 分配到 Client 810 SALE ... 217

4 、创建入站处理函数 ... 218

5 、注册入站处理函数( BD51 ... 219

6 、将入站函数与 IDOC Type/Message Type 关联( WE57 ... 219

7 、创建入站处理代码 Inbound Process Code WE42 ... 219

8 、创建发送端合作和伴配置文件 Partner profile WE20 ... 219

9 、测试 BD87 . 220

19. 数据共享与传递 ... 222

19.1. 程序调用、会话、 SAP/ABAP 内存 关系 ... 222

19.2. ABAP Memory 数据共享 ... 224

19.2.1. EXPORT . 224

19.2.2. IMPORT . 226

19.2.3. DELETE . 227

19.3. SAP MEMORY 数据共享 ... 228

19.3.1. PARAMETERS/SELECT-OPTIONS 选项 MEMORY ID .. 228

19.3.2. GET/SET PARAMETER ID .. 228

19.4. DATABASE . 229

19.4.1. 将文件存入表中 ... 230

19.4.2. 从表中读取文件 ... 232

19.5. JOB 间数据传递 ... 233

20. 拾遗 ... 233

20.1. Function 调用 ... 233

20.1.1. 更新 FM LUW .. 233

20.1.2. RFC 函数:远程调用 ... 234

20.1.2.1. 同步 ... 234

20.1.2.2. 异步 ... 234

20.1.2.2.1. 事务性 RFC 调用 ... 234

20.1.2.3. DESTINATION 取值 ... 234

20.2. 函数、类 ... 235

20.3. FTP . 235

20.4. 文件读写 ... 235

20.5. Email 236

20.6. XML . 236

20.6.1. 生成 ... 237

20.6.2. 解析 ... 240

20.7. OLE . 242

20.7.1. 导出 Exel 文件多种方式 ... 243

20.8. ABAP 示例代码 ... 244

20.9. 长文本 ... 244

20.9.1. 物料长文本 ... 244

20.9.2. 生产定单长文本 ... 245

20.9.3. 采购定单长文本 ... 246

20.9.4. 销售定单长文本 ... 246

20.10. Smart Forms . 246

20.11. BOM .. 247

20.12. 传输请求 SE01 SE09 SE10 . 247

20.13. Script Form 传输: SCC1 . 247

20.14. 权限检查 ... 247

20.15. 允许对表数据维护 ... 248

20.16. SE93 创建事务码 ... 248

20.17. 表字段初始值、 NULL 等问题 ... 249

20.17.1. SE11 表设置中的 Initial Values . 249

20.17.2. 底层数据库表字段默认值 ... 249

20.17.3. ABAP 初始值、底层数据库表默认值相互转换 ... 250

20.17.3.1. 向表中插入初始值 ... 250

20.17.3.2. 读取数据 ... 251

20.17.4. SAP 系统中的表字段不允许为 NULL 的原因 ... 251

20.18. ABAP 中的 “空”、 INITIAL . 251

20.19. 调试工具 ... 252

20.19.1. ST05 . 252

20.20. 程序创建 Job (报表自已设置后台运行,前后台数据共享) ... 253

20.21. SE78 SWM0 . 254

20.22. 客户端文本文件或 Excel 文件上传与下载 ... 255

20.22.1. 读取客户端 Txt Excel 文件到内表: TEXT_CONVERT_XLS_TO_SAP . 255

20.22.2. 将数据内表导出为 EXCEL 文件: SAP_CONVERT_TO_XLS_FORMAT . 256

20.23. Unicode 字符串互转 ... 256

20.24. 字符编码与解码 ... 256

20.25. ABAP 中的特殊字符列表 ... 257

20.26. 下载文件 ... 257

20.26.1. BIN 二进制下载 ... 257

20.26.2. 以字符模式下载 ... 258

20.27. 将文件上传到数据库表中,并可邮件发送 ... 259

20.28. Append Include 系统表结构增强 ... 261

20.29. 结构复用( INCLUDE ... 262

20.30. 常用事务码 ... 263

21. 常用 Function . 265

21.1. 日期函数 ... 265

21.1.1. 日期、时间验证 ... 265

21.1.2. 内部转换外部格式 ... 265

21.1.3. 外部转内部格式 ... 266

21.1.4. 获取 Client 格式 ... 267

21.1.5. 日期加减 ... 267

21.1.6. 转成工厂日期 ... 267

21.1.7. 日期属性 ... 269

21.1.8. 节假日 ... 270

21.1.9. 年月选择框 ... 271

21.1.10. 财政年 ... 271

21.1.11. 星期翻译对照表 ... 271

21.1.12. 日期所在周末、天 / 周、周 / ... 272

十六进制字符 0-9, A-F 具体的范围为: 00~FF

类型 X 是十六进制类型,可表示内存字节实际内容,使用两个十六制字符表示一个字节中所存储的内容。但直接打印输出时,输出的还是赋值时字面意义上的值,而不是 Unicode 解码后的字符

如果未在 DATA 语句中指定参数 <length> ,则创建长度为 1

注: 如果值是字母,则一定要 大写

1.1.1. P 类型 ( 压缩型 ) 数据

是一种压缩的定点数,其数据对象占据内存字节数和数值范围取定义时指定的整个数据大小和小数点后位数,如果不指定小数位,则将视为 I 类型。其有效数字位大小可以是从 1~31 位数字(小数点与正负号占用一个位置,半个字节), 小数点后最多允许 14 个数字

P 类型的数据,可 用于精确运算(这里的精确指的是存储中所存储的数据与定义时字面上所看到的大小相同,而不存在精度丢失问题——看到的就是内存中实实在在的大小) 。在使用 P 类型时,要先选择程序属性中的选项 Fixed point arithmetic (即定点算法,一般默认选中),否则系统将 P 类型看用整型。其效率低于 I F 类型。

"16 * 2 = 32 表示了整个字面意义上允许的最大字面个数,而 14 表示的是字面上小数点后面允许的最大小数位,而不是指 14 个字节,只有这里定义时的 16 才表示 16 个字节

DATA : p ( 16 ) TYPE p DECIMALS 14 VALUE '12345678901234567.89012345678901' .

" 正负符号与小数点固定要占用半个字节,一个字面上位置,并包括在这 16 个字节里面。
"16 * 2 = 32 位包括了小数点与在正负号在内
" 在定义时字面上允许最长可以达到 32 位,除去小数点与符号需占半个字节以后
" 有效数字位可允许 31 位,这 31 位中包括了整数位与小数位,再除去定义时小
" 数位为 14 位外,整数位最多还可达到 17 位,所以下面最多只能是 17 9
DATA : p1 ( 16 ) TYPE p DECIMALS 14 VALUE '-99999999999999999' .

"P 类型是以字符串来表示一个数的,与字符串不一样的是, P 类型中的每个数字位只会占用 4Bit 位,所以两个数字位才会占用一个字节 。另外, 如果定义时没有指定小数位,表示是整型,但小数点固定要占用半个字节,所以不带小数位与符号的最大与最小整数如下(最多允许 31 9 ,而不是 32 个)
DATA : p1( 16 ) TYPE p VALUE '+9999999999999999999999999999999' .
DATA : p2( 16 ) TYPE p VALUE '-9999999999999999999999999999999' .

其实 P 类型是以字符串形式来表示一个小数,这样才可以作到精确 ,就像 Java 中要表示一个精确的小数要使用 BigDecimal 一样,否则会丢失精度。

DATA : p ( 9 ) TYPE p DECIMALS 2 VALUE '-123456789012345.12' .
WRITE : / p . "123456789012345.12-

DATA : f1 TYPE f VALUE '2.0' ,
f2
TYPE f VALUE '1.1' ,
f3
TYPE f .
f3
= f1 - f2 . " 不能精确计算
"2.0000000000000000E+00 1.1000000000000001E+00 8.9999999999999991E-01
WRITE : / f1 , f2 , f3 .

DATA : p1 TYPE p DECIMALS 1 VALUE '2.0' ,
p2
TYPE p DECIMALS 1 VALUE '1.1' ,
p3
TYPE p DECIMALS 1 .
p3
= p1 - p2 . " 能精确计算
WRITE : / p1 , p2 , p3 . "2.0               1.1               0.9

Java 中精确计算:

publicstaticvoid main(String[] args) {

System.out.println(2.0 - 1.1);// 0.8999999999999999

System. out .println( sub (2.0, 0.1)); // 1.9

publicstaticdouble sub( double v1, double v2) {

BigDecimal b1 = new BigDecimal(Double. toString (v1));

BigDecimal b2 = new BigDecimal(Double. toString (v2));

return b1.subtract(b2).doubleValue();

1.2. TYPE LIKE

透明表(还有其它数据词典中的类型,如结构)即可看作是一种类型,也可看作是对象,所以即可使用 TYPE ,也可以使用 LIKE

TYPES type6 TYPE mara-matnr.
TYPES type7 LIKE mara-matnr.
DATA obj6 TYPE mara-matnr.
DATA obj7 LIKE mara-matnr.

"SFLIGHT 为表类型
DATA plane LIKE sflight-planetype.
DATA plane2 TYPE sflight-planetype.
DATA plane3 LIKE sflight.
DATA plane4 TYPE sflight.
"syst 为结构类型
DATA sy1 TYPE syst.
DATA sy2 LIKE syst.
DATA sy3 TYPE syst- index .
DATA sy4 LIKE syst- index .

注:定义的 变量名千万别与词典中的类型相同,否则表面上即可使用 TYPE 也可使用 LIKE ,就会出现这两个关键字( Type Like )都可用的奇怪现像 下面是定义一个变量时与词典中的结构同名的后果(导致)

DATA : BEGIN OF address2,
street(
20 ) TYPE c ,
city(
20 ) TYPE c ,
END OF address2.
DATA obj4 TYPE STANDARD TABLE OF address2. " 这里使用的实质上是词典中的类型 address2
DATA obj5 LIKE STANDARD TABLE OF address2. " 这里使用是的上面定义的变量 address2

上面程序编译通过,按理 obj4 定义是通过不过的(只能使用 LIKE 来引用另一定义变量的类型, TYPE 是不可以的),但由于 address2 是数字词典中定义的结构类型,所以 obj4 使用的是数字词典中的结构类型,而 obj5 使用的是 LIKE ,所以使用的是 address2 变量的类型

1.3. DESCRIBE

DESCRIBE FIELD dobj
[
TYPE typ [ COMPONENTS com]]
[
LENGTH ilen IN { BYTE | CHARACTER } MODE ]
[
DECIMALS dec]
[
OUTPUT-LENGTH olen]
[
HELP-ID hlp]
[
EDIT MASK mask] .


DESCRIBE TABLE itab [ KIND knd] [ LINES lin] [ OCCURS n] .

1.4. 字符串表达式

可以使用 & && 将多个字符模板串链接起来,可以突破 255 个字符的限制,下面两个是等效的:

|...| &  |...|

|...| && |...|

如果内容只有字面常量文本(没有变量表达式或控制字符 \r \n \t ),则不需要使用字符模板,可这样(如果包含了这些控制字符时,会原样输出,所以有这些控制字符时,请使用 |...| 将字符包起来):

`...` && `...`

但是上面 3 个与下面 3 个是不一样的:

`...` &  `...`

'...' &  '...'

'...' && '...'

上面前两个还是会受 255 个字符长度限制,最后一个虽然不受 255 限制,但尾部空格会被忽略

字面常量文本 literal text )部分,使用 || 括起来,不能含有控制字符(如 \r \n \t 这些控制字符),特殊字符 |{ } \ 需要使用 \ 进行转义:

txt = |Characters \| , \{ , and \} have to be escaped by \\ in literal text.| .

字符串表达式

str = |{ ( 1 + 1 ) * 2 }| . " 算术计算表达式
str = |{ |aa| && 'bb' }| . " 字符串表达式

str = |{ str }| . " 变量名

str = |{ strlen ( str ) }| . " 内置函数

1.5. Data element Domain

数据元素是构成结构、表的基本组件 域又定义了数据元素的技术属性 Data element 主要附带 Search Help Parameter ID 、以及标签描述,而类型是由 Domain 域来决定的。 Domain 主要从技术方面描述了 Data element ,如 Data Type 数据类型、 Output Length 输出长度、 Convers. Routine 转换规则、以及 Value Range 取值范围

将技术信息从 Data element 提取出来为 Domain 域的好处:技术信息形成的 Domain 可以共用,而每个表字段的业务含意不一样,会导致其描述标签、搜索帮助不一样,所以牵涉到业务部分的信息直接 Data element 中进行描述,而与业务无关的技术信息部分则分离出来形成 Domain

1.6. 词典预定义类型与 ABAP 类型映射

当你在 ABAP 程序中引用了 A B A P Di c t io n a r y 则预置 D i c t i o na r y 类型则会转换为相应的 ABAP 类型 ,预置的 Di c t io n a r y 类型 转换规则表如下

这里的“允许最大长度 m ”表示的是字面上允许的字符位数,而不是指底层所占内存字节数,如

int1 的取值为 0~255 ,所以是 3 位(不包括符号位)

int2 的取值为 -32768~32767 ,所以是 5

l LCHR and LRAW 类型允许的最大值为 INT2 最大值

l RAWSTRING and STRING 具有可变长度,最大值可以指定,但没有上限

l SSTRING 长度是可变的,其最大值必须指定且上限为 255 。与 CHAR 类型相比其优势是它与 ABAP type string 进行映射。

这些预置的 Di c t io n a r y 类型在创建 Data element Domain 时可以引用

Unicode 系统中,一个字符占两个字节

1.7. 字符串处理

SPLIT dobj AT sep INTO { {result1 result2 ... } | { TABLE result_tab} } 必须指定足够目标字段。否则,用字段 dobj 的剩余部分填充最后目标字段并包含分界符;或者使用内表动态接收

SHIFT dobj { [ { BY num PLACES }|{ UP TO sub_string} ][ [ LEFT | RIGHT ][ CIRCULAR ] ] }
| {
{LEFT DELETING LEADING }|{ RIGHT DELETING TRAILING } } pattern

对于固定长度字符串类型, shift 产生的空位会使用空格或十六进制的 0 (如果为 X 类型串时)来填充

向右移动时前面会补空格,固定长度类型字符串与 String 结果是不一样: String 类型右移后不会被截断,只是字串前面补相应数量的空格,但如果是 C 类型时,则会截断;左移后后面是否被空格要看是否是固定长度类型的字符串还是变长的 String 类型串,左移后 C 类型会补空格, String 类型串不会(会缩短)

CIRCULAR :将移出的字符串放在左边或者左边

pattern :只要前导或尾部字符在指定的 pattern 字符集里就会被去掉,直到第一个不在模式 pattern 的字符止

CONDENSE <c> [NO - GAPS] . 如果是 C 类型只去掉前面的空格(因为是定长,即使后面空格去掉了,左对齐时后面会补上空格),如果是 String 类型,则后面空格也会被去掉; 字符串中间的多个连续的空格使用一个空格替换 (String 类型也是这样 ) NO-GAPS :字符串中间的所有空格都也都会去除 (String 类型也是这样 ) ;空格去掉后会左对齐 [kənˈdens]

CONCATENATE {dobj1 dobj2 ... }|{ LINES OF itab} [kənˈkatɪneɪt]
INTO result
[
SEPARATED BY sep]
[
RESPECTING BLANKS ] .

CDNT 类型的前导空格会保留,尾部空格都会被去掉,但对 String 类型所有空格都会保留;对于 c, d, n, t 类型的字符串有一个 RESPECTING BLANKS 选项可使用,表示尾部空格也会保留。注: 使用 `` String 类型进行赋值时才会保留尾部空格 字符串连接可以使用 && 来操作,具体请参考这里

strlen ( arg ) Xstrlen( arg ) String 类型的尾部空格会计入字符个数中,但 C 类型的变量尾部空格不会计算入

substring ( val = TEXT [off = off] [len = len] )

count ( val = TEXT {sub = substring}|{regex = regex} ) 匹配指定字符串 substring 或正则式 regex 出现的子串次数,返回的类型为 i 整型类型

contains ( val = TEXT REGEX = REGEX ) 是否包含。返回布尔值 ,注:只能用在 if While 等条件表达式中

matches ( val = TEXT REGEX = REGEX ) regex 表达式要与 text 完全匹配 ,这与 contains 是不一样的。返回布尔值,也只能用在 if While 等条件表达式中

match ( val = TEXT REGEX = REGEX occ = occ ) 返回的为匹配到的字符串。注:每次只匹配一个。 occ :表示需匹配到第几次出现的子串。如果为正,则从头往后开始计算,如果为负,则从尾部向前计算

find ( val = TEXT {sub = substring}|{regex = regex}[occ = occ] ) 查找 substring 或者匹配 regex 的子串的位置。如果未找到,则返回 -1 ,返回的为 offset ,所以从 0 开始

FIND ALL OCCURRENCES OF REGEX regex IN dobj
[
MATCH COUNT mcnt] 成功匹配的次数
{ { [ MATCH OFFSET moff][ MATCH LENGTH mlen] } 最后一次整体匹配到的串(整体串,最外层分组,而不是指正则式最内最后一个分组)起始位置 与长度
| [ RESULTS result_tab|result_wa] }
result_tab 接收所有匹配结果, result_wa 只能接收最后一次匹配结果
[ SUBMATCHES s1 s2 ... ] . 通常与前面的 MATCH OFFSET/ LENGTH 一起使用。只会接收使用括号进行分组的子组。如果变量 s1 s2 ... 比分组的数量多,则多余的变量被 initial ;如果变量 s1 s2 ... 比分组的数量少,则多余的分组将被忽略;且只存储第一次或最后一次匹配到的结果

replace ( val = TEXT REGEX = REGEX WITH = NEW ) 使用 new 替换指定的子符串,返回 String 类型

REPLACE ALL OCCURRENCES OF REGEX regex IN dobj WITH new

1.7.1. count match 结合

DATA : text TYPE string VALUE `Cathy's cat with the hat sat on Matt's mat.` ,
regx
TYPE string VALUE `\<.at\>` . "\< 单词开头 \> 单词结尾
DATA : counts TYPE i ,
index TYPE i ,
substr
TYPE string .
WRITE / text .
NEW-LINE .
counts
= count ( val = text regex = regx ). " 返回匹配次数
DO counts TIMES .
index = find ( val = text regex = regx occ = sy - index ). " 返回匹配到的的起始位置索引
substr
= match ( val = text regex = regx occ = sy - index ). " 返回匹配到的串
index = index + 1 .
WRITE AT index substr .
ENDDO .

1.7.2. FIND …SUBMATCHES

DATA : moff TYPE i ,
mlen
TYPE i ,
s1
TYPE string ,
s2
TYPE string ,
s3
TYPE string ,
s4
TYPE string .
FIND ALL OCCURRENCES OF REGEX `((\w+)\W+\2\W+(\w+)\W+\3)` "\2 \3 表示反向引用前面匹配到的第二与第三个子串
IN ` Hey hey, my my , Rock and roll can never die Hey hey, my my ` " 会匹配二次,但只会返回第二次匹配到的结果,第一次匹配到的子串不会存储到 s1 s2 s3 中去
IGNORING CASE
MATCH OFFSET moff
MATCH LENGTH mlen
SUBMATCHES s1 s2 s3 s4 . " 根据从外到内,从左到右的括号顺序依次存储到 s1 s2… 中,注:只取出使用括号括起来的子串,如想取整体子串则也要括起来,这与 Java 不同
WRITE : /  s1 , / s2 , / s3 , / s4 , / moff , / mlen . "s4 会被忽略

1.7.3. FIND …RESULTS  itab

DATA : result TYPE STANDARD TABLE OF string WITH HEADER LINE .
" Java 不同,只要是括号括起来的都称为子匹配(即使用整体也用括号括起来了),
" 不管括号嵌套多少层,统称为子匹配,且匹配到的所有子串都会存储到,
"MATCH_RESULT- SUBMATCHES 中,即使最外层的括号匹配到的子串也会存储到 SUBMATCHES
" 内表中。括号解析的顺序为: 从外到内,从左到右 的优先级顺序来解析匹配结构。
"Java 中的 group(0) 存储的是整体匹配串,即使整体未(或使用)使用括号括起来
PERFORM get_match TABLES result
USING '2011092131221032' '(((\d{2})(\d{2}))(\d{2})(\d{2}))' .
LOOP AT result .
WRITE : / result .
ENDLOOP .
FORM get_match TABLES p_result " 返回所有分组匹配 (括号括起来的表达式)
USING p_str
p_reg
.
DATA : result_tab TYPE match_result_tab WITH HEADER LINE .
DATA : subresult_tab TYPE submatch_result_tab WITH HEADER LINE .
" 注意:带表头时 result_tab 后面一定要带上中括号,否则激活时出现奇怪的问题
FIND ALL OCCURRENCES OF REGEX p_reg IN p_str
RESULTS result_tab[] .
" result_tab 中存储了匹配到的子串本身 (与 Regex 整体匹配的串,存储在
"result_tab-offset result_tab-length 中) 以及所子分组 (括号部分,存储在
"result_tab-submatches 中)
LOOP AT result_tab .
" 如需取整体匹配到的子串(与 Regex 整体匹配的串),则使用括号将整体 Regex 括起来
" 来即可,括起来后也会自动存储到 result_tab-submatches ,而不需要在这里像这样读取
*    p_result = p_str+result_tab-offset(result_tab-length).
*    APPEND p_result.
subresult_tab[]
= result_tab - submatches .
LOOP AT subresult_tab .
p_result
= p_str+subresult_tab - offset ( subresult_tab - length ).
APPEND p_result .
ENDLOOP .
ENDLOOP .
ENDFORM .

1.7.4. 正则式类

regex = Regular expression [ˈreɡjulə]

cl_abap_ regex Java 中的 java.util.regex. Pattern 的类对应

cl_abap_matcher Java 中的 java.util.regex. Matcher 的类对应

1.7.4.1. matches match

是否完全匹配 正则式中不必使用 ^ $ ); matches 为静态方法 match 为实例方法 作用都是一样

DATA : matcher TYPE REF TO cl_abap_matcher ,
match
TYPE match_result ,
match_line
TYPE submatch_result .
" ^$ 可以省略 因为 matches 方法本身就是完全匹配整个 Regex
IF cl_abap_matcher => matches ( pattern = '^(db(ai).*)$' text = 'dbaiabd' ) = 'X' .
matcher
= cl_abap_matcher => get_object ( ). " 获取最后一次匹配到的 Matcher 实例
match
= matcher -> get_match ( ). " 获取最近一次匹配的结果 ( 是整体匹配的结果 )
WRITE / matcher -> text+match - offset ( match - length ).
LOOP AT match - submatches INTO match_line . " 提取子分组 括号括起来的部分
WRITE : /20 match_line - offset , match_line - length , matcher -> text+match_line - offset ( match_line - length ).
ENDLOOP .
ENDIF .

DATA : matcher TYPE REF TO cl_abap_matcher ,
match
TYPE match_result ,
match_line
TYPE submatch_result .
"^$ 可以省略,因为 matche 方法本身就是完全匹配整个 Regex
matcher
= cl_abap_matcher => create ( pattern = '^(db(ai).*)$' text = 'dbaiabd' ).
IF matcher ->
match (  ) = 'X' .
match
= matcher -> get_match ( ). " 获取最近一次匹配的结果
WRITE / matcher -> text+match - offset ( match - length ).
LOOP AT match - submatches INTO match_line . " 提取子分组(括号括起来的部分)
WRITE : /20 match_line - offset , match_line - length , matcher -> text+match_line - offset ( match_line - length ).
ENDLOOP .
ENDIF .

1.7.4.2. contains

是否包含 也可在正则式中使用 ^ $ 用于完全匹配检查 或者使用 ^ 检查是否匹配开头 或者使用 $ 匹配结尾

DATA : matcher TYPE REF TO cl_abap_matcher ,
match
TYPE match_result ,
match_line TYPE submatch_result .
IF cl_abap_matcher => contains ( pattern = '(db(ai).{2}b)' text = 'dbaiabddbaiabb' ) = 'X' .
matcher
= cl_abap_matcher => get_object ( ). " 获取最后一次匹配到的 Matcher 实例
match
= matcher -> get_match ( ). " 获取最近一次匹配的结果
WRITE / matcher -> text+match - offset ( match - length ).
LOOP AT match - submatches INTO match_line . " 提取子分组(括号括起来的部分)
WRITE : /20 match_line - offset , match_line - length , matcher -> text+match_line - offset ( match_line - length ).
ENDLOOP .
ENDIF .

1.7.4.3. find_all

一次性找出所有匹配的子串,包括子分组(括号括起的部分)

DATA : matcher TYPE REF TO cl_abap_matcher ,
match_line
TYPE submatch_result ,
itab
TYPE match_result_tab WITH HEADER LINE .
matcher = cl_abap_matcher => create ( pattern = '<[^<>]*(ml)>' text = '<html>hello</html>' ). " 创建 matcher 实例
" :子分组存储在 itab-submatches 字段里
itab [] = matcher -> find_all ( ).
LOOP AT itab .
WRITE : / matcher -> text , itab - offset , itab - length , matcher -> text+itab - offset ( itab - length ).
LOOP AT itab -
submatches INTO match_line . " 提取子分组(括号括起来的部分)
WRITE : /20 match_line - offset , match_line - length , matcher -> text+match_line - offset ( match_line - length ).
ENDLOOP .
ENDLOOP .

1.7.4.4. find_next

逐个找出匹配的子串,包括子分组(括号括起的部分)

DATA : matcher TYPE REF TO cl_abap_matcher ,
match
TYPE match_result ,
match_line TYPE submatch_result ,
itab
TYPE match_result_tab WITH HEADER LINE .
matcher
= cl_abap_matcher => create ( pattern = '<[^<>]*(ml)>' text = '<html>hello</html>' ).
WHILE matcher -> find_next ( ) = 'X' .
match
= matcher -> get_match ( ).
" 获取最近一次匹配的结果
WRITE : / matcher -> text , match - offset , match - length , matcher -> text+match - offset ( match - length ).
LOOP AT match - submatches INTO match_line . " 提取子分组(括号括起来的部分)
WRITE : /20 match_line - offset , match_line - length , matcher -> text+match_line - offset ( match_line - length ).
ENDLOOP .
ENDWHILE .

1.7.4.5. get_length get_offset get_submatch

DATA : matcher TYPE REF TO cl_abap_matcher ,
length
TYPE i , offset TYPE i ,
submatch
TYPE string .
matcher
= cl_abap_matcher => create ( pattern = '(<[^<>]*(ml)>)' text = '<html>hello</html>' ).
WHILE matcher -> find_next ( ) = 'X' . " 循环 2
" 0 时,表示取整个 Regex 匹配到的子串,这与 Java 一样,但如果整个 Regex 使用括号括起来后,
" 则分组索引为 1 ,这又与 Java 不一样( Java 不管是否使用括号将整个 Regex 括起来,分组索引号都为 0
"
上面 Regex 中共有两个子分组,再加上整个 Regex 为隐含分组,所以一共为 3
DO 3 TIMES .
" 在当前匹配到的串(整个 Regex 相匹配的串)中返回指定子分组的匹配到的字符串长度
length
= matcher ->
get_length ( sy - index - 1 ).
" 在当前匹配到的串(整个 Regex 相匹配的串)中返回指定子分组的匹配到的字符串起始位置
offset
= matcher ->
get_offset ( sy - index - 1 ).
" 在当前匹配到的串(整个 Regex 相匹配的串)中返回指定子分组的匹配到的字符串
submatch
= matcher ->
get_submatch ( sy - index - 1 ).
WRITE : / length , offset , matcher -> text+offset ( length ), submatch .
ENDDO .
SKIP .
ENDWHILE .

1.7.4.6. replace_all

DATA : matcher TYPE REF TO cl_abap_matcher ,
count
TYPE i ,
repstr
TYPE string .
matcher
= cl_abap_matcher => create ( pattern = '<[^<>]*>' text = '<html>hello</html>' ).
count
= matcher ->
replace_all ( `` ). " 返回替换的次数
repstr
= matcher -> text . " 获取被替换后的新串
WRITE : / count , repstr .

1.8. CLEAR REFRESH FREE

内表 :如果使用有表头行的内表, CLEAR 仅清除表格工作区域 。要重置整个内表而不清除表格工作区域,使用 REFRESH 语句或 CLEAR 语句 CLEAR <itab>[] . REFRESH 加不加中括号都是只清内表,另外 REFRESH 是专为清内表的,不能清基本类型变量,但 CLEAR 可以

以上都不会释放掉内表所占用的空间,如果想初始化内表的同时还要 释放所占用的空间 ,请使用: FREE <itab> .

1.9. ABAP 程序中的局部与全局变量

报表程序中 选择屏幕 事件块( AT SELECTION-SCREEN )与 逻辑数据库 事件块、以及 methods (类中的方法)、 subroutines FORM 子过程)、 function modules Function 函数)中声明的变量 为局部 的,即在这些块里声明的变量不能在其他块里使用,但这些局部变量可以覆盖同名的全局变量;除这些处理块外,其他块里声明的变量都属于全局的(如 报表事件块 列表事件 块、 对话 Module ),效果与在程序最开头定义的变量效果是一样的,所以可以在其他处理块直接使用(但要注意的是,需遵守先定义后使用的原则,这种先后关系是从语句书写顺序来说的,与事件块的本身运行顺序没有关系);另外, 局部变量声明时,不管在处理块的任何地方,其效果都是相当于处理块里的全局变量,而不像其他语言如 Java 那样 :局部变量的作用域可以存在于任何花括号 {} 之间(这就意味着局部变量在处理过程范围内是全局的),如下面的 i ,在 ABAP 语言中还是会累加输出,而不会永远是 1 (在 Java 语言中会是 1 ):

FORM aa .
DO 10 TIMES .
DATA : i TYPE i VALUE 0 .
i = i + 1 .
WRITE : / i .
ENDDO .
ENDFORM .

1.10. Form Function

Form Function 中的 TABLES 参数, TYPE LIKE 后面只能接 标准内表 类型或标准内表对象,如果要使用排序内表或者哈希内表,则只能使用 USING Form )与 CHANGING 方式来代替。当把一个带表头的实参通过 TABLES 参数传递时,表头也会传递过去,如果实参不带表头或者只传递了表体(使用了 [] 时),系统会自动为内表参数变量创建一个局部空的表头

不管是以 TABLES 还是以 USING Form 非值 CHANGE 非值 方式传递时,都是以 引用方式 (即 别名 ,不是指地址,注意与 Java 中的传引用区别: Java 实为传值,但传递的值为地址的值,而 ABAP 中传递的是否为地址,则要看实参是否是通过 Type ref to 定义的)传递;但如果 USING 值传递 ,则 对形参数的修改不会改变实参,因为此时不是引用传递;但如果 CHANGE 值传递 ,对形参数的修改还是会改变实参,只是修改的时机在 Form 执行或 Function 执行完后,才去修改

Form 中通过 引用传递时, USING CHANGING 完全一样 ;但 CHANGING 为值传递方式时,需要在 Form 执行完后,才去真正修改实参变量的内容,所以 CHANGING 传值与传引用其结果都是一样:结果都修改了实参内容,只是修改的时机不太一样而已

1.10.1. FORM

FORM subr [ TABLES t1 [{ TYPE itab_type}|{ LIKE itab}|{ STRUCTURE struc}]
t2 […] ]

[ USING { VALUE ( p1 ) |p1 } [ { TYPE generic_type }

| { LIKE <generic_fs>|generic_para }
| {
TYPE {[ LINE OF ] complete_type}|{ REF TO type} }
| {
LIKE {[ LINE OF ] dobj} | { REF TO dobj} }
|
STRUCTURE struc]

{ VALUE ( p2 ) |p2 } […] ]

[ CHANGING { VALUE ( p1 ) |p1 } [ { TYPE generic_type }

| { LIKE <generic_fs>|generic_para }

| { TYPE {[ LINE OF ] complete_type} | { REF TO type} }
| {
LIKE {[ LINE OF ] dobj} | { REF TO dobj} }
|
STRUCTURE struc]

{ VALUE (p2)|p2 } […] ]

[ RAISING {exc1| RESUMABLE ( exc1 )} { exc2| RESUMABLE ( exc2 )} ... ] .

generic _type :为通用类型

complete_type :为完全限制类型

<generic_fs> :为字段符号变量类型,如下面的 fs 形式参数

generic_para :为另一个形式参数类型,如下面的 b 形式参数

DATA : d ( 10 ) VALUE '11' .
FIELD-SYMBOLS : <fs> LIKE d .
ASSIGN d TO <fs> .
PERFORM aa USING <fs> d d .
FORM aa USING fs like <fs> a like d b like a .
WRITE : fs , / a , / b .
ENDFORM .

如果没有给形式参数指定类,则为 ANY 类型

如果 TABLES USING CHANGING 一起使用时,则 一定要 按照 TABLES USING CHANGING 顺序声明

值传递中的 VALUE 关键字只是在 FORM 定义时出现,在调用时 PERFORM 语句中无需出现 ,也就是说,调用时值传递和引用传递不存在语法格式差别

DATA : i TYPE i VALUE 100 .
WRITE : / 'frm_ref====' .
PERFORM frm_ref USING i .
WRITE : / i . "200

WRITE : / 'frm_val====' .
i = 100 .
PERFORM frm_val USING i .
WRITE : / i . "100

WRITE : / 'frm_ref2====' .

" 不能将下面的变量定义到 frm_ref2 过程中,如果这样,下面的 dref 指针在调用 frm_ref2 后, 指向的是 Form 中局部变量内存,为不安全发布 ,运行会抛异常,因为 From 结束后,它所拥有的所有变量内存空间会释放掉
DATA : i_frm_ref2 TYPE i VALUE 400 .
i = 100 .
DATA : dref TYPE REF TO i .
get REFERENCE OF i INTO dref.
PERFORM frm_ref2 USING dref . " 传递的内容为地址,属于别名引用传递
WRITE : / i . "4000

field - SYMBOLS : <fs> TYPE i .
ASSIGN dref->* to <fs>. " 由于 frm_ref2 过程中已修改了 dref 的指向,现指向了 i_frm_ref2 变量的内存空间
WRITE : / <fs>. "400

WRITE : / 'frm_val2====' .
i = 100 .
DATA : dref2 TYPE REF TO i .
get REFERENCE OF i INTO dref2.
PERFORM frm_val2 USING dref2 .
WRITE : / i . "4000
ASSIGN dref2->* to <fs>.
WRITE : / <fs>. "4000

FORM frm_ref USING p_i TYPE i . " C++ 中的引用参数传递 p_i 为实参 i 的别名
WRITE : /  p_i. "100
p_i =
200 . "p_i 为参数 i 的别名,所以可以直接修改实参
ENDFORM .

FORM frm_val USING value (p_i). " 传值 p_i 为实参 i 的拷贝
WRITE : /  p_i. "100
p_i =
300 . " 由于是传值,所以不会修改主调程序中的实参的值
ENDFORM .
FORM frm_ref2 USING p_i TYPE REF TO i . "p_i 为实参 dref 的别名, 类似 C++ 中的引用参数传递 (传递的内容为地址,并且属于别名引用传递)
field - SYMBOLS : <fs> TYPE i .
" 现在 <fs> 就是实参所指向的内存内容的别名,代表实参所指向的实际内容
ASSIGN p_i->* to <fs>.
WRITE : /  <fs>. "100
<fs> =
4000 . " 直接修改实参所指向的实际内存


DATA : dref TYPE REF TO i .
get REFERENCE OF i_frm_ref2 INTO dref.
" 由于 USING C++ 的引用参数 ,所以这里修改的直接是实参所存储的地址内容,这里的 p_i 为传进来的 dref 的别名,是同一个变量,所以实参的指向也发生了改变 ( 这与 Java 中传递引用是不一样的, Java 中传递引用时为地址的拷贝,即 Java 中永远也只有传值,但 C/C++/ABAP 中可以传递真正引用——别名)
p_i = dref.
" 此处会修改实参的指向
ENDFORM .

FORM frm_val2 USING VALUE (p_i) TYPE REF TO i . "p_i 为实参 dref2 的拷贝, 类似 Java 中的引用传递 (虽然传递的内容为地址,但传递的方式属于地址拷贝——值传递)
field -SYMBOLS : <fs> TYPE i .
" 现在 <fs> 就是实参所指向的内存内容的别名,代表实参所指向的实际内容
ASSIGN p_i->* to <fs>.
WRITE : /  <fs>. "100
<fs> =
4000 . " 但这里还是可以直接修改实参所指向的实际内容


DATA : dref TYPE REF TO i .
get REFERENCE OF i_frm_ref2 INTO dref.
" 这里与过程 frm_ref2 不一样,该过程 frm_val2 参数的传递方式与 java 中的引用传递是原理是一样的:传递的是地址拷贝,所以下面不会修改主调程序中实参 dref2 的指向,它所改变的只是拷贝过来的 Form 中局部形式参数的指向
p_i = dref.
ENDFORM .

1.10.2. FUNCTION

1.10.2.1. Function Group 结构

当使用 Function Builder 创建函数组时,系统会自动创建 main program 与相应的 include 程序:

l <fgrp> Function Group 的名称

l SAPL<fgrp> 为主程序名,它将 Function Group 里的所有 Include 文件包括进来,除了 INCLUDE 语句之外,没有其他语句了

l L<fgrp>TOP ,里面有 FUNCTION-POOL 语句,以及所有 Function Module 都可以使用的全局数据定义

l L<fgrp>UXX ,也只有 INCLUDE 语句,它所包括的 Include 文件为相应具体 Function Module 所对应 Include 文件名: L<fgrp>U01 L<fgrp>U02 ... 这些 Include 文件实际上包含了所对应的 Function Module 代码(即双击它们进去就是对应的 Function ,而显示的不是真正 Include 文件所对应的代码)

l L<fgrp>U01 L<fgrp>U02 中的 01 02 编号对应 L<fgrp>UXX 中的“ XX ”,代表其创建先后的序号,例如 L<fgrp>U01 L<fgrp>U02 是头两个被创建的函数,在函数组中创建出的函数代码就放在相应的 L<fgrp>UXX (这里的 XX 代表某个数字,而不是字面上的 XX Include 头文件中

l L<fgrg>FXX ,用来存一些 Form 子过程,并且可以 被所有 Function Modules 所使用(不是针对某个 Function Module 的,但一般在设计时会针对每个 Function Module 设计这样单独的 Include 文件,这是一个好习惯),并且在使用时不需要在 Function Module 中使用 INCLUDE 语句包含它们(因为这些文件在主程序 SAPL<fgrp> 里就已经被 Include 进来了)。另外, L<fgrg>FXX 中的 F 是指 Form 的意思,这是一种名称约束而已,在创建时我们可以随便指定,一般还有 IXX (表示些类 Include 文件包括的是一些 PAI 事件中调用的 Module ,有时干脆直接使用 L<fgrg>PAI 或者 L<fgrg> PAIXX ), OXX (表示些类 Include 文件包括的是一些 PBO 事件中调用的 Module ,有时干脆直接使用 L<fgrg>PBO 或者 L<fgrg> PBOXX )。注:如果 Form 只被某一函数单独使用,实质上还可直接将这些 Form 定义在 Function Module 里的 ENDFUNCTION 语句后面

当你调用一个 function module 时,系统加将整个 function group (包括 Function Module Include 文件等)加载到主调程序所在的 internal session 中,然后该 Function Module 得到执行,该 Function Group 一直保留在内存中,直到 internal session 结束。 Function Group 中的所定义的 Include 文件中的变量是全局,被所有 Function Module 共享,所以 Function Group 好比 Java 中的类,而 Function Module 则好比类中的方法,所以 Function Group 中的 Include 文件中定义的东西是全局型的,能被所有 Function Module 所共享使用

function fuc_ref .
*" -------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*" REFERENCE (I_I1) TYPE  I REFERENCE 别名 为参数的默认传递类型
*" VALUE (I_I2) TYPE  I 定义时勾选了 Pass Value 选项才会是 VALUE 类型
*"     REFERENCE(I_I3) TYPE REF TO I
*"     VALUE(I_I4) TYPE REF TO  I
*"  EXPORTING
*"     REFERENCE(E_I1) TYPE  I
*"     VALUE(E_I2) TYPE  I
*"     REFERENCE(E_I3) TYPE REF TO  I
*"     VALUE(E_I4) TYPE REF TO  I
*"  TABLES
*"      T_1 TYPE  ZJZJ_ITAB
*"  CHANGING
*"     REFERENCE(C_I1) TYPE  I
*"     VALUE(C_I2) TYPE  I
*"     REFERENCE(C_I3) TYPE REF TO  I
*"     VALUE(C_I4) TYPE REF TO  I
*"-------------------------------------------------------------------
write : / i_i1. "1
" 由于 i_i1 为输入类型参数 且又是引用类型 实参不能被修改 。这里 i_i1 是以 C++ 中的引用(别名)参数方式传递参数,所以如果修改了 i_i1 就会修改实际参数,所以函数中不能修改 REFERENCE IMPORTING 类型的参数,如果去掉下面注释则编译出错
"i_i1 = 10.

write : / i_i2. "2
" 虽然 i_i2 是输入类型的参数,但不是引用类型,所以可以修改 ,编译能通过但不会修改外面实参的值,只是修改了该函数局部变量的值
i_i2 = 20 .

field - symbols : <fs> type i .
assign i_i3->* to <fs>.
" 由于 i_i3 存储的是地址,所以先要解引用再能使用
write : / <fs>.
" 同上面, REFERENCE IMPORTING 类型的参数不能被修改:这里即不能修改实参的指向 "GET REFERENCE OF 30 INTO i_i3." 虽然不可以修改实参的指向,但可以修改实参所指向的实际内容
<fs> =
30 .

assign i_i4->* to <fs>.
"i_i4 存储也的是地址,所以先要解引用再能使用
write : / <fs>.
" 虽然 i_i4 是输入类型的参数,但不是引用类型,所以可以修改,只会修改函数中的局部参数 i_i4 的指向,但并不会修改实参的指向
get reference of 40 into i_i4.
" 虽然不能修改实参的指向,但可以直接修改实参的所指向的实际内容
<fs> =
400 .

WRITE : / c_i1. "111
"c_i1 为实参的别名,修改形参就等于修改实参内容
c_i1 =
1110 .

WRITE : / c_i2. "222
"c_i2 为实参的副本,所以不会影响实参的内容,但是, 由于是 CHANGING 类型的参数 , 且为值传递 ,在函数正常执行完后,还是会将该副本再次拷贝给实参,所以最终实参还是会被修改
c_i2 =
2220 .
ENDFUNCTION .

调用程序:

DATA : i_i1 TYPE i VALUE 1 ,
i_i2
TYPE i VALUE 2 ,
i_i3
TYPE REF TO i ,
i_i4
TYPE REF TO i ,
c_i1
TYPE i VALUE 111 ,
c_i2
TYPE i VALUE 222 ,
c_i3
TYPE REF TO i ,
c_i4
TYPE REF TO i ,
t_1
TYPE zjzj_itab WITH HEADER LINE .

DATA : i_i3_ TYPE i VALUE 3 .
GET REFERENCE OF i_i3_ INTO i_i3.
DATA : i_i4_ TYPE i VALUE 4 .
GET REFERENCE OF i_i4_ INTO i_i4.
DATA : c_i3_ TYPE i VALUE 333 .
GET REFERENCE OF c_i3_ INTO c_i3.
DATA : c_i4_ TYPE i VALUE 444 .
GET REFERENCE OF c_i4_ INTO c_i4.

CALL FUNCTION 'FUC_REF'
EXPORTING
i_i1 = i_i1
i_i2 = i_i2
i_i3 = i_i3
i_i4 = i_i4
TABLES
t_1 = t_1
CHANGING
c_i1 = c_i1
c_i2 = c_i2
c_i3 = c_i3
c_i4 = c_i4.
WRITE : / i_i2. "2
WRITE : / i_i3_. "30
WRITE : / i_i4_. "400
WRITE : / c_i1. "1110
WRITE : / c_i2. "2220

1.11. 字段符号 FIELD-SYMBOLS

字段符号可以看作仅是已经被解引用的指针(类似于 C 语言中带有解引用操作符 * 的指针),但更像是 C++ 中的引用类型( int i ;&ii= i; ),即某个变量的别名,它与真正的指针还是有很大的区别的,在 ABAP 中引用变量(通过 TYPE REF TO 定义的变量)才好比 C 语言中的指针

ASSIGN ... TO <fs> :将某个内存区域分配给字段符号,这样字段符号就代表了该内存区域,即该内存区域别名

1.11.1. ASSIGN 隐式强转

TYPES : BEGIN OF t_date,
year(
4 ) TYPE n,
month(
2 ) TYPE n,
day(
2 ) TYPE n,
END OF t_date.

FIELD - SYMBOLS <fs> TYPE t_date. " <fs> 定义成了具体限定类型
ASSIGN sy-datum TO <fs> CASTING . " 后面没有指定具体类型,所以使用定义时的类型进行隐式转换

1.11.2. ASSIGN 显示强转

DATA txt ( 8 ) TYPE c VALUE '19980606' .
FIELD-SYMBOLS <fs> .
ASSIGN txt TO <fs> CASTING TYPE d . " 由于定义时未指定具体的类型,所以这里需要显示强转

1.11.3. ASSIGN 动态分配

请参考 动态语句 à ASSIGN 动态分配

1.11.4. UNASSIGN CLEAR

UNASSIGN :该语句是初始化 <FS> 字段符号,执行后字段符号将不再引用内存区域, <fs> is assigned 返回假

CLEAR :与 UNASSIGN 不同的是,只有一个作用就是初始化它所指向的内存区域,而不是解除分配

1.12. 数据引用、对象引用

TYPE REF TO data 数据引用 data references

TYPE REF TO object 对象引用 object references

除了 object 所有的通用类型都能直接用 TYPE 后面 TYPE data 但没有 TYPE object ,object 不能直接跟在 TYPE 后面 只能跟在 TYPE REF TO 后面

TYPE REF TO 后面可接的 通用类型 只能 data ( 数据引用 ) 或者是 object 对象引用 通用类型 其他通用类型不行

1.12.1. 数据引用 Data References

DATA : dref TYPE REF TO i . " dref 即为 数据引用 ,即数据指针,指向某个变量或常量 ,存储变量地址
CREATE DATA dref .
dref
-> * = 2147483647 . " 可直接解引用使用 不需要先通过分配给字段符号后再使用

DATA : BEGIN OF strct ,
c ,
END OF strct .
DATA : dref LIKE REF TO strct .
CREATE DATA dref .
dref
-> * - c = 'A' .

TYPES : tpy TYPE c .
DATA : c1 TYPE REF TO tpy .
DATA : c2 LIKE REF TO c1 . " 二级指针
GET REFERENCE OF 'a' INTO c1 .
GET REFERENCE OF c1 INTO c2 .
WRITE : c2 -> * -> * .
" a

1.12.2. 对象引用 Object references

CLASS cl DEFINITION .
PUBLIC SECTION .
DATA : i VALUE 1 .
ENDCLASS .
CLASS cl IMPLEMENTATION .
ENDCLASS .

DATA : obj TYPE REF TO cl .
CREATE OBJECT obj .
" 创建对象

DATA : oref LIKE REF TO obj . " oref 即为 对象引用 ,即对象指针,指向某个对象,存储对象地址
GET REFERENCE OF obj INTO oref . " 获取对象地址
WRITE : oref -> * -> i . "1

1.12.3. GET REFERENCE OF 获取变量 / 对象 / 常量地址

DATA : e_i3 TYPE REF TO i .
GET REFERENCE OF 33 INTO e_i3 .
WRITE : e_i3 -> * . "33
" 但不能修改 常量 的值
"e_i3->* = 44.

DATA : i TYPE i VALUE 33 ,
dref
LIKE REF TO i . " 存储普通变量的地址
GET REFERENCE OF i INTO dref .
dref
-> * = 44 .
WRITE : i . "44

1.13. 动态语句

1.13.1. 内表动态访问

SORT itab BY ( comp 1 )...( comp n )

READ TABLE itab WITH KEY ( k 1 )= v 1 ... ( k n )= v n

READ TABLE itab ... INTO wa COMPARING ( comp 1 ) ... ( comp n ) TRANSPORTING ( comp 1 ) ...

MODIFY [ TABLE ] itab TRANSPORTING ( comp 1 ) ... ( comp n )

DELETE TABLE itab WITH TABLE KEY (comp 1 )...(comp n )

DELETE ADJACENT DUPLICATES FROM itab COMPARING ( comp 1 ) ... ( comp n )

AT NEW / END OF (comp)

1.13.2. 动态类型

CREATE DATA ... TYPE ( type )...

DATA: a TYPE REF TO i.
CREATE DATA a TYPE ('I').
a->* = 1.

CREATE OBJECT ... TYPE (type)... 请参考类对象反射章节

1.13.3. 动态 SQL

Select 请参照后面的动态 SQL

MODIFY/ UPDATE (dbtab)...

1.13.4. 动态调用类的方法

CALL METHOD (meth_name)
cref->(meth_name)
iref->(meth_name)
(class_name)=>(meth_name)
class=>(meth_name)
(class_name)=>meth

实例请参考 类对象反射 章节

1.13.5. ASSIGN 动态分配

FIELD - SYMBOLS :<fs>.

DATA : str ( 20 ) TYPE c VALUE ' Output String' ,
name
( 20 ) TYPE c VALUE 'STR' .

" 静态分配 : 编译时就知道要分配的对象名
ASSIGN name TO <fs>.
" 结果 <fs> name 变量等同

" 通过变量名动态访问变量
ASSIGN ( name ) TO <fs> . " 结果是是 <fs> 的值为 str 变量值

DATA : BEGIN OF line ,
col1
TYPE i VALUE '11' ,
col2
TYPE i VALUE '22' ,
col3
TYPE i VALUE '33' ,
END OF line .
DATA comp ( 5 ) VALUE 'COL3' .
FIELD-SYMBOLS : <f1> , <f2> , <f3> .
ASSIGN line TO <f1> .
ASSIGN comp TO <f2> .

" 还可 以直接使用以下的语法访问其他程序中的变量
ASSIGN ( ' (ZJDEMO)SBOOK-FLDATE ' ) TO <fs> .

" 通过 索引 动态的访问结构成员
ASSIGN COMPONENT sy-index OF STRUCTURE <f1> TO <f3>.

" 通过 字段名 动态的访问结构成员
ASSIGN COMPONENT <f2> OF STRUCTURE <f1> TO <f3>.

" 如果定义的内表没有组件名时,可以使用 索引为 0 的组件来访问这个无名字段(注:不是1)
ASSIGN COMPONENT 0 OF STRUCTURE itab TO <fs> .

1.13.5.1. 动态访问类的属性成员

ASSIGN oref -> ( 'attr' ) TO <attr> .
ASSIGN oref -> ( 'static_attr' ) TO <attr> .
ASSIGN ( 'C1' ) => ( 'static_attr' ) TO <attr> .
ASSIGN c1 => ( 'static_attr' ) TO <attr> .
ASSIGN ( 'C1' ) =>static_attr TO <attr> .

实例请参考 类对象反射 章节

1.14. 反射

CL_ABAP_ TYPEDESCR

|--CL_ABAP_ DATA DESCR

|   |--
CL_ABAP_ ELEMDESCR

|   |--CL_ABAP_ REFDESCR

|   |--CL_ABAP_COMPLEXDESCR

|       |--CL_ABAP_
STRUCTDESCR

|       |--CL_ABAP_
TABLEDESCR

|--CL_ABAP_ OBJECT DESCR

|--CL_ABAP_ CLASSDESCR

|--CL_ABAP_INTFDESCR

DATA : structtype TYPE REF TO cl_abap_structdescr.
structtype ?= cl_abap_typedescr=>
describe_by_ name ( 'spfli' ).

*COMPDESC-TYPE ?= CL_ABAP_DATADESCR=>DESCRIBE_BY_NAME( ' EKPO-MATNR ' ).

DATA : datatype TYPE REF TO cl_abap_datadescr,
field ( 5 ) TYPE c .
datatype ?= cl_abap_typedescr=> describe_by_
data ( field ).

DATA : elemtype TYPE REF TO cl_abap_elemdescr.
elemtype = cl_abap_elemdescr=>
get_ i ( ).
elemtype = cl_abap_elemdescr=>
get_ c ( 20 ).

DATA : oref1 TYPE REF TO object .
DATA : descr_ref1 TYPE REF TO cl_abap_typedescr .
CREATE OBJECT oref1 TYPE ( 'C1' ). "C1 为类名
descr_ref1
= cl_abap_typedescr =>
describe_by_ object_ref ( oref1 ).

还有一种: describe_by_ data_ref

1.14.1. TYPE HANDLE

handle 只能是 CL_ABAP_ DATADESCR 或其子类的引用变量 只能用于 Data 类型, 不能用于 Object 类型 即不能用于 CL_ABAP_ OBJECTDESCR 所以没有:

CREATE OBJECT dref TYPE HANDLE objectDescr.

DATA : dref TYPE REF TO data ,
c10type
TYPE REF TO cl_abap_
elemdescr .
c10type
= cl_abap_elemdescr => get_c ( 10 ).
CREATE DATA dref TYPE HANDLE c10type .

DATA : x20type TYPE REF TO cl_abap_elemdescr .
x20type
= cl_abap_elemdescr => get_x ( 20 ).
FIELD-SYMBOLS : <fs> TYPE any .
ASSIGN dref -> * TO <fs> CASTING TYPE HANDLE x20type .

1.14.2. 动态创建数据 Data 或对象 Object

TYPES : ty_i TYPE i .
DATA : dref TYPE REF TO ty_i .
CREATE DATA dref TYPE ( 'I' ). " 根据基本类型名动态创建数据
dref -> * = 1 .
WRITE : / dref -> * . "

CREATE OBJECT oref TYPE ('C1') . " 根据类名动态创建实例对象

1.14.3. 动态创建基本类型变量、结构、内表

DATA : dref_str TYPE REF TO data ,
dref_tab
TYPE REF TO data ,
dref_i
TYPE REF TO data ,
itab_type
TYPE REF TO cl_abap_tabledescr ,
struct_type
TYPE REF TO cl_abap_structdescr ,
elem_type
TYPE REF TO cl_abap_elemdescr ,
table_type
TYPE REF TO cl_abap_tabledescr ,
comp_tab
TYPE cl_abap_structdescr => component_table WITH HEADER LINE .
FIELD-SYMBOLS : <fs_itab> TYPE ANY TABLE .

**========= 动态创建基本类型
elem_type ?= cl_abap_elemdescr => get_i ( ).
CREATE DATA dref_i TYPE HANDLE elem_type . " 动态的创建基本类型数据对象

**========= 动态创建结构类型
struct_type ?= cl_abap_typedescr
=> describe_by_name ( 'SFLIGHT' ). " 结构类型
comp_tab[]
= struct_type -> get_components ( ). " 组成结构体的各个字段组件
* 向结构中动态的新增一个成员
comp_tab - name = 'L_COUNT' . " 为结构新增一个成员
comp_tab - type = elem_type . " 新增成员的类型对象
INSERT comp_tab INTO comp_tab INDEX 1 .
* 动态创建结构类型对象
struct_type
= cl_abap_structdescr => create ( comp_tab[] ).
CREATE DATA dref_str TYPE HANDLE struct_type . " 使用结构类型对象来创建结构对象

**========= 动态创建内表
* 基于结构类型对象创建内表类型对象
itab_type = cl_abap_tabledescr => create ( struct_type ).
CREATE DATA dref_tab TYPE HANDLE itab_type . " 使用内表类型对象来创建内表类型
ASSIGN dref_tab -> * TO <fs_itab> . " 将字段符号指向新创建出来的内表对象

"**======== 给现有的内表动态的加一列
table_type ?= cl_abap_tabledescr
=> describe_by_data ( itab ).
struct_type ?= table_type
-> get_table_line_type ( ).
comp_tab[]
= struct_type -> get_components ( ).
comp_tab
- name = 'FIDESC' .
comp_tab
- type = cl_abap_elemdescr => get_c ( 120 ).
INSERT comp_tab INTO comp_tab INDEX 2 .
struct_type
= cl_abap_structdescr => create ( comp_tab[] ).
itab_type
= cl_abap_tabledescr => create ( struct_type ).
CREATE DATA dref_tab TYPE HANDLE itab_type .

1.14.4. 类对象反射

CLASS c1 DEFINITION .
PUBLIC SECTION .
DATA : c VALUE 'C' .
METHODS : test .
ENDCLASS .

CLASS c1 IMPLEMENTATION .
METHOD : test .
WRITE : / 'test' .
ENDMETHOD .
ENDCLASS .
START-OF-SELECTION .
TYPES : ty_c .
DATA : oref TYPE REF TO object .
DATA : oref_classdescr TYPE REF TO cl_abap_classdescr .
CREATE OBJECT oref TYPE ( 'C1' ) . " 根据类名动态创建实例对象
" 相当于 Java 中的 Class 类对象
oref_classdescr ?= cl_abap_classdescr => describe_by_object_ref ( oref ).

DATA : t_attrdescr_tab TYPE abap_attrdescr_tab WITH HEADER LINE , " 类中的属性列表
t_methdescr_tab
TYPE abap_methdescr_tab WITH HEADER LINE . " 类中的方法列表
FIELD-SYMBOLS <fs_attr> TYPE any .
t_attrdescr_tab[]
= oref_classdescr -> attributes .
t_methdescr_tab[]
= oref_classdescr -> methods .
LOOP AT t_attrdescr_tab . "
动态访问类中的属性
ASSIGN oref -> ( t_attrdescr_tab - name ) TO <fs_attr> .
WRITE : / <fs_attr> .
ENDLOOP .
LOOP AT t_methdescr_tab . " 动态访问类中的方法
CALL METHOD oref -> ( t_methdescr_tab - name ) .
ENDLOOP .

2. 面向对象

2.1. 类与接口定义

CLASS class DEFINITION [ ABSTRACT ] [ FINAL ] .
[
PUBLIC SECTION .
[
components ]]
[
PROTECTED SECTION .
[
components ]]
[
PRIVATE SECTION .
[
components ]]
ENDCLASS .

INTERFACE intf .
[
components ]
ENDINTERFACE .

2.1.1. components

² TYPES, DATA, CLASS-DATA , CONSTANTS for data types and data objects

² METHODS, CLASS-METHODS , EVENTS, CLASS-EVENTS for methods and events

² INTERFACES 如果在类中,表示需要实现哪个接口;如果是在接口中,表示继承哪个接口 for implementing interfaces

² ALIASES for alias names for interface components 给接口组件取别名

2.2. 类定义 、实现

CLASS math DEFINITION .
PUBLIC SECTION .
METHODS divide_1_by
IMPORTING operand TYPE i
EXPORTING result TYPE f
RAISING cx_sy_arithmetic_error .
ENDCLASS .
CLASS math IMPLEMENTATION .
METHOD divide_1_by .
result
= 1 / operand .
ENDMETHOD .
ENDCLASS .

2.3. 接口 定义、实现

INTERFACE int1 .

ENDINTERFACE .

CLASS class DEFINITION . [ˌdefiˈniʃən]
PUBLICSECTION .
INTERFACES : int1,int2. " 实现 多个接口
ENDCLASS .

CLASS class IMPLEMENTATION . [ˌɪmplɪmənˈteɪ-ʃən]
METHOD
intf1 ~ imeth1 .
ENDMETHOD .
ENDCLASS .

2.4. 类、接口继承

CLASS <subclass> DEFINITION INHERITING FROM <superclass>. [inˈherit]

INTERFACE i0 .
METHODS m0 .
ENDINTERFACE .
INTERFACE i1 .
INTERFACES i0 .
" 可以有相同的成员名 因为继承过来后 成员还是具有各自的命名空间 在实现时
" 被继承过来的叫 i0~m0
这里 的名为 i1~m0 所以是不同的两个方法
METHODS m0 .
METHODS m1 .
ENDINTERFACE .

2.5. 向下强转型 ?=

CLASS person DEFINITION.

ENDCLASS.

CLASS stud DEFINITION INHERITING FROM person .

ENDCLASS.

START-OF-SELECTION.

DATA p TYPE REF TO person .

DATA s TYPE REF TO stud .

CREATE OBJECT s .

p = s . " 向上自动转型

" 拿开注释运行时抛异常,因为 P 此时指向的对象不是 Student ,而是 Person 所以能强转的前提是 P 指向的是 Student

" CREATE OBJECT p .

s ?= p. " 向下强转型

2.6. 方法

METHODS/CLASS-METHODS meth [ ABSTRACT | FINAL ]
[
IMPORTING
parameters [ PREFERRED PARAMETER p]]
[
EXPORTING parameters ]
[
CHANGING parameters ]
[{
RAISING | EXCEPTIONS } exc1 exc2 ... ] .

应该还有一个 Returning 选项, RETURNING 不能与 EXPORTING CHANGING 同时使用

2.6.1. parameters

... { VALUE (p1) | REFERENCE (p1) | p1 }
{
TYPE generic_type }
|{ TYPE { [ LINE OF ] complete_type } | { REF TO { data | object | complete_type |class|intf} } }
|{ LIKE { [ LINE OF ] dobj } | { REF TO dobj } }
[OPTIONAL|{DEFAULT def1}]
{ VALUE (p2) | REFERENCE (p2) | p2

² data object 表示是通用数据类型 data object

² complete_type 为完全限定类型

² OPTIONAL DEFAULT 两个选项不能同时使用 且对于 EXPORTING 类型的输入参数不能使用

² 如果参数名 p1 前没有使用 VALUE REFERENCE 默认为还是 REFERENCE 即引用传递

² 方法中的输入输出参数是否能修改 请参考 Form Function 参数的传值传址

2.6.2. PREFERRED PARAMETER 首选参数

设置多个 IMPORTING 类型参数中的某一个参数为首选参数。

首选参数的意义在于:当所有 IMPORTING 类型都为可选 optional 时,我们可以通过 PREFERRED PARAMETER 选项来指定某一个可选输入参数为首选参数,则在以下简单方式调用时: [CALL METHOD] meth ( a ) . 实参 a 的值就会传递给设置的首选参数,而其他不是首参数的可选输入参数则留空或使用 DEFAULT 设置的默认值

注:此选项只能用于 IMPORTING 类型的参数;如果有必选的 IMPORTING 输入参数,则没有意义了

2.6.3. 普通调用

[ CALL METHOD ]  meth| me ->meth|oref->meth| super ->meth|class=>meth[ ( ]

[ EXPORTING p1 = a1 p2 = a2 ...]

{ { [ IMPORTING p1=a1 p2=a2 ...][ CHANGING p1 = a1 p2 = a2 ...] }

| [ RECEIVING r  = a  ] } RECEIVING 不能与 EXPORTING CHANGING 同时使用

[ EXCEPTIONS [exc1 = n1 exc2 = n2 ...]

[ OTHERS = n_others] ] [ ) ].

如果省略 CALL METHOD ,则一定要加上括号形式 如果通过 CALL METHOD 来调用,则括号可加可不加

RECEIVING :用来接收 METHODS /CLASS-METHODS RETURNING 选项返回的值

如果 EXPORTING IMPORTING CHANGING RECEIVING EXCEPTIONS OTHERS 同时出现时,应该按此顺序来编写

使用此种方式调用(使用 EXPORTING IMPORTING 等这些选项)时 ,如果原方法声明时带了返回值 RETURNING ,只能使用 RECEIVING 来接受,而不能使用等号来接收返回值,下面用法是错误的:

num2 = o1 -> m1 ( EXPORTING p1 = num1 ).

2.6.4. 简单调用

此方式下输入的参数都 只能是 IMPORTING 类型的参数 ,如果要传 CHANGING EXPORTING RAISING EXCEPTIONS 类型的参数时,只能使用 上面通用调用方式

² meth(

此种方式 仅适用于没有 输入参数 IMPORTING 输入 \ 输出参数 CHANGING 或者有但都是可选的、或者不是可选时但有默认值也可

² meth(

此种方式 仅适用于只有一个必选输入参数 IMPORTING )( 如果还有其他输入参数,则其他都为可选,或者不是可选时但有默认值也可 ), 或者是有多个可选输入参数 IMPORTING )(此时没有必选输入参数情况下)的情况下但方法声明时通过使用 PREFERRED PARAMETER 选项指定了其中某个可选参数为 首选参数 (首选参数即在使用 meth( a ) 方式传递一个参数进行调用时,通过实参 a 传递给设置为首选的参数

² meth( p1 = a1 p2 = a2 ... )

此种方式 适用于有多个必选的输入参数 IMPORTING )方法的调用(其它如 CHANGING EXPORTING 没有,或者有但可选 ),如果输入参数( IMPORTING )为可选,则也可以不必传

2.6.5. 函数方法

Return 唯一返回值

METHODS meth
[ IMPORTING
parameters [ PREFERRED PARAMETER p]]
RETURNING VALUE (r) typing
[{ RAISING | EXCEPTIONS } exc1 exc2 ...].

RETURNING 用来替换 EXPORTING CHANGING 不能同时使用 。定义了一个形式参数 r 来接收返回值 并且 只能是值传递

具有唯一返回值的函数方法可以直接用在以下语句中 逻辑表达式 IF ELSEIF WHILE CHECK WAIT CASE LOOP 、算术表达式、赋值语句

函数方法 可以采用上面简单调用方式来调用 meth( ) meth( a ) meth( p1 = a1 P2 = a2 ... )

ref -> m ( RECEIVING r = i ).
CALL METHOD ref -> m ( RECEIVING r = i ).
CALL METHOD ref -> m RECEIVING r = i .

2.7. me super

等效于 Java 中的 this super

2.8. 事件

2.8.1. 事件定义

EVENTS|CLASS-EVENTS evt [ EXPORTING VALUE (p1)
{ TYPE generic_type }
|{ TYPE { [ LINE OF ] complete_type } |
{ REF TO
{ data | object |complete_type|class|intf} } }
| { LIKE { [ LINE OF ] dobj } | { REF TO dobj } }
[OPTIONAL|{DEFAULT def1 }]
VALUE (p2) ...].

² data object :表示是通用数据类型 data object

² complete_type :为完全限定类型

² OPTIONAL DEFAULT 两个选项 不能同时使用

² EXPORTING :定义了事件的输出参数,并且 事件定义时只能有输出参数,且只能是传值

非静态事件声明中除了明确使用 EXPORTING 定义的输出外,每个 实例事件 其实还有一个隐含的输出参数 sender ,它指向了事件源对象,当使用 RAISE EVENT 语句触发一个事件时, 事件源 的对象就会分配给这个 sender 引用,但是 静态事件没有 隐含参数 sender

事件 evt 的定义也是 接口 定义部分 进行定义的

非静态事件只能在非静态方法中触发 ,而不能在静态方法中触发;而 静态事件即可在静态也可在非静态方法中进行触发, 或者反过来说: 实例方法既可触发静态事件,也可触发非静态事件,但静态方法就只能触发静态事件

2.8.2. 事件触发

RAISE EVENT evt [ EXPORTING p1 = a1 p2 = a2 ...].

该语句只能在定义 evt 事件的同一类或子类 或接口实现方法中进行调用

当实例事件触发时 如果在 event handler 事件处理器声明语句中指定了形式参数 sender 则会自动接收事件源 但不能在 RAISE EVENT …EXPORTING 语句中明确指定 它会自动传递 (如果是静态事件,则不会传递 sender 参数)

CLASS c1 DEFINITION .
PUBLIC SECTION .
EVENTS e1 EXPORTING value ( p1 ) TYPE string value ( p2 ) TYPE i OPTIONAL . " 定义
METHODS m1 .
ENDCLASS .
CLASS c1 IMPLEMENTATION .
METHOD m1 .
RAISE EVENT e1 EXPORTING p1 = '...' . " 触发
ENDMETHOD .
ENDCLASS .

2.8.3. 事件处理器 Event Handler

静态或非静态事件 处理方法 都可以处理静态或非静态事件, 与事件的静态与否没有直接的关系

INTERFACE window. " 窗口事件接口
EVENTS : minimize EXPORTING VALUE (status) TYPE i . " 最小化事件
ENDINTERFACE .

CLASS dialog_window DEFINITION . "
窗口事件实现
PUBLIC SECTION .
INTERFACES window.
ENDCLASS .

INTERFACE window_handler. " 窗口事件处理器接口
METHODS : minimize_window
FOR EVENT window~minimize OF dialog_window
IMPORTING status sender . " 事件处理器方法参数要与事件接口定义中的一致
ENDINTERFACE .

2.8.4. 注册事件处理

实例事件处理器(方法)注册(注:被注册的方法只能是用来处理非静态事件的方法):

SET HANDLER handler1 handler2 ... FOR oref|{ ALL INSTANCES }[ ACTIVATION act].

静态事件处理器(方法)注册(注:被注册的方法只能是用来处理静态事件的方法):

SET HANDLER handler1 handler2 ... [ ACTIVATION act].

oref :只将事件处理方法 handler1 handler2 注册到 oref 这一个事件源对象

ALL INSTANCES :将事件处理方法注册到所有的事件源实例中

ACTIVATION act :表示是注册还是注销

2.8.5. 示例

CLASS c1 DEFINITION . " 事件源
PUBLIC SECTION .
EVENTS : e1 EXPORTING value ( p1 ) TYPE c , e2 .
CLASS-EVENTS ce1 EXPORTING value ( p2 ) TYPE i .
METHODS : trigger . " 事件触发方法
ENDCLASS .

CLASS c1 IMPLEMENTATION .
METHOD trigger .
RAISE EVENT : e1 EXPORTING p1 = 'A' , e2 , ce1 EXPORTING p2 = 1 .
ENDMETHOD .
ENDCLASS .

静态(如下面的 h1 方法)或非静(如下面的 h2 方法)态事件处理方法都可以处理静态或非静态事件, 事件的处理方法是否只能处理静态的还是非静态事件 事件的静态与否没有关系 ,但事件的触发方法 事件的静态与否有关系 实例方法既可触发静态事件,也可触发非静态事件,但静态方法就只能触发静态事件 );但是,事件处理方法虽然能处理的事件与事件的静态与否没有关系,但如果处理的是 静态事件 ,那此处理方法就成为了 静态处理器 ,只能采用 静态注册方式 对此处理方法进行注册。如果处理的是 非静态事件 ,那此处理方法就是 非静态处理器 ,只能采用 非静态注册方式 对此处理方法进行注册

处理器的静态与否与处理方法本身是否静态没有关系,只与处理的事件是否静态有关


CLASS c2 DEFINITION . " 监听器 即事件处理器
PUBLIC SECTION .
" 静态方法也可以处理非静态事件 此方法属于 非静态处理器 只能采用非静态注册方式
CLASS-METHODS h1 FOR EVENT e1 OF c1 IMPORTING p1 sender .
" 非静态方法处理非静态事件 此方法属于 非静态处理器 只能采用非静态注册方式
METHODS : h2 FOR EVENT e2 OF c1 IMPORTING sender ,
" 非静态方法当然更可以处理静态事件 此方法属于 静态处理器 只能采用静态注册方式
h3
FOR EVENT ce1 OF c1 IMPORTING p2 .
ENDCLASS .
CLASS c2 IMPLEMENTATION .
METHOD h1 .
WRITE : 'c2=>h1' .
ENDMETHOD .
METHOD : h2 .
WRITE : 'c2->h2' .
ENDMETHOD .
METHOD : h3 .
WRITE : 'c2->h3' .
ENDMETHOD .
ENDCLASS .

DATA : trigger TYPE REF TO c1 ,
trigger2
TYPE REF TO c1 ,
handler TYPE REF TO c2 .
START-OF-SELECTION .
CREATE OBJECT trigger .
CREATE OBJECT trigger2 .
CREATE OBJECT handler .
" 由于 h1 h2 两个处理方法分别是用来处理非静态事件 e1 e2 的,所以只能采用实例注册方式
SET HANDLER : c2 => h1 handler -> h2 FOR trigger ,
"h3 处理方法是用来处理静态事件 ce1 的,属于静态处理器,所以只能采用静态注册方式
handler
-> h3 .
trigger
-> trigger ( ).
" 虽然 trigger( ) 方法会触发 e1,e2,ce1 三种事件,但 h1 h2 未向实例 trigger2 注册,而 h3 属于静态处理器,与实例无关,即好比向所有实例注册过了一样
trigger2
-> trigger ( ).

3. 内表

3.1. LOOP AT 循环内表

LOOP AT itab { INTO wa} | { ASSIGNING <fs> [ CASTING ]} | { TRANSPORTING NO FILDS }
[ [
USING KEY key_name| ( name ) ] [ FROM idx1] [ TO idx2] [ WHERE log_exp| ( cond_syntax ) ] ] .

ENDLOOP .

FROM … TO: 只适用于标准表与排序表 WHERE …  : 适用于所有类型的内表

如果没有通过 USING KEY 选项的 key_name 则循环读取的顺序与表的类型相关

l 标准表与排序表 会按照 primary table index 索引的顺序一条条的循环 且在循环里 SY-TABIX 为当前正在处理行的索引号

l 哈希表 由于表没有排序 所以 按照插入的顺序来循环处理 ,注,此时 SY-TABIX 总是 0

可以在循环内表时增加与删除当前行 If you insert or delete lines in the statement block of a LOOP , this will have the following effects:

you insert lines behind (后面) the current line, these new lines will be processed in the subsequent loop (新行会在下一次循环时被处理) passes. An endless loop (可能会引起死循环) can result you delete lines behind the current line, the deleted lines will no longer be processed in the subsequent loop passes you insert lines in front (前面) of the current line, the internal loop counter is increased by one with each inserted line. This affects sy-tabix in the subsequent loop pass( 这会影响在随后的循环过程 SY- TABIX )
  • If you delete lines in front of the current line, the internal loop counter is decreased by one with each deleted line. This affects sy-tabix in the subsequent loop pass
  • gt_table-c = 'b' OR gt_table-c = 'c' OR gt_table-c = 'e'.

    WRITE : /  sy-tabix COLOR = 6 INTENSIFIED ON INVERSE OFF ,

    gt_table COLOR = 6 INTENSIFIED ON INVERSE OFF .

    ELSE.

    WRITE : /  sy-tabix, gt_table.

    ENDIF.

    ENDLOOP.

    SKIP 2.

    DATA count TYPE

    LOOP AT gt_table .

    count = count + 1.

    "当循环到第三次时删除,即循环到 C 时进行删除

    IF count = 3.

    DELETE gt_table WHERE c = 'b' OR c = 'c' OR c = 'e'.

    ENDIF.

    "删除之后sy-tabix会重新开始对内表现有的行进行编号

    WRITE :/ sy-tabix, gt_table.

    ENDLOOP.

    SKIP 2.

    LOOP AT gt_table .

    WRITE : /  sy-tabix, gt_table.

    ENDLOOP.

    3.1.2. SUM

    如果在 AT - ENDAT 块中使用 SUM 则系统计算当前行组中 所有 行的数字字段之和并将其写入工作区域中相应的字段中

    3.1.3. AT... ENDAT

    在使用 AT...... ENDAT 之前, 一这要先按照这些语句中的组件名进行排序,且排序的顺序要与在 AT...... ENDAT 语句中使用顺序一致 ,排序与声明的顺序决定了先按哪个分组,接着再按哪个进行分组,最后再按哪个进行分组,这与 SQL 中的 Group By 相似

    用在 AT...... ENDAT 语句中的中的组件名不一定要是结构中的关键字段 ,但这些字段一定要按照出现在 AT 关键字后面的使用顺序在结构最前面进行声明,且 这些组件字段的声明之间不能插入其他组件的声明 如现在需要按照 <f1>, <f2>, .... 多个字段的顺序来使用在 AT...... ENDAT 语句中,则首先需要在结构中按照 < f1>, <f2>, ...., 多字段的顺序在结构最前面都声明,然后按照 <f1>, <f2>, ...., 多字段来排序的,最后在循环中按如下的顺序块书写程序(请注意书写 AT END OF 的顺序与 AT NEW 是相反 的,像下面这样):

    LOOP AT <itab>.

    AT FIRST. ... ENDAT.

    AT NEW <f1>. ...... ENDAT.

    AT NEW <f 2 >. ...... ENDAT.

    .......

    <single line processing>

    .......

    AT END OF <f2>. ... ENDAT.

    AT END OF <f1>. ... ENDAT.

    LAST. .... ENDAT.

    ENDLOOP.

    一旦进入到 AT...<f1>...ENDAT 块中时,当前工作区(或表头)中的从 <f1> 往后 ,但不包括 <f1> (按照在结构中声明的次序)所有字段的字符类型字段会以星号 (*) 号来填充,而数字字设置为初始值(注:在测试过程中发现 String 类型不会使用 * 来填充,而是设置成 empty String ,所以只有固定长度类型的非数字基本类型才设置为 * )。如果在 AT 块中使用了 SUM ,则会将所有数字类型字段统计出来将存入当前工作区(或表头);但一旦离开 AT....ENDAT 块后,又会将当前遍历的行恢复到工作区(或表头)中

    DATA : BEGIN OF th_mseg OCCURS 10 ,
    matnr TYPE mard-matnr, werks TYPE mard-werks,
    lgort TYPE mard-lgort, shkzg TYPE mseg-shkzg,
    menge
    TYPE mseg-menge, budat TYPE mkpf-budat,
    LOOP AT th_mseg.
    AT END OF
    shkzg. " 会根据 shkzg 及前面所有字段来进行分组
    sum .
    WRITE : / th_mseg-matnr, th_mseg-werks,th_mseg-lgort,
    th_mseg-shkzg,th_mseg-menge,th_mseg-budat.
    ENDAT .
    ENDLOOP .

    AS-101             2300 0001 S           10.000  ****.**.**

    AS-100             2300 0002 S           10.000  ****.**.**

    AS-100             2300 0001 S           20.000  ****.**.**

    上面由于没有根据 matnr + werks + lgort + shkzg 进行排序,所以结果中的第三行其实应该与第一行合并。 其实这个统计与 SQL 里的分组( Group By )统计原理是一样的, Group By 后面需要明确指定分组的字段,如上面程序使用 SQL 分组写法应该为 Group By matnr werks lgort shkzg ,但在 ABAP 里你只需要按照 matnr werks lgort shkzg 按照先后顺序在结构定义的最前面进行声明就可表达了 Group By 那种意义,而且不一定要将 matnr werks lgort shkzg 这四个字段全部用在 AT 语句块中 AT NEW AT END OF shkzg 才正确,其实像上面程序一样,只写 AT END OF shkzg 这一个语句,前面三个字段 matnr werks lgort 都可以不用在 AT 语句中出现,因为 ABAP 默认会按照结构中声明的顺序将 shkzg 前面的字段也全都用在了分组中了

    DATA : BEGIN OF line ,
    " C2 C3 组件名声明的顺序一定要与在 AT...... ENDAT 块中使用的次序一致,即这里不能将 C3 声明在 C2 之前,且不能在 C2 C3 之间插入其他字段的声明
    c2
    ( 5 ) TYPE c ,
    c3
    ( 5 ) TYPE c ,
    c4
    ( 5 ) TYPE c ,
    i1
    TYPE i ,
    i2
    TYPE i ,
    c1
    ( 5 ) TYPE c ,
    END OF line .

    " 使用在 AT...... E NDAT 语句中的 字段不一定要是关键字段
    DATA : itab LIKE TABLE OF line WITH HEADER LINE WITH NON-UNIQUE KEY i1 .
    PERFORM append USING 2 'b' 'bb' 'bbb' '2222' 22 . PERFORM append USING 3 'c' 'aa' 'aaa' '3333' 33 .
    PERFORM append USING 4 'd' 'aa' 'bbb' '4444' 44 . PERFORM append USING 5 'e' 'bb' 'aaa' '5555' 55 .
    PERFORM append USING 6 'f' 'bb' 'bbb' '6666' 66 . PERFORM append USING 7 'g' 'aa' 'aaa' '7777' 77 .
    PERFORM append USING 8 'h' 'aa' 'bbb' '8888' 88 .
    SORT itab ASCENDING BY c2 c3 .
    LOOP AT itab .
    WRITE : / itab - c2 , itab - c3 , itab - c1 , itab - c4 , itab - i1 , itab - i2 .
    ENDLOOP .
    SKIP .
    LOOP AT itab .
    AT FIRST .
    WRITE : / '>>>> AT FIRST' .
    ENDAT .
    AT NEW c2 .
    WRITE : / '    >>>> Start of' , itab - c2 .
    ENDAT .
    AT NEW c3 .
    WRITE : / '        >>>> Start of' , itab - c2 , itab - c3 .
    ENDAT .
    " 只要一出 AT 块,则表头的数据又会恢复成当前被遍历行的内容
    WRITE : / itab - c2 , itab - c3 , itab - c1 , itab - c4 , itab - i1 , itab - i2 .
    AT END OF c3 .
    SUM .
    WRITE : / itab - c2 , itab - c3 , itab - c1 , itab - c4 , itab - i1 , itab - i2 .
    WRITE : / '        <<<< End of' , itab - c2 , itab - c3 .
    ENDAT .
    AT END OF c2 .
    SUM .
    WRITE : / itab - c2 , itab - c3 , itab - c1 , itab - c4 , itab - i1 , itab - i2 .
    WRITE : / '    <<<< End of' , itab - c2 .
    ENDAT .
    AT LAST .
    SUM .
    WRITE : / itab - c2 , itab - c3 , itab - c1 , itab - c4 , itab - i1 , itab - i2 .
    WRITE : / '<<<< AT LAST' .
    ENDAT .
    ENDLOOP .
    TYPES : c5 ( 5 ) TYPE c .
    FORM append  USING    value ( p_i1 ) TYPE I value ( p_c1 ) TYPE c5 value ( p_c2 ) TYPE c5
    value ( p_c3 ) TYPE c5 value ( p_c4 ) TYPE c5 value ( p_i2 ) TYPE i .
    itab
    - i1 = p_i1 . itab - c1 = p_c1 . itab - c2 = p_c2 .
    itab
    - c3 = p_c3 . itab - c4 = p_c4 . itab - i2 = p_i2 .
    APPEND itab .
    ENDFORM .

    aaa   c     3          33

    aaa   g     7          77

    bbb   d     4          44

    bbb   h     8          88

    aaa   a     1          11

    aaa   e     5          55

    bbb   b     2          22

    bbb   f     6          66

    AT FIRST

    Start of aa

    Start of aa    aaa

    aaa   c     3          33

    aaa   g     7          77

    aaa   ***** *****          10         110

    End of aa    aaa

    Start of aa    bbb

    bbb   d     4444           4          44

    bbb   h     8          88

    bbb   ***** *****          12         132

    End of aa    bbb

    ***** ***** *****           22         242

    End of aa

    Start of bb

    Start of bb    aaa

    aaa   a     1          11

    aaa   e     5          55

    aaa   ***** *****           6          66

    End of bb    aaa

    Start of bb    bbb

    bbb   b     2          22

    bbb   f     6666            6          bbb   ***** *****           8          88

    End of bb    bbb

    ***** ***** *****           14         154

    End of bb

    ***** ***** ***** *****            36         396

    AT LAST

    3.1.4. 自已实现 AT... ENDAT

    如果循环的内表不是自己定义的 有时无法将分组的字段按顺序声明在一起 所以需要自己实现这些功能 下面是自己实现 AT NEW AT END OF 另一好处是在循环内表时 可以使用 Where 条件语句 )( 使用这种只需要按照分组的顺序排序即可 如要分成 bukrs bukrs anlkl 两组时 需要按照 BY bukrs anlkl 排序 而不能是 BYanlkl bukrs ):

    DATA : lp_bukrs TYPE bukrs , " 上一行 bukrs 字段的值
    lp_anlkl
    TYPE anlkl . " 上一行 anlkl 字段的值

    " 下面假设按 bukrs bukrs anlkl 分成两组
    SORT itab_data BY bukrs anlkl.
    DATA : i_indx TYPE i .

    DATA : lwa_data Like itab_data
    LOOP AT itab_data where flg = 'X ' .

    i_indx = sy - tabix.
    " **********AT NEW 对当前分组首行进行处理

    IF itab_data - bukrs <> lp_bukrs . "Bukrs
    ".........
    ENDIF .
    IF itab_data - bukrs <> lp_bukrs OR itab_data - anlkl <> lp_anlkl . "bukrs anlkl 分组
    ".........
    ENDIF .
    IF itab_data - bukrs <> lp_bukrs OR itab_data - anlkl <> lp_anlkl OR itab_data - . . <> lp_ . . . "bukrs anlkl .. 分组
    ".........
    ENDIF .


    "********** 普通循环处理
    ".........

    "**********AT END OF 对当前分组末行进行处理

    DATA : l_nolast1 , l_nolast12 . " 不是分组中最末行

    " 这里还是要清一下,以防该代码直接写在报表程序的事件里,而不是 Form 里(直接放在 Report 程序事件里时, l_nolast1,l_nolast12 将会成为全局变量)
    CLEAR:  l_nolast1,l_nolast12,l_nolast...
    DO .
    i_indx
    = i_indx  + 1 .
    READ TABLE itab_data INTO lwa_data INDEX i_indx . " 尝试读取下一行
    IF sy - subrc <> 0 . " 当前行已是内表中最后一行
    EXIT .
    " 如果第一分组字段都发生了变化,则意味着当前行为所有分组中的最后行
    " 注:即使有 N 个分组,这里也只需要判断第一分组字段是否发生变化,不
    " 需要对其他分组进行判断,即这里不需要添加其他 ELSEIF 分支
    ELSEIF lwa_data - bukrs <> itab_data - bukrs .
    EXIT .
    ENDIF .
    ********
    断定满足条件的下一行不是分组最的一行
    " 如果 Loop 循环中没有 Where 条件,则可以将下面条件 lwa_data-flg = 'X' 删除即可
    IF sy - subrc = 0 AND lwa_data - flg = 'X' .
    IF lwa_data - bukrs = itab_data - bukrs . " 判断当前行是否是 bukrs 分组最后行
    l_nolast1
    = '1' .
    IF lwa_data - nanlkl = itab_data - nanlkl . " 判断当前行是否是 bukrs nanlkl 分组最后行
    l_nolast2
    = '1' .
    IF lwa_data - . . = itab_data - . . . " 判断当前行是否是 bukrs nanlkl .. 分组最后行
    l_nolast
    . . = '1' .
    ENDIF .
    ENDIF .
    EXIT . " 只要进到此句所在外层 If ,表示找到了一条满 Where 条件的下一行数据,因此,只要找到这样的数据就可以判断当前分组是否已完,即一旦找到这样的数据就不用再往后面找了,则退出以防继续往下找
    ENDIF .
    ENDIF .

    ENDDO .

    IF l_nolast . . IS INITIAL " 处理 bukrs nanlkl .. 分组
    . . . . . .
    ENDIF .

    IF l_nolast2 IS INITIAL . " 处理 bukrs nanlkl 分组
    . . . . . .
    ENDIF .
    IF l_nolast1 IS INITIAL . " 处理 bukrs 分组
    . . . . . .
    ENDIF .

    lp_bukrs = itab_data - bukrs .
    lp_anlkl
    = itab_data - anlkl .

    lp_.. = itab_data - .. .
    ENDLOOP .

    3.2. LOOP AT 中修改当前内表行

    3.2.1. 循环中修改 索引表

    TYPES : BEGIN OF line ,
    key
    ,
    val
    TYPE i ,
    END OF line .
    DATA : itab1 TYPE line OCCURS 0 WITH HEADER LINE .
    DATA : itab2 TYPE line OCCURS 0 WITH HEADER LINE .
    itab1
    - key = 1 .
    itab1
    - val = 1 .
    APPEND itab1 .
    itab2
    = itab1 .
    APPEND itab2 .
    itab1
    - key = 2 .
    itab1
    - val = 2 .
    APPEND itab1 .
    itab2
    = itab1 .
    APPEND itab2 .

    LOOP AT itab1 .
    WRITE : / 'itab1 index: ' , sy - tabix .
    READ TABLE itab2 INDEX 1 TRANSPORTING NO FIELDS . " 试着读取其他内表
    "READ TABLE itab1 INDEX 1 TRANSPORTING NO FIELDS." 读取本身也不会影响后面的 MODIFY 语句
    WRITE : / 'itab2 index: ' , sy - tabix .
    itab1
    - val = itab1 - val + 1 .
    " 在循环中可以使用下面简洁方法来修改内表,修改的内表行为当前正被循环的行,即使循环中使用了
    "READ TABLE 语句读取了其他内表 ( 读取本身也没有关系 ) 而导致了
    sy-tabix 发生了改变 ,因为以下
    " 语句不是根据 sy-tabix 来修改的 ( 如果在前面读取内表导致 sy-tabix 发生了改变发生改变后,再使用

    " MODIFY itab1 INDEX sy-tabix 语句进行修改时,反而不正确。 而且该语句还适用于 Hash 内表, 需在
    "MODIFY
    后面加上 TABLE 关键字后再适用于 Hash ——请参见后面章节示例 )

    MODIFY itab1 .
    ENDLOOP .
    LOOP AT itab1 .
    WRITE : / itab1 - key , itab1 - val .
    ENDLOOP .

    3.2.2. 循环中修改 HASH

    TYPES : BEGIN OF line ,
    key
    ,
    val
    TYPE i ,
    END OF line .
    DATA : itab1 TYPE HASHED TABLE OF line WITH HEADER LINE  WITH UNIQUE KEY key .
    DATA : itab2 TYPE line OCCURS 0 WITH HEADER LINE .
    itab1
    - key = 1 .
    itab1
    - val = 1 .
    INSERT itab1 INTO TABLE itab1 .
    itab2
    = itab1 .
    APPEND itab2 .
    itab1
    - key = 2 .
    itab1
    - val = 2 .
    INSERT itab1 INTO TABLE itab1 .
    itab2
    = itab1 .
    APPEND itab2 .

    LOOP AT itab1 .
    WRITE : / 'itab1 index: ' , sy - tabix . " 循环哈希表时 sy-tabix 永远是 0
    READ TABLE itab2 INDEX 1 TRANSPORTING NO FIELDS .
    WRITE : / 'itab2 index: ' , sy - tabix .
    itab1
    - val = itab1 - val + 1 .
    MODIFY TABLE itab1 . " 该语句不一定在要放在循环里才能使用 —— 循环外修改 Hash 也是一样的 这与上面的索引表循环修改是不一样的 并且修改的条件就是 itab1 表头工作区, itab1 即是条件,也是待修改的值,修改时会 根据内表设置的主键来修改,而不是索引号

    ENDLOOP .
    LOOP AT itab1 .
    WRITE : / itab1 - key , itab1 - val .
    ENDLOOP .

    3.3. 第二索引

    三种类型第二索引:

    ² UNIQUE  HASHED 哈希算法第二索引

    ² UNIQUE  SORTED 唯一升序第二索引

    ² NON-UNIQUE  SORTED 非唯一升序第二索引

    TYPES sbook_tab TYPE STANDARD TABLE OF sbook
    " 主索引 如果要为主索引指定名称 则只能使用预置的 primary_key 但可以通过后面的 ALIAS 选项来修改 ALIAS 选项只能用于排序与哈希表
    WITH NON-UNIQUE KEY primary_key "ALIAS my_primary_key
    COMPONENTS carrid connid fldate bookid
    " 第一个第二索引 唯一哈希算法
    WITH UNIQUE HASHED KEY hash_key
    COMPONENTS carrid connid
    " 第二第二索引 唯一升序排序索引
    WITH UNIQUE SORTED KEY sort_key1
    COMPONENTS carrid bookid
    " 第三第二索引 非唯一升序排序索引
    WITH NON-UNIQUE SORTED KEY sort_key2
    COMPONENTS customid .

    3.3.1. 使用第二索引

    1、 可以在 READ TABLE itab MODIFY itab DELETE itab LOOP AT itab 内表操作语句中通过 WITH [TABLE] KEY key_name COMPONENTS K 1 = V 1 ... 或者 USING KEY key_name ,语句中的 key_name 为第二索引名:

    READ TABLE itab WITH TABLE KEY [ key_name COMPONENTS ] {K 1 | ( K 1 ) } = V 1 ... INTO wa

    READ TABLE itab WITH KEY key_name COMPONENTS {K 1 | ( K 1 ) } = V 1 ... INTO wa

    READ TABLE itab FROM wa [ USING KEY key_name] INTO wa

    READ TABLE itab INDEX idx [ USING KEY key_name] INTO wa

    MODIFY TABLE itab [ USING KEY key_name] FROM wa

    MODIFY itab [ USING KEY loop_key] FROM wa 此语句只能用在 LOOP AT 内表循环 语句中,并且此时 USING KEY loop_key 选项也可以省略(其实默认就是省略的),其中 loop_key 是预定义的,不能写成其他名称

    MODIFY itab INDEX idx [ USING KEY key_name] FROM wa

    MODIFY itab FROM wa [ USING KEY key_name] ... WHERE ...

    DELETE TABLE itab FROM wa [ USING KEY key_name]

    DELETE TABLE itab WITH TABLE KEY [ key_name COMPONENTS ] {K 1 | ( K 1 ) } = V 1 ...

    DELETE itab INDEX idx [ USING KEY key_name| ( name ) ]

    DELETE itab [ USING KEY loop_key]

    DELETE itab [ USING KEY key_name ] ... WHERE ...

    DELETE ADJACENT DUPLICATES FROM itab [ USING KEY key_name] [ COMPARING K1 K2 ...]

    LOOP AT itab USING KEY key_name WHERE ... .
    ENDLOOP .

    2、 可以在 INSERT itab APPEND 语句中通过 USING KEY 选项来使用第二索引

    INSERT wa [ USING KEY key_name] INTO TABLE itab

    APPEND wa [ USING KEY key_name] TO itab

    3.3.2. 示例

    DATA itab TYPE HASHED TABLE OF dbtab WITH UNIQUE KEY col1 col2 ...
    " 向内表 itab 中添加大量的数据 ...
    READ TABLE itab " 使用 主键进行搜索,搜索速度将会很慢
    WITH KEY col3 = ... col4 = ...
    ASSIGNING ...
    上面定义了一个哈希内表,在读取时未使用主键,在大数据量的情况下速度会慢,所以在搜索字段上创建第二索引:
    DATA itab TYPE HASHED TABLE OF dbtab
    WITH UNIQUE KEY col1 col2 ...
    " 为非主键创建第二索引
    WITH NON-UNIQUE SORTED KEY second_key
    COMPONENTS col3 col4 ...
    " 向内表 itab 中添加大量的数据 ...
    READ TABLE itab " 根据第二索引进行搜索,会比上面程序快
    WITH TABLE KEY
    second_key
    COMPONENTS col3 = ... col4 = ...
    ASSIGNING ...
    " 在循环内表的 Where 条件中,如果内表不是排序内表,则不会使用二分搜索,如果使用 SORTED KEY ,则循环时, 会用到二分搜索?
    LOOP AT itab USING KEY second_key where col3 = ... col4 = ... .
    ENDLOOP .

    3.4. Unique Key Sort Hash 表中插入重得数据

    TYPES : BEGIN OF typ_tab,
    mandt
    TYPE t001-mandt,
    bukrs
    TYPE t001-bukrs,
    END OF typ_tab.

    DATA : lt_hash TYPE HASHED TABLE OF typ_tab WITH UNIQUE KEY mandt WITH HEADER LINE .
    DATA : lt_sort TYPE SORTED TABLE OF typ_tab WITH UNIQUE KEY mandt WITH HEADER LINE .

    lt_hash-mandt =
    '200' .lt_hash-bukrs = '200' .
    INSERT lt_hash INTO TABLE lt_hash.
    WRITE :/ 'sy-subrc:' ,sy-subrc.
    lt_hash-mandt =
    '100' .lt_hash-bukrs = '300' .
    INSERT lt_hash INTO TABLE lt_hash.
    WRITE :/ 'sy-subrc:' ,sy-subrc.
    lt_hash-mandt =
    '200' .lt_hash-bukrs = '400' .
    " 重复的无法插入,也不会覆盖,也不会抛异常
    INSERT lt_hash INTO TABLE lt_hash.
    WRITE :/ 'sy-subrc:' ,sy-subrc.

    SKIP .

    LOOP AT lt_hash.
    WRITE : / lt_hash-mandt,lt_hash-bukrs.
    ENDLOOP .
    WRITE :/ '-----------------------' .
    lt_sort-mandt =
    '200' .lt_sort-bukrs = '200' .
    INSERT lt_sort INTO TABLE lt_sort.
    WRITE :/ 'sy-subrc:' ,sy-subrc.
    lt_sort-mandt =
    '100' .lt_sort-bukrs = '300' .
    INSERT lt_sort INTO TABLE lt_sort.
    WRITE :/ 'sy-subrc:' ,sy-subrc.
    lt_sort-mandt =
    '200' .lt_sort-bukrs = '400' .
    " HASH 一样,重复的无法插入,也不会覆盖,也不会抛异常
    INSERT lt_sort INTO TABLE lt_sort.
    WRITE :/ 'sy-subrc:' ,sy-subrc.

    SKIP .

    LOOP AT lt_sort.
    WRITE : / lt_sort-mandt,lt_sort-bukrs.
    ENDLOOP .


    WRITE :/ '-----------------------' .

    " 不管是 Sort ,还是 Hash 表,只要是 Unique Key ,且 从数据库查出来的数据存在重复,则会 Dump
    *SELECT mandt bukrs INTO TABLE lt_hash FROM t001 CLIENT SPECIFIED WHERE mandt BETWEEN '100' AND'999'.
    *SELECT mandt bukrs INTO TABLE lt_sort FROM t001 CLIENT SPECIFIED WHERE mandt BETWEEN '100' AND'999'.

    另:针对 Unique Sort Hash 内表,可以使用 Collect 语句进行汇总统计

    另外:对于排序内表,不要使用 APPEND APPEND LINES 附加数据,要使用 INSERT INSERT LINES 向排序内表中插入数据,因为如果附加的数据不按排序内表排序规则来的话,会 Dump ,但使用 INSERT 就不会了

    3.5. 适合所有类型的内表操作

    COLLECT [<wa> INTO ] < itab> 将具有相同关键字段值的行中同名的数字字段的值累计到一条记录上 只有非表关键字段被累加 当在内表中找不到指定的被累加行时 COLLECT 语句的功能与 APPEND 语句是一样的 即将一个工作区的内容附加到 itab 内表中。使用 COLLECT 操作的内表有一个限制,即该的行结构中, 除了表键字段以外的所有字段都必须是数字型( i p f

    INSERT <wa> INTO TABLE <itab>. " 单条插入

    INSERT LINES OF <itab1> [ FROM <n1>] [ TO <n2>] INTO TABLE <itab2> " 批量插入

    UNIQUE 的排序表或哈希表插入 重复的数据 不会抛异常 但数据 不会被插入进去 这与 APPEND 是不一样的

    Hash 内表的 KEY 设置只能是 开头前部分定义的连续的组件字段 不能只将中间或先后几个字段设置为 KEY ,否则在查找时会出问题,数据无法查到

    " 只要根据关键字或索引在内表中读取到相应数据,不管该数据行是否与 COMPARING 指定的字段相符,都会存储到工作区

    READ TABLE <itab> WITH KEY {<k1> = <f1> ... <kn> = <fn>... [ BINARY SEARCH ] }

    INTO <wa> [ COMPARING <f1><f2> ...|ALL FIELDS]

    [ TRANSPORTING <f1><f2> ...|ALL FIELDS| NO FIELDS ]

    | ASSIGNING <fs>

    READ TABLE <itab> FROM <wa>… 以表关键字为查找条件 条件值来自 <wa>

    COMPARING 系统根据 <k1>...<kn> 关键字段 读取指定的单行与工作区 <wa> 中的相应组件进行比较。

    如果系统找根据指定 <k1>...<kn> 找到了对应的条目,且进行比较的字段内容相同,则将 SY-SUBRC 设置为 0 ,如果进行比较的字段内容不同,则返回值 2 ;如果系统根据 <k1>...<kn> 找不到条目,则包含 4 。如果系统找到条目,则无论比较结果如何,都将其读入 wa

    MODIFY TABLE <itab> FROM <wa> [ TRANSPORTING <f1> <f2> ...] " 修改单条 MODIFY TABLE < itab> 一般用在循环中 修改哈希表 ,且 itab 内表带表头) 。这里的 <wa> 扮演双重身份,不仅指定了要修改的行(条件),还包括要修改的新的值。系统以整个表的所有关键字段来搜索要修改的行; USING KEY :如果未使用此选项,则会使用默认的主键 primary table key 来修改相应的行;如果找到要修改的行,则将 <wa> 中所有非关键字段的内容拷贝到对应的数据行中对应的字段上;如果有多行满足条件时只修改第一条

    MODIFY <itab> FROM <wa> TRANSPORTING<f1><f2>... WHERE <cond> " 修改多条

    DELETE TABLE <itab> FROM <wa> " 删除单条 。多条时,只会删除第一条。条件为所有表关键字段,值来自 <wa>

    DELETE TABLE <itab> WITH TABLE KEY <k1> = <f1> ... " 删除单条 。多条时只会删除第一条 , 条件为所有表关键字

    DELETE itab WHERE ( col2 > 1 ) AND ( col1 < 4 ) " 删除多行

    DELETE ADJACENT DUPLICATES FROM <itab> [ COMPARING <f1><f2> ... | ALL FIELDS ]

    注,在未使用 COMPARING 选项时,要删除重复数据之前, 一定要按照内表关键字声明的顺序来进行排序,才能删除重复数据 ,否则不会删除掉;如果指定了 COMPARING 选项,则需要根据指定的比较字段顺序进行排序(如 COMPARING <F1><F2> 时,则需要 sort by <F1><F2> ,而不能是 sort by <F2><F1> ),才能删除所有重复数据 [əˈdʒeisənt] 邻近的 [ˈdju:plikit] 完全一样的 复制的

    3.6. 适合索引内表操作

    APPEND <wa> TO <itab>

    APPEND LINES OF <itab1> [ FROM <n1>] [ TO <n2>] TO <itab2>

    INSERT <wa> INTO <itab> INDEX <idx> " 如果不使用 INDEX 选项,则将新的行插入到当前行的前面,一般在 Loop 中可省略 INDEX 选项

    INSERT LINES OF <itab1> [ FROM <n1>] [ TO <n2>] INTO <itab2> INDEX <idx>

    APPEND/INSERT …INDEX 操作不能用于 Hash

    APPEND/INSERT…INDEX 用于排序表时条件:附加 / 插入时一定要按照 Key 的升序来附加;如果是 Unique 排序表, 则不能附加 / 插入重附的数据 ,这与 INSERT…INTO TABLE 是不一样的

    READ TABLE <itab> INDEX

    INTO <wa> [ COMPARING <f1><f2> ...|ALL FIELDS]

    [ TRANSPORTING <f1><f2> ...|ALL FIELDS| NO FIELDS ]

    | ASSIGNING <fs>

    MODIFY <itab> [ INDEX <idx> ] FROM <wa> [ TRANSPORTING <f1> <f2> ... ] " 如果没有 INDEX 选项,只能在 循环中使用该语句

    DELETE <itab> [ INDEX <idx>] " 删除单条 。如果省略 <index> 选项,则 DELETE <itab> 语句只能用在循环语句中

    DELETE <itab> [ FROM <n1>] [ TO <n2>] WHERE <condition> " 删除多条

    4. OPEN SQL

    4.1. SELECT INSERT UPDATE DELETE MODIFY

    如果从数据库读出来的数据存在重复时 不能存储到 Unique 内表中去 —— Unique 的排序表与哈希表

    SELECT SINGLE ... INTO [ CORRESPONDING FIELDS OF ] wa WHERE ...
    SELECT SINGLE <cols> ... INTO ( dobj1 , dobj2 ,
    ... ) WHERE ...

    SELECT ... FROM <tables> UP TO <n> ROWS ...

    SELECT ... INTO | APPENDING CORRESPONDING FIELDS OF TABLE <itab> ...

    单条插入 :在插入时是按照数据库表结构来解析 <wa> 结构,与 <wa> 中的字段名无关,所以 <wa> 的长度只少要等于或大于所对应表结构总长度

    INSERT INTO <tabname> VALUES <wa>

    INSERT <tabname> FROM <wa>

    多条插入 itab 内表的行结构也必须和数据库表的行结构一致; ACCEPTING DUPLICATE KEYS :如果现出关键字相同条目,系统将 SY-SUBRC 返回 4 ,并跳过该条目,但其他数据会插入进去

    INSERT <tabname> FROM TABLE < itab> [ ACCEPTING DUPLICATE KEYS ]

    单条更新: 会根据数据库表关键字来更新其他非关键字段。如果 WA 工作区是自己定义的且未参照数据库表,则 WA 的结构需要与数据库表相一致,且不能短于数据库表结构,但字段名可任意取

    UPDATE dbtab FROM wa

    多条更新: 主键不会被更新,即使在 SET 后面指定后也不会被更改

    UPDATE dbtab SET f1 = g1 … fi = gi WHERE <conditions>

    UPDATE dbtab FROM TABLE itab 与从 WA 工作区单条更新原理一样,根据数据表库关键字段来更新,且行结构要与数据库表结构一致,并且不能短于数据库表结构,一样内表行结构组件名可任意

    单条删除: 下面的 WA Itab 原理与 Update 是一样的

    DELETE dbtab FROM wa

    多条删除:

    DELETE dbtab FROM TABLE itab

    DELETE FROM dbtab WHERE <conditions>

    插入或更新: 下面的 WA Itab 原理与 Update 是一样的

    MODIFY dbtab FROM wa 单行

    MODIFY dbtab FROM TABLE itab 多行,有就修改,没有就插入

    4.2. 条件操作符

    = <> < <= > >=

    [ NOT ] BETWEEN ... AND
    [
    NOT ] LIKE
    [
    NOT ] IN
    IS [ NOT ] NULL

    4.3. RANG 条件内表

    两种定义方式:

    RANGES seltab FOR dobj [ OCCURS n]. 其中 dobj 自定义变量 或者是参照某个 表字段

    SELECT-OPTIONS selcrit FOR {dobj|(name)}

    上面两个语句会生成如下结构的内表,该条件内表的每一行都代表一个逻辑条件

    DATA : BEGIN OF seltab OCCURS 0 ,
    sign
    TYPE c LENGTH 1 , 允许值为 I E I 表示包含 Include E 表示排除 Exclude
    option
    TYPE c LENGTH 2 , OPTION 表示选择运算符
    low
    LIKE dobj ,
    下界 ,相当于 前面文本框 中的值
    high
    LIKE dobj , 上界 ,相当于 后面文本框中的值
    END OF rtab.

    option : HIGH 字段为空,则取值可以为: EQ = )、 NE <> )、 GT > )、 GE >= )、 LE <= )、 LT < )、 CP NP CP (集合之内的数据)和 NP (集合之外数据)只有当在输入字段中使用了通配符(“ * ”或“ + ”)时它们才是有效的

    SELECT ... WHERE ... field [NOT] IN seltab ...

    如果 RANG 条件内表为空 IN seltab 逻辑表达试恒为真 XX NOT IN seltab 恒为假

    注: 不会像 FOR ALL ENTRIES 那样 忽略其他的条件表达式 其他条件还是起作用

    4.4. FOR ALL ENTRIES

    1、 使用该选项后,对于最后得出的结果集系统 会自动删除重复行 。因此如果你要保留重复行记录时,记得在 SELECT 语句中添加足够字段

    2、 FOR ALL ENTRIES IN 后面使用的内部表 itab 如果 为空 ,将查出 当前 CLIENT 所有数据( 即忽略整个 WHERE 语句 ,其他条件都会被忽略)

    3、 内表中的条件字段 不能使用 BETWEEN LIKE IN 比较操作符

    4、 使用该语句时, ORDER BY 语句和 HAVING 语句 不能使用

    5、 使用该语句时, COUNT ( * ) (并且如果有了 COUNT 函数,则不能再选择其他字段,只能使用在 Select ... ENDSelect 语句中了)以外的 所有合计函数( MAX,MIN,AVG,SUM )都不能使用

    即使 Where 后面还有其它条件,所有的条件都会忽略

    SELECT vbeln posnr pstyv werks matnr arktx lgort waerk kwmeng
    FROM vbap INTO TABLE gt_so FOR ALL ENTRIES IN lt_matnr
    WHERE matnr = lt_matnr - matnr AND vbeln IN s_vbeln AND posnr IN s_posnr .

    如果上面的 lt_matnr 为空,则“ AND  vbeln IN s_vbeln AND posnr IN s_posnr ”条件也会忽略掉,即整个 Where 都会被忽略掉。

    SELECT matnr FROM mara INTO CORRESPONDING FIELDS OF TABLE strc
    FOR ALL ENTRIES IN strc WHERE matnr = strc - matnr .

    生成的 SQL 语句: SELECT "MATNR" FROM "MARA" WHERE "MANDT" = '210' AND "MATNR" IN ( '000000000000000101' , '000000000000000103' , '000000000000000104' )

    :这里看上去 FOR ALL ENTRIES 使用 IN 表达式来代替了,这是只有使用到内表中一个条件是这样的,如果使用多个条件时,不会使用 In 表达式,而是使用 OR 连接,像这样:

    另外,在使用 FOR ALL ENTRIES 时,不管使用了条件内表中的一个还是多个条件字段,都会以 5 值为单位进行 SQL 发送

    4.5. INNER JOIN LEFT OUTER JOIN 使用限制

    ON 后面的条件与 Where 条件类似,但有以下不同:

    ² 必需有 ON 条件语句,且多个条件之间只能使用 AND 连接

    ² 每个条件表达式中 两个操作数之中必须有一个字段是来自于 JOIN 右表

    ² 如果是 LEFT OUTER JOIN ,则 至少有一个条件表达式的两个操作数一个是来自于左表,另一个来自右表

    ² 不能使用 NOT LIKE IN (但如果是 INNER JOIN ,则 > < BETWEEN …AND <> 都可用)

    ² 如果是 LEFT OUTER JOIN ,则只能使用等号操作符: ( = EQ )

    ² 如果是 LEFT OUTER JOIN 同一右表不能多次出现在不同的 LEFT OUTER JOIN ON 条件表达式中

    ² LEFT OUTER JOIN 的右表所有字段不能出现在 WHERE

    ² 如果是 LEFT OUTER JOIN ,则在同一个 ON 条件语句中 只能与同一个左表进行关联

    4.6. 动态 SQL

    SELECT ( column_syntax ) FROM ...

    column :可以是内表,也可以是字符串

    TYPES : line_type TYPE c LENGTH 72 .
    DATA : column_syntax TYPE TABLE OF line_type .
    APPEND 'CARRID' TO column_syntax .
    APPEND 'CITYFROM CITYTO' TO column_syntax .

    SELECT ... FROM (dbtab_syntax)...

    PARAMETERS : p_cityfr TYPE spfli - cityfrom ,
    p_cityto
    TYPE spfli - cityto .
    DATA : BEGIN OF wa ,
    fldate
    TYPE sflight - fldate ,
    carrname
    TYPE scarr - carrname ,
    connid
    TYPE spfli - connid ,
    END OF wa .
    DATA itab LIKE SORTED TABLE OF wa
    WITH UNIQUE KEY fldate carrname connid .
    DATA : column_syntax TYPE string ,
    dbtab_syntax
    TYPE string .
    column_syntax
    = `c~carrname p~connid f~fldate` .
    dbtab_syntax
    = `( ( scarr AS c `
    &
    ` INNER JOIN spfli AS p ON p~carrid  = c~carrid`
    &
    ` AND p~cityfrom = p_cityfr `
    &
    ` AND p~cityto   = p_cityto )`
    &
    ` INNER JOIN sflight AS f ON f~carrid = p~carrid `
    &
    ` AND f~connid = p~connid )` .
    SELECT ( column_syntax ) FROM ( dbtab_syntax )
    INTO CORRESPONDING FIELDS OF TABLE itab .

    SELECT ... WHERE ( cond_syntax ) ...
    SELECT ... WHERE <cond> AND/OR ( cond_syntax ) ...

    DATA : cond ( 72 ) TYPE c ,
    itab
    LIKE TABLE OF cond .
    APPEND 'cityfrom = ''NEW YORK''' TO itab .
    APPEND 'or cityfrom = ''SAN FRANCISCO''' TO itab .
    SELECT * INTO TABLE itab_spfli FROM spfli WHERE ( itab ) .

    DATA : cond1 ( 72 ) TYPE c VALUE 'cityfrom = ''NEW YORK''' ,
    cond2
    ( 72 ) TYPE c VALUE 'cityfrom = ''SAN FRANCISCO''' .
    SELECT * INTO TABLE itab_spfli FROM spfli WHERE (cond1) OR (cond2) .

    DATA : cond ( 72 ) TYPE c ,
    cond1
    ( 72 ) TYPE c VALUE 'cityfrom = ''NEW YORK''' ,
    itab
    LIKE TABLE OF cond .
    APPEND 'cityfrom = ''SAN FRANCISCO''' TO itab .
    SELECT * INTO TABLE itab_spfli FROM spfli WHERE (itab) OR (cond1) .

    DATA : cond ( 72 ) TYPE c ,
    itab
    LIKE TABLE OF cond .
    APPEND 'cityfrom = ''SAN FRANCISCO''' TO itab .
    SELECT * INTO TABLE itab_spfli FROM spfli WHERE (itab) OR cityfrom = 'NEW YORK'

    4.7. 子查询

    colum operator [ALL|ANY|SOME] [NOT] EXISTS [NOT] IN 连接至 WHERE 从句与 HAVING 从句中

    4.7.1. = <> < <= > >= 子查询

    子查询的 SELECT 只有 一个表字段 或者是 一个统计列 ,并且 只能返回一条数据

    SELECT * FROM sflight INTO wa_sflight
    WHERE seatsocc = ( SELECT MAX ( seatsocc ) FROM sflight ).
    ENDSELECT .

    操作符可以是: = <> < <= > >=

    4.7.1.1. ALL ANY SOME

    如果子查询 返回的是多条 ,则使用 ALL ANY SOME 来修饰

    SELECT customid COUNT ( * ) FROM sbook INTO (id, cnt) GROUP BY customid
    HAVING COUNT ( * ) >= ALL ( SELECT COUNT ( * ) FROM sbook GROUP BY customid ).
    ENDSELECT .

    ² ALL :主查询数据大于 所有 子查询返回的行数据时,才为真

    ² ANY|SOME :主查询数据只要大于 任何 一条子查询返回的行数据时,才为真

    ² = ANY|SOME :等效 IN 子查询

    4.7.2. [NOT] IN 子查询

    此类子查询 SELECT 中也只有单独的 一列 选择列,但查询出的 结果可能有多条

    SELECT SINGLE city latitude longitude INTO (city, lati, longi) FROM sgeocity
    WHERE city IN ( SELECT cityfrom FROM spfli
    WHERE carrid = carr_id AND connid = conn_id ).

    4.7.3. [NOT] EXISTS 子查询

    这类 子查询没有返回值 ,也 不要求 SELECT 从句中只有一个选择列 ,选择列可以任意个数, WHERE 或者 HAVING 从句根据该子查询的是否查询到数据来决定外层主查询语句来选择相应数据

    SELECT carrname INTO TABLE name_tab FROM scarr
    WHERE EXISTS ( SELECT * FROM spfli
    WHERE carrid = scarr~carrid AND cityfrom = 'NEW YORK' ).

    4.7.4. 相关子查询

    上面的示例子查询即为 相关子查询

    如果某个子查的 WHERE 条件中引用了外层查询语句的列,则称此子查询为相关子查询。相关子查询对外层查询结果集中的每条记录都会执行一次,所以尽量少用相关子查询

    4.8. 统计函数

    MAX MIN AVG SUM COUNT ,聚合函数都可以加上 DISTINCT 选项

    4.9. 分组过滤

    如果将统计函数与 GROUP BY 子句一起使用,那么 Select 语句中未出现在统计函数的数据库字段都必须在 GROUP BY 子句中出现 。如果使用 INTO CORRESPONDING FIELDS 项,则需要在 Select 语句中通过 AS 后面的别名将统计结果存放到与之相应同名的内表字段中:

    SELECT MIN ( price ) AS m INTO price FROM sflight GROUP BY carrid
    HAVING MAX( price ) > 10 . Having 从句中比较统计结果时,需要将统计函数重写一遍,而不能使用 Select 中定义的别名
    ENDSELECT .

    4.10. 游标

    DATA : c TYPE cursor . [ˈkɜ:sə]
    DATA : wa TYPE spfli.
    " 1 打开游标
    OPEN CURSOR : c FOR SELECT carrid connid FROM spfli WHERE carrid = 'LH' .
    DO .
    " 2 、读取数据
    FETCH NEXT CURSOR c INTO CORRESPONDING FIELDS OF wa.
    IF sy-subrc <> 0 .
    " 3 、关闭游标
    CLOSE
    CURSOR c .
    EXIT .
    ELSE .
    WRITE : / wa-carrid, wa-connid.
    ENDIF .
    ENDDO .

    4.11. 三种缓存

    l 单记录缓存 :从数据库中仅读取一条数据并存储到 table buffer 中。此缓存只对 SELECT SINGLE… 语句起作用

    l 部分缓存 :需要在指定 generic key (即关键字段组合,根据哪些关键字段来缓存,可以是部分或全部关键字段)。 如果主键是由一个字段构成,则不能选择此类型缓存 。当你使用 generic key 进行数据访问时,则属于此条件范围的整片数据都会被加载到 table buffer

    1 查询时如果使用 BYPASSING BUFFER 选项,除了绕过缓存直接到数据库查询外,查出的数据不会放入缓存

    2 只要查询条件中出现了用作缓存区域的所有关键字段,则查询出所有满足条件全部数据进行缓存

    3 如果查询条件中 generic key 只出现某个或者某部分,则不会进行缓存操作

    4 如果主键是只由一个字段组成,则不能设定为此种缓存

    5 如果有 MANDT 字段,则为 generic key 的第一个字段

    l 全部缓存 :在第一次读取表数据时,会将整个表的数据都会缓存下来,不管 WHERE 条件

    4.12. Native SQL

    4.12.1. 查询

    DATA : BEGIN OF wa ,
    connid
    TYPE spfli - connid ,
    cityfrom
    TYPE spfli - cityfrom ,
    cityto
    TYPE spfli - cityto ,
    END OF wa .
    DATA c1 TYPE spfli - carrid VALUE 'LH' .

    " Native SQL 语句不能以句点号结尾

    " 不能在 EXEC SQL…ENDEXEC 间有注释 ,即不能有星号与双引号的出现;

    " 参数占位符使用冒号,而不是问号;

    EXEC SQL PERFORMING loop_output .
    SELECT connid , cityfrom , cityto
    INTO : wa

    " 或使用: INTO :wa-connid ,:wa-cityfrom ,:wa-cityto
    FROM spfli
    WHERE carrid = : c1
    ENDEXEC.

    FORM loop_output .
    WRITE : / wa - connid , wa - cityfrom , wa - cityto .
    ENDFORM

    4.12.2. 存储过程

    EXEC SQL.
    EXECUTE PROCEDURE proc1 ( IN : x, OUT : y, INOUT : z )
    ENDEXEC .

    4.12.3. 游标

    DATA : arg1 TYPE string VALUE '800' .
    TABLES : t001 .
    EXEC SQL .
    OPEN c1 FOR SELECT MANDT , BUKRS FROM T001 " 打开游标
    WHERE MANDT = : arg1 AND BUKRS >= 'ZA01'
    ENDEXEC .
    DO .
    EXEC SQL .
    FETCH NEXT c1 INTO : t001 - mandt , : t001 - bukrs " 读取游标
    ENDEXEC .
    IF sy - subrc <> 0 .
    EXIT .
    ELSE .
    WRITE : / t001 - mandt , t001 - bukrs .
    ENDIF .
    ENDDO .
    EXEC SQL .
    CLOSE c1 " 关闭游标
    ENDEXEC .

    4.13. SAP

    通用数据库表锁函数: ENQUEUE_E_TABLE DEQUEUE_E_TABLE DEQUEUE_ALL (解锁所有) [kju:]

    特定数据库表锁函数: EN QUEUE _<LOCK OBJECT> DENQUEUE _<LOCK OBJECT>

    自定义的锁对象都必须以 EZ_ 或者 EY_ 开头来命名

    括号内为同一程序(即同一事务内)内,括号外为非同一程序内

    CALL FUNCTION 'ENQUEUE _EZ_ZSPFLI' " 加锁
    EXPORTING
    mode_zspfli
    = 'E'
    mandt
    = sy - mandt
    carrid
    = 'AA'
    connid
    = '0011'
    *   X_CARRID             = ' '" 设置字段初始值( Initial Value ),若为 X ,则当遇到与 CARRID 的初始值 Initial Value 相同值时才会设置锁对象。 CARRID 的初始值只需在数据库表字段中选择 Initial Value 选项 (SE11 中设置 ) 。当没有设置 X 时,则会用该锁函数所设置的 Default Value 指定初始值
    *   X_CONNID             = ' '
    *   _SCOPE               = '2'" 该选项只有在 UPDATE 函数( CALL FUNCTION FM IN UPDATE TASK )中才起作用,用来控制锁的传递。一般当调用解锁函数 DEQUEUE 或程序结束时( LEAVE PROGRAM 或者 LEAVE TO TRANSACTION )锁就会被自动解除,另外,遇到 A X 消息时或用户在命令框中输入 /n 时锁也会被解除,但是,当事务码正在执行 UPDATE 函数时就不一样了,函数结束时是否自动解除锁则要看该选项 _SCOPE 了:

    1- 表示程序内有效 2- 表示 update module 内有效 3- 全部
    *   _WAIT                = ' '" 表示如果对象已经被锁定 , 是否等待后再尝试加锁
    *   _COLLECT             = ' '" 参数表示是否收集后进行统一提交

    程序锁定: ENQUEUE_ ES_PROG DEQUEUE_ES_PROG

    5. SAP/DB LUW

    5.1. DB LUW

    DB LUW Logic Unit Work )是确保数据库更新一致性的机制,是数据库级别的,和底层 DBMS 有关,和 SAP 系统无关。如下图,从一致性状态 A B ,中间有一系列的数据库操作,一个 BD luw 以数据库提交 commit 结束,这些操作要么全都执行,要么全都不执行。当全部执行成功,则数据库进入一致性状态 B ,如果在此 DB luw 中发生错误,则将从 DB luw 开始的所有操作进行回滚,数据库还是在 A 状态。

    这是在数据库级别实现的,和 SAP 系统无关。

    SAP 系统中, DB luw commit and rollback 可以被显式或隐式的触发。

    不管是 commit 还是 rollback ,结束了一个 DB luw ,也即是开始了一个新的 DB luw

    5.1.1. 显式提交

    · Native SQL 提交语句

    · Calling the function module DB_COMMIT .

    · COMMIT WORK 语句

    5.1.2. 隐式提交

    · 对话屏幕结束时(跳转另一屏幕)

    · 同步或异步方式远程调用 RFC

    CALL FUNCTION func DESTINATION dest

    CALL FUNCTION func STARTING NEW TASK DESTINATION dest taskname

    RFC 事务调用不会触发隐式提交: CALL FUNCTION func IN BACKGROUND TASK DESTINATION dest

    · RFC 异步执行结果回调 Form RECEIVE 语句: CALL FUNCTION rfm_name STARTING NEW TASK DESTINATION dest taskname PERFORMING return_form ON END OF TASK 中的 RECEIVE RESULTS FROM FUNCTION rfm_name 语句会触发

    · WAIT UNTIL log_exp [ UP TO sec SECONDS ] 语句

    · Sending error messages E , information messages I , and warnings W .

    5.1.3. 显示回滚

    · 使用 Native-SQL 进行回滚

    · 使用 ROLLBACK WORK. 进行回滚

    5.1.4. 隐式回滚

    · runtime error :在程序执行过程中 未捕获 异常 以及 message type X 的消息,因为 runtime error 会导致程序的 termination

    · 因为 发送的 Message 而导致程序的 termination (除了 A X 类型的消息会导致程序终止外,其他类型的消息在特定的情况下也会导致程序终止,具体请参见《 User Dialogs.docx 》中的 消息显示及处理 章节)

    Runtime Error :系统不能继续运行,并且 ABAP 程序终止,以下情况会发生运行时错误:

    ² 未处理的异常(当一个能够被处理的异常却未被处理时;抛出了一个不能够处理的异常)

    ² X 类型消息

    ² ASSERT 断言语句不成立时

    5.2. SAP LUW

    sap 系统中一个业务操作会有多个对话屏幕,只到 save 操作成功,才算完成了一个业务。那么仅使用 DB luw 是不能保证 SAP 系统数据一致性的。如下图,如果只是最后屏幕 300 保存时的 DB luw 发生错误,那么在数据库级一致性机制作用下只能回滚这一个 db luw 使数据库处于 g 状态,而前几个 db luw 在屏幕结束时已经提交,进行的数据库更新会全部生效,从业务层面上讲,这是不合理的,因为这个业务并没有保存成功,我们需要回滚到 A a 状态。 SAP luw 就是 sap DB luw 基础上保证数据库一致性的一种机制。

    上图( 未使用 SAP LUW ),绿色表示数据库更新操作。每个 dialog steps 都会形成一个单独的 DB LUW 。默认情况下(未通过 SAP LUW 的绑定方式来实现延迟执行数据库更新操作),每个 DB LUW 都会提交事务

    上图( 将多个 DB LUW 绑定成一个 SAP LUW ),将每个 dialog steps (即 DB LUW )中的数据库操作“移”到最后一个 dialog steps (即 DB LUW )中执行,这里的“移”实质上是一种延迟执行的方式,即先将前面每个 dialog steps 中的数据库更新操作记录下来,待 最后统一由 COMMIT 来提交。

    SAP LUW 可以跨多个 DB LUW ,是一个业务逻辑上的概念,可人为的去定义开始与结束,而 DB LUW 是不能人为控制的

    SAP LUW DB LUW 的一个增强 ,受体系结构限制, SAP 程序每次屏幕切换时 ( 控制权从后台 DIALOG 进程转移到前台 GUI Session) ,都会触发一个隐式的数据库提交, 一个程序在运行是会产生多个 DB LUW ,这样无法做到全部提交或全部回滚,在某些业务场景下,这种事务的提交机制不足以保证数据的一致性,为此有了 SAP LUW 机制。

    SAP LUW 是一种延迟执行的技术,它将本来需要执行的程序块,记录下来 (通过特定的方式来调用来实现 SAP LUW 的绑定: perform XXX on commit update Funciton module ,待系统在执行 COMMIT WORK 的时候会查询记录,真正执行需要运行的代码。 COMMIT WORK 一般在最后一个屏幕执行,这样就实现了将跨屏幕的数据更新逻辑绑定到一个 DBLUW 中( 在后台会自动将这一系列的数据操作绑定在同一个数据事务 DBLUW 里,这样当提交出问题后,数据库事务 DBLUW 会自动回滚,这样就保证了数据的一致性 ),实现复杂情况数据更新的一致性

    5.2.1. SAP LUW 的绑定方式

    5.2.1.1. Function

    可以使用 CALL FUNCTION update_function IN UPDATE TASK 将多个数据更新绑定到一个 database LUW

    此类型的更新函数 Update Function 中主要 实现对数据库数据进行更新操作

    ² Immediate start :表示 V1 方式,将此更新函数设置为高优先级 V1 并运行在同一个 SAP LUW 中。 更新出错后,可以在 SM13 里重新执行

    ² Immediate start -no restrat possible V1 方式,将此更新函数设置为高优先级 V1 并运行在同一个 SAP LUW 中。 出错后不可以在 SM13 里重新执行

    ² Start delayed V2 方式,将此更新函数设置为低优先级 V2 run in their own update transactions V1 方式更新完成后触发。出错后更新函数可以重启

    ² Collective run V2 方式,将此更新函数设置为低优先级 V2 run in their own update transactions 。需使用 Collective(RSM13005) 程序手动或 JOB 方式执行

    V1 V2 区别:

    ² V1 优先级高于 V2 V2 被设计为依赖于 V1 ,适合执行需要在 V1 完成后进行的操作

    ² V1 更新使用 V1 进程处理, V1 进程名字一般为 UPD V1 进程绑定独立的数据库进程。在 V1 进程中调度的更新函数如果更新失败,回滚,不再进行 V2 操作。成功则提交更改到数据库,同时删除所有的 SAP

    ² V2 更新使用 V2 进程处理,如果没有配置 V2 进程则共用 V1 进程, V2 进程名字为 UP2 V2 更新在独立 DB LUW 中, V2 更新回滚后不会影响到 V1 更新提交的数据,由于 V1 更新结束后会删除 SAP 的锁,所以 V2 更新是在没有逻辑锁的情况下进行的, V2 更新出错后可以在 SM13 中重新执行

    ² V1 的执行模式可以为 异步、同步 本地 V2 只能为异步执行

    CALL FUNCTION ... IN UPDATE TASK Function 并不会立即执行 ,这只是先将它记录到内存(本地方式)或数据库表中(非本地方式),等到 COMMIT WORK 语句执行时才真正执行。 如果在程序执行过程中, 没有在 Update Function 后面执行 COMMIT WORK ,则 function module 不会执行

    此种类型的输入参数只能是值传递方式,不允许为引用传递,所以输入参数对应的 “传递值”要钩上 ,并且传递的内容还不能是地址,即不允许为 TYP EREF TO 方式,只能是 TYPE 方式

    5.2.1.2. subroutine

    PERFORM subr ON { { COMMIT [ LEVEL idx]} | ROLLBACK } .

    子过程 subr 不会被马上执行,而是直到 COMMIT WORK 或者是 ROLLBACK WORK 。可以使用 LEVEL 参数指定优先级 , 优先级按升序进行排列 , 较小的会优先执行。 如果同样名字的 subroutine 被注册了多次 , COMMIT WORK 时只执行一次 ,IN UPDATE TASK 方式执行的 Funciton 没有这个限制

    由于子过程绑定不需要写数据库表,所以比较更新函数绑定,性能上要高一些,缺点是你不能传递任何参数,它们所使用的必须是 global data object ,或可通过 ABAP memory 来共享参数

    5.2.2. 开启新的 SAP LUW

    没有专门类似开启数据库事务的语句( Begin trasaction ), SAP LUW 会在以下情况自动开启:

    ² 提交 COMMIT 或回滚 ROLLBACK 后会开启另一个新的 SAP LUW 事务

    ² 事务调用 会重新开启一个新的 SAP LUW

    上图中 F2 不会被执行,因为它处于另一个事务中执行,但没有使用 COMMIT WORK ,而 F1 F3 会被执行,因为主调程序中使用了 COMMIT WORK 。所以 事务调用会重新开启一个新的 SAP LUW

    dialog modules 与上面调用事务( CALL transaction )、调用 executable programs (reports) 不同的是: dialog modules 中不会开启新的 SAP LUW ,如果 dialog modules 中调用了 update function modules CALL FUNCTION ... IN UPDATE TASK ),则要等到主调程序中的 COMMIT WORK 时才会真正执行,哪怕在 dialog modules 中调用了 COMMIT WORK 也是没有用(不会启动 update task )。

    另外, 由于数据共享问题,尽量不要在 dialog modules 中使用 PERFORM XXX ON COMMIT

    5.2.3. 同步或异步更新(提交)

    COMMIT WORK 异步更新 ,该语句执行后不会等待所有更新函数都执行完后才继续执行后面的代码

    COMMIT WORK AND WAIT 同步更新 ,该语句执行后会等待所有更新函数都执行完后才继续执行后面的代码, 执行结果可以通过 sy-subrc 来判断事务提交是否成功

    5.2.4. 本地 、非本地方式提交

    本地方式 如果在调用 UPDATE FUNCTION 之前,先调用 SET UPDATE TASK LOCAL . 语句 ,这样所有在该语句后使用 CALL FUNCTION...IN UPDATE TASK 注册的更新函数不会记录到数据库中 , 而是记录在内存中 , Commit work 之后 , 会从内存取得待执行的函数。在默认情况下, local update 不会被设置,在使用 COMMIT WORK 之后 SET UPDATE TASK LOCAL 的效果会被清除掉

    本地方式 更新只能采用 同步方式 ,即使没有在 Commit work 后指定了 and wait 参数 , 仍然是同步执行

    非本地方式 未使用 SET UPDATE TASK LOCAL. 语句。此方式下,注册的 Update Function 的名字以及接口实参都会以日志的形式记录到特殊的表。 非本地方式即可同步也可异步 COMMIT

    ² 本地方式 不将待执行的更新函数写到数据表中,减少了 I/O 操作,效率上较高,但由于采用的是同步方式,程序需等待更新结果,用户交互时的会感觉程序运行较慢

    ² 非本地方 式会将更新结果记录到数据表中,可以通过 SM13 查看更新情况,同时由于可以进行异步更新,用户交互时感觉会比较快

    6. 逻辑数据库

    6.1. 组成

    6.2. 结构

    决定了数据从哪些数据库表、视图中提取数据 以及这些表、视图之间的层次关系( 层次将决定数据读取的顺序

    数据库表 T 类型节点)、 词典类型 S 类型节点),比如节点类型为 S 的节点: root_node ,数据类型为 INT4

    LDB 的数据库程序的最 TOP Include 文件包括以下语句:

    NODES root_node .

    另外,在 LDB 数据库程序包括了以下过程:

    FORM put _ root_node .
    DO 10 TIMES .
    root_node
    = sy - index .
    PUT root_node . " 会去调用报表程序中的 GET root_node. 事件块
    ENDDO .
    ENDFORM .

    在与此 LDB 关连的可执行程序:

    REPORT demo_nodes .
    NODES root_node .
    GET root_node .
    WRITE root_node .

    6.3. 选择屏幕( Selections

    定义了 LDB 的选择屏幕,该选择屏幕的布局由 LDB 的结构决定,一旦将 LDB 链接到报表程序后,该选择屏幕会自动嵌入到默认选择屏幕 1000

    第一次进入选择屏幕程序时,系统会为每个 LDB 生成一个名为 DB< LDB_Name >SEL Include 选择屏幕包含文件:

    而且,所有表( T 类型的节点)的主键都会出现在 SELECT-OPTIONS 语句中,成为屏幕选择字段(自动生成的需要去掉注释,并设置屏幕选择字段名):

    除了上面自动生成的 LDB 屏幕字段外,还可以使用以下面语句来扩展 LDB 选择屏幕:

    6.3.1. PARAMETERS 屏幕参数扩充

    增加一个单值输入条件框( PARAMETERS 语句一般在 LDB 中只用于除节点表外的非表字段屏幕参数),在 PARAMETERS 语句中必须使用选项 FOR NODE XXX 或者 FOR TABLE XXX 来指定这些扩展参数属性哪个节点的: PARAMETERS CITYTO LIKE SPFLI-CITYTO FOR NODE SPFLI .

    注: SELECT-OPTIONS 没有 FOR NODE 这样的用法

    具体请参数后面的 LDB 选择屏幕章节

    6.3.2. SELECTION-SCREEN 格式化屏幕

    使用 SELECTION-SCREEN 语句来格式化屏幕

    具体请参数后面的 LDB 选择屏幕章节

    6.3.3. DYNAMIC SELECTIONS 动态选择条件

    SELECTION-SCREEN DYNAMIC SELECTIONS FOR NODE|TABLE <node>. 用来开启 <node> 节点的 LDB dynamic selections 功能,即可以在 WHERE 从句中使用动态选择条件(形如: …WHERE field1 = value1 AND ( 条件内表 ) … 只有开启了动态选择条件功能的表,才可以在 LDB 数据库程序中对表进行动态选择条件处理。下面是数据库程序中如何使用动态选择条件示例:

    上面 LDB 数据库程序中的 RSDS_WHERE 条件内表来自 RSDS 类型组,相应源码如下:

    另外,上面 LDB 数据库程序中要能从 DYN_SEL-CLAUSES 内表读取数据,则必须在 LDB 选择屏幕里开启相应节点的动态选择条件:

    其中, DYN_SEL-CLAUSES 内表行结构如下:

    6.3.3.1. DYN_SEL

    PUT_<node> Form 中的 SELECT 语句中 Where 从句如果要使用 DYNAMIC SELECTIONS 动态选择条件 时,需要用到 变量 DYN_SEL ,该数据对象是在 LDB 数据库程序中自动生成的,其类型如下(注:不必在 LDB 程序中加入下面代码行就可以直接使用 DYN_SEL ):

    TYPE-POOLS RSDS .
    DATA DYN_SEL TYPE RSDS_TYPE .

    你不必在程序中定义它就可以直接使用,但它 只能在 LDB 数据库程序中使用,而不能用在报表程序中 RSDS_TYPE 数据类型是在类型组 RSDS 中定义的:

    TYPE-POOL RSDS .
    TYPES : RSDS_WHERE_TAB LIKE
    RSDSWHERE OCCURS 5 . " RSDSWHERE 类型为 C(72)
    TYPES : BEGIN OF RSDS_WHERE ,
    TABLENAME
    LIKE RSDSTABS - PRIM_TAB ,
    WHERE_TAB
    TYPE RSDS_WHERE_TAB ,
    END OF RSDS_WHERE .
    TYPES : RSDS_TWHERE TYPE RSDS_WHERE OCCURS 5 .

    TYPES : BEGIN OF RSDS_TYPE ,
    CLAUSES TYPE RSDS_TWHERE ,
    TEXPR
    TYPE RSDS_TEXPR ,
    TRANGE TYPE RSDS_TRANGE ,
    END   OF RSDS_TYPE .

    RSDS_TYPE 是一个深层结构的结构体,里面三个字段都是内表类型,其中以下两个字段重要:

    6.3.3.1.1. RSDS_TYPE-CLAUSES

    Where 从句部分,实则存储了可直接用在 WHERE 从句中的动态 Where 条件内表,可以在 Where 动态语句中直接使用,该组件为内表,存储了用户在选择屏幕上选择的 LDB 动态选择字段

    每个被选择的 LDB 屏幕动态选择字段都会形成一个条件,并存储到 RSDS_TYPE-CLAUSES-WHERE_TAB 内表中, WHERE_TAB 内表中存储的就是直接可以用在 Where 从句中的动态选择条件中

    每个表(节点)都会有自己的 CLAUSES-WHERE_TAB 动态条件内表,这是通过 CLAUSES-TABLENAME 区别的

    现假设有名为 ZHK LDB SCARR 为该 LDB 的根节点,且仅有 SPFLI 一个子节点。 LDB 选择屏幕 Include 文件 DBZHKSEL 内容如下:

    SELECT-OPTIONS S_CARRID FOR SCARR - CARRID .
    SELECT-OPTIONS S_CONNID FOR SPFLI - CONNID .

    " 需要先开始动态选择条件功能
    SELECTION-SCREEN DYNAMIC SELECTIONS FOR TABLE SCARR .

    LDB 数据库程序 SAPDBZHK 中, PUT_SCARR 过程中使用 dynamic selection 的过程如下:

    FORM PUT_SCARR .
    STATICS : DYNAMIC_SELECTIONS TYPE RSDS_WHERE , FLAG_READ . " 定义成静态类型的是防止再次进入此 Form 时,再次初始化 DYNAMIC_SELECTIONS 结构,即只执行一次初始化代码
    IF FLAG_READ = SPACE .
    DYNAMIC_SELECTIONS
    - TABLENAME = 'SCARR' .
    READ TABLE DYN_SEL - CLAUSES WITH KEY DYNAMIC_SELECTIONS-TABLENAME INTO DYNAMIC_SELECTIONS .
    FLAG_READ
    = 'X' .
    ENDIF .
    SELECT * FROM SCARR WHERE CARRID IN S_CARRID AND ( DYNAMIC_SELECTIONS - WHERE_TAB ) . " 使用动态 Where 条件
    PUT SCARR .
    ENDSELECT .
    ENDFORM.

    6.3.3.1.2. RSDS_TYPE-TRANGE

    该字段是一个内表,存储了 CLAUSES 的原数据, CLAUSES 内表里的数据实质就是来源于 TRANGE 内表,只是 CLAUSES 已经将每个表字段的条件拼接成了一个或多个条件串了(形如:“ XXX 字段 = XXX 条件”),但是 TRANGE 内表与 RANGES tables 相同,存储的是字段最原始的条件值,使用时,在 WHERE 从句中使用 IN 关键字来使用这些条件值(这与 SELECT-OPTIONS 类型的屏幕参数用户是完全一样的)。

    但是,使用 TRANGE 没有直接使用 CLAUSES 灵活,因为使用 TRANGE 时, WHERE 从句里的条件表字段需要事先写好,这实质上不是动态条件了,可以参考以下实例,与上面 CLAUSES 用法相比就更清楚了:现修改上面的示例:

    SELECT-OPTIONS S_CARRID FOR SCARR - CARRID .
    SELECT-OPTIONS S_CONNID FOR SPFLI - CONNID .

    " 需要先开始动态选择条件功能
    SELECTION-SCREEN DYNAMIC SELECTIONS FOR TABLE SCARR .

    LDB 数据库程序 SAPDBZHK 中, PUT_SCARR 过程中使用 dynamic selection 的过程如下:

    FORM PUT_SCARR .
    STATICS : DYNAMIC_RANGES TYPE RSDS_RANGE , " 存储某个表的所有屏幕字段的 Ranges
    DYNAMIC_RANGE1 TYPE RSDS_FRANGE , " 存储某个屏幕字段的 Ranges
    DYNAMIC_RANGE2 TYPE RSDS_FRANGE ,
    FLAG_READ . " 确保 DYN_SEL 只读取一次
    IF FLAG_READ = SPACE .
    DYNAMIC_RANGES
    - TABLENAME = 'SCARR' .
    " 先取出 SCARR 表的所有屏幕字段的 Ranges
    READ TABLE DYN_SEL - TRANGE WITH KEY DYNAMIC_RANGES - TABLENAME INTO DYNAMIC_RANGES .
    " 再读取出属于某个字段的 Ranges
    DYNAMIC_RANGE1
    - FIELDNAME = 'CARRNAME' .
    READ TABLE DYNAMIC_RANGES - FRANGE_T WITH KEY DYNAMIC_RANGE1 - FIELDNAME
    INTO DYNAMIC_RANGE1 .
    DYNAMIC_RANGE2
    - FIELDNAME = 'CURRCODE' .
    READ TABLE DYNAMIC_RANGES - FRANGE_T WITH KEY DYNAMIC_RANGE2 - FIELDNAME
    INTO DYNAMIC_RANGE2 .
    FLAG_READ
    = 'X' .
    ENDIF .
    SELECT * FROM SCARR
    WHERE CARRID IN S_CARRID
    AND CARRNAME IN DYNAMIC_RANGE1- SELOPT_T " 使用 IN 关键字使用 Ranges 内表
    AND CURRCODE IN DYNAMIC_RANGE2 - SELOPT_T . " (与 select-options 屏幕参数是一样的用法)
    PUT SCARR .
    ENDSELECT .
    ENDFORM.

    6.3.4. FIELD SELECTION 动态选择字段

    SELECTION-SCREEN FIELD SELECTION FOR NODE|TABLE <node>. 语句的作用是开启节点 <node> 的动态字段选择的功能(形如: SELECT (选择字段内表) FROM… ,而不是 SELECT * FROM … 即选择了哪些字段,就只查询哪些字段 ,而不是将所有字段查询出来,进而可以提高性能)

    在可执行报表程序里,可以通过 GET node [ FIELDS f1 f2 ... ] 语句中的 FIELDS 选项来指定要读取字段;

    另外,上面 LDB 数据库程序中要能从 SELECT_FIELDS 内表读取数据,则必须在 LDB 选择屏幕里开启相应节点的动态选择字段:

    其中, SELECT_FIELDS 内表行结构如下:

    6.3.4.1. SELECT_FIELDS

    PUT_<node> Form 中的 SELECT 语句中 Where 从句如果要使用 FIELD SELECTION 动态选择字段 时,需要用到数据对象 SELECT_FIELDS ,在 LDB 数据库程序中,通过从 SELECT_FIELDS 内表中就可以读取 GET node [ FIELDS f1 f2 ...] 语句传递进来的选择字段, SELECT_FIELDS LDB 数据库程序自动生成的,其类型如下(不必在 LDB 程序中加入下面代码行,直接就可以使用 SELECT_FIELDS 内表, 另外在相连的报表程序中也可以使用,这与 DYN_SEL 不同 ):

    TYPE-POOLS RSFS .
    DATA SELECT_FIELDS TYPE RSFS_FIELDS .

    RSDS_FIELDS 中的 FIELDS 里存储的就是 GET…FIELDS… 语句传递过来的用户指定的查询字段, FIELDS 内表可以直接使用在 SELECT… 从句中。

    现假设有名为 ZHK LDB SCARR 为该 LDB 的根节点,且仅有 SPFLI 一个子节点。 LDB 选择屏幕 Include 文件 DBZHKSEL 内容如下:

    SELECT-OPTIONS S_CARRID FOR SCARR - CARRID .
    SELECT-OPTIONS S_CONNID FOR SPFLI - CONNID .
    " 需要先开始动态选择字段功能
    SELECTION-SCREEN FIELD SELECTION FOR TABLE SPFLI .

    LDB 数据库程序 SAPDBZHK 中, PUT_SCARR 过程中使用 dynamic selection 的过程如下:

    FORM PUT_SPFLI .
    STATICS : FIELDLISTS TYPE RSFS_TAB_FIELDS ,
    FLAG_READ
    . " 确保 SELECT_FIELDS 只读取一次
    IF FLAG_READ = SPACE .
    FIELDLISTS
    - TABLENAME = 'SPFLI' .
    " 读取相应表的动态选择字段
    READ TABLE SELECT_FIELDS WITH KEY FIELDLISTS - TABLENAME INTO FIELDLISTS .
    FLAG_READ
    = 'X' .
    ENDIF .
    SELECT ( FIELDLISTS- FIELDS ) " 动态选择字段
    INTO CORRESPONDING FIELDS OF SPFLI FROM SPFLI
    WHERE CARRID = SCARR - CARRID AND CONNID IN S_CONNID .
    PUT SPFLI .
    ENDSELECT .
    ENDFORM.

    在相应的可执行报表程序里,相应的代码可能会是这样的:

    TABLES SPFLI .
    GET SPFLI FIELDS CITYFROM CITYTO .
    ...

    GET 语句中的 FIELDS 选项指定了除主键外需要查询来的字段, 主键不管是否选择都会被从数据库表中读取出来 ,可以由下面报表程序中的代码来证明

    DATA : ITAB LIKE SELECT_FIELDS ,
    ITAB_L
    LIKE LINE OF ITAB ,
    JTAB
    LIKE ITAB_L - FIELDS ,
    JTAB_L
    LIKE LINE OF JTAB .
    START-OF-SELECTION .
    ITAB
    = SELECT_FIELDS . " 在报表程序中也可以直接使用 LDB 程序中的全局变量!
    LOOP AT ITAB INTO ITAB_L .
    IF ITAB_L - TABLENAME = 'SPFLI' .
    JTAB
    = ITAB_L - FIELDS .
    LOOP AT JTAB INTO JTAB_L .
    WRITE / JTAB_L .
    ENDLOOP .
    ENDIF .
    ENDLOOP .

    如果报表程序中的 GET 语句是这样的: GET SPFLI FIELDS CITYFROM CITYTO. ,则输入结果为:

    CITYTO

    CITYFROM

    MANDT

    CARRID

    CONNID

    可以从输出结果看出,主键 MANDT CARRID CONNID 会自动的加入到 SELECT_FIELDS 内表中,一并会从数据库中读取出来

    6.4. 数据库程序中重要 FORM

    FORM INIT

    在选择屏幕处理前仅调用一次(在 PBO 之前调用)

    FORM PBO

    在选择屏幕每次显示之前调用,即 LDB 选择屏幕的 PBO 事件块

    FORM PAI

    用户在选择屏幕上输入之后调用,即 LDB 选择屏幕的 PAI 事件块(之后?)。

    FORM 带两个接口参数 FNAME and MARK 将会传到 subroutine 中。 FNAME 存储了选择屏幕中用户所选择 SELECT-OPTION PARAMETERS 屏幕字段名 MARK 标示了用户选择的是单值还是多值条件: MARK = SPACE 意味着用户输入了一个简单单值或者范围取值, MARK = '*' 意味着用户在 Multiple Selection screen 的输入 ( 即多个条件值 ) FNAME = '*' MARK = 'ANY' 表示所有的屏幕参数都已检验完成,即可以对屏幕整体参数做一个整体的检测了(这里的意思应该就是相当于 AT SELECTION-SCREEN )。

    FORM PUT_<node>

    最顶层节点 <node> 所对应的 FORM PUT_<node> 会在 START-OF-SELECTION 事件结束后 自动 被调用,而其他下层节点所对应的 FORM 会由它的上层节点所对应的 FORM 中的 PUT <node> 语句来触发(在上层节点所对应的可执行程序中的相应 GET 事件块执行之后触发)

    PUT <node>.

    此语句用是 PUT_<node> 子过程中的特定语句,它是与 PUT_<node> Form 一起使用的,通常是放在循环处理数据循环过程中。 PUT 语句根据 LDB 的结构指引了报表程序的逻辑。该语句会触发相应的报表程序的 GET <node> 事件。当 GET 事件块执行完后,如果有下层节点,则还会调用下层节点所对应的 FORM PUT_<node>

    PUT 语句是该 Form PUT_<node> )中最主要的语句:此语句仅仅只能在 LDB 数据库程序的 Form 中使用。

    PUT_<node> 调用结束后,报表程序中相应 GET <node> LATE 事件块也会自动调用。

    首先,根节点所对应的 PUT_<root> 会自动执行,此 Form 中的 PUT <node> 会以下面的先后顺序来执行程序:

    1. 如果 LDB 数据库程序包含了 AUTHORITY_CHECK_<table> 语句,则 PUT 语句的第一件事就是调用它

    2. 然后, PUT 语句会触发报表程序相应的 GET 事件

    3. 再后, PUT 语句会去调用 LDB 程序中下一节点的 PUT_<node> 子过程(此过程又会按照这里的三步来运行),直到下层所有子孙节点 PUT_<node> 过程处理完成( 深度遍历 ),才会回到最上一层节点的 PUT 语句

    4. 当控制权从下层节点的 PUT_<node> 返回时, PUT 语句还会触发当前节点的 GET <node> LATE 报表事件

    GET 事件块会在 LDB 程序从数据库表中读取到一行数据时被触发

    6.5. LDB 选择屏幕:静 ( ) 态选择屏幕、动态选择视图

    在报表选择屏幕上是否显示 LDB 普通选择条件 (即静态的,与动态选择条件相对应),则要看 报表程序中是否使用了对应的 TABLE <node> 语句 ,如果有,则与 <node> 节点相关的所有 LDB 选择条件都会显示在报表程序的选择屏幕上,如果没有此语句,则与 <node> 节点相关的所有 LDB 选择条件都会不会显示( 但如果某个节点没有在 TABLE 语句中进行定义,但其子节点,或子孙节点在 TABLE 语句中进行了定义,则这些子孙节点所对应的父节点所对应 LDB 屏幕选择条件还是会嵌入到报表选择屏幕中 )。有几种情况:

    l 如果报表程序中只有根节点的定义语句:

    则报表程序的选择屏幕只会将 spfli 节点相关的普通选择条件内嵌进来,子孙节点不会显示出来:

    l 如果报表程序只有子孙节点定义语句:

    则报表程序的选择屏幕中,会将 sbook 的父节点 SFLIGHT 以及爷节点 SPFLI 相关的 LDB 静态选择内嵌进来

    如果 LDB 的选择屏幕在没有创建 选择视图 的情况下: 动态选择 是否显示在报表程序的选择屏幕中, 首先 要看报表程序中是否使用了 TABLE <node> 对需要动态显示的节点进行了定义(如果这个节点是上层节点,则此节点为本身也可以不在 TABLE 语句定义,而是对其子孙节点进行定义也是可以的), 再者 ,还需要相应的 <node> 节点在 LDB 屏幕选择 Include 程序中的 SELECTION-SCREEN DYNAMIC SELECTIONS FOR TABLE <node> 语句中进行定义,注:要显示,则对应节点一定要在此语句中定义过,而不是像报表程序中的节点只对其子孙节点进行定义即可,而是谁需要动态显示,则谁就得要在动态定义语句中进行定义,如下面在 LDB 选择屏幕 Include 程序中只对 SBOOK 的上层节点 SPFLI,SFLIGHT 进行了定义,并没有对 SBOOK 进行定义:

    而在报表程序中只能 SBOOK 进行了定义:

    但最后在报表动态选择屏幕中,只有 SPFLI,SFLIGHT 两个表的条件(需使用 SELECTION-SCREEN DYNAMIC SELECTIONS 语句对 SPFLI,SFLIGHT 节点进行定义),而 SBOOK 并没有:

    在没有创建选择视图的情况下,以表名来建小分类,且动态条件字段为整个表的所有字段

    如果 LDB 的选择屏幕在有 选择视图 的情况下:只要存在选择视图,则只显示选择视图里被选择的字段,其他任何字段一概不显示。下面只将 SPFLI-CARRID SFLIGHT-CONNID 两个字段已分别纳入到了 01 02 分组中,而 SBOOK 节点中没有字段纳入:

    报表程序里将 SBOOK 节点定义在了 TABLES 语句中,所以,从 SBOOK 这一级开始(包括)向上所有节点的所对应的字段,如果纳入了选择视图中,则选择屏幕显示如下:

    7. ALV

    7.1. Layout 重要字段

    zebra ( 1 ) type c , " striped pattern 斑马线显示,颜色隔行交替显示
    edit ( 1 ) type c , " for grid only ALV 是否可编辑,注意只对 Grid 模式有效,对 List 模式无效
    f2code like sy-ucomm, "gs_layout-f2code=' &ETA' .
    双击时触发的 Funcode
    这里为弹出详情窗口

    colwidth_optimize ( 1 ) type c , ALV 网格(单元格)宽度设置为自动最优化,按输出内容宽度自动调整 [ˈɔptəˌmaɪz]

    lights_fieldname type slis_fieldname, " fieldname for exception 列显示为红绿灯
    box_fieldname type slis_fieldname, " fieldname for checkbox
    指定数据内表中哪列以选择按钮形式显示(首列前可按下或弹上来的按钮), ALV 最左上角会出现全选按钮

    key_hotspot( 1 ) type c , " keys as hotspot " K_KEYHOT 设置关键字段是否是热点,可单击

    info_fieldname type slis_fieldname, " infofield for listoutput 指定数据输出内表中哪列存储的是颜色,用来设置 ALV 每行数据的颜色。注:使用属性需要同时在数据内表中定义一个与该参数所定义字段名相同的栏位,如: LAYOUT-INFO_FIELDNAME=’COLOR’ ,假设数据内表名为 LT_OUT ,则需要在该内表增加一个栏位“ COLOR ”,颜色范围 C000~C999
    coltab_fieldname type slis_fieldname, "colors 指单元格式颜色,每行的单元格颜色就需一个单独的内表

    7.2. FIELDCATALOG 重要字段

    [ˈkætəlɔɡ]

    key ( 1 ) type c , " column with key-color 指定字段是否是关键字段,如果是则单元格显示的颜色会不同,并会靠前显示

    col_pos like sy-cucol, " position of the column 列的输出位置字段在表中第几列

    fieldname type slis_fieldname, " 针对输出内表哪列进行设置, 只有设置了的列才会显示 ,如果没有设置,则不会显示在 ALV 中。如果此字段是 CURR 金额 (currency field) , QUAN 数量 (Quantity field) 需要指定所参照的 CUKY 货币单位、 UNIT 字段名,需设置 Cfieldname Ctabname Qfieldname Qtabname
    c fieldname type slis_fieldname, "field with currency unit 金额字段所参照的货币单位 字段名

    c tabname type slis_tabname, " and table
    q fieldname type slis_fieldname, " field with quantity unit 数量字段所参照的数量单位 字段名
    q tabname type slis_tabname, " and table

    just( 1 ) type c , " (R)ight (L)eft (C)ent. 单元格中内容显示时对齐方式。不设置时按钮数据类型默认对齐方式来对齐
    lzero( 1 ) type c , " leading zero
    X 时输出前导零
    no_sign( 1 ) type c , " write no-sign
    不显示数字符号
    no_zero( 1 ) type c , " write no-zero
    只输出有意义的值 , 空值不输出。为 X 时全为零(如: 00000 )时不输出,所以不输出零时应该最好同时设置 lzero = sapce no_zero = X ,相反如果要输出,则应同时设置 lzero = X no_zero = space
    fix_column( 1 ) type c , " Spalte fixieren
    列固定不滚动,与 Key 属性相似,但颜色不会发生变化
    do_sum( 1 ) type c , " sum up
    该列是否进行小计,需与 gt_sort-subtot 一起使用(即需要参考排序),否则只对整列进行一个合计


    seltext_l like dd03p-scrtext_l, " long key word 标题字段显示的名称(长)
    seltext_m like dd03p-scrtext_m, " middle key word 标题字段显示的名称(中)
    seltext_s like dd03p-scrtext_s, " short key word 标题字段显示的名称(短)
    ddictxt( 1 ) type c , " (S)hort (M)iddle (L)ong 设置以长、中还是短名称来显示,取值分别为 S M L 。直接指定文本显示为长文本、中、还是短文本 , 指定这个字段后则会固定下来 , 不会随着用户的宽度调整变化 .

    如果是 金额 P 小数( 数量 )类型时,需要对下面两个属性进行设置,否则,如果不设置时,在修改对应 ALV 单元格内容时, 会自动将你所输入的数除 100 ,即小数点提前两位 ;并且如果是数量类型,除了设置 datatype 外, inttype 也需要进行 设置,且为 C
    datatype like dd03p-datatype, 数据 类型
    int type like dd03p-inttype, 内部 类型

    ref _fieldname like dd03p-fieldname, " 如需单元格显示 F4 输入帮助 ,则需要指定字段所参照的表
    ref _tabname like dd03p-tabname," 如需单元格显示 F4 输入帮助,则需要指定字段所参照的表中的字段名

    另外, 以上两个字段还可以解决 ALV 中形如参照 VBELN MATNR 词典类型的列导出 ( 自带的导出功能 )Excel 时被截断的问题 ,具体请参照: ALV 自带导出文件时字段数据末尾被截断问题

    CONVEXIT : 设置转换规则,对应于 Domain 中的转换规则,也可用于解决导出 Excel 数据前导 0 被截断的问题

    edit ( 1 ) type c , " internal use only 是否可编辑
    hotspot ( 1 ) type c , " hotspot
    设置字段内容下面是否有热点(有下划线,可点击,单击即可触发相应事件)

    7.3. 指定 双击 触发的 FunCode

    gs_layout - f2code = ' &ETA ' 设置 ALV 数据行双击触发的 Tcode ,这里为弹出详情窗口

    7.4. 相关函数

    REUSE_ALV_GRID_DISPLA Y

    REUSE_ALV_LIST_DISPLAY

    REUSE_ALV_GRID_DISPLAY_LVC

    REUSE_ALV_ FIELDCATALOG _ MERGE [mə:dʒ] 混合 , ( 使 ) 合并

    7.5. 重要参数接口

    I_ CALLBACK_ PF_STATUS_SET 设置工具条

    I_ CALLBACK_ USER_COMMAND 用户点击工具栏中自定义按钮、预置按钮(需通过 IT_EVENT_EXIT 参数指定预置 FunCode 才会回调指定的 Form )、数据行双击、单元格热点等时,会回调此参数指定的 Form

    IT_SORT 排序、分类汇总

    I_SAVE 保存表格布局: 'X' 只能保存为全局标准变式, 'U' 只能保存特定用户变式, 'A' 都可以保存, SPACE 不能保存变式

    I_DEFAULT 用户是否可以设置 默认的布局变式 (即是否可以将某个布局变式设置为默认的布局)

    IS_VARIANT 指定布局变式

    IT_ EVENTS 事件回调,可以用来代替 I_CALLBACK_USER_COMMAND 参数

    IT_ EVENT_EXIT 预置 FunCode 回调 I_CALLBACK_USER_COMMAND 指定的 Form

    IS_LAYOUT

    IT_FIELDCAT

    T_OUTTAB 需要显示的数据内表

    i_grid_settings

    7.6. 让预置按钮回调 I_CALLBACK_USER_COMMAND

    IT_EVENT_EXIT :让预置按钮回调 I_CALLBACK_USER_COMMAND 指定的 Form 。可以向 IT_EVENT_EXIT 参数内表填充需要被拦截的保留 Funcode ,及在是执行对应功能代码之前还是之后调用:

    DATA : event_exit TYPE slis_t_event_exit WITH HEADER LINE .
    event_exit
    - ucomm = '&OAD' .
    " Funcode 为点击 AlV 工具栏上的选择布局按钮 所对应的 FunCode ,会被 USER_COMMAND 指定的 Form 拦截
    event_exit - after = 'X' . " 在执行完预置功能代码之前还是之后调用
    APPEND event_exit .
    CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
    EXPORTING
    i_callback_program
    = sy - repid
    it_fieldcat
    = fieldcat[]
    i_callback_user_command
    = 'USER_COMMAND'
    IT_EVENT_EXIT = event_exit[]
    TABLES
    t_outtab
    = gt_result .
    FORM user_command USING r_ucomm LIKE sy - ucomm rs_selfield TYPE slis_selfield .
    ENDFORM .

    7.7. 颜色

    行颜色 gs_ layout - info_fieldname = 'COLOR' . " 指定数据内表中的哪一 用来作为行颜色的列,颜色值与下面列颜色取值格式是一样的,也是 4 位,不同的是此种方式下的颜色值是与显示数据内表存放在一起,而下面的颜色值则是与 gt_fieldcat 存放在一起
    列颜色 gt_ fieldcat - emphasize ['emfəsaɪz] 强调) = 'C510' . " 此种方式下的颜色值定义为 4 位字符,各位含意:

    1 位:固定取值为 C

    2 COL 颜色值 ,取值为 0~7

    3 INT 高亮 ,即颜色是否加深,取值为 0 1 1 表示加深显示

    4 INV 颜色是否反转 ,即颜色是作用在背景上,还是作用在输出字符上,取值上为 0 1 。为 1 时表示设置的是前景色,即输出字符本身的颜色(好像只有在第 3 位为 0 时才有效?)

    单元格颜色 gs_ layout - coltab_fieldname = 'COLORTABLE' . " 数据内表中哪列为颜色内表,颜色内表结构如下:

    slis_color 颜色结构类型各字体对应于上面颜色值串 'C510' 后三位,意义也是一样,只是没有第一位固定字符 C

    7.8. 可编辑

    整体可编辑 gs_ layout - edit = 'X' .

    某列可编辑 gt_ fieldcat - edit = 'X' .

    单元格可编辑 :只能使用 REUSE_ALV_GRID_DISPLAY_ LVC ,并且还作以下一些设置:
    cellstab TYPE lvc_t_styl , " 在输出内表中加上这一类型的列
    " 先将所有单元格设置为可编辑状态
    gt_fieldcat
    - edit = 'X' .

    DATA : gt_cellstab TYPE lvc_t_styl WITH HEADER LINE .

    " 再将原本可编辑的单元格切换到不可编辑样式。注:这里只是样式的切换,不能仅 仅使用 cl_gui_alv_grid=>mc_style_enabled 来将单元格设置为可编辑状态,单元格真正是否可编辑是由 fieldcat-edit layout-edit 来决定的,而仅设置为 cl_gui_alv_grid=>mc_style_enabled 是不可编辑的
    gt_cellstab
    - style = cl_gui_alv_grid => mc_style_disabled .

    APPEND gt_cellstab .
    gt_data
    - cellstab = gt_cellstab[] .
    gs_layout
    -
    stylefname = 'CELLSTAB' . " 数据内表中哪列为可编辑信息内表

    7.9. 单元格数据修改后立即自动刷新

    单元格中的数据被修改后,将 ALV 单元格中的数据立即刷新到 ABAP 对应的内表中:

    法一 通过对 REUSE_ALV_GRID_DISPLAY 函数参数 i_grid_settings -edt_cll_cb 进行设置:

    i_grid_settings - edt_cll_cb = 'X' .
    CALL FUNCTION '
    REUSE_ALV_GRID_DISPLAY'
    EXPORTING
    i_grid_settings = i_grid_settings
    法二 通过函数参数 I_CALLBACK_USER_COMMAN D 指定的回调 Form 参数 slis_selfield 进行设置:

    FORM user_command USING ucomm LIKE sy - ucommselfield selfield TYPE slis_selfield .
    selfield - refresh = 'X' .
    CASE ucomm .
    WHEN 'UPDATE' .
    PERFORM frm_update .
    ENDCASE .
    ENDFORM .

    7.10. 数据有效性验证事件: data_changed

    通过 REUSE_ALV_GRID_DISPLAY 函数的 it_events 参数设置 DATA_CHANGE 事件及事件回调 Form

    t_events - name = slis_ev_ data_changed .
    t_events
    - form = 'ALV_DATA_CHANGED' .
    APPEND t_events .

    CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
    EXPORTING
    it_events = t_events[]

    " 注:如果没有设置 REUSE_ALV_GRID_DISPLAY 函数的参数 i_grid_settings-edt_cll_cb = 'X' ,在单元格数据被修改后,此 Form 不会自动调用(即不触发 data_changed 事件),直到点击了保存或刷新按钮后才会被调用,另外 cl_gui_alv_grid CHECK_CHANGED_DATA 方法也会触发 data_changed 事件;另外,如果是通过 OO 实现的 ALV ,要让 DATA_CHANGE 事件触发,则还需要注册回车或焦点失去动作,具体参看后面
    FORM alv_data_changed USING pel_data TYPE REF TO cl_alv_changed_data_protocol .
    DATA : l_name ( 20 ), ls_cells TYPE lvc_s_modi .
    FIELD-SYMBOLS <fs_value> .
    LOOP AT pel_data -> mt_mod_cells INTO ls_cells . " 读取被修改了的单元格
    CLEAR gt_data .
    READ TABLE gt_data INDEX ls_cells- row_id . " 被修改了的单元格所对应输出内表行数据
    CONCATENATE 'GT_DATA-' ls_cells- fieldname INTO l_name . " 读取被单元格所对应的输出内表中的相应列数据,注:读取出来是的单元格修改之前的数据
    ASSIGN ( l_name ) TO <fs_value> . "<fs_value> 即为修改前的值
    <fs_value>
    = ls_cells - value . " ls_cells-value 单元格中修改后的新值?
    " 实际上不需要此句来修改输出内表中的数据,因为只要在该 Form 中不弹出 E MSG ,则该 Form 执行完后会也会自动更新输出内表
    "MODIFY gt_data INDEX ls_cells-row_id.
    ENDLOOP .

    注:如果是通过 CL_GUI_ALV_GRID 来实现 ALV ,则在 ALV 单元格中修改数据后,要在失去焦点或回车时自动触发 DATA_CHANGE 事件 ,则还需要通过 CL_GUI_ALV_GRID 类的 REGISTER_EDIT_EVENT 方法来设置发数据改变事件在何时触发, 2 种方式:

    ² 按回车触发 : i_event_id = cl_gui_alv_grid=>mc_event_enter

    ² 单元格失去焦点 : i_event_id = cl_gui_alv_grid=>mc_event_modifies

    必须设置一种方式,要不然数据变化事件不会被触发事件

    7.11. 金额、数字类型输入问题

    对于货币与 P 类型小数(如数量)类型字段,需要对 gt_fieldcat- datatype 属性进行设置,才能将输入的数字保持原样大小,否则输入的数据会自动将小数点提前 2 位;对于数量类型,好像还需要对 gt_fieldcat- INTTYPE 属性进行设置才好使,并且只能设置为 C 类型:

    if &1 = 'CURR' .
    " 对于金额字段,需要设置为 CURR 数据库字典类型
    gt_fieldcat
    - datatype = 'CURR' .
    endif .
    if &1 = 'P' .
    " 对于小数,需要设置为 QUAN 数据库字典类型
    gt_fieldcat
    - datatype = 'QUAN' .
    " 除此之外,还需要将 inttype 类型设置为 C 类型。另外,按理来说要设置为 P 类型的,但发现不行, QUAN 类型映射为 C 类型??
    gt_fieldcat
    - inttype = 'C' .
    endif .

    7.12. 排序、分类汇总

    " 决定此列是否进行分类汇总与大汇总。注 如果不设置 gt_sort - subtot ,则只有大汇总,不会进行分类小 汇总
    gt_fieldcat - do_sum = 'X' . " 设置了 gt_fieldcat-do_sum 就会有大汇总,分类小汇总要出现的前提之一也是必须要设置此属性,另外还需对 gt_sort-subtot 进行设置;如果此参数 (gt_fieldcat-do_sum) 不设置的话,则大汇总与小汇总都没有

    gt_sort - spos = '1' . " 排序的顺序,如果根据多个字段来排时,决定哪个先排
    gt_sort
    - fieldname = 'KEY1' .
    gt_sort - up = 'X' . " 升序,如果不指定排序(即 gt_sort-up gt_sort-down 都没设置时), 默认为升序 只要 某字段参设置了 gt_sort-down/up ,则在展示时,排序以后垂直网格中 相邻相同的单元格就会合并起来 (即分类合并,如果要避免合并,请在布局中设置 "no_merging" "X"
    " 是否需要以此字段进行 分类小计 ( 小计汇总 )
    gt_sort
    - subtot = 'X' . " 是否需要以此字段进行分类合并、并进行小计(注:与本列是否参与排序无关系,只要设置此属性就进行分类合并且小计——但前提是要对 gt_fieldcat- do_sum 也进行了设置)
    APPEND gt_sort .
    CALL FUNCTION '
    REUSE_ ALV_GRID_DISPLAY'
    EXPORTING it_sort = gt_sort[]

    7.13. 可打印的表头输出

    t_events - name = slis_ev_ top_of_page .
    t_events
    - form = 'alv_top_of_page ' .
    APPEND t_events .

    CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
    EXPORTING it_events = t_events[]

    " 页眉触发时所回调 Form
    FORM alv_top_of_page .
    DATA : lr_rows TYPE REF TO cl_salv_form_layout_grid ,
    lr_grid_rows
    LIKE lr_rows ,
    lr_row
    TYPE REF TO cl_salv_form_layout_flow ,
    lr_logo
    TYPE REF TO cl_salv_form_layout_logo .

    DATA : l_row TYPE i VALUE '1' .
    CREATE OBJECT lr_rows .
    CREATE OBJECT lr_logo .
    ...
    ENDFORM .

    7.14. 布局变式读取、切换 、根据布局格式导出数据

    INITIALIZATION .
    CALL FUNCTION 'REUSE_ALV_
    VARIANT _DEFAULT_GET ' 获取默认的布局 [ˈveəri:ənt]
    EXPORTING
    i_save
    = 'A'
    CHANGING
    cs_variant
    = gx_variant
    p_varit
    = gx_variant - variant .

    AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_varit .
    CALL FUNCTION 'REUSE_ALV_ VARIANT_F4 ' 选择布局
    EXPORTING
    is_variant
    = g_variant
    i_save
    = 'A'

    p_varit = gx_variant - variant .

    START-OF-SELECTION .
    DATA : event_exit TYPE slis_t_event_exit WITH HEADER LINE .
    event_exit
    - ucomm = '&OAD' . " Funcode 为点击 AlV 工具栏上的选择布局按钮时 会被 USER_COMMAND Form 拦截
    event_exit
    - after = 'X' .
    APPEND event_exit .

    CALL FUNCTION '
    REUSE_ALV_GRID_DISPLAY'
    EXPORTING
    i_save
    = 'A'
    i_callback_user_command
    = 'USER_COMMAND1'
    it_event_exit
    = event_exit[]
    is_variant
    = g_variant "ALV 展示时,所使用的布局变式名。如果不存在,按默认来

    FORM user_command1 USING r_ucomm LIKE sy - ucomm rs_selfield TYPE slis_selfield .
    CASE r_ucomm .
    WHEN '&OAD' . " 当点击选择布局按钮时执行

    DATA l_ref1 TYPE REF TO cl_gui_alv_grid .
    CALL FUNCTION 'GET_GLOBALS_FROM_SLVC_FULLSCR' 获取当前 ALV 所对应的 OO Grid
    IMPORTING e_grid = l_ref1 .

    l_ref1->get_variant ( IMPORTING es_variant = l_variant )

    DATA : p_fieldcat_tab TYPE slis_t_fieldcat_alv .
    " 当知道当前用户所选择的布局变式后,再通过函数 REUSE_ALV_VARIANT_SELECT 可以
    " 得到布局变式所对应的布局具体信息,如哪些字段显示、字段显示的顺序如何等,当得到这些
    " 布局信息后,可以用在用户在导出 ALV 数据到文件时使用,这样可以保持 ALV 显示的布局与
    " 导出去的文件显示的哪些内容及字段顺序体质一致
    CALL FUNCTION 'REUSE_ALV_ VARIANT_SELECT ' 读取布局信息
    IMPORTING
    " 可以根据返回的 p_fieldcat_tab ,得到当前 ALV 所使用的布局变式所对应的 Layout 情况,如
    " ALV 数据下载成文件时需要与当前 Layout 布局一样:输出相同的字段与顺序,可以根据
    "p_fieldcat_tab NO_OUT( 控制是否输出)、 COL_POS (控制顺序)来控制,文件表头可取
    "seltext_l seltext_m seltext_s 。可用于导出文件布局
    et_fieldcat
    = p_fieldcat_tab[]
    CHANGING
    cs_variant
    = l_variant . " 传入的布局布局变式名
    """"""" 下面就是对 p_fieldcat_tab[] 内表字段结构进行分析及应用了
    ....
    ENDCASE .
    ENDFORM .

    7.15. 动态内表

    另外,在 ALV 中可以 根据 FieldCat 来动态创建内表

    rt_field catalog type lvc_t_fcat .
    CALL METHOD cl_alv_ table_create => create_
    dynamic_table [daiˈnæmik]
    EXPORTING
    it_ fieldcatalog
    = rt_fieldcatalog[]
    IMPORTING
    ep_table
    = g_table .

    8. OO ALV

    8.1. 相关类

    CL_ GUI_ALV_GRID

    CL_GUI_ CUSTOM_CONTAINER

    CL_GUI_ DOCKING _CONTAINER

    CL_GUI_ SPLITTER _CONTAINER

    8.2. 控制区域、容器、 Grid 关系

    先在屏幕上绘制一个 用户自定义控件区域 ,然后该用户以自定义控件区域为基础来创建 CL_GUI_ CUSTOM_CONTAINER 容器实例 ,最后以此容器实例来创建 CL_GUI_ ALV_GRID 实例

    8.3. CL_GUI_ALV_GRID 重要方法

    set_table_for_first_dispaly

    REFRESH_TABLE_DISPLAY

    IS_STABLE 刷新的稳定性,就是滚动条保持不动

    I_SOFT_REFRESH 软刷新,如果设置了这个参数,临时给 ALV 创建的合计、排序、数据过滤都将保持不变。这个是非常有意义的,例如:当你没有修改数据内表里的数据,但因布局修改了想刷新 ALV 时可使用

    8.4. set_table_for_first_dispaly() 方法重要参数

    8.5. 事件 绑定、触发、回调处理

    CLASS cl_event_handle DEFINITION . " 定义事件处理类
    PUBLIC SECTION .
    " ALV 工具栏初始化事件 ,如增加按钮并设定属性
    METHODS handle_toolbar FOR EVENT toolbar OF cl_gui_alv_grid
    IMPORTING e_object e_interactive .
    " ALV 工具栏按钮点击事件
    METHODS handle_user_command FOR EVENT user_command OF cl_gui_alv_grid
    IMPORTING e_ucomm .
    " ALV 表格双击事件
    METHODS handle_double_click FOR EVENT double_click OF cl_gui_alv_grid
    IMPORTING e_row e_column es_row_no .
    ENDCLASS .

    CLASS cl_event_handle IMPLEMENTATION . " 事件处理类实现部分
    METHOD handle_toolbar .
    gs_toolbar
    - function = 'B_SUM' . " 按钮的 FunctionCode
    gs_toolbar
    - icon = icon_display . " 按钮图标
    gs_toolbar
    - text = ' 总行数 ' . " 按钮标签
    gs_toolbar
    - butn_type = '0' . " 定义按钮类型, 0 为标准按钮
    APPEND gs_toolbar TO e_object -> mt_toolbar . " 添加按钮到工具栏中
    ENDMETHOD .

    METHOD handle_user_command .
    DATA : sum TYPE i .
    IF e_ucomm = 'B_SUM' .
    ...
    ENDIF .
    ENDMETHOD .
    METHOD handle_double_click .
    ... .
    ENDMETHOD .
    ENDCLASS .

    CREATE OBJECT container_r EXPORTING container_name = 'CONTAINER_1' . " 创建 ALV 容器对象
    CREATE OBJECT grid_r EXPORTING i_parent = container_r . " 创建 ALV 控件
    CALL METHOD grid_r -> set_table_for_first_display CHANGING it_outtab = gt_sflight[] .

    SET HANDLER : event_handle -> handle_toolbar FOR grid_r , " 注册处理器

    event_handle -> handle_user_command FOR grid_r ,
    event_handle
    -> handle_double_click FOR grid_r .

    CALL METHOD grid_r -> set_toolbar_interactive . " 调用此方法才能激活工具栏上增加的自定义按钮

    8.6. CL_GUI_ DOCKING _CONTAINER 容器

    Docking 容器最大特点是在代码中可以动态创建容器,不需要像创建自定义容器 CL_GUI_CUSTOM_CONTAINER 那样,在创建时需要将其绑定到一个预先绘制好的用户自定义控件区域中

    8.7. 覆盖(拦截)预设按钮的功能 FunCode BEFORE_USER_COMMAND

    before_user_command 事件中截取标准的功能,完成其他功能,然后使用方法 set_user_command 将功能代码修改为空(如何拦截事件,则参考 事件绑定、触发、回调处理 章节)

    FORM handle_before_user_command USING i_ucomm TYPE syucomm .
    CASE e_ucomm .
    WHEN '&INFO' .
    CALL FUNCTION 'ZSFLIGHT_PROG_INFO' .
    CALL METHOD gr_alvgrid -> set_user_command EXPORTING i_ucomm = space .
    ENDCASE .
    ENDFORM .

    8.8. 数据改变事件 data_changed data_changed_finished

    Alv grid 有两个事件: data_changed ata_changed_finished. 第一个事件在可编辑字段的数据发生变化时触发,可用来检查数据的输入正确性,第二个事件是当数据修改完成后触发

    如果数据没有被修改,当失去焦点或回车时,那么它不会走 data change ,而是直接触发 data change finish 事件

    可以通过 CL_GUI_ALV_GRID 类的 REGISTER_EDIT_EVENT 方法来设置在 失去焦点 回车 时,触发数据改变事件:

    ² 按回车触发 : i_event_id = cl_gui_alv_grid=>mc_event_enter

    ² 单元格失去焦点 : i_event_id = cl_gui_alv_grid=>mc_event_modifies

    必须设置一种方式,要不然数据变化事件不会被触发事件

    然后注册 CL_GUI_ALV_GRID data_changed data_changed_finished 事件,实现事件处理器方法,在数据发生改变时就会触发这两上事件

    8.9. 单元格可编辑

    与非 OO ALV 是一样的,请参照

    9. 问题

    9.1. ALV 自带导出文件时字段数据末尾被截断问题

    发现有前导 0 时,导出会被截断:现发现 VBAK-VBELN MARA- MFRNR都有这个问题,可能原因是他们带有转换输出与输入规则所导致

    另一种解决办法:

    9.2. Smartform Template 无法显示减号后面内容

    Smartform 中的 Template 里,如果输入的变量内容含有减号,则减号后面的内容会被丢掉

    问题原因 :输出的内容超出了 Template 单元格的长度

    解决办法 :更改 TEMPLATE 的长度,或者换成 TABLE

    9.3. Smartform 金额或者数量字段显示不出来

    数据是数量时候 要在全局定义 - 货币 / 数量页签里面把要打印的数量定义成 QUAN 如下图
    说明: http://hi.csdn.net/attachment/201112/8/0_1323318613OQqJ.gif

    SMARTFORM 中,数量和金额类型的字段在显示的时候会和其他字段不在同一个水平面上,解决的方法: &ITAB-MENGE(C)&  , 下面是 SMARTFORM 字段参数设置的几个注意事项:

    1 、使用 SFSY-FORMPAGES 显示总页数的时候,如果页数大于 9, ,将会在前 10 页显示成星号。解决办法:可以添加 3ZC &SFSY-PAGE(3ZC)&/&SFSY-FORMPAGES(3ZC)& ,不过可能会出现字体颠倒或者 重叠的现象,用一个单独的窗口来存放显示页码的文本,并且把窗口的类型设置为 L( 最终窗口 ) OK 了。

    2 、如果金额或者数量字段显示不出来的话,可以在 货币 / 数量字段 标签中指定相应的数据类型。

    3 Field not outputting more than 255 characters in a loop. This is happening because when you send a string to smartform with length >255 characters then it takes only first 255 characters. I overcomed this problem by splitting the string which was of around 500 char into two and then sending it to smartform as individual vairables and displaying the two variables one after the other in the smartform.

    将文本字段拆分成几个字符变量再连接在一起显示。

    9.4. 更新数据库表时, 工作区或 内表的结构需参考数据库表来定义

    使用使用 MODIFY 更新数据库表时, 工作区或内表的行结构与数据库表结构中各字段声明顺序要相同,否则更新会 错位 ,该内表最好参照数据库词典结构类型来声明,这样就不会有问题。

    9.5. DELETE ADJACENT DUPLICATES… 去重复

    DELETE ADJACENT DUPLICATES FROM <itab> [ COMPARING <f1><f2> ... | ALL FIELDS ]

    注,在未使用 COMPARING 选项时,要删除重复数据之前, 一定要按照内表关键字声明的顺序来进行排序,才能删除重复数据 ,否则不会删除掉;如果指定了 COMPARING 选项,则需要根据指定的比较字段顺序进行排序(如 COMPARING <F1><F2> 时,则需要 sort by <F1><F2> ,而不能是 sort by <F2><F1> ),才能删除所有重复数据

    9.6. Text 使用 Excel 打开乱码问题

    如果使用 GUI_DOWNLOAD 函数下载文本文件,或者是发送邮件的附件,在 英文 XP 操作系统 中使用 英文 Excel 软件 打开时,请使用 UTF-16LE 编码,否则可能出现乱码情况。

    data :  l_codepage( 4 ) type n .
    data :  l_encoding( 20 ).
    " 根据编码名获取对应的 CodePage
    callfunction 'SCP_CODEPAGE_BY_EXTERNAL_NAME'
    EXPORTING
    external_name = 'UTF-16LE'
    IMPORTING
    sap_codepage  = l_codepage.
    l_encoding = l_codepage.

    data : convout type ref to cl_abap_conv_out_ce.
    convout = cl_abap_conv_out_ce=>create( encoding = l_encoding ).
    convout->write( data = lv_content ). "
    将字符按照 l_encoding 编码格式转换为 X 类型(二进制)
    xstr =  convout->get_buffer( ).
    "
    在码流最前面加上编码信息,该编码由文本编辑软件在打开文件时使用
    concatenate cl_abap_char_utilities=>byte_order_mark_little
    xstr into xstr in byte mode .

    9.7. VBFA EKPO 联合查询问题

    由于 VBFA-POSNN EKPO-EBELP 字段的类型相同,但长度不一样( VBFA -POSNN 6 位的数字类型,而 EKPO -EBELP 5 位数字类型,但 VBAP-POSNR 行项目号是 6 位数字类型,不会出现此类问题),所以它们不能进行关联查询,相似的还有 VBFA- POSNV 也是 6 位的。下面这个关联查询是查不出数据的,只能分两次查询:

    SELECT SINGLE vbeln posnn txz01 menge
    INTO ( it_result - ebeln , it_result - ebelp , it_result - txz01 , it_result - menge_2 )
    FROM vbfa AS v INNER JOIN ekpo AS e ON v ~ vbeln = e ~ ebeln AND v ~ posnn = e ~ ebelp AND e ~ knttp = 'E'
    " Where 条件是 根据销售单查找前置单据——采购单, V 为采购单凭证类型
    WHERE vbelv = it_result - vbeln AND posnv = it_result - posnr AND vbtyp_n = 'V' .

    HNTTP :采购凭证中的帐户设置类型, E ——生产 / 销售所需物料的采购

    分成两个可以正常查询:

    SELECT SINGLE vbeln posnn
    INTO ( it_result - ebeln , it_result - ebelp )
    FROM vbfa AS v
    "Where 条件是:先根据销售单查找到前置采购单的单号与行项目号
    WHERE vbelv = it_result - vbeln AND posnv = it_result - posnr AND vbtyp_n = 'V' .

    SELECT SINGLE txz01 menge
    INTO ( it_result - txz01 , it_result - menge_2 )
    FROM ekpo "Where 条件是:再根据前面查出来的采购单号与行项目号,查出 EKPO 其他详细信息
    WHERE ebeln = it_result - ebeln AND ebelp = it_result - ebelp AND
    knttp = 'E' .

    10. 技巧

    10.1. READ TABLE...WITH KEY 可使用 OR 条件或其他非“ = ”操作符

    READ TABLE...WITH KEY... 后面不能接 OR 条件操作符,也不能使用其他非等于的比较操作符,因原是该语句即使在查询出多条时也 只取第一条 ,所以限制了 WITH KEY 后面条件使用。下面是错误的语法:

    READ TABLE it_tab WITH KEY k1 = 'C' OR k2 = 'C' .

    可以使用下面方式代替:

    LOOP AT il_item_status WHERE k1 = 'C' OR k2 = 'C' .
    ...
    EXIT .
    ENDLOOP .

    10.2. SELECT SINGLE ... WHERE ... 无法排序问题

    SELECT SINGLE ... WHERE ...

    使用 SINGLE 是表示根据表的关键字来查询,这样才能确保只有一条数据,所以当 使用 SINGLE 时,语法上 不能再使用 ORDER BY 语句 (因为没有必要了),如果查询时不是根据关键字来查询,且查询时先排序再取一条时,我们只能使用另一种语法:

    SELECT * FROM tj02t INTO CORRESPONDING FIELDS OF TABLE gt_result UP TO 1 ROWS

    WHERE SPRAS = 'E' ORDER BY ISTAT .

    如果是取某个最大值或最小值,则可以使用聚合函数更简洁:

    SELECT MIN ( edatu ) INTO ( g_tabcon_mps_wa - edatu ) FROM vbep
    WHERE vbeln = ztab_mps - vbeln AND posnr = ztab_mps - posnr .

    10.3. 当心 Where 后的条件内表为空时

    Select If Delete 内表、 read look at 内表语句中的 Where 条件中 如果使用的 Range 是一个空的条件内表 xx IN range 恒为真 那么 xx NOT IN range 则恒为假

    注: 不会像 FOR ALL ENTRIES 那样 忽略其他的条件表达式 其他条件还是起作用

    10.4. 快速查找 SO 所对应的交货单 DN PO

    快速查找 SO VBAP )所对应的 DN LIPS ):虽然可以通过 vbap - vbeln = lips - vgbel AND vbap - posnr = lips - vgpos 来关联查找,但 LIPS-VGBEL LIPS-VGPOS 非主键,查找起来非常慢(但根据 DN 来查找所的 SO 是很快的,因为此时为主键查找)但可以通过 VBFA 单据流表来查找 DN ,这样会非常快,因为这是 根据主键 来查找的:

    vbfa ~ vbelv = vbap - vbeln AND vbfa ~ posnv = vbap - posnr AND vbfa ~ vbtyp_v = 'C' AND vbfa ~ vbtyp_n = 'J'

    另外,根据 SO 查找 PO 时,可以根据 EKKN-VBELN= VBAP- VBELN AND EKKN-VBELP=VBAP- POSNR SO PO 中间表 EKKN 里去找,但查找条件为非主键也非索引,所以找起来时很慢,可以通过 VBFA 单据流表进行查找,因为 VBFA vbeln posnn 字段上创建了索引(虽然查询时 WHERE 从句条件字段不是按主键字段顺序——使用的是后半部分主键,所以用不到主键索引,但是是按非主键索引字段顺序书写,所以还是可以用到索引): vbfa ~ vbeln = vbap - vbeln AND vbfa ~ posnn = vbap - posnr AND vbfa ~ vbtyp_v ='V' AND vbfa ~ vbtyp_n = 'C'

    10.5. X 类型的 C 类型视图

    " <--> 6C5F
    " <--> 6B63
    *DATA: x(4) TYPE x VALUE '6C5F'.
    DATA : x ( 2 ) TYPE x VALUE '6C5F' .
    FIELD-SYMBOLS : <c> TYPE c .
    " 有时将 X 类型分配给 C 类型时会出错 长度需要是 4 的倍数 所以定义成 4 的倍数
    " 即可解决这个问题 但有时定义的长度只能是某个特定数 所以此时只能使用后面这种方式
    " 编译时报错误 The length of "X" in bytes must be a multiple of the size of
    "a Unicode character, regardless of the size of the Unicode character.
    * ASSIGN x to <c> CASTING.

    " 只能先定义一个 C 类型变量,再将这个 C 类型变量分配给 X 类型字段符号,这样就可
    " 以随便在 x 类型之间捣腾了,但此时 C 变量不是 X 变量的真正视图了(经过了拷贝)
    DATA : c ( 1 ) .
    FIELD-SYMBOLS : <x> TYPE x .
    ASSIGN c to <x> CASTING .
    <x>
    = x .
    " 6C5F 注:如果输出的是乱码,则是字节序的问题,需写成 5F6C (如 Windows 操作系统中)
    WRITE : / ( 2 ) c , <x> .
    " 6B63 注:如果输出的是乱码,则是字节序的问题,需写成 636B (如 Windows 操作系统中)
    x = '6B63' .
    <x>
    = x .
    WRITE : / ( 2 ) c , <x> .

    10.6. 字符串连接: && 替代 CONCATENATE

    有如将整型( I )与一个字符串( String )进行连接,此时不能直接使用 CONCATENATE 进行连接,因为 CONCATENATE 操作的是字符类型,所以需要将整型转换为字符型后才能使用 CONCATENATE 进行连接,但这里需要注意的,当正整型变量转换为字符类型时,符号位会转换为空格,这时使用 CONCATENATE 接连得到的字符串可能会多出一个空格;当将整型变量与字符串进行连接时,最好使用 && 操作符,除了直接能连接外,还不会出现多余空格的问题:

    DATA : i TYPE i VALUE '10' .
    DATA : str TYPE string VALUE 'string' .
    DATA : tmp TYPE string .
    str
    = i && str .
    WRITE : / str .
    tmp
    = i .
    CONCATENATE tmp str INTO str .
    WRITE : / str .

    10.7. Variant 变式中动态日期

    报表程序的选择屏幕中,输出条件后可以点击保存按钮,会弹出创建变式的屏幕,如果条件中有日期字段,日期字段可以随着时间变化,日期字段的值也可以动态的变化,如对于每天都要跑的 Job 报表很有用,每天查询当天。当然也可以通过报表程序的 INIT 事件里动态获取当前日期,但可能需要修改程序

    除用在中 Job 外,变式还可以用在 Tcode

    11. 优化

    降低 CPU 负荷 (减少循环次数)、 降低 DB 负荷(减少 IO 操作)、 降低 内存使用(减少内表大小)

    11.1. 数据库

    1. 不要使用 SELECT * ... ,选择需要的字段 , SELECT * 既浪费 CPU ,又浪费网络带宽资源,还需占用大量的 ABAP 内存

    2. 不要使用 SELECT DISTINCT ... 会绕过缓存,可使用 SORT BY + DELETE ADJACENT DUPLICATES 代替

    3. 少用 相关子查询 ,因为子查询对外层查询结果集中的每条记录都会执行一次

    4. 少用嵌套 SELECT … ENDSELECT ,可以使用联合查询或 FOR ALL ENTRIES 来替换 , 减少循环次数

    5. 如果确定只查一条数据时,使用 SELECT SINGLE ... 或者是 SELECT ... UP TO 1 ROWS ...

    6. 统计时, 直接使用 SQL 聚合函数 ,而不是将数据读取出来后在程序里再进行统计

    7. 使用游标读取数据 ,这样省掉了将从数据库中的取记录放入内表的 INTO 语句这一过程开销

    8. 多使用 inner join ,必要时才使用 left join

    9. inner join 获取数据时,尽量不要用太多的表关联,特别是大表关联,关联顺序为: 小表 - 大表

    10. where 条件里面多用索引、主键,顺序也要遵循 小表 - 大表

    11. inner join 条件放置的位置应该按照 On Where Having 的顺序放 ,因为 SQL 条件的的执行一般是按这个顺序来执行的,将条件放在最开始执行,则可过滤掉大部数据;但要注意 Left Outer Join ,是否可以将 ON 中的条件移动到 Where 从句则要考虑(如果真能放在 Where 从句中,则应该使用 Inner Join ,而非 Left Outer Join ,因为 Where 条件会过滤掉那些包括在右表中不存在的左表数据),因为此时条件放在 On 后面与放在 Where 语句后面结果是不一样的(因为 不管 on 中的条件是否为真,左表中在右边表不存在的数据也会被返回 ,但如放在 where 条件中,则会对 On 产生的数据再次过滤的条件,会滤掉不满足条件的记录——包括左表在右表中找不到的记录,这时已经没有 left join 的含义)

    12. 要根据主键或索引字段查找数据 ,且 WHERE 从句中的 条件字段需按 INDEX 字段顺序书写 ,且 将索引字段条件靠前 (左边),如:在 VBFA 表中查找 SO 所对应的交货单 DN ,因为如果直接到 LIPS 中找时, SO 的订单中号与行号在 LIPS 中非主键,但在 VBFA 是部分主键 VBFA 中根据部分主键查找 SO -> DN; 根据索引查找 SO -> PO,VBFA-VBELN+VBFA-POSNN 组合字段上创建了索引 ,即根据 SO PO 时,不要从 EKKN 关联表中查找,而是通过 VBFA 中查找 后来 查看 EKKN ,发现在 VBELN+VBELP 字段上创建了索引,所以从 VBFA EKKO 查找应该差不多,主要看哪个表数据量少的问题了)

    检查条件组合字段是否是主键,或者是上在上面创建了索引,避免条件组合字段即不是主键又没有索引

    13. SELECT 语句 WHERE 条件,应该先将 主键相关条件放在前面 然后按照比较符 = < > <> LIKE IN 的顺序排列 WHERE 条件

    14. 使用 部分索引字段 问题:如果一个索引是由多个字段组成的,只使用一部分关键字段来进行查询时,也是可以使用到索引,但使用时要注意要 按照索引定义的顺序且取其前面部分

    15. 根据索引字段进行 ORDER BY ,否则通过程序进行 SORT BY 。与其在数据库在通过非索引字段进行排序,不如在程序中使用 SORT BY 语句进行排序,因为此情况下应用服务器上的执行速度要比数据库服务器快 ( 应用服务器上采用的是内存排序 )

    16. 避免在索引字段上使用:

    l not <> != IS NULL IS NOT NULL, 可以用 > < 来替代

    l 避免使用 LIKE ,但 LIKE ' 销售组 1000' LIKE ' 销售组 1000%' 可以用到,而 LIKE '% 销售组 1000' (百分号前置)则用不到索引

    l 不要使用 OR 来连接多个索引字段 ( 但同一字段多个值之间可以使用 OR) 对于同一索引字段,可以使用 IN 来替代 OR

    l 带有 BETWEEN WHERE 条件不能通过索引来搜索?也可使用 IN 代替

    17. 避免使用以下语句,因为使用这些语句时,不能使用 Table Buffer

    l Aggregate expressions

    l Select distinct

    l Select … for update

    l Order by group by having 从句

    l Joins ,使用 JOIN 时,会绕过 SAP 缓存,可以使用 FOR ALL ENTRIES 来代替

    l WHERE 从句中使用 Sub queries (子查询)

    l WHERE 从句中使用 IS NULL 条件

    18. 在下面情况下使用 FOR ALL ENTRIES IN :

    l 在循环内表 LOOP...AT Itab 中循环访问数据库

    l 簇表 是禁止 JOIN 的表类型 , 当需要联接簇表查询数据时 ,如: BSEG (会计凭证)、 KONV (条件表)

    簇表一般是由多个表组成,簇表中的数据来自于多个表,有点像视图,但不能直接通过簇表进行数据维护

    l JOIN 超过 3 个表会出现性能问题 , 当使用 JOIN 联接的表超过 3 个时

    l 如果两个表的数据非常大时(上百万),使用 JOIN 进行联合查询会很慢,此时改用 FOR ALL ENTRIES IN

    19. 使用内表批量 操作数据库 ,而不要使用工作区一条条操作 , 如:

    SELECT ... INTO TABLE itab
    INSERT dbtab FROM TABLE itab
    DELETE dbtab FROM TABLE itab
    UPDATE / MODIFY dbtab FROM TABLE itab

    20. 如果你 使用 CLIENT SPECIFIED ,需在 WHERE 从句第一个位置上指明 MANDT 条件 ,否则使用不到索引

    11.2. 程序

    1. READ TABLE ...WITH [TABLE] KEY.. . BINARY SEARCH 读取标准内表 使用二分查找

    2. 循环( LOOP AT ...WHERE.. )或 查询 READ TABLE ... )某内表时,如果未使用索引(排序表、哈希表)或二分查找,则在查询组合字段 创建第二索引 查询时通过 USE KEY WITH [TABLE] KEY 选项 使用第二索引 ,这样 在查询时会自动进行二分查找或哈希找查

    在没有用二分查找的情况下,可在查询组合字段上创建第二索引(哈希或排序索引),则在读取或循环内表时会自动使用二分查找或哈希查找算法

    3. 查找时, 优先考虑使用哈希表进行查找,再考虑使用排序表进行二分查找 因为哈希查找的时间复杂度为 (O (1)) ,不会因数据的增加而受到影响;而二分查找虽然比顺序搜索快很多,但随着数据的增加会慢下来,其时间复杂度为 (O (log2 n )) ;标准内表的时间复杂度为 O(n) 。注:如果只使用到部分关键字为搜索条件,哈希表则会全表扫描,此时应该使用二分找查

    4. FOR ALL ENTRIES :需要判断内表是否为空 ,否则会查询出所有数据

    5. LOOP AT itab... ASSIGNING ... READ TABLE ... ASSIGNING ... 在循环或读取内表时, 使用字段符号来替换表工作区 ,将数据分配给字段符号 Field Symbols 减少数据来回传递

    6. 尽量避免嵌套循环,如必须时, 将循环次数少的放在外层,次数多的放在内层 ,这样可以减少在不同循环层之间的频繁地切换及内部循环次数

    7. 条件语句中多使用短路与或 ,“与”连接时将为假的机率大的条件放在前面,“或”连接时将为真的机率大的条件放在前面

    8. 少使用递归算法 ,递归时会增加调用栈层次,降低了性能,可使用队列或栈来避免递归

    9. 尽量 不要使用通用类型 (如 FIELD-SYMBOLS 、及形式参数), 使用具体限定类型 ;比较时尽量使用同一数据类型: IF c = c. IF i = c. 快,原因是未发生类型转换

    10. 不要使用混合类型进行计算与比较 ,除非有必须

    11. 尽量 使用静态语句,少用动态编程 ,动态编辑虽然灵活,但性能有所下降

    12. 在对字符进行操作进,尽量使用 String 代替 C 固定长度类型 ,如: concatenate [kənˈkatɪneɪt] 语句对固定长度的 C 连接时,会去扫描那些非空字符出来再进行连接,速度没有 String

    13. READ/MODIFY TABLE 时使用 TRANSPORTING 只读取或修改必要的字段 [trænsˈpɔ:t]

    14. 尽量避免使用 MOVE-CORRESPONDING SELECT... INTO CORRESPONDING FIELDS OF [TABLE] (SELECT 时,查询几个字段就定义具有这几个字段的内表,而不是直接使用基于数据库表类型创建的内表,否则如果直接使用 INTO TABLE 语法检查时会警告,但结果是没有问题的 ) CORRESPONDING 语句在系统内部存在隐式操作 : 逐个字段的检查 元素名称匹配 ; 检查元素 类型匹配 ; 元素 类型转换 [ˌkɔrisˈpɔndiŋ]

    15. 最好不要向排序内表中插入 INSERT ... INTO TABLE ... 数据 ,因为在插入时会进行排序,速度会随着数据量的增加而慢下来,所以最好只向标准内表或哈希表中插入数据

    16. 将某个内表中的全部记录或部分记录 追加 到另一内表时, 使用 INSERT/APPEND LINES OF … 代替循环逐条追加 ;如果是全新赋值,直接对内表使用 “=” 进行赋值操作即可

    17. 调用 类方法要快于 Function

    Calling Methods of global Classes call method CL_PERFORMANCE_TEST=>M1.

    Calling Function Modules call function 'FUNCTION1'.

    18. 通过运行事务代码 SLIN ( 或者直接通过 SE38 的菜单 ) ,进行代码 静态检查 ,根据 SAP 提供的反馈信息,优化代码

    19. 通过老式方式定义内表时,使用 OCCURS 0 而非 OCCURS n [əˈkə:s] 重现

    l OCCURS n 代表初始化内表的 空间大小为 n (空间固定), 当内表存储记录条数超出 n 时, 系统将依靠 页面文件 存放超出部分的数据 当系统内存资源十分紧缺的时候, 我们可以使用 OCCURS n 的初始化方法, 但是这样的效率稍微慢

    l OCCURS 0 代表初始化内表的 空间大小为无限 ,当内表存储记录条数不断增加时, 内表所使用的内存空间不断扩大, 直到系统无法分配为止。 使用内存比使用页面交换更快一些 但是要考虑系统的资源状态

    20. 使用完成后及时清空释放内表所占用的空间: FREE <itab> .

    21. 使用 CASE…WHEN 语句代替 IF…ELSEIF… ;使用 WHILE…ENDWHILE 代替 DO…ENDDO

    22. LOOP 循环内表时加上 Where 条件 减少 CPU 负荷,而不是在循环里通过 IF 语句来过滤数据

    12. 屏幕

    12.1. AT SELECTION-SCREEN PAI AT USER-COMMAND 触发时机

    当点击屏幕上元素 包括按钮、单选复选按钮、下拉列表、菜单、工具条 选择屏幕 触发的是 AT SELECTION-SCREEN 不是 AT USER-COMMAND 事件 ), 对话屏幕 触发的 PAI 事件 列表屏幕 触发的才是 AT USER-COMMAND 事件

    12.2. SELECTION-SCREEN 格式化屏幕、 激活预设按钮

    SELECTION-SCREEN SKIP 空行

    SELECTION-SCREEN ULINE 水平线

    SELECTION-SCREEN COMMENT text FOR FIELD sel 文本标签

    SELECTION-SCREEN PUSHBUTTON bt_text USER-COMMAND fcode 按钮

    SELECTION-SCREEN BEGIN OF LINE 多元素行

    SELECTION-SCREEN BEGIN OF BLOCK block 屏幕块

    SELECTION-SCREEN BEGIN OF TABBED BLOCK tblock Tabstrip

    SELECTION-SCREEN FUNCTION KEY n 激活 工具栏中 预设按钮

    SELECTION-SCREEN BEGIN OF SCREEN dynnr [ AS SUBSCREEN ] 定义屏幕或子屏幕

    12.3. PARAMETERS

    PARAMETERS {para[ ( len ) ]}|{para [ LENGTH len]}
    type_options [{ TYPE type [ DECIMALS dec] }| { LIKE dobj }| { LIKE ( name ) }]
    screen_options [{ {[ OBLIGATORY | NO-DISPLAY ] [ VISIBLE LENGTH vlen]}
    | {
    AS CHECKBOX [ USER-COMMAND fcode]}
    | {
    RADIOBUTTON GROUP group [ USER-COMMAND fcode]}
    | {
    AS LISTBOX VISIBLE LENGTH vlen [ USER-COMMAND fcode][ OBLIGATORY ]}}
    [
    MODIF ID modid]]
    value_options [ DEFAULT val][ LOWER CASE ][ MATCHCODE OBJECT hp][ MEMORY ID pid][ VALUE CHECK ]

    OBLIGATORY 如果某个屏幕输入元素处于隐藏状态,即使它是必输入的,则在提交时也不会提示你必输入 (但如果是必须的,在隐藏前一要输入,否则会出错并要求重新输入),只有在显示状态时且不输入时才会提示

    MODIF ID key :设置修改组代码, 方便屏幕的元素的批量修改, key 中设定的代码将被赋给系统内表 SCREEN-GROUP1 字段

    MATCHCODE OBJECT :指定一个 search_h elp

    MEMORY ID pid :通过 SAP Memory 进行同一用户会话不同窗口间的参数传递

    VALUE CHECK :开启系统自动检验(如果屏幕元素参照的数据元素所对应的 Domain 设置了 fixed Values Value Table

    PARAMETERS : p_check as CHECKBOX USER-COMMAND chk

    PARAMETERS : p_radio1 TYPE c RADIOBUTTON GROUP g1 USER-COMMAND rbt ,
    p_radio2
    TYPE c RADIOBUTTON GROUP g1 .

    PARAMETERS p_carri2 LIKE spfli - carrid
    AS LISTBOX VISIBLE LENGTH 20
    USER-COMMAND lst

    12.4. SELECT-OPTIONS

    SELECT-OPTIONS selcrit FOR {dobj| ( name ) }
    screen_options [ OBLIGATORY | NO-DISPLAY ][ VISIBLE LENGTH vlen][ NO-EXTENSION ][ NO INTERVALS ][ MODIF ID id ]
    value_options [ DEFAULT val1 [ TO val2] [ OPTION opt] [ SIGN sgn]][ LOWER CASE ]
    [
    MATCHCODE OBJECT search_help][ MEMORY ID pid]

    该语句会生成一个名为 selcrit 选择条件内表,具体请参数 OPEN SQL 章节中的 RANG 条件内表

    NO- EXTENSION :限制选择表为单行,元素输入后面不会出现 按钮 [iksˈtenʃən]

    NO INTERVALS :只会出现 LOW 字段, To 后面的 HIGH 字段不出现在选择屏幕上, 但是用户仍然可以在 Mutiple Selection 窗口中输入范围选择 。也就是说:只要有 按钮,就可以选择多个条件与范围值 [ˈintəvəl]

    OBLIGATORY :只有前面一个框框中出现钩,第二个框没有,也就是说该选项只能 LOW 字段有效 [əˈblɪgəˌtɔ:ri:]

    DEFAULT

    TABLES : mara,marc .

    SELECT-OPTIONS : werks FOR marc - werks OBLIGATORY DEFAULT 1001 TO 1007 SIGN I OPTION BT .

    SELEC T-OPTIONS : p2 FOR mara - matnr MO DIF .
    AT SELECTION-SCREEN OUTPUT .
    p2-low = 'aaaa'.
    APPEND p2 .

    MEMORY ID :将第一个输入框中的数据存放到 SAP MEMORY 中共享

    12.4.1. 输入 ABAP 程序默认值时,需要加上“ =

    如果输入框中 输入的值恰为 ABAP 程序中相应字段所对应的初始值时 (如字符类型为空串,时间与数字类型为“ 0 ”串时), 需要在第一个框前面选择 操作符,否则程序将会忽略这个值的输入 ,即查询所有的

    12.4.2. 选择条件内表多条件组合规则

    (( Select Single Values OR ) OR ( Select Intervals OR ))( AND NOT Exclude Single Values) … ( AND NOT Exclude Intervals)

    ( "MATNR" = '1' OR "MATNR" >= '2' OR "MATNR" <= '3' OR "MATNR" > '4' OR "MATNR" < '5' OR "MATNR" <> '6' OR "MATNR" <> '7' OR "MATNR" LIKE '23%' OR NOT ( "MATNR" LIKE '24_' ) OR "MATNR" BETWEEN '8' AND '9' OR NOT ( "MATNR" BETWEEN '10' AND '11' ) ) AND "MATNR" <> '12' AND "MATNR" < '13' AND "MATNR" > '14' AND "MATNR" <= '15' AND "MATNR" >= '16' AND "MATNR" = '17' AND "MATNR" = '18' AND NOT ( "MATNR" LIKE '25%' ) AND "MATNR" LIKE '26_' AND NOT ("MATNR" BETWEEN '19' AND '20' ) AND "MATNR" BETWEEN '21' AND '22'

    12.4.3. 使用 SELECT-OPTIONS 替代 PARAMETERS

    实际上 PARAMETERS 类型的参数完全可以使用 SELECT-OPTIONS 来替代,下面就是使用这种替换方式,外表看上去与 PARAMETERS 是一样的,但双击后 可以出现操作符选择 界面,所以 唯一不同点就是这个可以选择操作符 ,而且这样做的好处是:当 不输入值时,查询所有 的, PARAMETERS 值为空是查询就是为空(或 0 )的值 (如果此时要忽略这个条件,则要将单值转换为 Rang 或者是分两种情况来写 SQL 条件):

    TABLES : marc .
    SELECT-OPTIONS : s_werks FOR marc - werks NO INTERVALS NO-EXTENSION .

    12.5. 各种屏幕元素演示

    TABLES : mara , marc .
    DATA : g_pg ( 24 ).

    SELECTION-SCREEN BEGIN OF BLOCK bk1 WITH FRAME TITLE text - 001 .
    PARAMETERS : p_bukrs LIKE t001 - bukrs OBLIGATORY . "Company code
    SELECT-OPTIONS :
    s_werks
    FOR marc - werks OBLIGATORY NO INTERVALS ,
    s_matnr
    FOR mara - matnr NO - EXTENSION ,
    s_segme
    FOR g_pg . " 参照普通变量
    PARAMETERS : p_line ( 6 ).
    SELECTION-SCREEN SKIP 1 .
    PARAMETERS : p_x1 RADIOBUTTON GROUP gp1 DEFAULT 'X' ,
    p_x2
    RADIOBUTTON GROUP gp1 .
    PARAMETERS : p_old TYPE c  AS CHECKBOX .
    PARAMETERS : p_oldhir LIKE grpdynp - name_coall MODIF ID m1 DEFAULT 'ABB_CHINA.XXXX' .

    SELECTION-SCREEN : SKIP 1 .
    SELECTION-SCREEN BEGIN OF LINE .
    PARAMETERS : p_dwload AS CHECKBOX .
    SELECTION-SCREEN COMMENT 5 ( 29 ) text - 001 .
    PARAMETERS : p_file TYPE string .
    SELECTION-SCREEN END OF LINE .
    SELECTION-SCREEN END OF BLOCK
    bk1 .

    12.6. 按钮、单选复选框、下拉框 FunCode

    如果 复选框与单选按钮没有设置 Function Code ,则它们就会像普通的输入框一样,即使状态发生了改变,也 不会触发 PAI 事件

    对话屏幕中的按钮、复选框、单选按钮、下拉框的 Function Code 都是通过屏幕元素 attributes 来设置的;选择屏幕中的 FunCode 则通过 USER-COMMAND 选项来设置

    12.6.1. 选择屏幕中的按钮

    SELECTION-SCREEN : PUSHBUTTON 2 ( 12 ) but1 USER-COMMAND cli1 .
    INITIALIZATION .
    but1
    = 'Button 1' . " 可直接设置按钮上的标签文本
    AT SELECTION-SCREEN .
    CASE sy - ucomm .
    WHEN 'CLI1' .
    ENDCASE .

    12.6.2. 选择屏幕中的单选 / 复选按钮 :点击时显示、隐藏其他屏幕元素

    更多请参考 动态修改屏幕 章节

    PARAMETERS show_all AS CHECKBOX USER-COMMAND flag .
    PARAMETERS hide RADIOBUTTON GROUP rd USER-COMMAND flag2 DEFAULT 'X' .
    PARAMETERS show RADIOBUTTON GROUP rd .
    SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME .
    PARAMETERS : p1 TYPE c LENGTH 10 ,
    p2
    TYPE c LENGTH 10 .
    SELECTION-SCREEN END OF BLOCK b1 .
    SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE t .
    PARAMETERS : p3 TYPE c LENGTH 10 MODIF ID bl2 ,
    p4
    TYPE c LENGTH 10 MODIF ID bl2 .
    SELECTION-SCREEN END OF BLOCK b2 .
    SELECTION-SCREEN BEGIN OF BLOCK b3 WITH FRAME .
    PARAMETERS : p5 TYPE c LENGTH 10 MODIF ID bl3 ,
    p6
    TYPE c LENGTH 10 MODIF ID bl3 .
    SELECTION-SCREEN END OF BLOCK b3 .

    INITIALIZATION .
    t
    = '----ALL----' .
    " 单先与复选框、下拉列表项点击触发 PAI 后,接下来还会触发屏幕的 PBO (回车也是这样),但如果点击的是执行按钮,则不会接着触发屏幕的 PBO ,除非没有输出或在 Basic List 列表页面上点击返回按钮时,才会触发 PBO
    AT SELECTION-SCREEN OUTPUT .
    LOOP AT SCREEN .
    IF show_all = 'X' AND screen - group1 = 'BL2' .
    screen - active = '1' . " 显示
    MODIFY SCREEN .
    ELSEIF screen - group1 = 'BL2' .
    screen - active = '0' . " 隐藏
    MODIFY SCREEN .
    ENDIF .
    IF show = 'X' AND screen - group1 = 'BL3' .
    screen - active = '1' .
    MODIFY SCREEN .
    ELSEIF  screen - group1 = 'BL3' .
    screen - active = '0' .
    MODIFY SCREEN .
    ENDIF .
    ENDLOOP .

    12.6.3. 选择屏幕中下拉列表: AS LISTBOX

    如果参照的字段只有检查表,没有搜索帮助时,且检查表有对应的 T 表,则 Value T 表第一个文本字段值?

    下拉框基本上与 F4 搜索帮助数据一致,但发现参照某些表字段时(如 spfli - cityfrom )下拉框中没有值:

    PARAMETERS p_carri1 LIKE SPFLI - CARRID .
    PARAMETERS p_carri2 LIKE spfli - carrid
    AS LISTBOX VISIBLE LENGTH 20
    USER-COMMAND onli
    DEFAULT 'LH' .

    除了通过参数表字段外,还可以通过 VRM_SET_VALUES 函数为下拉框初始化列表项

    12.7. 屏幕流逻辑

    P ROCESS B EFORE O UTPUT.
    P ROCESS A FTER I NPUT.
    P ROCESS O N H ELP-REQUEST.
    P ROCESS O N V ALUE-REQUEST.

    12.7.1. FIELD

    FIELD <f> .

    使用 FIELD 语句后,屏幕字段 <f> 需要在该语句处理完后才传递到 ABAP 程序相应的字段中, 在后没有带 module 选项时,仅仅只是控制屏幕字段传输到 ABAP 程序中的时间点 ,如需对屏幕字段进行检验,通过以下语句来实现检验:

    FIELD <field_name> MODULE <module_name> .

    仅只有未出现在 FIELD 语句中的屏幕字段才会在 PAI 事件块处理 传输到 ABAP 程序中去 。所以当某个屏幕字段出现在 FIELD 语句中,并且在该 FIELD 语句未执行完之前,不要在 PAI dialog modules 中使用该屏幕字段(该屏幕字段相关的 FIELD 语句执行完成之后才可以在后续的 PAI dialog modules 调用中使用),否则, ABAP 程序同名字段中的值使用的是前一次对话屏幕中所设置的值。

    12.7.2. MODULE

    FIELD dynp_field MODULE mod [ { ON INPUT }
    | {
    ON REQUEST }
    | {
    ON *-INPUT }
    | {
    ON { CHAIN-INPUT | CHAIN-REQUEST }}
    | {
    AT CURSOR-SELECTION } .

    ON INPUT 只要该字段 不为初始值 就会触发 module

    ON REQUEST :该字段 发生变化 后触发 module

    FIELD <f> MODULE <mod> ON INPUT|REQUEST|*-INPUT. 相当于选择屏幕的 AT SELECTION-SCREEN ON field

    CHAIN .
    FIELD : <f1> , <f2> , <fi...> .
    MODULE <mod1> ON CHAIN-INPUT | CHAIN-REQUEST .
    FIELD : <g1> , <g2> , <gi...> .
    MODULE <mod2> ON CHAIN-INPUT | CHAIN-REQUEST .
    ...
    ENDCHAIN .

    只要 <fi> 某个 字段满足条件( <mod1> 后面的 CHAIN-INPUT CHAIN-REQUEST 条件), <mod1> 就会被调用,而只要 <fi> <gi> 中的某个字段满足条件,则 <mod2> 就会被调用。 如果在 module 中检测不通过(如 MESSAGE… E 类消息时),则 CHAIN…ENDCHAIN 之外的所有其他屏幕字段将会被锁定且置灰 ,这与选择屏幕的 AT SELECTION-SCREEN ON BLOCK 校验是一样的

    CHAIN .
    FIELD : <f1> , <f2> , <fi...> .
    FIELD <f> MODULE <mod1> ON INPUT | REQUEST |* - INPUT | CHAIN-INPUT | CHAIN-REQUEST .
    MODULE <mod2> ON CHAIN-INPUT | CHAIN-REQUEST .
    ENDCHAIN .

    <mod1> 被调用的条件是所对应字段 <f> 满足 ON 后面指定的条件即可执行。 <mod2> 被调用的条件是只要 <fi> <f> 中的某个字段满足条件即可执行。

    12.7.3. ON INPUT ON CHAIN -INPUT 区别

    CHAIN .
    FIELD : f1 , f2 .
    FIELD : f3 MODULE mod1 ON INPUT . 只有 f3 为非初始值时才调用 mod1
    ENDCHAIN .

    CHAIN .
    FIELD : f1 , f2 .
    FIELD : f3 MODULE mod1 ON CHAIN - INPUT . f1,f2,f3 中任一字段包含非初始值时都调用 mod1
    ENDCHAIN

    CHAIN 中时,不能像下面这样写:

    FIELD a . "FIELD MODULE 只能写在 同一语句当中
    MODULE check_a ON INPUT .
    只有在 CHAIN 中时, MODULE 语句才可以单独出现(不与 FIELD 在同一语句中),且只能是 CHAIN-INPUT
    MODULE mod1 ON
    CHAIN - INPUT .

    12.8. EXIT-COMMAND

    12.8.1. MODULE <mod> AT EXIT-COMMAND

    对话屏幕中,对于 E 类型的 Function Code ,可以使用如下语句在 PAI 事件块中来触发:

    MODULE <mod> AT EXIT-COMMAND .

    不管该语句在 screen flow logic PAI 事件块里的什么地方,都 会在字段的约束自动检测之 执行 ,因此, 此时其他的屏幕字段的值不会被传递到 ABAP 程序中去 当该 MODULE 执行完后,如果未退出该屏幕,则会进行正常 PAI (即 PAI 事件块里没有带 EXIT-COMMAND 选项的 MODULE 语句)事件块。

    该语句在字段约束自动检测之前会被执行,一般用来正常退屏幕来使用,如果未使用 LEAVE 语句退出屏幕,则会在这之后还会继续进行字段的自动检测,检测完后还会继续 PAI 的处理(即执行 PAI 事件块中不带 EXIT-COMMAND 选项的 MODULE 语句)

    12.8.2. AT SELECTION-SCREEN ON EXIT-COMMAND

    在选择屏幕上,对于 E 类型的 FunCode (如点击 )会触发 AT SELECTION-SCREEN ON EXIT - COMMAND 事件

    12.9. OK_CODE

    如果是回车(命令行中未输入内容时回车)时,由于 FunctionCode 为空,所以 SYST-UCOMM SY-UCOMM OK_CODE 不会 被重置;如果非回车,但 FunctionCode 也是空时, SYST-UCOMM SY-UCOMM 会被重置, OK_CODE 还是不会被重置,所以 OK_CODE 只有在 FunCode 非空时才会被重置

    12.9.1. ok_code 使用前需拷贝

    如果一个屏幕中的某个按钮未设置 Function Code 时也是可以触发 PAI 事件时, 并且由于其 Function Code 此时为空而不会去设置 OK_CODE (但此时 SYST-UCOMM SY-UCOMM 还是会被重新设置为空 ),这样的话 OK_CODE 中的值还为上一次触发 PAI 时所设置的 Function Code 。所以一般情况下在 使用 OK_CODE 之前,先将 OK_CODE 拷贝到 SAVE_OK 变量中(在后面的程序使用 SAVE_OK 而不是 OK_CODE ), 并随后将 OK_CODE 清空 ,以便为下一次 PAI 事件所使用做准备

    其实还有一种方案可能替换这种使用前拷贝方案:就是还是针对 OK_CODE 编程,不另外定义 save_ok ,而是 在每个屏幕的 PBO 里将 ABAP 中的 OK_CODE 清空

    12.10. Search help F4

    12.10.1. VALUE CHECK fixed Values Value Table

    PARAMETERS p_1 TYPE zmy_dm_200 VALUE CHECK . " 注: SELECT-OPTIONS 没有此选项

    如果选择屏幕字段参考数据元素所对应的 Domaim 设置了 固定值 fixed Values )或 值表 Value Table )时,使用 VALUE CHECK 选项后,会验证输入值是否在固定值或值表范围之内

    若要使值表检查生效 ,则首先需要将此 Domain 引用到表字段,再对此表字段通过 按钮进行外键分配,并且外键一定是来自的值表的主键,最后使用 PARAMETERS 定义屏幕参数时要参照此表(从表)字段(类型参照了该 DataElement 的字段),否则如果只是直接参照所对应的 DataElement 是不起作用 Value Table 一定要经过转换为 Check Table 后再起作用,并且 PARAMETERS 要参照此表字段。 比如这上面 PARAMETERS 示例语句中直接使用的是 zmy_dm_200 这个 DataElement ,这样即使该 DataElement 所对应的 Domain 设置了 ValueTable ValueCheck 不会起使用,也不会显示 F4 帮助(但如果设置了 Fixed Value 则会显示 F4 帮助)

    注: 如果要使用 VALUE CHECK 选项,则 Domain 的类型只能是 C 或者 N 类型 否则运行会抛异常。


    PARAMETERS c TYPE sflight - carrid VALUE CHECK . " 应该是这样,参照的是从表字段(主表为值表)
    PARAMETERS c TYPE s_conn_id VALUE CHECK . " 而不应该直接参照 DataElement ,不会出现 F4 帮助,也不会进行 Value Check

    12.10.2. 检查表 Check Table --- Value Table

    也可以在 Domain 中指定一个值表( Value Table )作为字段取值范围的限制,但是与指定 固定值 的方式不同的是:为一个 Domain 简单地指定一个取值表不会导致用户的输入被自动校验,也不会自动出现 F4 Help 只有 通过表外键 按钮将该 Value Table 指定为主表之后,一个值表才能真正成为 Check Table 。所以要想成为真正有效的 Check Table ,必须要做两个操作:

    一是 要为字段对应的 Domain 设置 Value Table (即主表 ,其实这一步不是必须的,在通过 按钮指定主表时,可以不用指定为字段所参照的元素所对应 Domain 所设置的 Value Table ,而是指定其他的主表也是可以的—— 但最好不要这样做 Value Check 时会出其他问题) ,二是 要为表字段通过 为它设置外键

    实质上 Domain 上设置的 Value Table 的作用,就是在创建透明表时,字段如果参照了该 Domain ,则这个 Value Table 默认可以成为 Check Table

    这是个默认建议,在指定外键关键时,可以指定另外的表作为 Check Table

    12.10.3. SE11 检查表与搜索帮助 关系

    当某个表字段有检查表,并且又有搜索帮助,则 数据一般来自源于检查表 ,而 F4 输入输出格式则由搜索帮助来决定

    PARAMETERS p_carid TYPE sbook - carrid VALUE CHECK .
    PARAMETERS p_cuter TYPE sbook - counter VALUE CHECK .

    命中清单中的 ID 列即 CARRID 背景色不是蓝色,所以选择一条时,不会自动填充屏幕字段 P_CARID 原因是对应的 Search Help 中的 CARRID 参数对应的 EXP 没有打上钩:

    如果将这个钩打上,则会相应列背景色会为蓝色,且会自动填充, 达到联动效果

    一般 当某个外键所参照主表的主键上如果设置了搜索帮助 (如上面 COUNTER 外键所引用的主表主键字段 SCOUNTER-COUNTNUM 已分配搜索帮助“ SCOUNTER_CARRIER_AIRPORT ”: ), 则这个主表主键上的搜帮助会自动带到从表中相应外键上来 ,请看上面的 SBOOK-COUNTER 外键字段的搜索帮助也为“ SCOUNTER_CARRIER_AIRPORT ”,该搜索帮助 决定了整个 F4 Help 处理及显示过程 (如哪些列将作为联动查询条件、哪些列将显示在 F4 列表中、 F4 列表中的哪些列会输出到相应屏幕字段中)。另外,虽然主表主键上的搜索帮助会带到相应外键上来,但带过来后还可以修改,比如上面示例中带过来的搜索帮助中, CARRID 参数所对应的 EXP 没有钩上,所以不能使用命中清单中的 ID 列来自动填充示例中的屏幕字段 P_CARID ,所以我们可以新建一个搜索帮助,并将 CARRID 搜索参数所对应的 EXP 钩上,则可达到自动上屏幕的效果;

    另外, 有些外键所参照的主表主键没有指定搜索帮助 ,此时参照从表的屏幕字段的 F4 Help 就只有简单的一列了 (如何让检查表 SCURX 中的 CURRDEC 字段也显示出来,请看后面的 F4 搜索帮助联动的决定因素 ),如下面 SBOOK-LOCCURKEY 字段:

    PARAMETERS p_cur TYPE sbook - LOCCURKEY VALUE CHECK .

    12.10.4. F4 搜索帮助联动的决定因素

    上节 SE11 检查表与搜索帮助关系 中,屏幕字段参考 sbook - LOCCURKEY 时,搜索帮助输出列表只有简单一列,如果要让主表中的 SCURX - CURRDEC 列也显示出来,则需要为 sbook - LOCCURKEY 字段绑定一个搜索帮助,该搜索帮助数据来源于主表(或检查表) SCURX ,搜索参数包括 CURRKEY CURRDEC 两列,并且让这两列在 F4 输出列表中显示(即在搜索参数“列表”栏位编号):

    由于 SBOOK 不能直接修改, ZSBOOK SBOOK 拷贝过来,将搜索帮助 ZSCURX_JZJ 绑定到 ZSBOOK- LOCCURKEY

    PARAMETERS p_cur TYPE zsbook - LOCCURKEY VALUE CHECK .

    上面检查表中的 SCURX-CURRDEC 列(即 F4 中的小数位)已显示来了,但如何让其背景色为蓝色( 虽然上面已将搜索参数 CURRDEC EXP 打上了钩,但底色还是白色的 ),即选择时自动填充到屏幕上去?由于上面在将搜索帮助 ZSCURX_JZJ 绑定到从表字段 zsbook - LOCCURKEY 字段上时,搜索帮助中的搜索参数 CURRDEC (即主表中的字段 SCURX- CURRDEC )在从表 ZSBOOK 找不到相应的外键,所以上图绑定过程中,搜索参数 CURRDEC 为空。但在这里可以手动分配一个,由于在从表 ZSBOOK 中找不到此字段,所以就暂时参照自己 ( 主表 SCURX-CURRDEC) 吧:

    如果此时选择屏幕的代码还是上面那样:
    PARAMETERS p_cur TYPE zsbook - LOCCURKEY VALUE CHECK .

    F4 搜索输出列表中的“小数位”列底色还是白色,但如果 加上 以下屏幕参数,但会变以蓝色,并可联动( 如果搜索帮助的 CURRDEC 参数的 IMP 打上钩,还可以实现联动查询 ):

    PARAMETERS p_cur2 TYPE SCURX-CURRDEC VALUE CHECK

    此时的下拉框也会只有两列:

    PARAMETERS p_cur3 TYPE zsbook - LOCCURKEY as LISTBOX VISIBLE LENGTH 20 .

    所以,联动的决定性条件是要求选择屏幕上的字段要参照 SE11 为表字段所绑定搜索帮助过程中 所分配的表字段 ,如下图中的 zsbook-loccurkey scurx-currdec ,这两个字段分别与搜索帮助的 CURRKEY CURRDEC 参数绑定了,所以屏幕上参照这两个表字段时,就会具有联动效果了:

    12.11. 搜索帮助参数说明

    ² IMP :输入参数。表示屏幕上相应字段是否作为搜索帮助的过滤条件(即 报表选择屏幕上的字段的值是否从报表选择屏幕上传递到搜索帮助中去

    如果是 F4 字段时,屏幕字段中的值包含“ * ”时,才会将 F4 字段传递到 Search Help 中。除开 F4 屏幕字段外,而其他只要是 Link 到了相应的 Search Help 参数的屏幕字段,只要相应屏幕字段中有值,则会传入到搜索中作为过滤条件(而其他非 F4 屏幕字段所对应的 Help 参数不管是否钩上 IMP 都会传递

    ² EXP :输出参数,表示 搜索帮助的此列会从搜索帮助中传递到报表选择屏幕 上(表示 F4 选中一条记录后显示到屏幕上文本框中的值——背景字段为浅蓝色的列的数据会被输出,输出的数据可能是多列。注: 只有当 EXP 钩上且相应字段出现在了屏幕上 ,才会自动填充到相应屏幕字段,如果没有钩上——没钩上的字段背景色为白色,即使相应参数字段出现在了屏幕上,选择命中清单时也不会自动填充),且 F4 字段一定要将 EXP 钩上 (否则选择后 F4 字段不能上屏)

    ² LPOS (列表): F4 输出 命中清单中各列的显示顺序 ,如果为 0 或留空的列则不会显示

    ² SPOS :相应的字段是否在搜索帮助 选择屏幕上显示出来 ,在命中清单显示之前,如果弹出限制对话框,则可以进一步修改那些从选择屏幕上带过来的条件值。此数字就是限制搜索帮助选择条件屏幕字段摆放顺序,如果为 0 或留空的列则不会出在限制条件页中

    ² SDis :如果勾选了,则在弹出的限制对话框中对应的字段用户 不可输入 ,是只读的。

    12.12. F4IF_SHLP_EXIT_EXAMPLE 帮助出口

    12.12.1. 修改数据源

    FUNCTION zfvbeln_find_exit .
    *"----------------------------------------------------------------------
    *"*"Local Interface:
    *"  TABLES
    *"      SHLP_TAB TYPE  SHLP_DESCT
    *" RECORD_TAB STRUCTURE  SEAHLPRES
    *"  CHANGING
    *"     VALUE(SHLP) TYPE  SHLP_DESCR
    *"     VALUE( CALLCONTROL ) LIKE  DDSHF4CTRL STRUCTURE  DDSHF4CTRL
    *"----------------------------------------------------------------------
    " 此内表用于存储命中清单数据 . 注:字段的名称一定要与搜索参数名一样,但顺序可以不同,
    DATA : BEGIN OF lt_tab OCCURS 0 ,
    wbstk
    TYPE wbstk ,
    lfdat
    TYPE lfdat_v ,
    vbeln
    TYPE vbeln_vl ,
    END OF lt_tab .
    " 用于存储从选择屏幕上传进的屏幕字段的选择条件值
    DATA : r_vbeln TYPE RANGE OF vbeln_vl WITH HEADER LINE ,
    r_lfdat
    TYPE RANGE OF lfdat_v WITH HEADER LINE ,
    r_wbstk
    TYPE RANGE OF wbstk WITH HEADER LINE ,
    wa_selopt
    LIKE LINE OF shlp - selopt . "
    "callcontrol-step
    该字段的值是由系统设置,并且你可以在程序中进行修改它。出口函数会在处理的每一步(时间点)都会调用一次
    IF callcontrol - step = 'SELECT' . " 如果有弹出限制对话框,则会在弹出限制对话框中点击确认按钮后 step 值才为 SELECT
    "shlp-selopt 存储的是经过映射转换后选择屏幕上字段的值,而不是直接为
    " 选择屏幕字段名,而是转映射为 Help 参数名后再存储到 selopt 内表中,
    " 屏幕字段到 Help 参数映射是通过 shlp-interface 来映射的
    LOOP AT shlp - selopt INTO wa_selopt .
    CASE wa_selopt - shlpfield .
    WHEN 'VBELN' . " 由于屏幕字段已映射为了 Help 相应参数,所以这里不是 S_VBELN
    MOVE-CORRESPONDING wa_selopt TO r_vbeln .
    APPEND r_vbeln .
    WHEN 'LFDAT' .
    MOVE-CORRESPONDING wa_selopt TO r_lfdat .
    APPEND r_lfdat .
    WHEN 'WBSTK' .
    MOVE-CORRESPONDING wa_selopt TO r_wbstk .
    APPEND r_wbstk .
    ENDCASE .
    ENDLOOP .
    " 根据屏幕上传进的条件查询数据
    SELECT likp ~ vbeln likp ~ lfdat vbuk ~ wbstk INTO CORRESPONDING FIELDS OF TABLE lt_tab
    FROM likp INNER JOIN vbuk ON likp ~ vbeln = vbuk ~ vbeln
    WHERE likp ~ vbeln IN r_vbeln AND
    likp
    ~ lfdat IN r_lfdat AND
    vbuk
    ~ wbstk IN r_wbstk .
    " 该函数的作用是将内表 lt_tab 中的数据转换成 record_tab ,即将某内表中的数据显示在命中清单中
    CALL FUNCTION 'F4UT_RESULTS_MAP'
    TABLES
    shlp_tab
    = shlp_tab
    record_tab
    = record_tab
    source_tab
    = lt_tab
    CHANGING
    shlp
    = shlp
    callcontrol
    = callcontrol .
    " 注:下一个时间点一定要直接设置为 DISP ,否则命中清单不会有值,也不显示出来
    " 从表面上看, SELECT 时间点下一个就是 DISP 时间点,按理是不需要设置为 DISP
    " 但如果不设置为 DISP ,出口函数在执行完后,系统会转入 DISP 时间点执行(即再次调用此出口函数)
    " ,但再次进入此出口函数时, record_tab 内表已经被清空了(是否可以通过判断 callcontrol-step 的值来决定走什么新的逻辑代码来解决此问题?)。如果这里直接设置为 DISP ,就好比欺骗了系统一样,告诉系统当前执行的正是 DISP 时间点,而不是 SELECT ,系统就不会再转到 DISP 时间点了而是直接显示
    callcontrol - step = 'DISP' . "DISP 在命中清单显示之前调用 ,表示数据已经查出,下一步就该显示了。该时间用于控制搜索帮助的输出结果。例如,在输出搜索结果时对用户检查权限,删除未授权的数据
    ENDIF .
    ENDFUNCTION .

    12.12.2. 删除重复

    FUNCTION zeh_lxsecond .
    IF callcontrol - step = 'DISP' .
    SORT RECORD_TAB .
    DELETE ADJACENT DUPLICATES FROM RECORD_TAB COMPARING ALL FIELDS . "zsecond.
    EXIT .
    ENDIF .
    ENDFUNCTION .

    12.13. 搜索帮助优先级

    P ROCESS O N V ALUE-REQUEST AT SELECTION-SCREEN ON VALUE-REQUEST

    PARAMETERS/ SELECT-OPTIONS MATCHCODE OBJECT

    检查表 Check Table ,再 (或 结构 字段是否 绑定 搜索帮助

    data element 是否 绑定 帮助 ,再 domain 是否存在 fixed values

    最后才是 DATS TIMS

    Domain 只设置 Value Table 也可以出 F4 ,同时 Data Element 绑定了搜索帮助,则 DataElement 上绑定的搜索帮助优先于 Domain 上的 Value Table????????

    12.14. 搜索帮助创建函数

    在屏幕的 ON VALUE-REQUEST 事件里可以通过下面几个函数来创建搜索帮助:

    F4IF_ FIELD _VALUE_REQUEST 函数的作用是在运行时,可以 动态 的为某个屏幕字段 指定 Search Help ,这个被引用的 Help 来自某个表(或结构)字段上绑定的 Help

    F4IF_ INT_TABLE _VALUE_REQUEST 在程序运行时, 将某个内表动态的用作 Search help 的数据来源 ,即使用该函数可以将某个内表转换为 Search help ,可实现联动效果

    TR_F4_HELP 简单实现 Search Help ,数据来源于内表

    12.15. POV 事件里读取屏幕字段中的值函数

    POV (包括选择屏幕上 AT SELECTION-SCREEN ON VALUE-REQUEST 事件)事件中,屏幕上的字段的值不像 PAI 里那样可以直接读取到,所以使用以下两个函数来 读写:

    DYNP_VALUES_READ DYNP_VALUES_UPDATE

    12.16. 动态修改屏幕

    选择屏幕、对话屏幕都有对应的 SCREEN 内表,下面是几个重要属性:

    NA M E Name of the screen field 如果参数是 select-options 类型参数,则参数名以 LOW HIGH 后缀来区分。

    GROU P1 选择屏幕元素通过 MODIF ID 选项设置 GROUP1 (对话屏幕通过属性设置),将屏幕元素分为一组, 方便屏幕的元素的批量修改

    REQUI R E D 控制 文本框、下拉列表屏幕元素的 必输性 ,使用此属性后会忽略 OBLIGATORY 选项。取值如下:

    0 :不必输,框中前面也没有钩
    1 :必输,框中前面有钩,系统会自动检验是否已输入,相当于
    OBLIGATORY 选项
    2 不必输,但框中前面有钩,系统不会检查是否已输入,此时需要手动检验

    INPUT 控制屏幕元素(包括复选框、单选框、文本框)的 可输性

    AC T I VE 控制屏幕元素的 可见性

    REQUIRED 选项的应用 该选项可以解决这个问题 :在 点击某个单选框( p_rd1 后显示某个必输字段( p_lclfil ),但当这个必输框显示出来后,如果点击 p_rd2 想隐藏它时,此时输入框中必须有值,否则系统会自动检验要求重新输入。现要求输入框没有输入值的情况下,也可在点击 p_rd2 时隐藏它,则 解决的办法是 :将输入框的这个属性设置为 2 (显示必须的钩,但系统不会自动进行必输验证),去掉 OBLIGATORY 选项(不去掉也会 被忽略 ),并在 AT SELECTION-SCREEN ON field 事件里时手动进行为空验证

    " 一定要设置 USER-COMMAND ,否则点击之后,不会触发屏幕 PAI 事件, PAI 事件不触发则会导致
    " 屏幕的 AT SELECTION-SCREEN OUTPUT 也就不会被触发(非执行按钮的 FunCode 触发时都会刷新
    " 屏幕,所以再次显示屏幕时再次执行 PBO
    PARAMETERS p_rd1 RADIOBUTTON GROUP gp1 USER-COMMAND mxx . " 用来隐藏 p_lclfil
    PARAMETERS p_rd2 RADIOBUTTON GROUP gp1 DEFAULT 'X' . " 用来显示 p_lclfil
    " 当通过程序动态修改屏幕元素属性 required 后,会忽略掉这里的 OBLIGATORY 选项
    *PARAMETERS p_lclfil(128) AS LISTBOX VISIBLE LENGTH 20 MODIF ID  mxy OBLIGATORY .
    PARAMETERS p_lclfil ( 128 ) MODIF ID mxy OBLIGATORY .
    PARAMETERS : c AS CHECKBOX . " 没什么作用,用来测试 CHECKBOX 的可输入性
    " C2 被钩选时,屏幕上的其他输入元素均不可输入
    PARAMETERS : c2 AS CHECKBOX USER-COMMAND ddd DEFAULT 'X' .

    AT SELECTION-SCREEN OUTPUT .
    LOOP AT SCREEN .
    " C2 没有钩选时,其他元素都设置为可输入
    IF screen - name <> 'C2' AND c2 IS INITIAL .
    screen - input = 1 .
    MODIFY SCREEN .
    ELSEIF screen - name <> 'C2' AND c2 IS NOT INITIAL .
    screen - input = 0 . "C2 钩选时,所以屏幕输入元素禁止输入
    MODIFY SCREEN .
    ENDIF .
    " 控制下拉列表(文本框也是一样)的必输性:外观上打钩,但不自动校验
    IF p_rd2 = 'X' AND screen - group1 = 'MXY' .
    " 显示
    screen - active = '1' .
    *      screen- input = '1'." 显示前设为可输入
    screen - required = '2' . " 外观上打钩,但不自动校验
    MODIFY SCREEN .
    ELSEIF screen - group1 = 'MXY' . "
    " 隐藏
    screen - active = '0' .
    screen - required = '2' .
    MODIFY SCREEN .
    ENDIF .
    ENDLOOP .

    AT SELECTION-SCREEN ON p_lclfil .
    IF p_rd2 IS NOT INITIAL " 手动检验:但当点击单选按钮与复选框 C2 时,不校验
    AND sy - ucomm <> 'MXX' AND sy - ucomm <> 'DDD' AND p_lclfil IS INITIAL .
    MESSAGE e055 ( 00 ).
    ENDIF .

    12.17. 子屏幕

    除通过屏幕的属性将某个屏幕设置为子屏幕外,还可以通过程序创建一个子屏幕:

    SELECTION-SCREEN BEGIN OF SCREEN dynnr AS SUBSCREEN .
    ...
    SELECTION-SCREEN END OF SCREEN dynnr .

    l 子屏幕可以嵌入到 Tabstrip 中使用:

    SELECTION-SCREEN BEGIN OF TABBED BLOCK tblock FOR n LINES .
    SELECTION - SCREEN TAB ( len ) tab USER-COMMAND fcode DEFAULT SCREEN dynnr .

    ... tab Tab 的标题 len Tab 标题显示的宽度 Tab 页签内容的行数由 n 来决定; DEFAULT SCREEN :给 Tab 静态分配子屏幕
    SELECTION-SCREEN END OF BLOCK tblock .

    l 通过 CALL SUBSCREEN 语句将子屏幕嵌入到对话屏幕中:

    PROCESS BEFORE OUTPUT .
    CALL SUBSCREEN <area> INCLUDING [<prog>] <dynp> .
    ... 子屏幕都需要放在主屏幕中的某个指定的 <area> 区域元素中

    为了调用子屏幕的 PAI 事件,需要在主屏幕的 PAI flow logic 里如下调用:
    PROCESS AFTER INPUT.
    CALL SUBSCREEN <area> .
    ...

    普通选择屏幕,可以使用 CALL SELECTION-SCREEN 来单独调用

    12.18. 屏幕跳转

    LEAVE SCREEN.

    LEAVE TO SCREEN <next screen>.

    LEAVE SCREEN 语句会结束当前屏幕并调用下一屏幕, next scree 可以是 static next screen ,或者是 dynamic next screen ,如果是动态的,你必须在使用 LEAVE SCREEN 语句前使用 SET SCREEN 语句来重写 static next screen

    LEAVE TO SCREEN 语句会结束当前屏幕并跳转到指定的下一屏幕 <next screen> ,其作用等效于下面两条语句:

    SET SCREEN <next screen>.

    LEAVE SCREEN.

    这两个语句不会结束屏幕序列,它们 仅仅是转向同一屏幕序列中的另一屏幕 。屏幕序列是否结束要看 <next screen> 是否为 0 或者屏幕的 next screen 属性是否设置为 0

    可以用 LEAVE TO SCREEN 0 来结束当前 SCREEN SEQUENCE

    12.18.1. CALL SCREEN 误用

    每次碰到 CALL SCREEN 语句就会产生新的 SCREEN SEQUENCE ,而且 SAP 系统设置了 SCREEN SEQUENCE 堆栈 不能超过 50 ,一旦超过就会出溢出错误,所以不要使用 CALL SCREEN 进行屏幕的切换

    为了避免产生新的 SCREEN SEQUENCE ,针对上图,可以使用 LEAVE...SCREEN 进行屏幕切换,而不是 CALL SCREEN

    SET SCREEN 110. " 该语句只是动态制定下一个屏幕,但不结束当前屏幕处理(即不立即跳转下一屏幕),只有 LEAVE SCREEN 才会结束屏幕的处理(后面的语句才不会执行)
    LEAVE SCREEN.

    或者使用: LEAVE TO SCREEN 110. 相当于上面两包的组合: SET SCREEN 110.  LEAVE SCREEN.

    使用 SET SCREEN XXX / LEAVE SCREEN LEAVE TO SCREEN XXX 来在同一屏幕序列里动态的进行屏幕切换 跳转,而 不要使用 CALL SCREEN XXX 进行屏幕序列的跳转与切换

    12.18.2. CALL SCREEN/SET SCREEN/LEAVE TO SCREEN 区别

    CALL SCREEN XXXX 将在 Screen 调用栈( CALL STACK )上面添加一层调用(进栈,即 重新开启一个新的屏幕序列 ),调用 XXXX PBO PAI ,如果 XXXX Next Screen 不为 0 ,那么将继续其 Next Screen PBO PAI ,如此继续 ~~~ 当最后碰到 Next Screen 0 时,该层调用将从调用栈中退出(出栈),然后系统将继续执行 CALL SCREEN XXXX 之后的语句。

    SET SCREEN XXXX 设置调用栈当前层次的 Next Screen XXXX ,它并不影响调用栈的层数(即 不会重新开启一个新的屏幕序列 只做屏幕之间的切换,而不是屏幕序列之间的切换 ),除非 XXXX 0 ,那将导致调用栈退掉一层(出栈)。要注意的是, PAI SET SCREEN XXXX 后的语句,系统将照样执行,只有执行完毕该 PAI 整个逻辑后,才考虑 Next Screen PBO PAI

    LEAVE TO SCREEN XXX SET SCREEN XXX 比较类似(也不会重新开启一个新的屏幕序列,只做屏幕之间的切换,而不是屏幕序列之间的切换),所不同的是, LEAVE TO SCREEN XXXX 强行中断当前 SCREEN PAI ,直接执行 XXXX PBO PAI 。换言之, PAI LEAVE TO SCREEN XXXX 后面的语句,系统将不会执行到

    LEAVE SCREEN. 后面的语句也不会执行

    注:上面语句的 XXX 也可以是选择屏幕的屏幕号,而不只是对话屏幕号

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

    12.19. 修改标准选择屏幕的 GUI Status

    选择屏幕的 GUI status 是由系统自动生成的, 标准选择屏幕的 PBO 事件里(即 AT SELECTION-SCREEN OUTPUT )的 SETPF-STATUS 语句将不会再起作用 。如果想修改标准选择屏幕的 GUIstatus (或去激活标准选择屏幕上的 GUI Status 上预设的功能),则可以在选择屏幕的 PBO(AT SELECTION-SCREEN OUTPUT) 事件里调用 RS_ SET_SELSCREEN_STATUS 函数来实现。

    12.20. 事件分类

    12.20.1. 报表事件

    INITIALIZATION .
    START-OF-SELECTION .
    END-OF-SELECTION .

    12.20.2. 选择屏幕事件

    INITIALIZATION START-OF-SELECTION 之间触发 ,对应于对话屏幕的 PBO PAI POH POV 事件

    AT SELECTION-SCREEN { OUTPUT }
    | {
    ON {para|selcrit} }
    | {
    ON END OF selcrit }
    | {
    ON BLOCK block }
    | {
    ON RADIOBUTTON GROUP radi }
    |
    { O N { H ELP-REQUEST | V ALUE-REQUEST }
    {
    FOR {para|selcrit - low|selcrit - high} }
    | {
    ON EXIT - COMMAND }
    | { }
    .

    12.20.3. 逻辑数据库事件

    GET node [ LATE ] [ FIELDS f1 f2 ... ] .

    12.20.4. 列表事件

    TOP-OF-PAGE .
    END-OF-PAGE .
    AT LINE-SELECTION .
    AT USER-COMMAND .
    AT pf<nn> .

    12.20.5. 事件流图

    12.21. 事件终止

    12.21.1. RETURN

    RETURN 用来 退出当前执行的 程序块 ,例如一个 FORM METHOD 报表事件块 ,不管是否出现在循环 (LOOP) 中, RETURN 都会退出当前执行的程序块 ,而 不仅仅是退出循环 (如果是在 Form METHOD 中,只会退出 Form METHOD ,不会退出 Form METHOD 被调用所在的报表事件块,即退 Form METHOD 后继续向被调用点后面执行)

    12.21.2. STOP

    l INITIALIZATION 中的 STOP 会导致跳转到 AT SELECTION-SCREEN OUTPUT 事件块;

    l 如果 STOP AT SELECTION-SCREEN OUTPUT 块里,则只是退出当前块( STOP 后面语句不执行而已),仅接着是显示选择屏幕;

    l AT SELECTION-SCREEN [ON]… 选择屏幕事件块中的 STOP 也只是退出当前事件块,继续后面的事件块;

    l 另外,即使 STOP 在循环中 , 还是在 FORM METHOD ,也是 直接从被调用的点退出所在 事件块 ,而不仅仅只退出当前循环 FORM METHOD ,这与直接在事件块中的效果是一样的;

    RETURN 用来退出当前执行的程序块 (processing block) ,例如一个 FORM METHOD ,或 EVENT ,不管是否出现在循环 (LOOP) 中, RETURN 都会退出当前执行的程序块,而不仅仅是退出循环。

    虽然 ABAP EXIT RETURN 都可以用来实现退出当前执行的语句块,但 SAP 的帮助文件建议只在循环中使用 EXIT ,其他情况下要退出当前执行进程,使用 RETURN

    12.21.3. EXIT

    l INITIALIZATION 中的 EXIT 会导致跳转到 AT SELECTION-SCREEN OUTPUT 事件块;

    l 如果 EXIT AT SELECTION-SCREEN OUTPUT 块里,则只是退出当前块( EXIT 后面语句不执行而已),仅接着是显示选择屏幕;

    l AT SELECTION-SCREEN [ON]… 选择屏幕事件块中的 EXIT 也只是退出当前事件块,继续后面的事件块;

    l START-OF-SELECTION 开始往后的事件块 ,如果出现 EXIT ,则会开始 listprocessor (列表处理),并 跳转到相应的 List 输出界面 (前提条件是要在退出前已经向屏幕输出内容了,否则也不会跳转);注: END-OF-SELECTION 事件块也会被跳过

    l 另外, 如果 EXIT 在循环 DO WHILE LOOP 里,只是跳出当前循环而已

    l 如果是在 FORM METHOD 中,而非循环中,则退出当前的 FORM METHOD ,其作用与 RETURN 类似

    1) EXIT 如果出现在循环中,退出的是整个循环操作, . 程序会从循环结束处开始继续执行,其作用相当于 Java C++ 中的 break

    2 EXIT 如果出现在循环之外,退出的是当前执行的程序块 (processing block) ,例如一个 FORM METHOD ,或 EVENT ,其作用与 RETURN 类似。

    12.21.4. CHECK

    CHECK 跳转的前提是 <expr> 为假时。

    l CHECK 只是跳出当前事件块,继续下一个事件块的处理,相当于方法的 return

    l 另外,如果 CHECK 在循环 DO WHILE LOOP 里,只是跳出当前循环而已

    l 如果 CHECK 出现在循环以外 , 退出的是当前执行的程序块 (processing block) ,例如一个 FORM METHOD ,或 EVENT

    1 CHECK 后面要跟一个表达式,当表达式值为假 (false) 时, CHECK 发生作用,退出循环 (LOOP) 或处理程序( Processing Block )。

    2 )如果 CHECK 出现在循环中,则发生作用时,退出的是当前一次循环操作,程序会继续执行下一次循环操作,其作用类似于 Continue Java C++ continue 也是如此) .

    3 )如果 CHECK 出现在循环以外,则发生作用时,退出的是当前执行的程序块 (processing block) ,例如一个 FORM METHOD ,或 EVENT

    12.21.5. LEAVE

    LEAVE PROGRAM . 退出整个程序

    LEAVE TO TRANSACTION ta

    LEAVE LIST-PROCESSING . list pro cessor 回到 dialog processor

    LEAVE TO LIST-PROCESSING 控制权从 dialog processor 转交给 list processor

    LEAVE { SCREEN | { TO SCREEN dynnr} }

    12.21.5.1. REJECT

    REJECT 是用在逻辑数据库 GET event blocks 中,与 EXIT CHECK 不一样的是( EXIT CHECK 如果是在循环中时,只是退出循环 ;如果是在 FORM 中,则只是退出当前 FORM ), REJECT 可以从循环或者一个 FORM 中直接跳出所在的 GET 事件块

    REJECT [<dbtab>] .

    终结逻辑数据库当前节点数据行的处理

    如果省略选项 <dbtab>, 则逻辑数据库会自动读取同一节点的下一行数据,即同一节点的 GET 事件块会被触发。如果使用了 <dbtab> 选项,则逻辑数据库会读取节点 <dbtab> 的下一行数据,此时的 <dbtab> 节点必须是 REJECT 所在当前节点的上级节点。

    13. 列表屏幕

    START/END-OF-SELECTION 事件处理块中,用 WRITE 语句向列表缓冲区( List Buffer )输出要显示的内容,当该事件结束的时候,所有在列表缓冲区中的内容将被显示到一个 基本列表屏幕 Basic List )上

    当用户在基础列表屏幕上双击一行或按功能键“ F2 ”时,将会触发 ABAP 事件 AT LINE-SELECTION ,如果还想进一步显示该行数据的详细信息,则可以继续使用 WRITE 语句输出要显示的内容,这次生成另外一个 详细列表屏幕 Details List Screen )。此详细列表屏幕将覆盖其上一层的基础列表屏幕,若在其界面的工具条上点“返回”或按功能键 F3 ,将返回到基础列表屏幕。

    在详细列表屏幕上,当用户双击一行数据或按功能键 F2 AT LINE-SELECTION 事件将会再次触发,因此还可以继续生成下一级的详细列表屏幕

    ABAP 提供了全屏变量 sy-lsind 来区别屏幕的层次, sy-lsind 的值主要用于在 AT LINE-SELECTION 事件处理块中进行程序流程控。 0 表示为基础列表屏幕, 1 表示第一级详细列表屏幕,依次类推,最多可以有 20 个详细列表

    13.1. 标准 LIST

    至少包括二行 standard header ,第一行 standard header 包括了 l i s t h e ade r p a g e n u m be r ,第二行 standard header 是一条水平线。当程序是可执行报表程序时, l i s t h ea d e r 存储在 S Y - T I T LE 中。如果有必要,可以给 s t a n da r d h e a de r 添加最多四行的 c olu m n he a de r s 与一条水平线。在水平或垂直滚动时 s t a n da r d h e a de r 是不会动的。

    13.2. 自定义 LIST

    TOP-OF-PAGE .

    REPORT <rep> NO STANDARD PAGE HEADING .

    REPORT <rep> LINE-SIZE <width> 当前页面宽度存储在 SY- LINSZ 系统变量中

    REPORT <rep> LINE-COUNT <length>[ ( <n> ) ] 如果指定了 <n> 参数,系统会保留 <n> 行给 page footer 页面的实际可输出正本行数为 <length> 减去 page header length ,再减去 <n> 。每页的行数包含了列表头( headings )、列表内容与列表脚注行( footer lines );系统变量 SY-LINCT 会存储页面行数 <length> ,注意:如果 REPORT 语句没有设置该值,则该系统变量值为 0 ,除非使用 NEW-PAGE 语句进行了设置。

    SY-PAGNO 系统变量存储了当前页码

    END-OF-PAGE . 触发的条件是数据要满一页时才触发,否则不会被触发
    NEW-PAGE

    RESERVE <n> LINES 在调用此语句时,如果最后一次输出到 Page Footer 之间所剩行小于(注意:等于时不会分页) <n> 时会进行分页,在开始新页之前会触发 END-OF-PAGE 事件(如果最后一页数据不足一页,还是不会显示页脚)

    NEW-PAGE [NO - TITLE|WITH - TITLE] [NO - HEADING|WITH - HEADING] WITH-TITLE NO-TITLE :控制 NEW-PAGE 新开启的页面以及后面使用 NEW-PAGE 开启的页面是否使用标准的 list header

    NO-HEADING WITH-HEADING :控制 NEW-PAGE 新开启的页面以及后面使用 NEW-PAGE 开启的页面是否使用标准的 column header

    NEW-PAGE LINE-COUNT <length> 系统变量 SY-LINCT 会存储页面行数(即 <length> )。该选项决定了随后的所有(除非又重新通过该语句的这个选项重新指定了)使用 NEW-PAGE 语句分出的页面的允许的最大数据行数

    NEW-PAGE LINE-SIZE <width> SY-SCOLS :存储了当前窗口在没有滚动条的情况下允许的最大字符数(或叫 Column ),而 LINE-SIZE 选项指的是列表本身最大允许的字符数,与窗口大小没有关系(如果 LIST 列表的宽度 LINE-SIZE 大于了窗口允许的最大字符数 SY-SCOLS ,则会出现滚动条,否则窗口不会出现滚动条)

    13.3. LIST 事件

    PF<nn>(obsolete) 已过时

    键盘上的 F<nn> 01 24 )键不再具有系统预置功能,它们都将与 function codes PF<nn> 关联

    AT LINE-SELECTION

    当用户在 LIST 屏幕上的某行上按 F2 或者鼠标双击时,就会触发该事件块,并且此时的 function code 默认名为 PICK

    AT USER-COMMAND

    除上面 PF<nn> PICK 两个 Function code 以外,其他的 function codes 将被 runtime environment (预置按钮的 FunCode 会被运行时环境捕获)拦截或者是触发 AT USER-COMMAND 事件。

    除了通过手动触发 LIST 屏幕事件之外,还可以直接通过编程的方式来触发:

    SET USER-COMMAND

    该语句会在当前列表事件块里的所有输出结束后生效(这意味着该语句放在输出语句的前后都没有关系),并在列表显示之前触发与 <fc> 对应的事件。其作用与用户手动选择了相应的 Function Code 是一样的

    13.4. Detail Lists 创建

    凡事在 AT PF<nn> AT LINE-SELECTION AT USER-COMMAND 三个事件块里输出的内容,都会创建一个新的 detail lists

    13.5. 标准的 List Status

    如果在报表程序中没有设置( Basic List Detail List 都未设置时) GUI status ,则系统会将 list screen Status 设置为系统内置的 default list status ;在其他类型的程序中(如当你在 Screen 中创建一个 list 时),你可以明确的调用下面语句来设置成系统内置的 default list status

    SET PF-STATUS space .

    该语句定义了 Standard List 所拥有的默认 functions ,系统所提供的内置 default list status 如下 (这些功能都已实现,可直接使用):

    13.6. 列表屏幕上的数据与程序间的传递

    13.6.1. SY-LISEL

    SY-LISEL 类型为 C(255) ,它是将整行的内容都存储下来了,所以要取得每个 Field ,则需要通过 offset 方式来截取

    13.6.2. HIDE

    HIDE <f> .

    变量 <f> 可以是整行或部分输出列的变量,甚至是其他与行内容无关的变量。

    当单击 List 屏幕中的行时,如果对应的行设置了隐藏字段,则 HIDE 隐藏字段变量 <f> 会自动被存储值填充,当然也可以使用 READ LINE 来手动控制读取填充隐藏域

    注:局部变量不能存储到 HIDE 区域中

    语句要在数据行被 WRITE 语句输出到列表缓冲区后,在后面紧着编写(为了隐藏的数据与当前输入的数据一致),并且当被双击时 保存在隐藏域中的字段的值将自动被传回到原始字段中

    隐藏域实质上是一个 内表 其行结构包含了三个字段:被选中行的行号、字段名、字段值,当保存数据时,每个被保存的字段在隐藏域中形成一行:

    上图中已将 wa_spfli-carrid wa_spfli-connid 存入了隐藏域中了,所以在双击行后,可以直接使用 AT LINE-SELECTION . 事件中使用

    13.6.3. READ LINE

    READ LINE <lin> [ INDEX <idx>]
    [
    FIELD VALUE <f1>[
    INTO <g1>] ... <fn>[ INTO <gn>]] [ OF CURRENT PAGE | OF PAGE <p>] .

    该语句会将事件触发所在的 List (index SY-LILLI) )中的第 <lin> 行的内容存储到 SY- LISEL 系统变量中,并且随之将 <lin> 行所对应的 HIDE 字段也进行相应填充 。另外会将 Write 显示输出的名为 <fn> 字段的值存储到 <gn> 全局变量中

    13.7. Screen Processing 屏幕处理切换到 Lists 列表输出

    为了将 dialog processor 控制权转交给 list processor ,你可在 PBO dialog modules 中调用下面这样的语句:

    LEAVE TO LIST-PROCESSING [ AN D RE T UR N T O SCR E E N < n nn n > ]

    该语句可以使用在 PBO 或者 PAI event 中,它的作用是在当前屏幕的 PAI processing (一般在 PBO 块里使用 SUPPRESS DIALOG. LEAVE SCREEN. 语句后不会显示这个屏幕,此时在 PBO 事件块结束后立即显示 Basic List )结束后开始 list processor 并显示 Basic List 。调用该语句所在屏幕的 PBO and PAI modules 中的 list output 都会被输出到 Basic List 中缓存起来,待该屏幕处理完后显示(如果没有在 PBO 中使用 SUPPRESS DIALOG. LEAVE SCREEN. 语句,则在 PAI 结束后显示;如果使用了这两个语句,则会在 PBO 块结束后就会显示)

    可以使用以下两种方式来离开 li s t p r o c e ss in g

    ba s i c li s t 中点击 B a ck , E x i t, o r C a n c el

    li s t p r o c e ss ing 程序中使用: L E A V E LIST-PROCESSING .

    以上两种方式都会使控制权从 list processor 转交到 dialog processor

    在默认的情况下,不带 AND RETURN TO SCREEN 选项的 LEAVE TO LIST-PROCESSING 语句在当 list processor 处理结束后(如关闭 list 列表输出窗体时), dialog processor 将会又会返回到 LEAVE TO LIST-PROCESSING 语句调用所在屏幕的 PBO 事件块,并重新执行 PBO 事件块(所以这样使用会出现死循环:关不掉 List 列表输出窗口);选项 AND RETURN TO SCREEN 允许你指定当前屏幕序列(即 LEAVE TO LIST-PROCESSING 语句调用所在屏幕所在的屏幕序列)中的某个屏幕,当 list processor 处理结束后, dialog processor 将会回到指定的屏幕并执行其相应的 PBO 事件块

    13.8. LIST 打印输出

    打印参数设置:

    SET_PRINT_PARAMETERS

    GET_PRINT_PARAMETERS

    从程序中启动打印: N E W - PA G E PRIN T O N

    14. Messages

    14.1. 00 消息 ID 中的通用消息

    00 消息 ID 中的 001 消息本身未设置任何消息串,这条消息可以传递 8 个参数,在用于拼接消息时很有用

    MESSAGE e001 ( 00 ) WITH 'No local currecny maintained for company:' p_bukrs .

    14.2. 消息常量

    直接显示消息常量,不引用消息 ID 与消息号

    MESSAGE 'aaaa' TYPE 'S' .

    14.3. 静态指定

    MESSAGE <t><nnn> ( <id> ) [ with <f1> ... <f4>][ raising <exc>] .

    MESSAGE s002 ( 00 ).

    14.4. 动态指定

    MESSAGE ID <id> TYPE <t> NUMBER <n> [ with <f1> ... <f4>] [ raising <exc>] .

    DATA : t ( 1 ) VALUE 'S' ,
    id ( 2 ) VALUE '00' ,
    num
    ( 3 ) VALUE '002' .
    MESSAGE ID id TYPE t NUMBER num .

    14.5. 消息拼接 MESSAGE …INTO

    DATA mtext TYPE string .
    CALL FUNCTION ... EXCEPTIONS error_message = 4 .
    IF sy - subrc = 4 .
    MESSAGE ID sy - msgid TYPE sy - msgty NUMBER sy - msgno
    INTO msgtext
    WITH sy - msgv1 sy - msgv2 sy - msgv3 sy - msgv4 .
    ENDIF .

    14.6. 修改消息显示性为 …DISPLAY LIKE…

    此种方式不会影响到消息本身的处理性为,只是改变了消息的显示图标类型,如下面只是改变了 S 类型消息在状态栏中以错误图标来显示(本来是绿色状态图标):

    MESSAGE msg TYPE 'S' DISPLAY LIKE 'E' .

    14.7. RAISING <exc> :消息以异常形式抛出

    MESSAGE ID 'SABAPDEMOS' TYPE MESSAGE_TYPE NUMBER '777'
    WITH MESSAGE_TYPE MESSAGE_PLACE MESSAGE_EVENT
    RAISING MESS .

    当使用该选项后 ,并且如果在调用的地方( CALL FUNCTION 或者是 CALL METHOD 的地方)使用了 EXCEPTION 选项来捕获 RAISING 抛出的异常, 不再以 MESSAGE 的原有形式来显示消息 而是被主调捕获 后进一步处理 或者是程序 Dump A E W I S 类型都能被捕获到,但 X 类型的 Message 不会走到被主调者捕获这一步,因为在被调程序中就宕掉了);反过来, 当主调者 未使用 EXCEPTION 选项 (或者使用了但未捕获到所抛出的异常),则 RAISING 选项会被 忽略 MESSAGE 语句会按照无 RAISING 选项时那样运行(弹框还是在状态栏中显示、以及程序是否终止等性为、还是转换为 error_message 抛出)

    如果加了选项 RAISING 时: MESSAGE ... RAISING <exc> 此时的 Message 的处理方式与是否显示,就要依赖于主调者在调用时,是否加上了 exception <exc> 选项:

    1、 如果调用时没有带 exception <exc> 选项,此时 Message 语包中的 RAISING <exc> 选项抛出的异常将会被忽略, Message 语句会当作正常消息来处理

    2、 如果调用时加上了 exception <exc> 选项对 exc 异常进行了捕获,则不会再显示消息(但如果即使加上了 exception 选项,但没有捕获到 exc 异常,则此时会忽略 RAISING 选项),并设置 sy-subrc 。只要异常被捕获,相关消息内容将会入存入到 SY-MSGID,SY-MSGTY, SY-MSGNO, and SY-MSGV1 to SY-MSGV4 有关系统变量中

    下面程序中,第一次调用时中会弹出消息框(因为没有使用 EXCEPTIONS 选项捕获),而第二次不会弹出消息框,也不会在状态栏中显示,而是被后继程序捕获后输出:

    CLASS c1 DEFINITION .
    PUBLIC SECTION .
    CLASS-METHODS m1 EXCEPTIONS exc1 .
    ENDCLASS .
    CLASS c1 IMPLEMENTATION .
    METHOD m1 .
    MESSAGE 'Message in a Method' TYPE 'I' RAISING exc1 .
    ENDMETHOD .
    ENDCLASS .

    START-OF-SELECTION .
    c1
    => m1 ( ). " 第一次调用
    c1
    => m1 ( EXCEPTIONS exc1 = 4 ). " 第二次调用
    IF sy - subrc = 4 .
    write : / ' 被捕获 ' .
    ENDIF .

    14.8. CALL FUNCTION…EXCEPTIONS

    CALL FUNCTION func [ EXCEPTIONS
    [
    exc1 = n1 exc2 = n2]
    [
    others = n_others] ]
    [
    ERROR_MESSAGE = n_error] .

    exc1 exc2... OTHERS 异常只 能捕获到 MESSAGE...RAISING 选项或 RAISE 语句抛出的异常 ,而 error_message 是无法捕获 MESSAGE...RAISING RAISE 抛出的异常的

    MESSAGE 中的 RAISING <exc 1 ...exc i > 抛出异常时,如果在 Call Function Exception 列表中有 exc 1 ...exc i others 异常,则异常会优先被 exc 1 ...exc i others 捕获到;否则 RAISING 选项将直接被 忽略掉, MESSAGE 会被 error_message 所捕获 (在使用 error_message 捕获的前提下)

    CALL FUNCTION 'ZJZJ_FUNC1'
    EXCEPTIONS
    error_message
    = 5
    " e 捕获,如果注释掉下面 E 类型异常捕获列表,则会被 error_message 捕获
    e
    = 4
    d
    = 6 .

    14.8.1. error_message = n_error 捕获消息

    可以 Message 语句 没有使用 RAISING 选项的情况下 ( 使用 exc1...exci others 但未捕获 ) ,在主调程序中的 CALL FUNCTION ...Exception 参数列表中使用隐式异常 error_message 选项来捕获 Message ,但 error_message 是否能捕获到 Message( 实为是否设置 sy-subrc = n_error) ,与消息类型有关:

    1、 对于 W I S 类型的消息,将不显示消息( 本来是要显示的 ),也 不会去设置 sy-subrc = n_error 此时还是会将消息的相关信息存储到 SY-MSGID, SYMSGTY,SY-MSGNO, and SY-MSGV1 to SY-MSGV4 这些系统变量中

    2、 对于 A E 类型消息,也将不显示提示消息,但 会抛出 ERROR_MESSAGE 异常 ,即这两类型的 消息会自动被 转换 error_message 异常抛出,并终最被 CALL FUNCTION Exception 异常列表中的 error_message 异常所捕获, 并设置 sy-subrc = n_error 。此时还会将消息的相关信息存储到 SY-MSG ID , SYMSG TY ,SY-MSG NO , and SY-MSGV1 to SY-MSGV4 这些系统变量中

    此时,对于 A 类型消息而言, ROLLBACK WORK 语句将会隐式执行

    3、 对于 X 类型消息将会抛出 runtime error ,并且程序会 dump

    14.9. 各种消息的显示及处理

    AT SELECTION-SCREEN OUTPUT INITIALIZATION START-OF-SELECTION

    GET

    END-OF-SELECTION

    TOP-OF-PAGE

    END-OF-PAGE

    l RAISE <except>. 只在函数中使用

    l MESSAGE ... RAISING <except> .

    一旦主调程序捕获了异常,以上两种触发异常的方式都会返回到主调程序 ,并且不会返回值 Function Module 参数输出)。 MESSAGE ..... RAISING 语句也不会再显示消息,而是将相关的信息填充到 SY-MSGID, SY-MSGTY,SY-MSGNO, and SY-MSGV1 to SY-MSGV4 这些系统变量中( 即使是 I,S,W 三种消息类型也会设置这些系统变量

    14.10.1.1. 触发类异常

    RAISE [RESUMABLE] EXCEPTION { { TYPE cx_class [ EXPORTING p1 = a1 p2 = a2 ...]} | oref }.

    cx_class 为异常 Class EXPORTING 为构造此异常类的构造参数, oref 可以是已存在的异常 Class 引用。

    RAISE EXCEPTION 语句一般用来抛出基于 Class 的异常类 class-based exceptions ,而 RAISE 一般是直接用来抛出 non-class-based exceptions (在函数中使用)

    DATA result TYPE p DECIMALS 2 .
    DATA oref TYPE REF TO cx_root .
    DATA text TYPE string .
    DATA i TYPE i .
    TRY .
    i = 1 / 0 .
    CATCH cx_root INTO oref .
    text = oref -> get_text ( ).
    WRITE : '---' , text .
    RAISE EXCEPTION oref .
    ENDTRY .

    DATA : exc TYPE REF TO cx_sy_dynamic_osql_semantics ,
    text TYPE string .
    TRY .
    RAISE EXCEPTION TYPE cx_sy_dynamic_osql_semantics
    EXPORTING textid = cx_sy_dynamic_osql_semantics => unknown_table_name token = 'Test' .
    CATCH cx_sy_dynamic_osql_semantics INTO exc .
    text
    = exc -> get_text ( ).
    MESSAGE text TYPE 'I' .
    ENDTRY .

    14.10.1.2. RESUMABLE 选项

    表示 可恢复的异常 ,可以在 CATCH 块里 使用 RESUME 语句直接跳到抛出异常语句后面继续执行 RESUME 后面语句不再被执, CLEANUP 块也不会被执行 。该选项只能用于 BEFORE UNWIND 类型的 CATCH 块中:

    DATA oref TYPE REF TO cx_root .
    DATA text TYPE string .
    DATA i TYPE i .
    TRY .
    RAISE RESUMABLE EXCEPTION TYPE cx_demo_constructor
    EXPORTING
    my_text
    = sy - repid .
    i = i + 1 .
    WRITE : / i .
    CATCH BEFORE UNWIND cx_demo_constructor INTO oref .
    text = oref -> get_text ( ).
    IF i < 1 .
    RESUME .
    ENDIF .
    WRITE : / '--' .
    ENDTRY .

    结果只输出 1

    14.10.2. 捕获异常

    14.10.2.1. 类异常捕获 TRY…CATCH

    DATA myref TYPE REF TO cx _sy_ arithmetic_error .
    DATA err_text TYPE string.
    DATA result TYPE i .
    TRY .
    result =
    1 / 0 .
    CATCH cx _sy_ arithmetic_error INTO myref.
    err_text = myref->get_text( ).
    ENDTRY .

    14.10.2.2. 老式方式捕获 runtime errors( 运行时异常 )

    CATCH SYSTEM-EXCEPTIONS [exc1 = n1 exc2 = n2 ...][ OTHERS = n_others].

    ENDCATCH .

    DATA : result TYPE i.
    CATCH SYSTEM-EXCEPTIONS arithmetic_errors = 5 .
    result =
    1 / 0 .
    ENDCATCH .
    IF sy-subrc = 5 .
    WRITE / 'Division by zero!' .
    ENDIF .

    14.10.3. 向上抛出异常

    如果 Form 中出现了运行时错误,但 Form 签名又没有使用 RAISING 向上抛,则程序会直接挂掉,所以最好是向上抛

    FORM subform RAISING cx_static_check cx_dynamic_check.
    ...

    ENDFORM .

    Funcion 函数不会主动向外抛出运行时错误,所以 要先在 Function 手动 CATCH ,再手动向外抛 如果出现运行时错误不抛出,则 Function 与会直接宕掉

    14.10.4. 类异常

    l CX_ STATIC _CHECK

    l CX_ DYNAMIC _CHECK

    l CX_ NO _CHECK

    CX_NO_CHECK 类似于 Java 中的 Error CX_ DYNAMIC_CHECK 类似于 Java 中的 RuntimeException CX_STATIC_CHECK 类似于 Java 检测性异常 Exception [daiˈnæmik]

    自己定义的异常一般继承 CX_STATIC_CHECK CX_DYNAMIC_CHECK ,但 CX_NO_CHECK 也可以创建,不像 Java

    CX_STATIC_CHECK 是一个抽象类。在程序中使用 RAISE EXCEPTION 手动抛出 这类异常时,方法或过程接口上一定要显示的通过 RAISING 来向上层抛出异常、或者直接在方法或过程中进行处理也可以,否则静态编译时就会出现警告

    CX_NO_CHEC K 类型的异常一般表示系统资源不足引起的, 不能在方法或过程接口后面抛出 CX_NO_CHECK 类型的异常,它会被隐含的抛出与传递 。系统中已有预定义这类异常。

    如果程序逻辑能够排除可能性的潜在性错,相应的异常就可能不用处理或继续抛出,此类情况下可以使用 CX_DYNAMIC_CHECK 类型的异常,这与 Java 中的运行时异常相似,一旦发生也该类异常,表示问题出现在程序的本身设计上,程序设计不严谨(如没有判断空指针问题)。 ABAP 大多数的系统预定义的异类都是属于该类型异常,这就意味着 不需要处理或抛出 ABAP 语句可能出现的每一种异常 ,但 一旦发生了该类异常,则表示程序的出现了 问题,程序执行的结果将不会在正确。

    自定义的全局异常类名以 ZCX_ 作为前缀

    如果是通过 Class Builder 创建的全局异常类时,由于构造器是默认创建好 的(异常相关参数已经固定下来了),不能传递自定义参数,所以异常文本 ID 只能通过 TEXTID 传递( 参考触发类异常 ),但局部异常类没有这个限制。

    15. 数据格式化、转换

    15.1. 数据输入输出转换

    15.1.1. 输出时自动转换

    如果某个变量参照的数据元素所对应的 Domain 具有转换规则 ,那么在输出时(如 Write 输出、 ALV 展示、文本框中显示 ),最后显示的结果会 自动发生转换 ,如参照 ekpo-meins 表字段的变量赋值时就会发生转换,因为 ekpo-meins 所对应的元素 Doamin 设置了转换规则:

    所以,在显示输出这样的数据时要注意, 如果要显示原始数据,则不能参照该表字段来定义变量,而是自己定义

    DATA : i_meins LIKE ekpo - meins ,
    i_meins2
    TYPE c LENGTH 3 .
    START-OF-SELECTION .
    SELECT meins meins FROM ekpo INTO ( i_meins , i_meins2 ) WHERE ebeln = '4500012164' .
    " 输出时, i_meins 会自动发生转换,但 i_meins2 不会
    WRITE : i_meins , i_meins2 .
    ENDSELECT .
    SKIP .

    DATA : i_meins3 LIKE ekpo - meins .
    " 注:这里只能是内部单位 ST ,而不是 PC ,因为 Write 时是输出转换(即 -> 的转换)
    i_meins3
    = 'ST' .
    " 只要是参考过 ekpo-meins 的变量, Write 输出时自动转换
    WRITE : / i_meins3 .

    调试过程中发现都是原始数据, 自动转换发生在 Write 输出时

    15.1.2. 输入时自动转换

    输出时会发生自动转换,那么,在输入时,如从 选择屏幕上录入的数据是参照带有规则转换的 Domain 的数据元素创建的选择屏幕字段时,从界面录入到 ABAP 程序中时,会自动按照转换规则进行转换 ,如下面从界面上输入的是 PC (外部格式的单位),但录入到 ABAP 程序中时,自动转换为 ST (内部格式的部位),但再次 Write 输出时,又将 ST 转换为 PC 输出(从内部转换为外部格式):

    15.1.3. 通过转换规则输入输出函数 手动转换

    除了上面通过借助于参照带有转换规则的表字段进行自动转换外,实质上可以通过转换规则对应的输入输出函数进行手动转换,如 VBAK-vbeln 的转换规则:

    CONVERSION_EXIT_ALPHA_INPUT :输入转换,前面补齐零

    此函数将字符类型的变量转换成 SAP 数据库中内部格式数据,如定单号 vbeln 的类型为 Char 10 ,如果输入的 vbeln 6 位,则会在前面补 4 个零( 注:该函数的转换规则为:如果含有其他非数字,则不会补零,只有全部是数字时才补,这可以通过 VBELN 查看到 ), Number 类型的不需要,因为在 ABAP 程序中 N 类型不足时长度时默认就会在前面补零(如 POSNR ),而且 Number 类型的默认值就是全为零,而 C 类型不足时会以后面全补空格

    CONVERSION_EXIT_ALPHA_OUTPUT :输出转换,去掉前导零

    DATA : vbeln TYPE vbak - vbeln .
    DATA : str TYPE string VALUE '600000' .
    CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
    EXPORTING input = str
    IMPORTING output = vbeln .
    " 自动输出转换,输出最初始数据,但程序内部已发生变化
    WRITE : / vbeln . "600000

    15.2. 数量小位数格式化

    WRITE <f> UNIT <u> .

    该语句根据 Unit <u> 来设置 <f> 的小数位数( 保留小数点多少位 ,或 精确到小数点后多少位 ), <u> <f> 的单位。 <u> 必须要在 T006 中进行过配置,并且 <u> 的值(单位 KEY 值)就是 T006-MSEHI 字段值,而 T006-DECAN 字段值决定 <f> 显示的小数位数,如果 <u> 在表 T006 没有找到,将会忽略该 UNIT 选项

    该选项的使用限制如下:

    •     <f> 必须是 P 类型

    如果 <f> 本身的小数位比 <u> 所配置的小数位 时,系统会 忽略该选项

    如果 <f> 本身的小数位比 <u> 所配置的要 时,并且 多余的小数位全部是零时 会被截断 ;如果 多余的小数部分 不是零时 也会直接忽略该选项

    从上面的限制条件来看,该格式化输出只针对 <f> 的小数位 超过了其单位 <u> 设置的小数位 ,且超过的小数要全是零才会起作用(去掉多余的零),如果 <f> 的小数位短于 <u> 设置的小数位,也不会再补后输出

    " 必须是 P 类型
    DATA : p1 TYPE p LENGTH 8 DECIMALS 2 .
    p1
    = '1.10' .
    " 如果 <f> 本身的小数位比 <u> 所配置的小数位小时,系统会忽略该选项
    WRITE : / p1 UNIT 'D10' . "1.10
    DATA : p3 TYPE p LENGTH 8 .
    p3
    = '1' .
    WRITE : / p3 UNIT 'D10' . "1
    DATA : p2 TYPE p LENGTH 8 DECIMALS 4 .
    p2
    = '1.1000' .
    " 多余的小数位全部是零时,会被截断
    WRITE : / p2 UNIT 'D10' . "1.100
    p2
    = '1.1001' .
    " 多余的小数部分不是零时,也会直接忽略该选项
    WRITE : / p2 UNIT 'D10' . "1.1001

    DATA : i_menge LIKE ekpo - menge VALUE '1.000' .
    " 注: UNIT 选项后面一定要是内部单位 ST ,而不是外部单位 PC ,因为这里是 WRITE 输出,
    " 即内部转换外部,将数据库表存储的原数据格式化输出显示
    WRITE : / i_menge UNIT 'ST' . "1
    WRITE : / i_menge . "1.000

    15.2.1. 案例

    问:通过 se11 我们可以看到 ekpo menge 的数据元素是 BSTMG BSTMG 的域是长度 13 小数位 3 位。在程序中我参照 ekpo-menge 定义的变量显示的时候后面都有 3 位小数,而我希望输出时与 me23n 一样,即去掉小数点后面多余的零,请问大侠们有没有比较好的办法。为什么 me23n 中“ PO 数量”显示的时候没有多余的零,而他们的数据元素是一样的。

    答: MENGE 实际上是个存储度量衡值的字段,他的基本数据类型是 QUAN ,他的 小数位数 并不是你看到的 3 ,而是 由这个字段关联的度量衡单位决定 的,以 MENGE 为例,你可以在 SE11 的最右边一个 Tab 页, Currency/Quantity Fields 里看到,他关联的单位是 EKPO-MEINS

    DATA : i_menge LIKE ekpo - menge ,
    i_meins
    LIKE
    ekpo - meins ,
    i_meins2
    TYPE c LENGTH 3 . " 没有参照表字段 ekpo-meins ,所以 Write 输出时不会自动输出转换

    SELECT menge meins meins FROM ekpo INTO ( i_menge , i_meins , i_meins2 ) WHERE ebeln = '4500012164' .
    " 带单位的数量需要根据单位进行格式化输出,这样才与 ME23N 中显示的数据一样
    WRITE : / i_menge UNIT i_meins , i_meins , i_menge , i_meins2 .
    ENDSELECT .

    ALV 中显示时,如果是金额或数量时,需通过 Fieldcat 设置 cfieldname ctabname qfieldname qtabname 这样在显示时才会正确

    也可直接使用 Domain 所配置的转换规则所对应的输入输出转换函数 CONVERSION_EXIT_CUNIT_INPUT CONVERSION_EXIT_CUNIT_OUTPUT 来手动对单位进行转换:

    15.3. 单位换算: UNIT_CONVERSION_SIMPLE

    PARAMETERS : p_in TYPE p DECIMALS 3 ,
    unit_in
    LIKE t006 - msehi DEFAULT 'M' , "
    unit_out
    LIKE t006 - msehi DEFAULT 'MM' , " 毫米
    round ( 1 ) TYPE c DEFAULT 'X' .
    DATA : result TYPE p DECIMALS 3 .
    CALL FUNCTION '
    UNIT_CONVERSION_SIMPLE '
    EXPORTING
    input = p_in
    round_sign
    = round " 舍入方式 (+ up, - down, X comm, SPACE.)
    unit_in
    = unit_in
    unit_out
    = unit_out
    IMPORTING
    output = result .
    WRITE : 'Result: ' , result .

    15.4. 货币格式化

    WRITE <f> CURRENCY <c> .

    输出金额 <f> 时,会根据该语句设置的货币代码 <C> 来决定其小数位置,如果货币代码 <c> 在表 TCURX C URR KE Y )表中存在,则系统将根据 TCURX-CURRDEC 字段的值来设置 <f> 的小数点的位置, 否则将 <f> 转换成具有 2 位小数的数字 。这就意味着 除非 <f> 本身就是类型为 P(.2) (即货币的最大单位与最小单位换算为 100 时,如 CNY 人民币、 USD 美元)的金额字段,否则 需要在 TCURX 表中配置所对应币种的小数位 (因为不配置时会采用默认的 2 位)。

    注意:这里的 <f> 一般是从数据库里读取出来的金额数据才需要这样格式化输出的,如果 <f> 本身存储的就是真实的金额,则不需要格式再输出,而是直接输出;另外,这里的格式化只是 简单机械 的根据 TCURX-CURRDEC 所配置的小数位置来设置金额的小数点位置(而并不是乘以或除以某个转换率),并与金额变量 <f> 类型本身的具有多少小数位有关:如果 <f> 的类型为 P(6.5) ,值为 <f> = 1.234 时,且 TCURX 表里配置的小数位为 2 时,最后输出的是 1234.00 ,而不是 12.34 如果是根据转换率来除,则结果会正确 ),因为在格式化前,会将小数末的 0(1.23400) 也参与处理, 并不理会 <f> 本身原来的小位数 ,而是将所有的数字位(抛开小数点,但包括末尾的 0 )看作是待格式会的数字字符串:

    DATA : p ( 6 ) TYPE p DECIMALS 5 .
    p
    = '1.234' .
    WRITE : p CURRENCY 'aa' . "1,234.00

    TCURX 货币小数位表

    TCURC :货币代码表

    TCURR 汇率表

    SAP 表里存储的并不是货币的最小单位 ,一般是以货币最大单位(也是常用计量单元)来存储,不过在存储之前会使用经过转换:比如存储的金额是 100 ,则存储到表之前会 除以 一个转换因子后再存入数据表中(该转换因子是通过 CURRENCY_CONVERTING_FACTOR 函数获得的,如比 CNY 的转换因子为 1 JPY 100 ),所以如果要读取出来自已进行展示,则需要再次 乘以 这个因子才能得到真正的金额数。另外, 数据库中存储的虽然不是最小单位,但取出来后 都是放在 P 类型的变量中的,所以取出来在内存中统计是不会有精度丢失的 P 类型相当于 Java 中的 BigDecimal 类类型

    TCURX-CURRDEC 中存储的小数位实质上是根据同种币种的最大单位与最小的 换算率 = 10 X 来计算得到的,式中的 X TCURX-CURRDEC 表字段中的小数位,如 CNY 中的最大单位元与最小单位分相差 100 倍,所以 100 = 10 X X 就为 2 ,最后 TCURX-CURRDEC 存储的就是 2 如果值为 2 是可以不需要在 TCURX 表中配置的 ,所以查不到 CNY 的配置数据,因为不配置时默认值也是 2 );另外, JPY 日元没有最小单位,所以最大单位与最小单位的换算率就是 1 1 = 10 X ),所以 X 就为 0 ,所以 TCURX-CURRDEC 存储的就是 0 。而转换因子计算式为: 转换因子 = 100/10 X ,( CNY 人民币 100/10 X =100/10 2 = 1 JPY 日元 100/10 X =100/10 0 = 100 ),即 转换因子 = 100/ 货币的最大单位与最小单位换算率 金额入库时需要除以这个转换因子,读取出来展示前需要乘以这个转换因子

    数据库中用来存储金额的字段的类型都是 P(.2) ,即带两位小数,因为 转换因子最大也就是 100 (除以 100 后,即为小数点后 2 位),有的是零点几(在存入之前会将真实金额 除以 这个转换因子后再存入), 所以存储类型为两位小数的数字类型即可 ABAP 程序中用来存储从表中读取出来的内部金额的变量类型一定要具有两位类型的 ,否则在使用诸如 CONVERT_TO_LOCAL_CURRENCY CONVERT_TO_FOREIGN_CURRENCY 转换函数或者是格式化输出时,都会有问题, 所以在 ABAP 程序中定义这些用来存储数据库表中所存内部金额变量时,最好参照相应词典类型

    15.4.1. 从表中读取日元并正确的格式化输出

    DATA : netpr LIKE vbap - netpr , " 实际的类型为 p(6.2)
    waers
    LIKE vbap - waerk ,
    jpy_netpr
    TYPE i ,
    netpr1
    ( 6 ) TYPE p DECIMALS 3 .
    " 通过 SQL 从数据库查询出来的是真实存储在表里的数据,没有经过其他转换,日元存到数据库中时缩小了 100 倍,所以要还原操作界面上输入的日元金额,则需要使用后面的格式化输出
    SELECT SINGLE netpr waerk INTO ( netpr , waers ) FROM vbap WHERE waerk = 'JPY' AND vbeln = '0500001326' .
    WRITE : waers , netpr . " 数据库中的值,被缩小了 100

    " 第一种还原方式
    WRITE : / 'Format:' , netpr CURRENCY waers .

    " 第二种转换方式 :也可以通过以下函数先获取 JPY 货币代码的转换因子,再直乘以这个因子也可
    DATA : isoc_factor TYPE p DECIMALS 3 .
    CALL FUNCTION '
    CURRENCY_CONVERTING_FACTOR '
    EXPORTING
    currency = waers
    IMPORTING
    factor
    = isoc_factor .
    jpy_netpr
    = netpr * isoc_factor . " 乘以 100 倍,因为在存入表中时缩小了 100
    WRITE : / 'Calc factor:' , jpy_netpr .

    " 格式化输出实质上是与存储金额的变量本身的类型小数位有关:上面将从表中读出的金额(小数两位)赋值给变量 netpr1 (小数三位),格式化后会扩大 10 倍(因为多了一位小数位) 所以格式化正确输出的前提是要用来接收从表中读取的金额变量的类型要与数据表相应金额字段类型相同 ,否则格式化输出会出错
    netpr1
    = netpr .
    WRITE : / netpr1 , netpr1 CURRENCY waers . " 格式化的结果是错误的

    15.4.2. SAP 货币转换因子

    一般而言,币种的小数位为 2 ,所以系统默认的位数也是 2, 但是有一些特殊币种如日元 JPY ,没有小数位。只要小数位不等于 2 ,需要在系统中特殊处理(通过转换因子进行转换,具体请参看后面 SAP 提供的函数 currency_converting_factor 实现过程)。在编程中

    l List 中,当输出 CURR 字段时,记得指定对应的货币:

    如: WRITE : vbap - netwr CURRENCY vbap - waerk .

    l Screen 中,对于 CURR 字段,需要设置对应的货币字段:

    l ALV 中,需要对 FIELD CATALOG 进行设置

    如: ls_cfieldname = 'WAERS'. " 这里的 WAERS 是内表中的另一货币字段,里面存储了相应金额的货币代码

    货币的是: fieldcat-cfieldname fieldcat-ctabname (内表名,可以不设置)

    顺便数量也是相似的方法来处理的:

    数量的是: fieldcat-qfieldname fieldcat-qtabname (内表名,可以不设置)

    下面是 SAP 转换因子函数,在金额存储与在 ALV 展示时都会自动除以与乘以这个转换因子:

    FUNCTION currency_converting_factor .
    *"----------------------------------------------------------------------
    *"*"Lokale Schnittstelle:
    *       IMPORTING
    *"             VALUE(CURRENCY) LIKE  TCURR-TCURR
    *"       EXPORTING
    *"             VALUE(FACTOR) TYPE  ISOC_FACTOR
    *"       EXCEPTIONS
    *"              TOO_MANY_DECIMALS
    *"----------------------------------------------------------------------
    DATA : cur_factor TYPE isoc_factor .
    *- determine Decimal place in currency from  TCURX
    CLEAR tcurx .
    " 首先根据币种到 db tcurx 中读取相应的小数位数 currdec
    SELECT SINGLE * FROM tcurx WHERE currkey EQ currency .
    " 如果没有维护相应币别信息则默认 currdec = 2
    IF sy - subrc NE 0 .
    tcurx
    - currdec = 2 .
    ENDIF .
    " 如果 currdec 大于了 5 就报错
    IF tcurx - currdec GT 5 .

    *- entry in tcurx with more than 5 decimals not allowed
    RAISE too_many_decimals .
    ENDIF .
    *- compute converting factor respecting currency
    "
    然后默认转换比率是 100 。如果表 tcurx 中的 currdec = 0 就默认转换比率为 100
    cur_factor
    = 100 .
    IF tcurx - currdec NE 0 .
    " currdec 不等于 0 的情况下循环 currdec 次,每次将转换比率除以 10
    DO tcurx - currdec TIMES .
    " 当表 tcurx 中没有找到相应数据时则默认 currdec = 2 ,转换比率也就是 100 / 10 / 10 = 1 。其他
    "
    的比如表 tcurx 中的 currdec = 4 ,则转换比率应该为 100 / 10 / 10 / 10 / 10 = 0.01
    cur_factor
    = cur_factor / 10 .
    ENDDO .
    ENDIF .
    IF cur_factor = 0 .
    *- factor 0 not allowed; check data definition of factor
    *- entry in tcurx with more than 5 decimals not allowed
    RAISE too_many_decimals .
    ENDIF .
    factor
    = cur_factor .
    ENDFUNCTION .

    简单的使用 Function CURRENCY_CONVERTING_FACTOR ,输入币种,就可以得到相应的转换比率了。我们在 SE16 中看到的货币金额基本上都经过了这个转换,如日元,都是除以 100 后存入数据库的。所以当我们从数据库中读取日元金额时也应该作相应的转换,乘以 100

    1、 如果某货币的小数位不是 2 位,则需要通过 OY04 设置其小数位数,即需在 TCURX 表中进行维护

    2、 系统中的数据表存放的 日元 JPY 俄卢布 RUR 等货币比前台输入的金额小 100 倍,因为它们没有小数位,所以转换因子为 100 ,存入表之前 SAP 会先将金额除以这个因子后再存入

    3、 系统根据转换因子将原金额转换成含小位小数的金额后存储(据说根据 ISO 的什么标准),如日元为 0 位小数,转换因子为 100 120 日元除以因子 100 后转换后变成 1.20 ,缩小 100 倍。如为 USDN 5 位小数,其转换因子为 100/10/10/10/10/10=0.001 12.01230 除以 0.001 后则转换成 12012.30 ,扩大 1000 倍。 SAP 在金额数据存储时会自动的转换,其实 SAP 是有 external internal 的数据格式,可以调用以下函数实现相互转换。 BAPI_CURRENCY_CONV_TO_INTERNAL 转换成数据库中内部存储金额 BAPI_CURRENCY_CONV_TO_external 转换成外部实际金额

    4、 每次币别的汇率更改在正式生产系统中新创建一条记录,利用函数 CONVERT_TO_LOCAL_CURRENCY 自动会把当前最近的时间的汇率作为转化的汇率,而不是直接在原纪录上更改

    5、 OB07 OB08 ,维护各币种之间的汇率。

    6、 碰到比较变态的货币,例如日元,它们是没有小数点的,系统内存储的和你看到的不同,有个 BAPI 可以使用: BAPI_CURRENCY_CONV_TO_INTERNAL

    7、 还有两个不同币种之间的转换 FM CONVERT_TO_FOREIGN_CURRENCY ,和 CONVERT_TO_LOCAL_CURRENCY 基本没有区别,功能都是一样的,只是转换的源与目标相反而已: CONVERT_TO_FOREIGN_CURRENCY 是将外币转换为本位币,而 CONVERT_TO_LOCAL_CURRENCY 是将本位币转换为其他外币

    15.4.3. 货币内外格式转换

    " 所有金额的在数据库里(内部格式)都是带两位的小数数字类型用来存储内部金额时,用来存储金额的 变量类型一定要与数据库表里的类型一致 ,否则使用 WRITE 输出时会不准确
    DATA : usd ( 7 ) TYPE p DECIMALS 2 ,
    jpy
    ( 7 ) TYPE p DECIMALS 2 ,
    jpy_e
    ( 12 ) TYPE p DECIMALS 4 .
    DATA : usd_k TYPE waers , jpy_k TYPE waers .
    DATA : ret TYPE bapireturn .
    " 此处为实际金额,所以不宜直接格式化(只有对内部表中存储格式的金额格式化输出才有意义,否则是错误的输出),不过这里为实际的金额似乎也有点不对,因为日元真实金额是不会有小数的,所以变量 jpy 用来存储外部实际金额是不妥的, jpy 应该为整数类型才恰当
    jpy
    = '10000.01' .
    usd_k
    = 'USD' .
    jpy_k
    = 'JPY' .

    " 使用 CONVERT_TO_LOCAL_CURRENCY CONVERT_TO_FOREIGN_CURRENCY 函数时,涉及到的 金额输入输出参数都是采用内部金额 ,所以在使用这些函数时,如果是外部金额,应先将它们转换为内部金额后再传入
    CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY' " 将一种货币兑换成另一种货币
    EXPORTING
    date = sy - datum
    foreign_amount
    = jpy " 该程序中的 jpy 本身为外部金额,但在这里会将
    " 它当作是内部金额,所以最后相当于外部金额 1000001
    foreign_currency
    = jpy_k
    local_currency
    = usd_k
    IMPORTING
    local_amount
    = usd . " 转换出来的也是 内部金额

    *CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
    *  EXPORTING
    *    date             = sy-datum
    *    foreign_amount   = '1.00'" 内部金额,美元的外部金额也是 1.00 美元
    *    foreign_currency = 'USD'
    *    local_currency   = 'JPY'
    *  IMPORTING
    *    local_amount     = usd." 结果为内部金额: 1.15 ,相当于外部金额为 115 日元

    *CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
    *  EXPORTING
    *    date             = sy-datum
    *    " 如果内部金额没有小数,也要补上两位小数位 0 ,否则实质金额不准确,这里正是
    *    " 因为末尾未补两位 0 ,所以这里的金额实质上为 0.01 美元,而不是 1 美元
    *     foreign_amount   = '1'" 内部金额,相当于外部 0.01 美元
    *    foreign_currency = 'USD'
    *    local_currency   = 'JPY'
    *  IMPORTING
    *    local_amount     = usd. " 结果为: 0.01 内部金额,实质相当于外部金额 1 日元

    WRITE : jpy , jpy_k , usd , usd_k .
    " 由于 jpy 本身为实际金额,所以不能在这里格式输出;但 usd 为内部
    " 格式的金额,所以需要使用格式化输出(但 usd 本身就是带两位小数
    " 的内部金额,转换
    WRITE : / jpy CURRENCY jpy_k , jpy_k ,
    usd
    CURRENCY usd_k , usd_k .
    ULINE .

    jpy_e
    = jpy .
    " 将外部金额转换为内部存储金额,实质上 过程是将外部金额除以转换因子 即可得到
    CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_INTERNAL'
    EXPORTING
    currency = jpy_k
    amount_external
    = jpy_e " 外部金额
    max_number_of_digits
    = 23 " 没什么作用,一般写 23 即可
    IMPORTING
    amount_internal
    = jpy " 转换后的内部存储金额
    return = ret .

    CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
    EXPORTING
    date = sy - datum
    foreign_amount
    = jpy " 源货币金额(内部格式)
    foreign_currency
    = jpy_k " 源货币类型
    local_currency
    = usd_k " 目标货币类型
    IMPORTING
    local_amount
    = usd . " 目标货币金额(内部格式)
    WRITE : jpy , jpy_k , usd , usd_k .
    WRITE : / jpy CURRENCY jpy_k , jpy_k ,
    usd
    CURRENCY usd_k , usd_k .

    16. 业务

    16.1. 表、业务流程

    询价 VA11--> 报价单 (VA21-VA23) -->创建订单( VA01-VA03 --> 交货 (VL01N)--> 捡配( LT03 à 发货过帐( VL02N ,产生物料凭证) à 开发票( VF01-VF03 ,即账务过账,产生会计凭证 )---> 应收 F-28 即清账)

    即交货确认,产生物料凭证

    即产生会计凭证

    发票即开票?是账务凭证产生的前一道操作?

    捡配单: TO = Transfer Order

    拣配, 是指从保管场所 ( 库存 ) 取出的商品 , 到进行商品出货准备的拣配区 , 进行数量正确的备货

    ² 定价 由四个重要部分组成: 定价过程 条件类型 存取顺序 存取表。 之间的关系:定价过程由一系列的条件类型组成,而每个条件类型都有一个与之对应的存取顺序,而每个存取顺序由一系统的 A 表组成,即每个存取顺序的 Item 都对应一个存取表( A 表)

    ² 定价过程 是通过 销售区域 销售组织 + 分销渠道 + 产品组) 订单类型 以及 客户 共同决定的,这也是符合实际业务逻辑的。一般来说,定价过程总是针对一定销售区域,一定客户以及特定的业务类型(决定了单据类型)来完成的

    ² 定价过程由 许多条件类型 组成的。 比如最常用的 PR00 含税销售价、 K007 客户折扣、 NTPW 含税金额 = PR00 含税销售总价 - 折扣、 MWSI =NTPW 含税价 /(1+ 税率 )* 税率、 NTPS 不含税净价 = NTPW 折扣后含税价 -MWSI 税额、 VPRS 成本(物料主数据中的移动平均价) 等条件类型

    ² 条件类型 ( 指定价格要素如何被计算 ) ,如果是项目条件类型,则需要在定义时维护一个 存取顺序 存取顺序区分了在不同层次上的定价 。比如对于一个条件类型,可以按照 客户 / 物料 来定价,也可以只对 物料 定价(还有一种: 价格清单 / 货币 / 物料 ,每一种关键字段组合都会产生一个 A 表)。如果前一个是存取顺序项目号 10 (每一个存取顺序 Item 都是一个 A 表),后一个是 20 ,那么在定价时,系统会判断是否满足第一个条件,以及前台是否有维护过相应的数据,如果没有则找 20 项,如果有则取 10 项,如果找遍所有存取顺序,都找不到,则根据条件类型定义的是否是必须的,可能会弹出定价条件丢失的警告

    ² 如果是项目条件类型,则需要在定义时维护一个 存取顺序 ,存取顺序就区分了可以在不同层次上定价。比如对于一个条件类型,可以按照物料 / 客户来定价,也可以只对物料定价(每一种关键字段组合都会产生一个 A 表)。如果前一个是存取顺序项目号 10 (每一个存取顺序 Item 都是一个 A 表),后一个是 20 ,那么在定价时,系统会判断是否满足第一个条件,以及前台是否有维护过相应的数据,如果没有则找 20 项,如果有则取 10 项,如果找遍所有存取顺序,都找不到,则根据条件类型定义的是否是必须的,可能会弹出定价条件丢失的警告

    ² 创建步骤:

    把想要的字段放进字段目录(即长关键字段组合设定)

    创建条件表、存取顺序,把条件表分配给存取顺序

    创建条件类型,把存取顺序分配给条件类型

    创建定价过程,把条件类型分配给定价过程

    BSEG 主要通过 凭证号 ”“ 会计年度 ”“ 行号 和这六张表关联

    一般情况下一笔业务产生的凭证都是未清的,那么:如果该业务行是客户相关的,则被记录到 BSID
    如果该业务行是供应商相关的,则被记录到 BSIK
    无论和客户相关还是和供应商相关,都是和总帐相关,所以也会有记录到 BSIS
    但是如果这笔业务被清帐了,则相应的记录会从 BSIS 转移到 BSAS

    一般情况下:应收账款、预收账款、其他应收款、应收汇票等科目既和客户相关,又和未清项管理的总帐科目相关;
    应付账款、预付账款、其他应付款、应付汇票等科目既和供应商相关,又和未清项管理的总帐科目相关;
    其他总帐科目一般不启用未清项管理,所以记录一般都放在 BSIS 中。
    BSEG
    本身是一个 Cluster Table( 簇表 ) BSEG 就是由上述的六大表的集成,当要读取 ”BSEG”Table 时就等于去读取那六个表,这样你可以想像它读起来会就多慢。对於 簇表或 Pool Table ,都是 SAP 系统本身在使用的,因此簇表本身是不存在资料库实体的,虽然是可以在 ABAP 使用,不过还是有一些限制: 1. 不能使用 select distinct or group by 语法 2. 不能使用 Native SQL  3. 不能使用 specify field names after the order by clause  4. 不能在建立次索引 5. 查询时一定要用 KEY FIELD

    16.2. MM

    16.2.1. 常用表

    可用库存 :可通过 BAPI_MATERIAL_AVAILABILITY 来获取

    当前库存 :一般保存在 MARD-LABST 字段中

    在途库存 MARC-UMLMC (中转库存) + MARC-TRAME (在途库存),在途库存是不存在库位关系的

    寄售库存 MSKU-KULAB ,寄售库存是不存在库位关系的

    16.2.3. 物料凭证

    物料凭证批记录物料变动的单据:如收发货、调拨、销售、盘点过账等都会产生。

    凭证类型,即物料的移动类型,三位编码

    常用物料移动类型:

    101 采购订单收货 生产订单收货 MB01 采购订单的过账收货、 MB31 按生产订单收货 102 :反冲; 122 :退货

    261 从仓库发货到订单的消耗 领料生产 MB1A

    301 跨工厂 间一步 库存转移

    311 同一工厂 一步 库存转移

    561 期初库存 的导入

    601 成品、原材料的 销售出库

    103 入冻结库

    105 释放冻结库

    16.3. SD

    16.3.1.

    16.3.2.1. 条件技术七要素

    1. 条件字段 Condition Field ):条件表( A 表)的关键字段,如客户、物料

    2. 条件字段目录 Field Catalog ):条件字段的集合,也即 AXXX 条件表的关键字段,决定在不同层次上的定价

    3. 条件表 Condition Table ):即 AXXX 表,包含一个或多个条件字段为关键字段,关键字段对应一个字段目录

    4. 存取顺序 Access Sequence ):存取顺序中包含一个或者多个条件表,多个条件表时需指定读取这些表的顺序

    5. 条件类型 Condition Type ):条件类型即条件分类,如计算价格的 PR00 、计算折扣的 K007 、计算税额的 MWSI

    6. 定价过程 Procedure ):由多个条件类型组成,每个一条件类型就是一个定位步骤

    7. 定价过程确定 Procedure Determination ): 根据销售范围(销售组织 + 分销渠道 + 产品组)、客户、订单类型来确定

    当创建销售订单时,系统首先确定 定价过程 ,再根据定价过程确定需要的 条件类型 ,然后订单中条件类型(如销售折扣 K007 )就根据 存取顺序 在条件表中读取相应 价格主数据

    16.3.2.2. 条件表 V/03 V/04 V/05

    自定义的条件表编号从 600 999 。定价条件表中允许的条件字段都来自于定价表 KOMK KOMP

    16.3.2.3. 存取顺序 V/07

    PR00 为例,该条件类型可根据三个定价的关键字组合(即对应三个条件 A 表)进行维护,系统则通过存取顺序确定三个条件表的优先级顺序,该优先级顺序称存取顺序( Access Sequence )。

    图中存取顺序为 PR02 ,系统按编号 10 20 30 40 依次读取条件表 A305 A306 A304 ,系统最先读取 305 ,即根据销售组织 + 分销渠道 + 客户 + 物料确定销售单价,图中字段“排斥的”钩选后表示如果在 A305 表中读取到了数据,则不再继续读取后面的 A

    定价参考物料 :通常来说定价参考物料等于销售订单中输入的物料,但也可在物料主数据的销售视图维护定价物料,定价物料的目的在于减少价格维护的工作量

    16.3.2.4. 条件类型 V/06

    条件类型重要属性:

    l 条件类型 Condition Class ):定义条件类型属于 单价 折扣 、还是 税收 类型

    l 计算类型 Calculation Type ):定义单价、折扣、税收 是如何计算

    l 条件类别 Condition Category ):对于特殊类型的条件类型,系统预定义了条件类别,如对于条件类型 VPRS ,其条件类型为 G ,因此系统自动根据物料主数据中维护的标准价格(移动平均价格)确定该条件类型的条件类型金额

    16.3.2.5. 定价过程 V/08 与确定 OVKK

    1、 From ~ To 步骤范围

    作用: 确定条件类型基础 Condition Base Value

    适用情况:仅针对 计算类型为百分比的条件类型

    逻辑说明:系统中提供三种百分比的计算类型, A Percentage )、 H Percentage Include )、 I Percentage/travel expenses )。以计算类型 A 为例,其 条件类型金额等于条件类型基础乘以百分数 ,而 条件类型基础 默认 等于该条件类型的步骤范围内的条件类型的值的合计 ,当 不输入步骤范围 时,则 条件类型基础等于该条件类型前所有步骤的条件类型金额之和

    本例中,折扣的条件类型 K007 的计算类型设置为百分比,按图中配置, K007 条件类型基础等于条件类型 K007 前所有条件类型的金额之手,即条件类型 PR00 的条件类型金额,具体为 1600 元,所以条件类型 K007 的金额等于条件类型基础( 1600 * 前台 VK11 维护的折扣率( 10% =160 元。

    若设置条件类型 K007 的步骤范围为“ From 11 To 11 ”与不输入步骤配置时条件类型基础值是相同的

    2、 Manual 手工的

    作用:确定条件类型是否在销售订单定价界面中 自动出现

    适用情况: 无存取的条件类型

    逻辑说明:图中一共长个条件类型,条件类型 EDI1 代表期望的价格,被标准为了“手工”,该条件类型没有分配存取顺序,所以此种条件类型不读取主数据(因为没有条件 A 表),而是在销售订单中手工输入

    3、 Required 必须的

    作用:如果单据中条件类型没有值,系统数据检查时会提示

    适用情况:任何条件类型

    逻辑说明:定价类类型( Condition Class )为 B Price 单价)或者 A Discount Or Surcharge 折扣或附加费)的条件类型,必须维护相应的价格主数据,具维护值不能等于 0 ;对于定价类型为 D Taxes 税收)的定价类型,只要维护相应的主数据即可,可以是零(表示无税收)

    4、 Statistics 统计的

    对于一张销售订单来说,金额信息中最主要的是计算订单的 不含税金额 Net Value 净价值 ,下图中的 Net 字段)和 税额 Tax Value ,下图中的 Tax 字段),后续开票时,不含税金额将过账到账务 收入科目 ,税额将过账到 税金科目 ,二者合计代表着现金( 应收账款

    系统将定价过程中的各个中满足下面三个条件的值纳入到订单的 净价值 Net Value ,下图中的 Net 字段 )计算:

    1、 条件 1 :该步骤勾选上统计,如钩选统计,则代表该金额不纳入净价值

    2、 条件 2 :该步骤存在条件类型。如下图( VA03 )中的最后一个步骤“ Test Only ”不存在条件类型,因此尽管未被勾选统计,但系统也不将该步骤的值纳入净价值中。不存在条件类型的步骤,系统一律认为是统计性

    条件 3 :该步骤的条件类型的定价类型( Condition Class )不是“税类型 D ”( Taxes :税收)。如上图中的 MWSI ,虽然未勾选统计,但系统也不将它纳入净价值统计

    5、 subtotal 小计

    小计即将步骤(条件类型)的值或者价格赋值到小计中,不同的小计有不同的用途,有些小计可以用来做后续的计算,有些小计的值将会保存到数据库中。上上图中条件类型 PR00 的小计被设置为 5 ,表示小计对应字段为 KOMP- KZWI5 KOMP-KZWI5 的金额等于条件类型 PR00 金额,当销售订单保存后, VBAP- KZWI5 的值等于 KOMP-KZWI5

    下面列举了定价过程中预置的 22 个小计,并对这些小计做了简单分类,划分为四类型:

    第一类:不可以随意使用,系统已经赋予其特定含义。该类小计的值最终都会保存到 VBAP 相同名称的字段里

    第二类:系统预留的,可根据公司需要赋予特定含义。这 6 个小计的值最终都会保存在销售订单行项目表字段 VBAP- KZWI1 ~ VBAP-KZWI6 中。如可设置小计 1 用来记录领导审批的折扣金额,小计 2 用来记录客户期望价格

    第三、四类:系统未赋予特定含义。这两类小计字段值不会保存到数据表中,仅作为中间变量使用,是为了定价过程中进行一步计算使用。其中第三类小计是将步骤的条件类型金额赋值到小计中,第四类是将条件类型价格赋值到小计中

    6、 Requirement 需求

    条件类型生效的前提条件,可以写自定义的程序来定义条件类型生效的前提条件,系统也提供了很多程序可供选择。查看程序代码:

    7、 Condition Value Formula 条件金额公式

    通过该字段主要用来改变当前条件金额的值(字段 XKWERT ),也可同时改变当前条件类型的其他字段的值,如条件类型价格。

    条件类型 NTPW (含税金额)等于折扣前含税金额减去折扣金额,因此系统将条件类型( PR00 )含税价金额复制到小计字段 5 KOMP-KZWI5 ,将条件类型折扣( K007 )的金额复制到小计字段( KOMP-KZWI6 )中,同时该条件类型 NTPW 条件金额公式的例程设置为 81 ,下面是例程 81 源码:

    在程序 81 中定义了条件类型 NTPW 金额( XKWERT )等于小计 5 komp-kzwi5 + 小计 6 komp-kzwi6 ),同时在程序还根据条件类型 NTPW 金额计算得到该条件类型价格( X KOMV-KWERT

    8、 Condition Base formula 条件基础公式

    与条件金额公式类似,主要是通过例程来改变条件类型基础值

    9、 Acckey Accruals 账户码和应计项

    通过定价过程的账号码( Account Key )和应计码( Accrual Key )实现与销售开票时的会计科目的确定无缝集成,实现收入、折扣、成本、税额进入各自的会计科目

    销售开票的会计科目确定一般由 6 个条件字段确定,销售组织、分销渠道、产品组、客户的科目分配组、物料的科目分配组、账户码(应计项),其中客户科目分配组由客户确定,物料的科目分配组由物料确定,而账户码(应计项)则由定价过程中的定价类型确定

    另外,凡是纳入销售订单净值计算的条件类型都必须有对应的账户码

    16.3.2.6. VK11 :价格主数据维护

    通过 VK11 维护条件后,数据存放在对应的条件 AXXX

    16.3.2.7. 定价计算: KONV

    系统根据定价过程,并结合价格主数据( AXXX 表中的数据)计算得到条件类型金额,并将 条件类型单价( KBETR )、条件类型基础( KAWRT )、条件类型金额( KWERT ,计算结果)一并存放到 KONV 表中

    下表为最终 KONV 计算结果:

    注:为了阅读方便,上图中表格做了适当修改,如对于百分比的条件类型 MWSI ,表中实际存储的为 170 ,而非 17%

    KONV- KBETR :从价格主数据( AXXX 表)表中读取得到

    KONV- KAWR T 部分条件类型(如折扣、税)是根据定价过程定义在创建订单时动态计算得到

    KONV-KWERT :以 KBETR KAWRT 为基础,并通过相应的计算公式计算得到,该字段即为最终计算出的定价结果

    创建销售订单时,条件屏幕的字段在内表 XKOMV 中,最终内容将保存在数据库表 KONV 中, VBAK KONV 通过 KNUMV (条件记录号)进行关联(注,只有 VBAK 里才有 KNUMV VBAP 里没有,因为一张单就只对应一个定价过程,所以整张单只需一个 KNUMV ,与 Item 无关)

    16.3.2.7.1. 条件类型的计算公式

    条件类型金额的计算公式三要素:

    1. 条件类型价格 [Rate condition amount or percentage ] :如单价( 800 / 个)、折扣( 10% )、税率( 17% ),来自 VK11 所维护的价格主数据

    2. 条件类型基础 Condition Base Value ):如数量( 2 个)、折扣前金额(即销售金额 1600 元),部分条件类型(如折扣、税)是根据定价过程定义动态计算得到,一般为前面几步条件类型统计结果

    3. 条件类型金额 Condition Value ):以条件类型价格和条件类型基础为基础计算出来的金额,如含税金额 1600 元、折扣 160

    条件类型金额 是以 条件类型单价、条件类型基础 为基础,通过某个公式计算得来

    根据条件类型的计算类型,条件类型金额有不同的计算方式,最常用的三种计算方式:

    1. 条件类型中的计算类型 KONV-KRECH C 数量 ): 条件类型金额 = 条件类型价格 (单价) * 条件类型基础 (数量),如 销售金额 PR00 的金额等于单价 800 / * 数量 2 = 金额 1600

    2. 条件类型中的计算类型 KONV-KRECH A 百分比 ): 条件类型金额 = 条件类型价格 (百分比) * 条件类型基础 (金额),如 折扣 K007 的金额等于 10% * 1600 = 160

    3. 条件类型中的计算类型 KONV-KRECH H 包括在内的百分比 ): 条件类型金额 = 条件类型基础 (金额) / 1 + 百分比 * 百分比 ,如 税收 MWSI 的金额等于 1440/(1+17%)*17% = 209.23

    16.3.2.8. 定价过程示例

    其中 NTPW (含税金额)的 条件类型价格 = 1440 / 2 = 720 NTPS (不含税净价)的 条件类型价格 = 1230.77 / 2 = 615.385

    16.3.2.9. 销售订单中的定价示例

    销售单价: 800 折扣: 10% 税率: 17%

    最终销售含税金额: 1440 不含税金额: 1230.77 税额: 209.23

    注:上图中的条件类型 NTPW NTPS 描述错误,应该分别为含税金额和不含税金额,弄反了

    销售订单的 净值 实为 NTPS 条件类型的 KONV-KWERT 的值 税额 (实为 MWSI 条件类型的 KONV-KWERT 的值) 还存储在了 销售订单行项目表 VBAP- NETWR VBAP- MWSBP 中(还有一个 VBAP- NETPR 净价 (实为 NTPS 条件类型的 KONV-KBETR 的值?) ,也直接存储在了 VBAP 表中);

    与之对应的采购订单的 净价 EKPO- NETPR 净值 EKPO- NETWR 也是直接存储在了 Item EKPO 中了:

    另外还有两个重要的字段: VBAP-KWMENG :销售数量、 EKPO-MENGE :采购数量。 (这两个字段实际上可对应到某个条件类型如 PR00/NTPW/NTPS KONV-KAWRT 的基础值 ?

    创建订单时,系统根据 销售组织 + 分销渠道 + 产品组 + 客户的定价过程 + 单据的定价过程 来确定一个定价过程,其中 客户的定价过程在客户主数据( KNA1 XD03 )中定义

    单据的定价过程 (不同的单据类型有不同的定价过程)通过事务代码 OVKJ 分配给销售订单

    16.3.2.10. 定价通信表 KOMK KOMP

    系统通过两个表 KOMK KOMP 为销售单与前台价格主数据、后台定价配置之间建立桥梁。创建订单时,系统首先根据销售订单中的信息为这两个表赋值,然后根据这两个表中的值确定销售单据中的定价,即这两个表起通信( Communication )作用

    创建销售单时,会将 VBAK 中的部分信息赋值给 KOMK 表, VBAP 同赋值给 KOMP

    这两个表仅仅起通信作用,是销售订单维护时产生的两个内表,当销售订单保存后,表中的信息也会消失

    定价表 KOMK KOMP 的字段与 VBAK VBAP 中的字段,大多相同,但并不是所有字段命完全相同,系统也并非根据字段名进行赋值,在系统内部有一套默认赋值规则

    对于定价表 KOMK KOMP 赋值是定价的基础,系统只是将部分信息赋值了,某些字段如物料主数据中销售视图下的物料组 1 字段 MVKE-MVGR1 就没有自动赋值过去,现在某个折扣需要根据物料组 1 确定,标准功能无法实现,原因是该字段的值系统没有将其复制到定价表 KOMP 中。此时首先检查该字段是否在表 KOMK KOMP 中存在, SE11 时发现 KOMP 中有同名字段 MVGR1 ,此时只需要利用 SAP 系统预留的用户出口程序 MV45AFZZ ,在 USEREXIT_PRICING_PREPARE_TKOMP FORM 中加入以下代码即可: TKOMP- MVGR1 = VBAP-MVGR1. 在创建订单时系统会自动将 TKOMP-MVGR1 原封不动的复制到表 KOMP-MVGR1 中。如果有相应字段,可通过 SE11 修改 KOMK KOMP 表,修改时查找 CI_ 开头的 .INCLUDE 结构可其他以 Z 打头的 INCLUDE APPEND 结构

    16.3.3. 销售相关的凭证类型、类型

    销售相关的凭证类别( SD document category ),表示凭证的分门别类,常用的有销售订单、交货订单、发票,在 VBAK LIKP VBRK 抬头表里都有一个 VBTYP 字段,用来区别表里的凭证具体属性哪一类凭证。

    VBAK-VBTYP 的取值可以是: A B C D E G H I K L W 等, C 是最常用,表示销售订单 凭证;

    LIKP-VBTYP 的取值可以是: 7 J T 等, J 是最常用,表示交货单凭证;

    VBRK-VBTYP 的取值可以是: 3 5 M N O P U 等, M 是最常用,表示发票凭证;

    每种凭证又可分为 N 种类型,如 VBAK LIKP VBRK 这些表里都有相对应的类型字段,它们分别是: AUART LFART FKART 。如销售订单的类型,常用的有标准订单类型 OR

    发票抬头表里还有一个发票的小分类 FKTYP ,用来表示某种具体的发票类别,在是发票这一大分类中再细分

    查看了一下采购订单,也有类别与类型两个字段,但类别不是销售里的凭证类别 VBTYP ,采购与销售是不相同的,所以采购相关凭证类型使用 BSTYP 来表示了:

    16.4. 业务概念

    16.4.1. 售达方、送达方、开票方、付款方

    The party who receives delivery of the goods.
    售达方
    The customer who orders the goods or services. The sold-to party is contractually responsible for sales orders

    举例的:
    其实简单的说,售达方就是买东西的客户,送达方就是你要发货之后收货的客户。
    通常这 2 个字段输入同一个客户,但是有例外,比如你的客户是一个大集团总部,那大集团下面的一些公司也是你的一些客户,那因为这个集团采购是统一总部进行,你的订单的售达方就是这个集团总部这个客户代码,但是你的货物是指定要送到集团下的一个分公司,那么你的送达方就是这个分公司客户代码。

    通俗的:曾经看到一个强悍的解释:你老婆刷你的卡买了瓶茅台送给老丈人,发票抬头开的是你老婆单位的名称。那么它们之间的关系如下:
    售达方:你老婆
    送达方:你老丈人
    付款方:你
    收票方:你老婆单位

    Sold-to-party : 售达方 ,下订单客户

    Ship-to-party : 送达方 ,收货之客户

    Bill-to-party : 开票方 ,仅指收发票之客户,发票开给谁

    Payer-to-party : 付款方 ,付款人

    16.4.2. 进项税、销项税

    所谓 进项税 销项税 是指增值税的进项和销项税。增值税是国家就增值额征的一种税。如果你是一般人纳税人,你花 1 元钱买的商品的同时 ( 卖方如果能提供增值税发票的话 ) ,给你商品的一方要替税务局向你收 0.17 元的税款,你要向卖给你商品方支付 1.17 元。当你把 1 元的商品以 1.2 元卖出的时侯 ( 或加工成别的商品以 1.2 元卖出时 ) ,你要替税务局向购买方收取 1.2*0.17=0.204 元税款,实际你的纳税额是 0.204- 0.17=0.034 元(因为 0.17 在进货时已经交过了,所以需扣除掉), 0.17 元叫进项税 0.204 元叫销项税。用 0.17 元抵减 0.204 元的过程就叫抵扣进项税。抵扣的前提是你是一般纳税人,有认证过的进项税额,当月没抵扣完的可到以后抵扣。小规模的企业帐上没有进项税,只有销项税。进项税可以抵扣一部分的销项税, 进项税 采购 时发生的, 销项税 销售 环节产生的。进项税是在购入材料或物品取得增值税发票时记入应交税金的借方

    借:原材料

    应交税金 --- 进项税

    贷:银行存款或现金等

    销项税是开了发票入帐进入应交税金的贷方

    借:银行存款或应收帐款

    贷:主营业务收入

    应交税金 -- 销项税

    这是借方的税大于贷方的税就可相抵 如果贷方的税大于借方的税,那就要交税了

    计算公式为:应纳税额 = 当期 销项税额 - 当期 进项税额

    销售额 = 含税销售额 ÷ 1+ 税率)

    销项税额 = 销售额 × 税率

    销项税额: 是指纳税人提供应税服务按照销售额和增值税税率计算的 增值税 额。

    进项税额: 是指纳税人购进货物或者接受加工修理修配劳务和应税服务,支付或者负担的 增值税 税额。

    A 公司 4 月份购买甲产品支付货款 10000 元,增值税进项税额 1700 元(销售货物或者提供加工、修理修配劳务以及进口货物的公司按 17% 税率来征收,个人消费按 6% 税率来征收 ),取得增值税专用发票。销售甲产品含税销售额为 23400 元。

    进项税额 =1700

    销项税额 =23400/ 1+17% ×17%=3400

    应纳税额 =3400-1700=1700

    16.4.3. 订单日期、凭证日期、过账日期

    订单日期:订单创建时所在日期

    凭证日期:一般直接从订单创建日期带过来

    过账日期:某笔交易小记到哪天的账上

    凭证日期:业务发生的日期

    记账日期:记账到哪个期间

    录入日期:什么时候录入的

    会计期间与日期天不一样,一般为 1 12 期间,中国的期间与日期天对应,即第一期间对应于 1 月,第二期间对应 2

    16.5. 业务知识

    16.5.1. 客户联系人相关信息

    T001 公司( BUKRS ADRNR

    T001W :工厂( WERKS ADRNR

    KNA1 :客户主数据( KUNNR ADRNR

    EKKO ADRNR 收货地址)

    VBPA :合作伙伴

    这些表的 ADRNR 都在是 ADRC (地址表 ADDRNUMBER )中定义的。

    ADRC :存储了公司、客户的 名称( NAME1/2/3/4 )以及 地址、邮编 等数据

    KNVK :客户主要 联系人 (客户一般指公司,各部门设有联系人 PARNR :主键,联系人号码; KUNNR :客户编号; PRSNR :人员编号

    KNA1 :客户 主数据 KUNNR :主键,客户编号; ADRNR :地址

    ADR2 :电话号码 (ADDRNUMBER(10),PERSNUMBER(10))

    ADR3 :传真号 (ADDRNUMBER(10),PERSNUMBER(10))

    ADR6 :邮件 (ADDRNUMBER(10),PERSNUMBER(10))

    如查找某客户联系人所对应邮件: 通过 KNVK-KUNNR= KNA1 -KUNNR KNVK 表中取到人员编号 KNVK-PRSNR (可能会有多个,某个公司的联系人可以有多个),再到 ADR6 E-Mail Addresses ADR6-ADDRNUMBER=KNA1- ADRNR AND ADR6-PERSNUMBER= KNVK- PRSNR )得到邮件地址 SMTP_ADDR ,使用 XD03 也可以查看某个客户所对应的邮件地址。

    另一种查找法 (上面根据非主键查,下面都是根据主键来查询,所以优先考虑下面查找法)

    根据订单号 VBAP- VBELN Item 行号 VBAP- POSNR = 000000 、合作伙伴功能 VBPA- PARVW ,到合作伙伴表 VBPA 查找得到 地址号 VBPA - ADRNR 联系人号码 VBPA- PARNR

    vbpa ~ vbeln = vbap ~ vbeln AND vbpa ~ posnr = '000000' AND vbpa ~ parvw = 'AG' AG 表示伙伴为售达方,因为 VBAK 头表(整张单)中的 VBAK - KUNNR 只能是售达方客户编号 (注: VBAP 中没有 KUNNR 客户编号)。这里查的只是表头 (整张单),而非 Item 对应的合作伙伴,如果查某个 Item 合作伙伴,则需将 vbpa~posnr='000000' 修改为 vbpa~posnr= vbap~posnr ,并且 vbpa~parvw = 'AG' 中的 AG 修改为对应的伙伴功能,或去掉此条件

    根据 联系人号码 VBPA- PARNR KNVK 中( KNVK-PARNR=VBPA-PARNR )查找得到 人员编号 KNVK- PRSNR 最后 根据 地址号 人员编号 ADR2/3/6 -ADDRNUMBER=VBPA- ADRNR AND ADR2/3/6-PERSNUMBER= KNVK- PRSNR 条件到 ADR2/3/6 表中查找得到 电话 / 传真 / 邮件 信息

    16.5.2. 销售订单合作伙伴功能

    VBAK 中有一个客户字段 KUNNR 的信息,但 只能表示 Sold-to party 售达方

    如果要知道每个 Item 的所对应的不同功能的客户,则需要将 VBAP VBPA (合作伙伴)通过 VBELN POSNR 进行关联来得到 Item 所对应的客户信息 ( 也是 KUNNR) ,至于该客户的功能,则需要根据 VBPA-PARVW (就是 Partner Function ,即合作伙伴功能)来判断,该字段决定了客户是 Sold-to-party (售达方)、 Ship-to-party (送达方)、 Bill-to-part (开票方)还是 Payer-to-party (付款方)等。得到 KUNNR 后,就可以到 KNA1 (客户记主数据表)中获取客户的联系人信息;另外还可以根据 VBPA-ADRNR ADRC 获取客户的名称( NAME1/2/3/4 )以及地址、邮编等数据

    17. 增强

    标准教材: BC425 BC427

    17.1. 第一代:基于源码增强(子过程 subroutine

    这些 Form 集中存储在一些文件名倒数第二个字符为 Z 的包含程序中(如 后面销售凭证 主程序 SAPMV45A 中的 MV45AT Z Z MV45AO Z Z Include 文件)

    这些 Form 的名称一般是以 UserExit_ 打头的子模块,所以一般找到所要增强的主程序,再查找 UserExit_ 关键字即可找到相关的出口

    Form 源代码增强事先要到 service marketplace 申请对象键( ACCESS KEY ),然后才能修改这些子程序

    另外,可以在 SPRO 中搜索 USER EXIT 关键字来查找

    17.2. 第二代:基于函数出口增强( Function

    SMOD (激活增强,只需一次激活) CMOD (实现增强) 维护;在 SAP 发布的版本中,使用 CALL CUSTOMER-FUNCTION <3 位数字 > 调用函数模块的,所以你可以通过在程序中查找 cusomer-function 来查找增强,出口函数名称由三部分组成 EXIT_< 程序名 >_<3 位数字 > (注:这里的 < 程序名 > 即指调用此出口函数的程序名 ),这样你就可以找到对应的增强函数了

    针对数据表的增强出口是 CI_ 打头的结构 ,这些结构将 .INCLUDE 结构的形式包含到时相应的数据表中,用户可以通过向这些结构中添加字段从而达到对数据表字段的增加

    第二代增强中主要有 4 类:

    1 E . Function exits 函数增强( 最常用,在 SAP 上线很多年后都会使用, 如:销售单 VA02 中,对 PO 长度限制在 10-15 位之间,且不能为中文与其他特殊字符,还有如对 PO 采购日期不能晚于交货日期的检验等,这些都会用来函数增强

    2 C .GUI codes GUI 增强

    3 S . Screens :屏幕增强 增强屏幕的调用是使用 CALL CUSTOMER-SUBSCREEN 不常用,一般在上线之初才会做,上线后不常用)

    4 T . Tabes :表结构增强

    查找 Enhancement 的方法:

    1、 在程序中搜索 CUSTOMER-FUNCTION 找到后面的 3 位数字编号,出口函数名的规则为 EXIT_< 程序名 >_<3 位数字 > ,然后通过找到的出口函数名到 MODSAP 表里查找所对应的出口对象(即增强点)

    2、 通过调试系统相关函数 MODX_FUNCTION_ACTIVE_CHECK

    3、 代码找增强

    VA01 对应的主程序 SAPMV45A 为例,在源码中可以查找包含 CALL CUSTOMER-FUNCTION 的字符串,可以找到这样的代码:

    根据出口所对应的函数名规则,这个函数名为 EXIT_SAPMV45A_003

    MODSAP 表: 增强点 (出口对象)与 函数 关系对应表

    再根据 出口函数 ,到 MODSAP 表中查找对应的 增强点 (出口对象):

    注:一个出口函数只对应一个出口对象,而一个出口对象可以对应到多个出口函数

    Enhancement 比较重要的表 MODSAP ,这个表里重要的字段有增强名( Name ,即出口对象名),组件类型( TYP: E C S T ),组件功能模块名( Member ):里面记录了所有 enhancement 的增强。 TFDIR 所有的函数表,重要字段有 FUNCName (函数名), MAND (功能模块激活状态如果是 C 代表此函数模块激活)

    17.2.1. 示例:采购订单屏幕增强

    通过调试 MODX_FUNCTION_ACTIVE_CHECK 系统函数 ,运行 ME23N ,找到名为 EXIT_ SAPMM06E _006 出口函数 ,再根据这个出口函数到 MODSAP 表中找到对应的出口对象 (增强点) MM06E005 ,再通过 SMOD 查看这个出口对象(增强点):

    MM06E005 包含 功能出口、屏幕出口、表出口 三种增强

    在上面 MM06E005 增强的 SMOD 界面上双击表出口“ CI_EKKODB ”,可以对 EKKO 表结构进行扩充

    在上面 MM06E005 增强的 SMOD 界面上双击出口函数“ EXIT_SAPMM06E_006 ”,则会打开函数编辑器 SE37 ,再点击工具栏中的“ Display Object List ”按钮,则切换到 SE80 编辑器模式中显示,这样就可以找到出口函数所在的函数组为 XM06 主程序为 SAPL XM06

    INCLUDE LXM06TOP Global Data 在此为增强定义 global data

    INCLUDE LXM06UXX . Function Modules 实际上包含所有可用的 user exit 出口函数)

    INCLUDE LXM06F00 . SAP-Formpool for Customer-Use 可在此建立 Form pool

    INCLUDE ZXM06ZZZ . Subprograms and Modules ,在此创建增强子屏幕)

    17.2.1.1. 定义全局变量

    屏幕字段名的前缀必须要设置为系统预先定义好的全局 EKKO_CI 内表类型名,这样屏幕字段的就可以自动与该内表结构进行交互, EKKO_CI 即为系统预先就定义好的增强屏幕所需的结构类型:

    当向结构预留结构 CI_EKKODB 中扩展字段时, EKKO_CI 也会自动的得到扩展,还有 EKKO 表结构也会被扩充

    17.2.1.2. 子屏幕

    MM06E005 增强点的 SMOD 界面上双击出口函数“ SAPMM06E         0101 CUSTSCR1 SAPLXM06     0101 ”屏幕出口行,则会新创建屏幕 0101 (屏幕属性需设置为 子屏幕 :

    17.2.1.3. 屏幕与业务表数据间传递

    17.2.1.4. 相关函数说明

    MM06E005 增强出口中各个出口函数功能说明:

    006 Export Data to Customer Subscreen for Purchasing Document Header ( PBO ) Header 显示子屏幕 调用,即在子屏幕的 PBO 事件块执行前就会先调用此函数 在该函数中可以:将数据表中扩展字段所存业务数据导出到采购凭证头中的客户增强子屏幕中

    007 Export Data to Customer Subscreen for Purchasing Document Header ( PAI ) Header ,输入 校验

    在该函数中:可以对输入的数据进行检验

    008 Import Data from Customer Subscreen for Purchasing Document Header Header ,将 通过验证后 的最终屏幕数据转存到业务数据内表中,将作为最终业务数据插入到数据库中

    012 Check Customer-Specific Data Before Saving 按保存按钮后执行, 保存前 调用

    016 : Export Data to Customer Subscreen for Purchasing Document Item (PBO) Item ,与 006 相同

    017 Export Data to Customer Subscreen for Purchasing Document Item (PAI) Item ,与 007 相同

    018 Import Data from Customer Subscreen for Purchasing Document Item Item ,与 008 相同

    17.2.2. 如何快速找到增强

    尽管可以快速根据 Tcode 找到其对应的增强 , 可是往往因为这样找到的是所有的增强 , 而且有些增强可能是随着系统启动了某模块才可能会用到的 , 这样你可能会面临究竟使用哪个增强的困惑 , 所以在此介绍一种方法不用任何程序可以快速定位每个事务码对应的增强 , 一刀致命 .

    第一步 : 在检查出口增强函数设置断点 (Tcode: SE37 ).

    SE37 输入出口检查函数 MODX_FUNCTION_ACTIVE_CHECK .

    系统有 3 种类增强 , 一是 FUNCTION 增强 , 这个最常用 , 我们一般所用的增强就是它 , 一是 MENUENTRY 菜单增强 , 还有一个就是 SUBSCREEN 增强 , 比如采购订单 (Tcode:ME21N), 工单等很多主数据上都允许屏幕增强 , 就是如果你有非常极其 BT 的需求 , 允许自定义一个用户屏幕 , 在屏幕上搞些自定义的字段,这些东西当然最后被保存在自定义的表格中 , 这种思路代表了 ERP 设计的先进方向 , 如果你有兴趣可以学习学习 .

    第二步 : 执行你想执行的任何 Tcode

    现在假设我执行 MB1B 我需要做一些检查增强 , 系统自然执行到 MODX_FUNCTION_ACTIVE_CHECK , 输入变量 l_funcname 看看它是啥值 , 比如是 EXIT_SAPLF048_001, 这个增强的输入参数有 doc header and Item( 3 ), 凭证头和身子在这俩内表都有了 , 应该可做任何检查 .

    根据屠宰经验 , 是这样的 , 函数包括增强函数都躺在表 TFDIR, 如果强函数 TFDIR -MAND = ‘C’ 则表示该增强是激活的 , 于是系统赋予一个标志 active = ‘X’, 测试一下 , 现在有人将 TFDIR-MAND 改成 ’C’ 或直接将 Active 改成 ’X’, 系统马上会到增强哪去逛一下 , 如果增强有诸如某个条件不 match 就错误的逻辑 , 系统就报告错误知道你纠正为止 . 不过 , 象我这样一看就非常老实厚道的人一般不会做这种欺骗系统的事情 .

    第三步 : 快速找到增强名称 (SE16|SMOD|CMOD).

    确定增强函数 EXIT_SAPLF048_001 可用后 ,SE16: MODSAP , 这表保存了函数和增强名称的对应关系 , MEMBER 输入 EXIT_SAPLF048_001, 如图 4, 找到增强 F180A001 .

    SMOD|CMOD 激活增强 F180A001, 激活函数 EXIT_SAPLF048_001, 建立程序 ZXF48U01, 在该程序中写入增强逻辑并激活 , 注意一个增强生效时必须同时激活这 3 个东东 .

    有个弟兄说跟我在项目中学到了不少”歪门邪道“,什么世道 ? 祖传的杀猪独门功夫都让他学去了

    17.3. 第三代:基于类的增强( BADI

    BADI 维护是通过 SE18 SE19 事务来来维护的。 SE18 用于创建及维护 BADI 对象; SE19 用于维护 BADI 的实例

    BADI 的查找方法:

    1 、主程序都会调用 cl_ exitHandler =>get_instance (这只是经典 BADI 是这样来调用的,如果是新式的 BADI ,则调用为 GET BADI handle - BADI 定义名 CALL BADI handle -> method )来判断对象是否存在,并返回实例。我们可以 se24 中对类 cl_exitHandler=>get_instance 方法进行调试 ,运行一个 tcode ,看一下 exit_name 的值,这就是要找的 BADI

    2 、在主程序中搜索 cl_exitHandler ,查看它所引用 ( TYPE REF TO ) 的接口名,根据接口命名规则 IF_EX_ <badi> ,得到 <badi> 命称

    3 、通过程序查找

    命名规则:

    Badi definition: Z<badi>

    Interface: Z IF_EX_<badi>

    BADI implementation Z<impl>

    Implementing class ZCL_IM_<impl>

    17.3.1. 新式 BADI 创建

    新式 BADI 中的 增强容器 Enhancement Spot BADI 定义 BADI Definitions 接口 Interface 增强实现 Enhancement Implementation BADI 实现 BADI Implementation 实现类 之间的关系:

    一个 增强容器 下可以创建多个 BADI 定义 ,每个 BADI 定义 由一个 接口 与多个 增强实现 组成,而每个 增强实现 里又可以创建多个 BADI 实现 ,而每个 BADI 实现 里可以创建一个 现实类

    17.3.1.1. 定义

    首先需要创建 BADI 增强点( Enhancement Spot , Enhancement Spot 是作为一个 BADI 的容器 , 在容器里面,我们可以定义自己的多个 BADI

    在新建立的 enhancement spot 中创建 BADI

    定义 BADI 时,默认采用的是单一使用 (single-use) ,如果没有选中复合使用选项 (Multiple Use) ,单一使用的限制是 只能有一个实现

    一个 Enhancement Spot 可以定义多个 BADI ,每个 BADI 又是由一个接口与多个实例类组成的。 Enhancement Spot 相当于容器概念,用来存储多个 BADI ,而 每一个 BADI 必须定义一个接口 ,该接口可以有一个或多个实现(增强实现 Enhancement Implementation ,每个增强实现里面才能定义 实现类 ), BADI 实质上就是将接口与实现类组织(打包、捆绑)在一起 了:

    BADI 对象是由 接口 实现 组成的,下面创建 BADI 接口:

    双击接口名,可以创建接口,以及定义接口中的方法

    17.3.1.2. 实现

    由于一个 BADI 的实现可以有多个类,这些多个实现类需要组织(打包、捆绑)在一起(与多个 BADI 放在一个 Enhancement Spot 容器中是一个概念),所以需要创建一个新的 BADI 增强实现容器 ZBADI_DEM001_IMP

    一个增强实现( Enhancement Implementation )可以有多个 BADI Implementations (相当于 多个版本 每个 BADI Implementations 即与一个且仅一个实现类对应 ),但起作用的同时只能有一个,有多个版本时需要进行设置:

    如果想要达到像 Java 中多态的话,需要创建多个不同的 Enhancement Implementation 增强实现, BADI 中的多态就是通过不同的 Enhancement Implementation 增强实现来实现的

    当有两个增强实现 Z_BADI_CALC_IMPL_C Z_BADI_CALC_IMPL_C2 ,需要把其中一个的 Implementation is active 前的钩去掉才能被激活:

    17.3.1.3. 过滤器

    注意:上面过滤值一定要大写,否则运行时匹配不到

    17.3.1.3.1. 调用

    parameters : filter ( 2 ) type c .
    DATA : handle TYPE REF TO z_badi_calc_vat , " z_badi_calc_vat BADI 定义 名,不是接口也不是类
    sum TYPE p ,
    vat
    TYPE p ,
    percent
    TYPE p .
    sum = 50 .
    GET BADI handle
    FILTERS " SE18 中定义的过滤器名作为这里的参数名
    filter1 = 'C'.
    CALL BADI handle -> get_vat
    EXPORTING
    im_amount
    = sum
    IMPORTING
    ex_amount_vat
    = vat
    ex_percent_vat
    = percent .

    17.3.1.4. 多个 BADI/ Enhancement 实现时究竟调谁

    在同一 Enhancement Implementation 中(如下图中的 Z_BADI_CALC_IMPL_C ), 不同的 BADI Implementations Z_BADI_CALC_IMPL Z_BADI_CALC_IMPL2 )之间究竟选谁的问题,是 Default Implementation Implementation is active 选项共同来决定 的,且在同一时间内只能有一个 BADI Implementations 能被激活调用,所以要通过这两个选项来控制究竟谁被用来当作当前实现被使用,是否被使用也可通过图中的 Runtime Behavior 说明文字来查看:

    不同的 Enhancement Implementation 之间 Z_BADI_CALC_IMPL Z_BADI_CALC_IMPL2 )调用 由过滤器来决定

    17.3.2. 经典 BADI 创建

    通过 SE18->Utilities->Create Classic BAdi 创建经典 BADI

    17.3.2.1. Filter-Depend. 过滤器

    BADI 的某个实现版本有多个实现类时,这时在调用时如果想要调用指定的类,则需添加过滤器参数,该参数实质上由其代理类来使用,在运行时代理类会去实例化所对应的类。

    加上该选项后,接口与实现类中的所有方法都会 自动的加上一个必输参数 FLT_VAL

    钩选 Filter-Depend 选项后,我们再为实现增加过滤值:

    17.3.2.1.1. 调用

    DATA : out TYPE string .
    DATA : l_badi_instance TYPE REF TO zif_ex__badidef_baditest2 . zif_ex__badidef_baditest2 BAdi Definition Interface name 接口名
    CALL METHOD cl_exithandler => get_instance
    CHANGING instance = l_badi_instance .

    IF l_badi_instance IS NOT INITIAL .
    CALL METHOD l_badi_instance -> test
    EXPORTING
    "flt_val 参数是由 l_badi_instance 实例来使用的,从这里可以推断 l_badi_instance 应该属于代理对象,由它在运行时根据过滤器值来选择性的调用相应实现类的方法
    flt_val = '800'
    in = 'hello'
    CHANGING
    out
    = out .
    WRITE : / out .
    ENDIF .

    17.3.2.2. 通过经典 BADI 扩展自定义程序(菜单、屏幕、功能)

    下面是实现:

    DATA : ok_code LIKE sy - ucomm .
    DATA : program TYPE program ,
    dynpro TYPE dynnr .
    DATA : ref_badi_interface TYPE REF TO zif_ex_badi_defined .
    CALL SCREEN 100 .

    MODULE status_0100 OUTPUT .
    SET PF-STATUS '100' .
    IF ref_badi_interface IS INITIAL .
    DATA : act_imp_existing .
    " 获取 BADI 的实现 Generated Exit Class
    CALL METHOD cl_exithandler => get_instance
    EXPORTING
    exit_name
    = 'ZBADI_DEFINED'
    " 如果未找到 BADI 实现或有实现但未激活时, ref_badi_interface 是否可以接受 NULL (即 INITIAL
    " 一般设置为空, 在为空时,如果未实现或未激活时,还是会返回一个代理实现,这样后面程序运行不
    " 会出错 ,否则设置为 X 时,在未实现或未激活时, ref_badi_interface 不会有值,则如果通过它调用
    " 方法时,会抛异常
    null_instance_accepted
    = ' '
    IMPORTING
    act_imp_existing
    = act_imp_existing " 实现是否已激活
    CHANGING
    instance
    = ref_badi_interface .
    IF act_imp_existing <> 'X' .
    MESSAGE 'BADI 实现没有被激活 ' TYPE 'I' .
    "EXIT.
    ENDIF .
    CALL METHOD cl_exithandler => set_instance_for_subscreens
    EXPORTING
    instance
    = ref_badi_interface .
    " 获取 BADI 实现中所配置的增强子屏幕信息
    CALL METHOD cl_exithandler => get_prog_and_dynp_for_subscr
    EXPORTING
    exit_name
    = 'ZBADI_DEFINED' "BADI 出口名,即 BADI 定义名
    calling_dynpro
    = '0100' " 主调屏幕号
    calling_program
    = 'ZRP_BADITEST' " 主调屏幕所属程序
    subscreen_area
    = 'SUB_AREA' " 主调屏幕中的增强子屏幕区域名
    IMPORTING
    called_dynpro
    = dynpro " 增强子屏幕号
    called_program
    = program . " 增强子屏幕所属程序
    ENDIF .
    ENDMODULE . " STATUS_0100  OUTPUT

    MODULE user_command_0100 INPUT .
    CASE ok_code .
    WHEN 'FC1' .
    MESSAGE ' 普通菜单 ' TYPE 'I' .
    " 只要 BADI 实现激活后,才会出现菜单,即可以点击,才可能走这里的逻辑
    WHEN '+BADI' .
    MESSAGE ' 增强菜单 ' TYPE 'I' .
    WHEN 'BUT1' .
    " 如果 BADI 未实现或实现但未激活时,只要 cl_exithandler=>get_instance
    " 时,设置输入参数 null_instance_accepted = ' ' ref_badi_interface
    " 就会指向一个代理实现类,调用不会抛异常,但只是个空的方法,什么作用
    " 也不会有
    CALL METHOD ref_badi_interface -> hello .
    ENDCASE .
    ENDMODULE .

    17.3.3. 示例:通过 BADI 实现采购订单屏幕增强

    主要用到两个 BADI ME_ GUI _PO_CUST ( 屏幕处理 ) ME_ PROCESS _PO_CUST (业务数据处理)

    详细请参考增强相关文档

    17.4. 第四代: Enhancement-Point

    此种不建议使用,只有无法通过 User Exit BADI 都无法实现时,才考虑这个

    第四代其实是第三代上的加强

    Ehancement Spot: 用来组织 Enhancement options it's a container of Enhancement options

    Enhancement Implementation :用来组织 Enhancement options 的实现代码

    Enhancement Spot 是对 Enhancement 的一个管理平台, Enhancement-Point 技术与 BADI 是有区别的,首先 BADI SAP 预留的类的接口,而 Enhancement-Point 则是允许用户对现有的 SAP 代码进行修改,例如插入、替换,只要符合一定的规则即可,不需要 SAP 预先定义好

    ENHANCEMENT-POINT 是在程序中直接插入代码,其概念与 BADI USER_EXIT 类似,标准程序预留了部分已定义好的增强点可以让 ABAP 做插入代码来实现这个增强(也可以自定义增强点( ENHANCEMENT-POINT ),但不能自定义增强选项( ENHANCEMENT-OPTION ),增强选项一定是系统预留下来的,如果没有增强选项则该处不可做增强),但是不能做屏幕和菜单增强。

    其最大的优势在于方便,可以直接使用程序中所有已定义的变量,不像 BADI USER EXIT 中只能使用方法或函数接口传过来看参数

    一般增强步骤:

    1. DEBUG 标准程序找到需要增强的位置,点 EDIT->SHOW IMPLICIT ENHANCEMENT OPTIONS 查看是否有预留增强选项。( 标准程序不能自己创建 enhancement option , 只能使用系统预留的

    2. 创建增强点实现

    17.4.1. 为自己程序创建显示增强

    进入创建增强选项界面,输入 增强点 名及 增强容器 名(以 Z 开头),确认回车。

    注: Enhancement Spot 就是 SE18 中的 Enhancement Spot

    随后 Editor 上会多出一条语句,然后转到增强模式

    注: Enhancement Spot 相当于一个容器,创建一个增强点的必要条件是要有一个容器。每个增强点(如 ZENH_POINT_01 )都可以创建到这个容器当中,也可以再创建一个容器。删除这个容器的方法:在本地对象或它的包中删除或在 SE18 中删除

    对于 ENHANCEMENT-SECTION ,定义和实现的方法与 ENHANCEMENT-POINT 一样。两者的区别是: enhancement-point 没有代码,只有一个预留点,允许在这个位置插入新代码( implementation ),而 nhancement-section end-enhancement-section. 之间有代码, implementation 之后,替换旧代码,只执行新代码,原来的代码不再执行

    17.4.2. 隐式与显示增强

    Implicit enhancements comprise (包含) class enhancements, function group enhancements and predefined enhancement points at particular predefined positions such as the end of a report, a function module, an include or a structure and the beginning and the end of a method 。隐式增强就是系统内置的 Enhancement options

    显式增强就是手动加入到程序中的 Enhancement options ,有两种显式增强:

    ENHANCEMENT-POINT ,用来插入新的功能代码,没有代码,只有一个预留点

    Defines a position in an ABAP program as an enhancement option, at which one or more source code plug-ins can be inserted .

    ENHANCEMENT - POINT enh_id SPOTS spot1 spot2 ...
    [STATIC]
    [INCLUDE BOUND]
    .

    ENHANCEMENT-SECTION ENHANCEMENT-SECTION END-ENHANCEMENT-SECTION. 之间有代码 , implementation 之后,替换旧代码,只执行新代码,原来的代码不再执行

    Defines a section of an ABAP program as an enhancement option, which can can be replaced by one or more source code plug-ins.

    ENHANCEMENT-SECTION enh_id SPOTS spot1 spot2 ...
    [STATIC]
    [INCLUDE BOUND]
    .
    ...
    END-ENHANCEMENT-SECTION .

    隐式增强:在 执行程序,包含程序,函数组,对话模块的结尾; Form 例程,函数模块,方法等的开始和结尾;结构的结尾这些地方都会有

    显示增强:需要在编辑器中创建,可参考上面

    18. 数据批量维护

    18.1. BDC SM35 SHDB

    生成带服务器端测试数据文件的程序:

    运行程序时可指定数据来源于服务器上的某个测试文件。

    可以通过 SAP 提供的工具 CG3Y ,将此生成的服务器测试数据文件下载到本地后进行编辑,修改好后,再通过工具 CG3Z 将文件上传到服务器上

    生成以内表作为测试数据源的程序:

    从本地读取测试数据文件:需手动读取本地文件

    BDC 后台运行

    BDC 的两种通用写法。

    1.    Call Transaction: 顾名思义,就是直接调用 BDC 进行数据批量导入。优点:方便快捷,程序处理方便。缺点:日志管理能力差,需自己建透明表来维护数据。我只是把它用作测试用途,不做正式使用。

    2.    BDC Insert (即 CALL Function ):这是一种不直接运行,而是将 BDC 程序生成 session (但不立即运行,需要手工或通过 RSBDCSUB 专用程序来运行会话) 。优点:通过 T-code SM35 可以进行运行管理及日志管理,方便查错 。缺点:相对方法 1 来说实现起来比较繁琐。我主要是用这种方法来实现 BDC 功能。

    下面主要来谈一下 BDC Insert 这种方法。

    1. 需要在程序中调用 function BDC_OPEN_GROUP ' BDC_INSERT ' 来把 BDCDATA 生成 SESSION.

    2. 通过程序 RSBDCSUB 来执行 SESSION (后续建立 JOB 中使用,目前手动运行会话)

    3. 建立 BATCH JOB 来定期执行 RSBDCSUB ,从而实现 SESSION 自动执行的目的

    4. 当然,不使用程序 RSBDCSUB JOB ,每次手工在 SM35 中执行 SESSION 也是可以的

    18.2. LSMW

    创建数据源存储结构

    数据源结构与目标结构映射

    分配字段转换规则

    指定数据源文件并分配给数据源结构

    将文件内容读取到服务器上

    数据转换: 数据会从 .read 文件中,经转换后存储到 .conv 文件中

    18.3. 业务对象和 BAPI

    18.3.1. SAP 业务对象( SWO1

    业务对象类型 是业务对象的定义和描述,对象类型 (Object Type) 相当于 (但不等于)对象设计语言中类 (Class) 的概念 ,它封装了业务功能和数据

    业务对象与 BAPI 早于 ABAP OO ,通过非面向对象语言,以面向对象的形式设计了业务对象与 BAPI

    18.3.1.1. 业务对象类型的组成

    l 接口 :需实现的业务接口,可以是多个

    l 关键字段 :用于唯一确定一个业务对象类型的实例,通常是业务对象底层数据库表的对应主键

    l 属性 :业务对象的数据部分 , 可以是数据表中的字段、运行值 ( 又称虚拟属性 , virtual attribute) 或指向其它业务对象的指针 ( 对象引用 , object reference)

    l 方法 :用于操作业务对象属性,可以通过调用事务、 function module report ABAP 程序来实现方法,即业务方法的实现方法有多种

    l 事件 :状态的改变,可通过事件触发工作流或任务

    业务对象类型也是可以继承的 ,如业务对象类型 BUS1001006 (标准物料)和 BUS1001001 (零售物料)的父类型都是 BUS1001 (物料):

    18.3.1.2. 业务对象( BO )设计

    18.3.1.2.1. 创建业务表

    业务对象代表具体的业务数据,因此业务对象类型都有相对应的数据字典结构对应:

    18.3.1.2.2. 创建业务对象类型

    系统已自动引用 SAP 标准接口 IFSAP ,并从中继承了一些默认的属性和方法

    18.3.1.2.3. 添加(继承)接口

    创建业务对象类型时,除自动默认继承的接口 IFSAP 外:

    还可以为业务对象添加(或叫实现吧)其他的 SAP 接口, 业务对象将从接口中自动继承接口里的属性或方法 ,其中大多方法需要在业务对象中重新实现。将光标放在 Interfaces 位置, 点击按钮 ,添加 IFCREATE 接口

    注: 这些接口类型也是通过 SWO1 创建的 ,只是在初始界面选择的是“ Interface type ( 相当于接口 ) ”,而不是“ Object type( 相当于类 )

    同理添加 IFEDIT 接口,最后结构如下 :

    通过 查看继承过来的方法属性 ,发现只有 Create 方法是静态的,与实例无关

    18.3.1.2.4. 添加关键字段 Key

    关键字段代表着一个业务对象类型的实例,由它来区分各个业务对象,实例业务对象需要此 Key

    将鼠标放在“ Key fields ”所在位置上, 点击新建按钮 ,系统将提示是否参照 ABAP 字典中的表结构创建关键字段,本例中的业务对象基于数据库表 ZTAB_EMPLOYEE

    生成的程序代码:

    18.3.1.2.5. 添加属性

    将光标置于 Attributes 所在行,点击新建按钮 功能,系统将提示是否参照 ABAP 字典中的表结构创建关键字段:

    并为每一个属性输入名称之后,各字段将出现在 Attributes 列表中:

    18.3.1.2.6. 通过报表程序来实现业务对象的方法
    18.3.1.2.6.1. 报表程序

    现以报表程序的方式,来重新实现业务对象类型继承过来原有方法( Create Edit Display 等),业务对象的这些方法将通过提交到该报表的方式来实现这些方法的功能:

    REPORT zbo_employee_rep .
    TABLES : ztab_employee .
    PARAMETERS : id LIKE ztab_employee - id OBLIGATORY ,
    name
    LIKE ztab_employee - name ,
    phone
    LIKE ztab_employee - phone ,
    email
    LIKE ztab_employee - email ,
    op
    .
    START-OF-SELECTION .
    ztab_employee
    - id = id .
    ztab_employee
    - name = name .
    ztab_employee
    - phone = phone .
    ztab_employee
    - email = email .
    CASE op .
    WHEN 'I' . " 插入数据
    INSERT ztab_employee .
    IF sy - subrc = 0 .
    MESSAGE s016 ( rp ) WITH 'One record inserted.' .
    ELSE .
    MESSAGE e016 ( rp ) WITH 'No record inserted,ID existed.' .
    ENDIF .
    WHEN 'U' . " 更新数据
    UPDATE ztab_employee .
    IF sy - subrc = 0 .
    MESSAGE s016 ( rp ) WITH 'One record updated.' .
    ELSE .
    MESSAGE e016 ( rp ) WITH 'No record update,ID not existed.' .
    ENDIF .
    WHEN 'V' . " 显示数据
    SELECT SINGLE * FROM ztab_employee WHERE id = ztab_employee - id .
    IF sy - subrc = 0 .
    WRITE : / 'Employee No:' , ztab_employee - id ,
    /
    'Employee Name:' , ztab_employee - name ,
    /
    'Employee Phone:' , ztab_employee - phone ,
    /
    'Employee Email:' , ztab_employee - email .
    ENDIF .
    ENDCASE .

    18.3.1.2.6.2. 重定义接口与方法实现

    在没有进行重定义之前,这些继承而来的方法名称显示为深红色。将光标置于方法名上,点击 (即方法的重定义)按钮,每个方法都经过该操作后,底色会变为白色:

    将光标置于 Display 方法名上,点击“ Program ”按钮,提示方法还未进行代码实现:

    YES 后,进行程序代码实现界面,在业务对象的实现程序中 添加 以下代码:

    ExistenceCheck 方法用于检查对象实现是否存在, 如在对象测试界面中,需要 选择“ Instance ”按钮 来测试实例相关的一些方法时,如果未实现该方法,则如果输入一个不存在的 ID ,系统将仍进入实例测试界面,但实例相关的测试功能不可用。 正确实现 ExistenceCheck 方法后,如果指定关键字段的对象不存在,系统将给出消息提示: Object does not exist

    其中 object 变量为前面自动生成的变量,相关代码如下:

    注:在每次修改程序后,都需要保存并重新生成 业务对象类型,才能进行测试

    18.3.1.2.6.3. 测试

    注: 测试界面上的屏幕参数就是 Report 程序选择屏幕

    18.3.1.2.7. 通过 BAPI 函数来实现业务对象方法

    本小节将在前面章节的基础上,为业务对象类型添加两个 BAPI 方法: ZEMPLOYEE _obj .GetList (读取职员列表)和 ZEMPLOYEE_obj.GetDetail (读取职员详细信息)。

    BAPI 方法 与上节中所添加的普通方法是不同的,那些借助于报表程序来实现的 Create Edit 等方法,仅可在 SAP 系统内部使用,但 BAPI 不同的是可以供非 SAP 系统对业务对象进行访问

    18.3.1.2.7.1. 创建 BAPI 参数结构

    BAPI 方法 ZEMPLOYEE_obj.GetList 需要用到的结构类型 BAPI 自定义结构类型需要以 ZBAPI 开头——其实 BAPI 函数、 BAPI 函数的 Group 、以及 BAPI 函数参数参照的结构名称中都需要包含 BAPI 名称串,另外, BAPI 函数参数所参照的表结构类型只能被一个 BAPI 使用,因为释放 BAPI 后相应结构会被冻结 ):

    BAPI 方法 ZEMPLOYEE_obj.GetDetail 需要用到的结构类型:

    18.3.1.2.7.2. 创建 BAPI 函数、 BAPI 调用返回 RETURN 结果处理

    先创建函数组 ZEMP ,并激活:

    现创建 BAPI 方法需要调用的两个 RFM 远程函数: ZBAPI_GET_EMPLOYEE_LIST ZBAPI_GET_EMPLOYEE_DETAIL ZEMPLOYEE_obj.GetList ZEMPLOYEE_obj.GetDetail 两个 BAPI 业务方法将借助于这两个 BAPI 函数来实现。

    注: BAPI 函数中 Export 必须有 return 返回参数 (约定俗成) , BAPI 调用成功与否一般通过 RETURN 参数返回,该参数的数据结构可以参照数据字典结构 BAPIRETURN ,其重要的字段有:

    l TYPE 消息类型,如 S E W I

    l ID 消息类型

    l NUMBER 消息编号

    l MESSAGE 消息文本

    l MESSAGE_V 1 MESSAGE_V2 MESSAGE_V3 MESSAGE_V4 ,消息变量

    在函数组 ZEMP LZEMPTOP 顶层包含文件中定义全局的数据变量:

    并为 ZBAPI_GET_EMPLOYEE_LIST 函数添加以下源码:

    FUNCTION zbapi_get_employee_list .
    *"----------------------------------------------------------------------
    *"*"Local Interface:
    *"  EXPORTING
    *"     VALUE( RETURN ) TYPE
    BAPIRETURN
    *"  TABLES
    *" ZEMP_LIST STRUCTURE ZBAPIEMPLIST
    *"----------------------------------------------------------------------
    CLEAR zemp_list .
    REFRESH zemp_list .
    CLEAR return .
    CLEAR ztab_employee .
    SELECT * FROM ztab_employee INTO  CORRESPONDING FIELDS OF TABLE zemp_list .
    IF sy - subrc <> 0 . " 如果 BAPI 函数中数据验证不通过,则对返回具有 E 类型消息的 RETURN 消息返回内表,则外部主调程序对 RETURN 内表进行判断,根据 RETURN 中的结果来决定是否 Commit Work
    CLEAR message .
    message
    - msgty = 'E' .
    message
    - msgid = 'RP' .
    message
    - msgno = 16 .
    message
    - msgv1 = 'No Employee is available.' .
    PERFORM
    set_return_message USING message CHANGING return .
    ENDIF .
    ENDFUNCTION .

    BAPIRETURN 构造:

    FORM set_return_message USING p_message LIKE message
    CHANGING p_return LIKE bapireturn .
    CHECK NOT message IS INITIAL .
    CALL FUNCTION 'BALW_BAPIRETURN_GET'
    EXPORTING
    type
    = p_message - msgty
    cl
    = p_message - msgid
    number
    = p_message - msgno
    par1
    = p_message - msgv1
    par2
    = p_message - msgv2
    par3
    = p_message - msgv3
    par4
    = p_message - msgv4
    IMPORTING
    bapireturn
    = p_return
    EXCEPTIONS
    OTHERS = 1 .
    ENDFORM .

    注:此 BAPI 函数为实例对象业务方法,所以 需要在 Import 设置与业务对象 Key 一样的参数 ,如这里的 ID (名称与类型最好都一致)

    FUNCTION zbapi_get_employee_detail .
    *"----------------------------------------------------------------------
    *"*"Local Interface:
    *"  IMPORTING
    *"     VALUE(ID) TYPE  ZTAB_EMPLOYEE-ID
    *"  EXPORTING
    *"     VALUE(ZEMP_DETAIL) TYPE ZBAPIEMPDETAIL
    *"  TABLES
    *"      RETURN STRUCTURE  BAPIRETURN
    *"----------------------------------------------------------------------
    CLEAR zemp_detail .
    CLEAR return .
    CLEAR ztab_employee .
    SELECT SINGLE * FROM ztab_employee INTO CORRESPONDING FIELDS OF zemp_detail WHERE id = id .
    IF sy - subrc <> 0 .
    CLEAR message .
    message - msgty = 'E' .
    message - msgid = 'RP' .
    message - msgno = 16 .
    message - msgv1 = 'Employee does not exist.' .
    PERFORM set_return_message USING message CHANGING return .
    ENDIF .
    ENDFUNCTION .

    18.3.1.2.7.3. BAPI 函数绑定到相应的业务方法

    为什么在 RFC 函数创建好后,还需要将该函数绑定到业务对象类型中:

    创建 BAPI 的最后一步就是为业务对象类型添加 BAPI 方法,并将上面创建 BAPI 函数分配给这些 BAPI 方法:

    由于 BAPI 函数中定义了关键字段作为输入参数,当在添加该方法时就默认此方法为实例方法,所以不能选中 Instance-independent 选项

    点击 按钮进行下一步操作 ,设置待加 BAPI 方法的参数,一般使用建议,与 BAPI 函数参数相对应:

    绿色 表示为 BAPI 方法

    通过相同的方法,创建 List BAPI 方法,并将 ZBAPI_GET_EMPLOYEE_LIST BAPI 函数分配给它,由于 List 为静态方法,所以钩上 Instance-independent

    业务对象方法如果是通过 BAPI 方法来实现的,则 实现方式要选择 API function ,另外还需要指定对应到的 BAPI 功能模块:

    18.3.2. BAPI

    BAPI Business Application Process Interface( 业务应用编辑接口 ) ,它实质上就是一种特殊的 RFC 函数

    BAPI 函数及函数参数参考的结构类型名,都要以 ZBAPI

    BAPI 函数参数只能是传值,不能有 Change Exception 参数

    BAPI 函数需要有 Return 返回参数

    18.3.2.1. BAPI 浏览器

    18.3.2.2. SE37 查找: BAPI 函数的命名规则

    BAPI 对应的功能模块命名规则 BAPI_< 业务对象 >_<method> ,因此可以直接在 SE37 中通过前缀 BAPI 加对象名称或方法名称作为关键字,快速查找一个 BAPI 功能模块函数。如检索 BAPI*Material*Get*

    18.3.2.3. 查找某事务码所对应的 BAPI

    如果只知道 事物代码,可以通过下面的方式查询相应的 BAPI 。例如找创建销售( 物料模板根据此方法好像找不出 )订单的 BAPI ,我们知道事物代码是 VA01

    1. 我们进入 VA01 界面,找到 system status

    2. 在事物代码位置上双击 注:不是程序上双击 ),找到 PACKAGE  VA

    3. SE80 打开包 VA ,或点击 Display Object List 按钮直接进入到 SE80 对象列表:

    18.3.2.4. 常用 BAPI 函数

    BAPI_TRANSACTION_COMMIT COMMIT WORK AND WAIT .

    BAPI_TRANSACTION_ROLLBACK ROLLBACK WORK .

    BAPI_ MATERIAL _SAVEDATA 创建及更改 物料 主数据

    BAPI_ GOODSMVT _CREATE 物料移动(创建 物料凭证

    BAPI_ MATERIAL_AVAILABILITY 可用库存

    BAPI_ PR _CREATE 创建 PR 采购申请

    BAPI_ PO _CREATE1 创建 PO 采购单

    BAPI_ SALESORDER _CREATEFROMDAT2 创建销 售订单

    BAPI_ OUTB_DELIVERY _CREATE_SLS 根据销售订单创建 交货单

    BAPI_ BILLINGDOC _CREATEMULTIPLE 创建 发票

    BAPI_ ACC_DOCUMENT _POST 创建 会计凭证

    18.3.2.5. 调用 BAPI

    SAP 系统提供的 BAPI 的参数结构有个特点:一般会将类似的字段放在同一个结构中,同时,还会存在一个与该结构名类似(后面以 X 结尾)标识结构,该标识结构中的字段名与赋值的结构中的字段名一致,但是其字段类型只是一个长度为 1 的字符,用于标识某个字段的数据是否需要通过 BAPI 来变更

    18.3.2.5.1. BAPI 事务处理

    根据事务的 ACID 原则,一个独立的 BAPI 实现必须具有事务性,同时, BAPI 事务模型还必须允许开发者在调用多个 BAPI 时可以将它们绑定到同一个 LUW 上。因此, 如果同时调用几个 BAPI ,开发者需要在程序中进行的事务控制,决定何时执行数据库提交或回滚操作; BAPI 内部则通常不包含 COMMIT WORK ROLLBACK WORK 命令

    操作多个 BAPI 时必须遵循以下原则:

    l 如果有更新操作的 BAPI ,如创建、修改或删除一个业务对象实例,则对该实例进行另外的读取操作的 BAPI 只能访问上一个 COMMIT WORK 执行后的最新数据

    l 在同一个 LUW 中,不能对同一个业务对象实例时行超过一次的更新操作。例如, 不允许在一个 LUW 中创建一个新实例,随后就修改它 。但可以创建多个相同的类型的不同实例

    BAPI 内部,数据库更新操作必须通过同步或异步的更新过程实现(需使用 CALL FUNCTION update_function IN UPDATE TASK 的方式来更新数据库 ),因为否则可能出现不必要的数据库提交过程,从而破坏了 BAPI 调用的 ACID 原则。同样原因, BAPI 内部也不能触发新的 LUW ,因而其内部程序代码中不能包含以下命令:

    l CALL TRANSACTION

    l SUBMIT REPORT

    l SUBMIT REPORT AND RETURN

    l COMMIT WORK

    l ROLLBACK WORK

    因此, BAPI 事务中的数据库提交和回滚必须 在主调程序中 通过调用 SAP 标准业务对象 BapiService (业务对象类型为 SAP0001 )的 BAPI 方法 BapiService. Transaction Commit (此 BAPI 方法实际上还是通过调用 BAPI 函数 BAPI_TRANSACTION_COMMIT 来实现的)和 BapiService. TransactionRollback (此 BAPI 方法实际上还是通过调用 BAPI 函数 BAPI_TRANSACTION_ROLLBACK 来实现的)来完成

    外部程序直接到 调用 BapiService.TransactionCommit 方法,才会触发 BAPI 方法中的数据库提交

    对于 BAPI 的操作都要用 BAPI_TRANSACTION_COMMIT 来提交的,在提交前,要根据 BAPI 函数 的执行返回参数 RETURN 来判断函数是否执行成功 RETURN 中是否有 E 类消息),如果有错误消息则要用 BAPI_TRANSACTION_ROLLBACK 取消所做的操作,而不是 COMMIT WORK ,如:

    CALL FUNCTION 'BAPI_FIXEDASSET_CHANGE'
    ...
    IMPORTING
    return = return .
    IF return - type <> 'S' .
    CALL FUNCTION 'BAPI_TRANSACTION_ ROLLBACK' .
    ELSE .
    CALL FUNCTION 'BAPI_TRANSACTION_ COMMIT'
    EXPORTING
    wait = 'X' .
    ENDIF .

    另外建议在调用 BAPI_TRANSACTION_COMMIT 函数进行提交 BAPI 操作时,加上 wait 参数,这样直到 BAPI 函数中的数据库操作提交数据库后,才去执行其后面的语句,这样后面程序依赖于此提交的数据时就不会出问题:

    CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
    EXPORTING
    wait = 'X' .

    WAIT X 时,会执行 COMMIT WORK AND WAIT 语句,否则执行 COMMIT WORK 语句

    18.3.2.5.2. 外部系统( Java )调用 BAPI 函数
    18.3.2.5.2.1. 直连、连接池

    import java.io.File;

    import java.io.FileOutputStream;

    import java.util.Properties;

    import com.sap.conn.jco. JCo Destination ; ˌdestɪˈneɪʃn

    import com.sap.conn.jco. JCoDestinationManager ;

    import com.sap.conn.jco.JCoException;

    import com.sap.conn.jco.ext.DestinationDataProvider;

    publicclass ConnectNoPool { // 直连方式,非连接池

    // 连接属性配置文件名,名称可以随便取

    static String ABAP_AS = "ABAP_AS_WITHOUT_POOL" ;

    static {

    Properties connectProperties = new Properties();

    connectProperties.setProperty(DestinationDataProvider. JCO_ASHOST ,

    "192.168.111.137" );

    connectProperties.setProperty(DestinationDataProvider. JCO_SYSNR , "00" );

    connectProperties

    .setProperty(DestinationDataProvider. JCO_CLIENT , "800" );

    connectProperties.setProperty(DestinationDataProvider. JCO_USER ,

    "SAPECC" );

    // 注:密码是区分大小写的,要注意大小写

    connectProperties.setProperty(DestinationDataProvider. JCO_PASSWD ,

    "sapecc60" );

    connectProperties.setProperty(DestinationDataProvider. JCO_LANG , "en" );
    // ********* 连接池方式与直接不同的是设置了下面两个连接属性

    // JCO_PEAK_LIMIT - 同时可创建的最大活动连接数, 0 表示无限制,默认为 JCO_POOL_CAPACITY 的值

    // 如果小于 JCO_POOL_CAPACITY 的值,则自动设置为该值,在没有设置 JCO_POOL_CAPACITY 的情况下为 0

    connectProperties.setProperty(DestinationDataProvider. JCO_PEAK_LIMIT ,

    "10" );

    // JCO_POOL_CAPACITY - 空闲连接数,如果为 0 ,则没有连接池效果,默认为 1

    connectProperties.setProperty(

    DestinationDataProvider. JCO_POOL_CAPACITY , "3" );

    // 需要将属性配置保存属性文件,该文件的文件名为 ABAP_AS_WITHOUT_POOL.jcoDestination

    JCoDestinationManager.getDestination() 调用时会需要该连接配置文件,后缀名需要为 jcoDestination

    createDataFile ( ABAP_AS , "jcoDestination" , connectProperties);

    // 基于上面设定的属性生成连接配置文件

    staticvoid createDataFile(String name, String suffix, Properties properties) {

    cfg = new File(name + "." + suffix);

    (!cfg.exists()) {

    try {

    FileOutputStream fos = new FileOutputStream(cfg, false );

    properties.store(fos, "for tests only !" );

    fos.close();

    catch (Exception e) {

    e.printStackTrace();

    publicstaticvoid connectWithoutPool() throws JCoException {

    // 到当前类所在目录中搜索 ABAP_AS_WITHOUT_POOL.jcoDestination

    // 属性连接配置文件,并根据文件中的配置信息来创建连接

    JCoDestination destination = JCoDestinationManager

    . getDestination ( ABAP_AS ); // 只需指定文件名(不能带扩展名 jcoDestination 名,会自动加上)

    System. out .println( "Attributes:" );

    // 调用 destination 属性时就会发起连接,一直等待远程响应

    System. out .println(destination.getAttributes());

    publicstaticvoid main(String[] args) throws JCoException {

    connectWithoutPool ();

    18.3.2.5.2.2. 访问结构

    JCoDestination destination = JCoDestinationManager

    . getDestination ( ABAP_AS );

    JCoFunction function = destination. getRepository (). getFunction (

    "RFC_SYSTEM_INFO" ); // 从对象仓库中获取 RFM 函数

    function.execute(destination);

    JCoStructure exportStructure = function. getExportParameterList ()

    . getStructure ( "RFCSI_EXPORT" );

    for ( int i = 0; i < exportStructure. getMetaData ().getFieldCount(); i++) {

    System. out .println(exportStructure.getMetaData().getName(i) + ":\t"

    + exportStructure.getString(i));

    System. out .println();

    // 也可以使用下面的方式来遍历

    for ( JCoField field : exportStructure) {

    System. out .println(field.getName() + ":\t" + field.getString());

    //********* 也可直接通过结构中的字段名或字段所在的索引位置来读取某个字段的值

    System. out .println(exportStructure.getString(0));

    System. out .println(exportStructure.getString( "RFCPROTO" ));

    18.3.2.5.2.3. 访问表 (Table)

    JCoDestination destination = JCoDestinationManager

    . getDestination ( ABAP_AS );

    JCoFunction function = destination.getRepository().getFunction(

    "BAPI_COMPANYCODE_GETLIST" ); // 从对象仓库中获取 RFM 函数:获取公司列表

    function.execute(destination);

    JCoStructure returnStructure = function.getExportParameterList()

    .getStructure( "RETURN" );

    // 判断读取是否成功

    (!(returnStructure.getString( "TYPE" ).equals( "" ) || returnStructure

    .getString( "TYPE" ).equals( "S" ))) {

    throw new RuntimeException(returnStructure.getString( "MESSAGE" ));

    // 获取 Table 参数: COMPANYCODE_LIST

    JCoTable codes = function. getTableParameterList (). getTable (

    "COMPANYCODE_LIST" );

    for ( int i = 0; i < codes.getNumRows(); i++) { // 遍历 Table

    codes.setRow(i); // 将行指针指向特定的索引行

    System. out .println(codes.getString( "COMP_CODE" ) + '\t'

    + codes.getString( "COMP_NAME" ));

    // move the table cursor to first row

    codes.firstRow(); // 从首行开始重新遍历 codes.nextRow() :如果有下一行,下移一行并返回 True

    for ( int i = 0; i < codes.getNumRows(); i++, codes.nextRow()) {

    // 进一步获取公司详细信息

    function = destination.getRepository().getFunction(

    "BAPI_COMPANYCODE_GETDETAIL" );

    function.getImportParameterList().setValue( "COMPANYCODEID" ,

    codes.getString( "COMP_CODE" ));

    // We do not need the addresses, so set the corresponding parameter

    // to inactive.

    Inactive parameters will be either not generated or at least

    converted. 不需要返回 COMPANYCODE_ADDRESS 参数(但服务器端应该还是组织了此数据,只是未经过网络传送?)

    function.getExportParameterList().setActive( "COMPANYCODE_ADDRESS" ,

    false );

    function. execute (destination);

    returnStructure = function.getExportParameterList().getStructure(

    "RETURN" );

    if (!(returnStructure.getString( "TYPE" ).equals( "" )

    || returnStructure.getString( "TYPE" ).equals( "S" ) || returnStructure

    .getString( "TYPE" ).equals( "W" ))) {

    throw new RuntimeException(returnStructure.getString( "MESSAGE" ));

    JCoStructure detail = function.getExportParameterList()

    .getStructure( "COMPANYCODE_DETAIL" );

    System. out .println(detail.getString( "COMP_CODE" ) + '\t'

    + detail.getString( "COUNTRY" ) + '\t'

    + detail.getString( "CITY" ));

    } // for

    18.3.2.5.2.4. Java 多线程调用有 / 无状态 RFM

    有状态调用 :指多次调用某个程序(如多次调用某个 RFC 函数、调用某个函数组中的多个不同的 RFC 函数、及 BAPI 函数——因为 BAPI 函数也是一种特殊的具有 RFC 功能的函数,它也有自己的函数组)时,在这多次调用过程中,程序运行时的内存状态(即全局变量的值)可以在每次调用后保留下来,供下一次继续使用,而不是每次调用后,程序所在的内存状态被清除。这种调用适用于那些使用到函数组中的全局变量的 RFC 函数的调用

    无状态调用 :每次的调用都是独立的一次调用(上一次调用与当前以及下一次调用之间不会共享任何全局变量),调用后不会保留内存状态,这种调用适用于那些没有使用到函数组中的全局变量的 RFC 函数调用

    如果主调程序为 Java ,有状态调用的前提是:

    l 多次调用 RFC 函数时, Java 端要确保每次调用所使用的连接与上次是同一个 应该不需要是同一物理连接,只需要确保是同一远程会话,从下面演示程序来看,用的是 连接池 ,但同一任务执行时并未去特意使用同一物理连接去发送远程调用,而只是要求是同一远程会话

    l ABAP 端需要在每次调用后,保留每一次被调用后函数组的内存状态,直到最后一次调用完成止,这需要 Java ABAP 配合来完成( Java 在第一次调用时,调用 JCo Context.begin JCoContext .end 这两个方法,告诉 SAP 这一调用过程将是有状态调用,需要保留内存状态,然后 SAP 端就会自动保留内存状态)

    如果主调程序是 ABAP (即 ABAP 程序调用 ABAP 函数),此种情况下没有特殊的要求,直接调用就即可,只要是在同一程序的同一运行会话其间(会话相当于 Java 中的同一线程吧),不管是多次调用同一个函数、还是调用同一函数组中的不同函数,则都会自动保留内存状态,直到程序运行结束,这是系统自己完成的。一个 函数组好比一个类 ,函数组中不同的函数就相当于类中不同的方法、全局变量就相当于类中的属性,所以只要是在同一程序的同一运行会话期间,调用的同一函数所在的函数组中的全局变量都是共享的,就好比调用一类的某个方法时,该方法设置了某个类的属性,再去调用该类的其它方法时,该属性值还是保留了以前其它方法修改后的状态值。

    状态调用只要保证同一 Java 线程中多次远程方法调用采用的都是同一会话即可

    18.3.2.5.3. ABAP 访问 Java 服务
    18.3.2.5.4. ABAP 创建远程目标

    SAP 通过 JCO 反向调用 JAVA RFC 服务其实也是相对简单的,只是在 JAVA 端需要使用 JCO 创建一个 RFC 服务,然后在 SAP 端注册这个服务程序。

    首先, JCo 服务器程序需在网关中进行注册,在 SM59 中,定义一个连接类型为 T 的远程目标

    RFC 目标系统:是 ABAP RFC 调用 Java 时,需指定的目标系统名。

    Program ID :是 JAVA 程序中使用的

    Gateway Host Gateway service 值来自以下界面( Tcode SMGW ):

    TCP 服务 sapgw 是固定的,后面的 00 就是系统编号

    所有配置好 Java 服务器代码跑起来 后,点击“ Connection Test ”按钮,如不出现红色文本,则表示链接成功( 注:此时需要 ServerDataProvider .JCO_PROGID 设置的 Program ID 要与 SM59 中设置的相同,否则测试不成功。另要注意的是:即使 Java 服务器设置的 Program ID 乱设置, Java 服务端还是能启起来,但 ABAP 测试连接时会不成功,也就代表 ABAP 不能调用 Java

    18.3.2.5.5. 连接异常 registrationnot allowed

    Java 服务启动时,如出现以下异常,则需在 SAP 中修改网关参数:

    com.sap.conn.jco.JCoException : (113) JCO_ERROR_REGISTRATION_DENIED: CPIC-CALL: SAP_CMACCPTP3 on convId:

    LOCATION    SAP-Gateway on host LRP-ERP / sapgw00

    ERROR       registration of tp JCOTEST from host JIANGZHENGJUN not allowed

    通过事务码 SMGW 修改参数:

    18.3.2.5.6. 带状态访问

    // 如果是某任务 LUW 中第一次调用时,则 jcoServerCtx 服务上下文为非状态,需设置为状态调用

    if (!jcoServerCtx.isStatefulSession())

    // 设置为状态调用,这样在有状态调用的情况下,上下文中会携带会话 ID

    jcoServerCtx. setStateful ( true );

    cachedSession = new SessionContext(); // 新建会话

    // 将会话存储在映射表中,以便某个任务里每次远程调用都可以拿到同一会话

    statefulSessions .put( jcoServerCtx . getSessionID (),

    cachedSession);

    else { // 非第一次调用

    cachedSession = statefulSessions .get(jcoServerCtx

    .getSessionID());

    18.4. IDoc

    IDoc 是基于文档,用作异步传输数据的载体,类似于 XML

    18.4.1. 数据段类型和数据段定义( WE31

    数据段是 IDoc 结构组件,是 IDoc 的构成的单元。它由 Segment type (数据段类型) Segm. definition (数据段定义) 两部分组成,其中 Segment type 的名称与 SAP 版本无关,但 Segm. definition 名称 是与 SAP 版本有关的,外部系统就是根据 Segm. definition 名称来确定当前数据段的版本的:

    SAP 提供的标准数据段类型定义中, Segment type 名称以“ E1 ”开头,而 Segm. definition 名称则以“ E2 ”开头( 如果为用户自定的数据段,则数据段类型名应以“ Z1 ”开头,数据段定义名称应以“ Z2 ”开头 ),且后面跟上版本号(如上面的 006 ),如数据段类型 E1FIKPF 对应多个版本的数据段定义(包括最初版本在内共 7 个版本):

    双击 006 版本,即可查看数据段类型 F1FIKPF 的最新具体定义,如上上图所示

    IDoc 数据段中各个字段的数据类型均为字符类型(如上上图中的 Export leng ),在出站时已将原数据类型都转换为字符型了。另外,在 ABAP 程序中,访问 IDoc 中的具体某个字段时,需要通过 Segment type (数据段类型)名而不是 Segm. definition (数据段定义)名,如: E1FIKPF -BUKRS ,而不是 E2FIKPF006-BUKRS 之类的。

    IDoc Type 中的数据段类型实质会在数据字典中的创建相应的结构,如上图数据段类型 E1FIKPF

    18.4.2. IDoc 定义( WE30

    IDoc 类型中定义了数据段以及数据段的 层级和次序

    标准 SAP 系统提供的 IDoc 类型称为基本类型( Basic type ),该类型可以通过 IDoc 扩展( Extention )进行调整,即在 SAP IDoc 类型结构的基础上增加新的数据段或者在数据段中增加新字段

    如果是自己完全创建一个新的类型 不扩展任何类型 则选择 Basic Type ”, 否则如果是要从已存在类型来扩展出新的类型时 需要选择 Extension ”, 并且需要指定 basic type

    18.4.3. 自定义 IDoc 发送与接收实例

    该实例使用 800 发送端向 810 接收端发送 Idoc 进行实验

    18.4.3.1. 发送端 800 outbound )配置

    1 、创建 segment WE31

    segment 类似于创建 XML 的节点及节点属性, 即定义 XML 文档中的节点及节点属性

    2 、创建 IDOC Type WE30

    创建 IDOC Type ,定义结点间的相互逻辑关系

    先输入 YPOIDOC ,然后点击创建,紧跟着选择 create new

    在主界面中,先点击创建按钮,将 YPOHEAD 添加,设置 Mandatory seg 打勾, min 1 max 1 ,代表我们每个 IDOC 仅包含一张采购订单

    然后在 YPOHEAD 下添加 YPOITEM ,同样的 Mandatory seg 打勾, min 1 max 99999

    3 、创建 Message Type WE81

    先切换到编辑状态,然后点击 New Entries ,输入 YPO_MESS_TYPE 即可。

    4 、关联 Message Type IDOC Type WE82
    5 、创建接收端 RFC Destination SM59

    创建一个到接收端 810 物理连接 ,由于是该实例是在同一个 SAP 系统内部进行实验,所以“连接类型”选择的是 ABAP Connection 连接,名为 ZTO810

    6 、创建到收端的端口( WE21

    注:这里讲的端口不是单纯的指定 Socket 端口号,而是指连接到 RFC 目标系统的统称,包括 IP 、端口等信息,实质上是在 SM59 创建的物理连接基础之上创建的另一种逻辑名而已

    基于上面第 5 步创建的 RFC Destionation ,创建端口 Port ,类型选 TRANSACTIONAL RFC ,名为 TO810PORT RFC destination 则填写 ZTO810

    7 、创建发送端 Logical System 并分配( SALE

    为发送端 800 创建逻辑系统 Z800LS

    并将逻辑系统分配到发送端 800

    注:这里不需要为本端( Client 800 )在本端创建发送端逻辑系统的合作和伴配置文件,但需要在接收端 810 中配置

    8 、创建接收端 Logical System SALE

    为接收端 810 创建逻辑系统 Z810LS ,这将在下一步创建到接收端的合作伙伴配置文件 Partner profile WE20 )时用到:

    但与上面创建发送端逻辑系统不一样的是,在发送端系统 800 中是不需要将它分配给 Client 810 ,而分配操作是在接收端 810 中进行的,这一分配操作请参考后面接收端( Inbound )配置章节

    9 、创建接收端合作和伴配置文件 Partner profile WE20

    合作和伴配置文件将 Message Type 消息类型 receiver port RFC 目标端口 IDoc 类型 关联起来

    创建一个 patner no Z810LS 的合作和伴配置文件,该配置文件描述了将 IDoc 发往何处

    上图中 合作伙伴编号填上一步创建的接收端逻辑系统 ,类型选择 LS Ty. 选择是“用户”类型,代理人为本系统(发送端 800 )中的用户,如果在 Idoc 发送过程中出现什么问题,会向此用户发送邮件。 当上面信息填好后 点击保存 保存之后才可以对 Outbound parmtrs 进行设置

    然后 点击 outbound 下方的加号 创建一个 outbound parameter Message Type YPO_MESS_TYPE receiver port TO810PORT output mode 选择 Transfer idoc immed. Basic Type 填写 YPOIDOC 保存即可

    10 、通过 ABAP 程序发送 IDOC

    程序的思路就是,把每个 IDOC 节点按字符串形式逐个添加,而字符串的添加次序自然也体现了 IDOC 节点间的逻辑关系。代码如下:


    DATA : ls_pohead TYPE ypohead , "IDoc 数据段:头
    ls_poitem
    TYPE ypoitem , "IDoc 数据段: Item
    ls_edidc
    TYPE edidc , " IDoc 的控制记录
    lt_edidc
    TYPE TABLE OF edidc ,
    lt_edidd
    TYPE TABLE OF edidd WITH HEADER LINE . " IDoc 的数据记录
    CLEAR ls_edidc .
    * 系统根据下面 4 行即可与 WE20( 合作和伴配置文件 ) 设置关联起来
    ls_edidc
    - mestyp = 'YPO_MESS_TYPE' . "Message Type
    ls_edidc
    - idoctp = 'YPOIDOC' . "IDOC Type
    ls_edidc
    - rcvprn = 'Z810LS' . "partner Number of Recipient 接收方合作伙伴
    ls_edidc
    - rcvprt = 'LS' . "partner Type of Receiver 接收方类型为逻辑系统

    * 添加 IDOC 节点
    CLEAR lt_edidd .
    lt_edidd
    - segnam = 'YPOHEAD' . " 头节点
    lt_edidd
    - dtint2 = 0 .
    CLEAR ls_pohead .
    ls_pohead
    - ebeln = '4001122334' . " 采购单号
    ls_pohead
    - bukrs = '1000' . " 公司代码
    ls_pohead
    - bedat = '20090630' . " 日期
    lt_edidd
    - sdata = ls_pohead . " 节点内容 ls_pohead 结构中的数据最后被拼接成字符串再赋值给 lt_edidd-sdata ,最大长度不能超过 1000
    APPEND lt_edidd .

    CLEAR lt_edidd .
    lt_edidd
    - segnam = 'YPOITEM' . "Item 节点
    lt_edidd
    - dtint2 = 0 .
    CLEAR ls_poitem .
    ls_poitem
    - ebeln = '4001122334' . " 采购单号
    ls_poitem
    - ebelp = '0001' . "Item 行号
    ls_poitem
    - matnr = '000000000000004527' . " 物料号
    ls_poitem
    - menge = '3' . " 数量
    ls_poitem
    - meins = 'ST' . " 单位
    lt_edidd
    - sdata = ls_poitem .
    APPEND lt_edidd .

    CLEAR lt_edidd .
    lt_edidd
    - segnam = 'YPOITEM' . "Item 节点
    lt_edidd
    - dtint2 = 0 .
    CLEAR ls_poitem .
    ls_poitem
    - ebeln = '4001122334' . " 采购单号
    ls_poitem
    - ebelp = '0002' . "Item 行号
    ls_poitem
    - matnr = '000000000000009289' . " 物料号
    ls_poitem
    - menge = '5' . " 数量
    ls_poitem
    - meins = 'M' . " 单位
    lt_edidd
    - sdata = ls_poitem .
    APPEND lt_edidd .
    CALL FUNCTION ' MASTER_IDOC_DISTRIBUTE ' " 发送 IDoc
    EXPORTING
    master_idoc_control
    = ls_edidc "IDoc 控制记录
    TABLES
    communication_idoc_control
    = lt_edidc " 接收:用来接收 IDoc 发送情况
    master_idoc_data
    = lt_edidd "IDoc 数据记录
    EXCEPTIONS "
    error_in_idoc_control
    = 1
    error_writing_idoc_status
    = 2
    error_in_idoc_data
    = 3
    sending_logical_system_unknown
    = 4
    OTHERS = 5 .
    IF sy - subrc <> 0 .
    MESSAGE ID sy - msgid TYPE sy - msgty NUMBER sy - msgno
    WITH sy - msgv1 sy - msgv2 sy - msgv3 sy - msgv4 .
    ELSE .
    COMMIT WORK .
    WRITE : 'Idoc sent:' .
    LOOP AT lt_edidc INTO ls_edidc .
    NEW-LINE .
    WRITE : 'Idoc number is' , ls_edidc - docnum ,
    '; receiver partner is' , ls_edidc - rcvprn ,
    '; sender partner' , ls_edidc - sndprn .
    ENDLOOP .
    ENDIF .

    选中消息,并点击“处理”按钮后,该消息就会发往目的客户端 810 ,状态从准备发送到成功发送:

    发送的数据可以选择数据记录节点来查看:

    以上都是在发送端 800 进行的,下面登录到接收端 810 去看看 IDoc 接收情况:

    由于在接收端 810 未配置到发送端 800 的合作伙伴配置文件,所以出错。下面章节在接收端 810 中进行发送端 800 的相关配置

    18.4.3.2. 接收端 810 Inbound )配置

    由于该实例是在同一服务器的同一实例中进行的,又由于 Segment IDoc Type Message Type 这些都是跨 Client 的,所以上面 1 2 3 4 步就不需要在 810 端再次配置了,这些是共享的( 但如果不是在同一服务器上,则需要像上面那样进行配置

    1 、创建发送端 RFC Destination SM59

    创建到发送端 810 物理连接

    2 、创建发送端的端口( WE21

    基于上面创建的 RFC Destionation ,创建端口 Port ,类型选 TRANSACTIONAL RFC ,名为 FRM800PORT RFC destination 则填写上面创建的 RFC 远程目标 ZFROM800

    3 、将接收端 Logical System 分配到 Client 810 SALE

    如果是在不同的服务器中,则接收端需要像在发送端那样:发送端逻辑系统 Z800LS 与接收端逻辑系统 Z810LS 都需要被创建,并且还需要将接收端逻辑系统 Z810LS 分配到 Client 810 ,并且还需要以发送端逻辑系统 Z800LS 为基础创建发送端合作伙伴配置文件

    由于是在同一服务器的同一实例中,所以在发送端中创建的发送端逻辑系统 Z800LS 与接收端逻辑系统 Z810LS 在此端是通用共享,这里不需要再次创建( out bound 中已经创建了这两个逻辑系统了 ),但( outbound 章节中并未分配到具体的 Client Z810LS 逻辑系统没有分配到相应的 Client ,这一步操作需要在接收端完成,所以需要在此进行分配:

    4 、创建入站处理函数

    创建一个 function Y_IDOC_PO_PROCESS.

    IDOC 设置完毕之后, SAP 可以自动调用该 Funtion Module 处理 IDOC ,所以这个函数的 参数接口是有规范 的,可以从 IDOC_INPUT_BBP_IV 这些标准函数拷贝参数接口部分:

    " ypohead\ypoitem 为上面定义的 IDoc 类型
    DATA : ls_chead TYPE ypohead ,
    ls_citem
    TYPE ypoitem .
    CLEAR idoc_contrl .
    READ TABLE idoc_contrl INDEX 1 .
    IF idoc_contrl - mestyp <> 'YPO_MESS_TYPE' .
    RAISE wrong_function_called .
    ENDIF .
    LOOP AT idoc_contrl .
    LOOP AT idoc_data WHERE docnum = idoc_contrl - docnum .
    CASE idoc_data - segnam .
    WHEN 'YPOHEAD' .
    " 直接将字符赋值给结构,赋值过程中会按照结构中的字段长度来划分各字段
    ls_chead
    = idoc_data - sdata .
    WRITE : / 'Head' , ls_chead .
    WHEN 'YPOITEM' .
    ls_citem
    = idoc_data - sdata .
    WRITE : / 'Item' , ls_citem .
    WHEN OTHERS .
    ENDCASE .
    ENDLOOP .
    " 根据数据处理情况设置当前 IDoc 处理的状态
    IF 1 = 0 .
    CLEAR idoc_status .
    idoc_status
    - docnum = idoc_contrl - docnum . " 当前正处理的 IDoc
    idoc_status
    - status = '53' . "IDOC 处理成功
    APPEND idoc_status .
    ELSE .
    CLEAR idoc_status .
    idoc_status
    - docnum = idoc_contrl - docnum .
    idoc_status
    - status = '51' . "IDOC 不成功
    idoc_status
    - msgty = 'E' . " 错误信息
    idoc_status
    - msgid = 'YMSG' .
    idoc_status
    - msgno = '001' .
    APPEND idoc_status .
    ENDIF .
    ENDLOOP .
    ENDFUNCTION .

    5 注册入站处理函数( BD51

    填入函数名 Y_IDOC_PO_PROCESS Input Type 1

    6 、将入站函数与 IDOC Type/Message Type 关联( WE57

    Function Module 输入 Y_IDOC_PO_PROCESS ,其下的 Type 填写 F IDOC Type 下的 Basic Type 填写 YPOIDOC Message Type 填写 YPO_MESS_TYPE Direction 填写 2(Inbound)

    7 、创建入站处理代码 Inbound Process Code WE42

    Process Code 输入 YPC_PO ,在 Option ALE 下选择 Processing with ALE service ,在 Processing Type 下选择 function module ,保存后,在随后的窗口中,输入 Inbound Module Y_IDOC_PO_PROCESS

    8 、创建发送端合作和伴配置文件 Partner profile WE20

    由于在发送端 800 中已创建了发送端逻辑系统 Z800LS ,所以在此端不需要在创建(发送端逻辑系统 Z800LS ),只是需要以此为基础创建和伴配置文件,保存后,点击加号增加进站参数:

    9 、测试 BD87

    在接收端 810 使用 BD87 ,登录进去后,会看到发送端发送过来的 IDoc ,但由于先前还没有配置发送伙伴配置文件,所以失败了:

    现在在入站处理理函数中设置断点:

    再执行 BD87 事务,继续处理出错的消息,最后发现处理成功:

    调试程序时,发现数据也传递过来了:

    19. 数据共享与传递

    19.1. 程序调用 、会话、 SAP/ABAP 内存 关系

    “被调用程序插入型”是指:主调用程序 (calling program) 并不结束,当遇到 Link 语句时,会去执行被调用程序 (called program) ,当被调用程序结束后, 调用程序回到调用处继续执行 ;

    “调用程序中止型”是指:调用程序 (calling program) 当遇到 Link 语句时会立即中止,然后去执行被调用程序 (called program) ,即使被调用程序执行完毕后,也 不会返回到主调程序 的调用处继续执行 ;

    SUBMIT <program> AND RETURN 中断 不终止)当前运行的程序,启动新的被调用程序 <program> ,当 <program> 运行完后,控制权又 返回 到被中断的调用程序,继续执行

    TRANSACTION
    <TCode> :可以插入一个具有事务代码的 ABAP 程序, 中断 当前运行的程序,待被调程序执行完后,再继续执行主调程序

    SUBMIT <program> 结束 当前运行的程序,启动新的被调用程序 <program>

    LEAVE TO TRANSACTION <TCode> 结束 当前运行的程序,并启动由事务码 <TCode> 指定的 ABAP 程序。在程序中使用该语句的效果等同于用户直接在命令行输入“ /n<TCode> ”并执行的效果。

    使用 LEAVE TO TRANSACTION <TCode> 调用另一程序时, 可以在主调程 序中使用 SET PARAMETER ID 将被传递 的数据存储到 SAP memory 中,在被调 用的 Tcode 中可以使用 GET PARAMETER ID 来获取,另外, 也可以为被调用 Tcode 屏幕参 数的 data element 设置 parameter ID 这样会自动的获取与存储该屏幕参数 (只设置 Data Element 中的 Parameter ID 是不起作用的,请看后面)

    只有 LEAVE TO TRANSACTION <TCode> 不能使用 ABAP MEMORY 共享数据 (其它调用方式均可以),而应该使用 SAP Memory ;注, SUBMIT <program> 调用后虽然不会返回到主调程序,但也是可以通过 ABAP memory 来传递数据

    异步的 Link Program: 当遇到这种 Function 时,将会 重新打开一个 external session (外部会话,窗口会话) , 它并行地、独立于当前的 external session.

    三种会话:

    一个 External session (外部会话,一个窗口就是一个外部会话)对应着一个 ABAP MEMORY 内存,所以同一个 Window 中的所有 Internal session (内部会话,一个程序的调用就是一个内部会话)共用着同一个 ABAP MEMORY ,同一 user logon session (用户终端会话,登录就会产生)中不同的 window 共用一个 SAP MEMORY 内存:

    同一窗口中的不同程序共享同一个 ABAP memory ,同一用户的不同窗口之间共享同一个 SAP memory

    当系统用户登录后,就会产生一个与该用户对应的“用户终端会话”( User Terminal Session ),用户可以开辟很多外部会话(即窗口,最多可打开 6 个窗口),在每个窗口会话(外部会话)中又可以先后执行多个 ABAP 程序(调用一个程序就会产生一个内部会话)

    每一个外部会话都有一个叫 ABAP Memory 的内存区域,在该会话内部可以通过“ EXPORT TO MEMORY ”和 IMPORT FROM MEMORY 在该会话中的多个运行程序之间(即多个内部会话之间)进行数据共享,并可使用“ FREE MEMORY ID ”语句将共享数据从 ABAP Memory 中清除

    Memory
    是所有的会话(指同一用户登录会话中)都可以访问的内存区域 , 因此可以通过“ SET PARAMETER ”与“ GET PARAMETER ”把数据保存在 SAP 内存中进行数据共享

    所以 ,在同一个 external session Link program 我们使用 ABAP Memory 进行传递数据;而在不同的 external session 间的 Link program 我们使用 SAP Memory 进行数据传递。

    19.2. ABAP Memory 数据共享

    19.2.1. EXPORT

    EXPORT {p1 = dobj1 p2 = dobj2 ... } | {p1 FROM dobj1 p2 FROM dobj2 ... } | ( ptab ) TO
    | { MEMORY ID id }
    | { DATABASE dbtab ( ar ) [ FROM wa] [ CLIENT cl] ID id }
    | {
    SHARED MEMORY dbtab ( ar ) [ FROM wa] [ CLIENT cl] ID id }
    | {
    SHARED BUFFER dbtab ( ar ) [ FROM wa] [ CLIENT cl] ID id }

    1. {p1 = dobj1 p2 = dobj2 ... } {p1 FROM dobj1 p2 FROM dobj2 ... } 的意义一样,只是写法不一样, dobj1 dobj2… 变量将会以 p1 p2… 名称存储到内存或数据库中。 p1 p2… 名称随便取,如果 p1 p2… 与将要存储的变量名相同时,只需写变量名即可,即等号与 FROM 后面可以省略。 p1 p2… 这些名称必须与 IMPORT 语句中相一致,否则读取不出

    2. ( ptab ) :为动态指定需要存储的变量, ptab 内表结构要求是这样的:只需要两列,列名任意,但类型需要是字符型;第一列存储如上面的 p1 p2… 名称,第二列为上面的 dobj1 dobj2… 变量,如果变量与名称相同,则也可以像上面一样,省略第二列的值。两列的值都必需要大写,实例如下:

    TYPES : BEGIN OF tab_type ,
    para
    TYPE string , " 列的名称任意,类型为字符型
    dobj
    TYPE string ,
    END OF tab_type .
    DATA : text1 TYPE string VALUE `TXT1` ,
    text2
    TYPE string VALUE `TXT2` ,
    line  TYPE tab_type ,
    itab
    TYPE STANDARD TABLE OF tab_type .
    line - para = 'P1' . " 值都需要大写
    line - dobj = 'TEXT1' . " 值都需要大写
    APPEND line TO itab .
    line - para = 'P2' .
    line - dobj = 'TEXT2' .
    APPEND line TO itab .
    EXPORT ( itab ) TO MEMORY ID 'TEXTS' .
    IMPORT p1 = text2 p2 = text1 FROM MEMORY ID 'TEXTS' .
    WRITE : / text1 , text2 . "TXT2 TXT1
    CLEAR : text1 , text2 .
    IMPORT ( itab ) FROM MEMORY ID 'TEXTS' .
    WRITE : / text1 , text2 . "TXT1 TXT2

    3. MEMORY ID :将变量存储到 ABAP MEMORY 内存( 同一用户的同一窗口 Session

    4. DATABASE :将变量存储到数据库中; dbtab 为簇数据库表的名称(如系统提供的标准表 INDX ); ar 的值为区域 ID ,它将数据库表的行分成若干区域,它必须被直接指定,且值是两位字符,被存储到簇数据库表中的 RELID 字段中; id 的值会存储到簇数据表中的 RELID 字段的下一用户自定义字段中:

    TYPES : BEGIN OF tab_type ,
    col1
    TYPE i ,
    col2
    TYPE i ,
    END OF tab_type .
    DATA : wa_indx TYPE demo_indx_table ,
    wa_itab
    TYPE tab_type ,
    itab
    TYPE STANDARD TABLE OF tab_type .
    WHILE sy - index < 100 .
    wa_itab
    - col1 = sy - index .
    wa_itab
    - col2 = sy - index ** 2 .
    APPEND wa_itab TO itab .
    ENDWHILE .
    wa_indx
    - timestamp = sy - datum && sy - uzeit .
    wa_indx
    - userid = sy - uname .
    EXPORT tab = itab TO DATABASE demo_indx_table ( sq ) FROM wa_indx ID 'TABLE' .

    数据导入请参考后面 IMPORT 中的实例

    TABLES : indx .
    DATA : BEGIN OF i_tab OCCURS 100 ,
    col1
    TYPE i ,
    col2
    TYPE i ,
    END OF i_tab .
    DO 3000 TIMES .
    i_tab
    - col1 = sy - index .
    i_tab
    - col2 = sy - index ** 2 .
    APPEND i_tab .
    ENDDO .
    indx
    - aedat = sy - datum .
    indx
    - usera = sy - uname .
    indx
    - pgmid = sy - repid .
    " 省略了 FROM 选项,因为已经使用 TABLES indx 语句定义了名为 indx 的结构变量了
    "Export 时会自动将表工作区 indx 变量中的用户字段存储到簇数据库表中
    EXPORT i_tab TO DATABASE indx ( HK ) ID 'Key' .

    WRITE : ' SRTF2' , AT 20 'AEDAT' , AT 35 'USERA' , AT 50 'PGMID' .
    ULINE .
    " 注:下面完全可以使用 IMPORT FROM DATABASE TO wa 语句来读取用户区字段
    SELECT * FROM indx WHERE relid = 'HK' AND srtfd = 'Key' .
    WRITE : / indx - srtf2 UNDER 'SRTF2' ,
    indx
    - aedat UNDER 'AEDAT' ,
    indx
    - usera UNDER 'USERA' ,
    indx
    - pgmid UNDER 'PGMID' .
    ENDSELECT .

    数据导入请参考后面 IMPORT 中的实例

    SRTF2             AEDAT          USERA          PGMID

    2011.10.12     ZHENGJUN       YJZJ_TEST2

    2011.10.12     ZHENGJUN       YJZJ_TEST2

    2011.10.12     ZHENGJUN       YJZJ_TEST2

    2011.10.12     ZHENGJUN       YJZJ_TEST2

    2011.10.12     ZHENGJUN       YJZJ_TEST2

    5. SHARED MEMORY/BUFFER :将数据存储到 SAP 应用服务器上的内存中, 可共同一服务上的所有程序访问 两种的作用是一样的,最大不同是在数据达到最大内存限制时的处理方式不同:最大内存限制值分别是通过 rsdb/esm/buffersize_kb (SHARED MEMORY) rsdb/obj/buffersize (SHARED BUFFER) 来设置的,当内存占用快满时, SHARED MEMORY 必须通过 DELETE FROM SHARED MEMORY 来手动清理,而 SHARED BUFFER 会自动删除很少被使用到的数据(当然也可以通过 DELETE FROM SHARED BUFFER 手动及时的删除不用的数据)

    6. FROM wa wa 工作区类型可以参照簇数据库 dbtab 类型,也可定义成 只含 有用户数据字段的结构, 它是用来设置簇数据库表中 SRTF2 CLUSTR 两个字段之间的用户数据字段 参见簇数据表图中的编号为 5 的用户数据 的值 ,然后在 Export 时将相应的字段存储到 SRTF2 字段与 CLUSTR 字段间的相应字段中去 。如果使用“ TABLES dbtab. ”定义语句,可以省略“ [FROM wa] ”,也会默认将其存储到数据库表中,但 如果没有“ TABLES dbtab. ”这样的定义语句,也没有“ [FROM wa] ”选项时,将不会有数据存储到簇数据库表中的 用户字段 中去

    7. CLIENT cl :默认为当前客户端,存储到簇数据库表中的 MANDT 字段中

    19.2.2. IMPORT

    IMPORT {p1 = dobj1 p2 = dobj2 ... } | {p1 TO dobj1 p2 TO dobj2 ... } | ( ptab ) FROM
    | { MEMORY ID id }
    | { DATABASE dbtab ( ar ) [ TO wa] [ CLIENT cl] ID id }
    | {
    SHARED MEMORY dbtab ( ar ) [ TO wa] [ CLIENT cl] ID id }
    | {
    SHARED BUFFER dbtab ( ar ) [ TO wa] [ CLIENT cl] ID id }

    从簇数据表中读取数据,各项参数与 EXPORT 是一样的,请参考 EXPORT 各项解释

    TYPES : BEGIN OF tab ,
    col1
    TYPE i ,
    col2
    TYPE i ,
    END OF tab .
    DATA : wa_indx TYPE demo_indx_table ,
    wa_itab
    TYPE tab ,
    itab
    TYPE STANDARD TABLE OF tab .
    IMPORT tab = itab FROM DATABASE demo_indx_table ( sq ) TO wa_indx ID 'TABLE' .
    WRITE : wa_indx - timestamp , wa_indx - userid .
    ULINE .
    LOOP AT itab INTO wa_itab .
    WRITE : / wa_itab - col1 , wa_itab - col2 .
    ENDLOOP .

    TABLES indx .
    DATA : BEGIN OF jtab OCCURS 100 ,
    col1
    TYPE i ,
    col2
    TYPE i ,
    END OF jtab .
    " 注意: i_tab 的名称不能是其他的,必须与 EXPORT 语句中的分类存储标签名一样。如果 i_tab 本身又是一个 jtab 类型的内表,则 TO 后面的 jtab 可以省略
    IMPORT i_tab TO jtab FROM DATABASE indx ( hk ) ID 'Key' .
    " 注:在该程序中并没有明显的为 indx 工作区设置值,但由于使用了 TABLES indx. 语句定义了与 indx 簇数据库表同名的结构变量,所以上面 IMPORT 会默认加上使用 TO indx 选项
    WRITE
    : / 'AEDAT:' , indx - aedat ,
    /
    'USERA:' , indx - usera ,
    /
    'PGMID:' , indx - pgmid .
    SKIP .
    WRITE 'JTAB:' .
    LOOP AT jtab FROM 1 TO 5 .
    WRITE : / jtab - col1 , jtab - col2 .
    ENDLOOP .

    AEDAT: 2011.10.12

    USERA: ZHENGJUN

    PGMID: YJZJ_TEST2

    JTAB:

    1           1

    2           4

    3           9

    4          16

    5          25

    19.2.3. DELETE

    DELETE FROM { { MEMORY ID id}
    | {
    DATABASE dbtab ( ar ) [ CLIENT cl] ID id}
    | { SHARED MEMORY dbtab ( ar ) [ CLIENT cl] ID id}
    | {
    SHARED BUFFER dbtab ( ar ) [ CLIENT cl] ID id} } .

    用来清理 EXPORT 语句的存储的数据。如果是针对 MEMORY ID ,则还可以使用 FREE MEMORY ID id DELETE FROM MEMORY ID id 作用一样

    DATA : id    TYPE c LENGTH 4 VALUE 'TEXT' ,
    text1
    TYPE string VALUE 'Tina' ,
    text2
    TYPE string VALUE 'Mike' .
    EXPORT p1 = text1 p2 = text2 TO SHARED BUFFER demo_indx_table ( xy ) ID id .
    IMPORT p1 = text2 p2 = text1 FROM SHARED BUFFER demo_indx_table ( xy ) ID id .
    DELETE FROM SHARED BUFFER demo_indx_table ( xy ) ID id .
    " 此语句会执行后, sy-subrc 返回 4
    IMPORT p1 = text2 p2 = text1 FROM SHARED BUFFER demo_indx_table ( xy ) ID id .

    19.3. SAP MEMORY 数据共享

    19.3.1. PARAMETERS/SELECT-OPTIONS 选项 MEMORY ID

    Data Element 中的 Parameter ID 的作用只是在屏幕设计时,起到一个 辅助 填充的作用,例如:当给某个屏幕字段命令时,如果这个字段的名称为“ 表名(或结构) - 字段名 形式时,回车时系统会提示:

    当点击 Yes 后,该字段的 Parameter ID 属性会自动的设置为 MARA-MATNR 字段所对应 Data Element 所设置的 Parameter ID MAT ,另外, From dict. 也会自动被钩上,但时, SET Parameter GET Parameter 没有自动钩上,如果需要通过 SAP Memory 传递值,则还需要将这两个手动钩上(如上图)

    Data Element 中的 Parameter ID 对选择屏幕是没有任何作用的,如下面的语句 不能 用来在 SAP Memory 中传递值:

    PARAMETERS : m TYPE mara - matnr .

    除非使用 MEMORY ID 选项才起作用:

    PARAMETERS : m TYPE mara - matnr MEMORY ID mat .

    所以选择屏幕中的参数选项 MEMORY ID 的作用就等同于对话屏幕中的 SET/GET Parameter ,它们是作用是相同的,只不过一个用于选择屏幕中,一个用于屏幕计中。

    REPORT ztest_sap_memory_1 .
    * PARAMETERS: p_spa TYPE zdele_1 MEMORY ID zpara1 . 或者是下面这种写法,根本不用参照 zdele_1 这个 Data Element ,因为 Data Element 中的 Parameter ID 对选择屏幕参数没有任何意义,所以只需要通过 MEMORY ID 选项动态的创建 Parameter ID 即可 , 不需要先选通过 SE80 SM30 来事先创建好
    PARAMETERS : p_spa ( 10 ) MEMORY ID
    zpara1 .
    START-OF-SELECTION .
    LEAVE TO TRANSACTION ' zsap_mem_2 ' .

    zsap_mem_2 事务码对应的报表程序为:

    DATA : mem ( 10 ).
    GET PARAMETER ID 'ZPARA1' FIELD mem .

    19.3.2. GET/SET PARAMETER I D

    除了通过上面 MEMORY ID 同一用户下不同窗口之间 自动传递选择屏幕参数外,也可以通过以下语句来手动传递屏幕参数以及非屏幕参数:

    REPORT ztest_sap_memory_3 .
    PARAMETERS : p_spa TYPE zdele_1 .
    START-OF-SELECTION .
    SET PARAMETER ID ’ZPARA1’ FIELD p_spa .
    LEAVE TO TRANSACTION ’ztest_sap_memory_4’ .

    使用 SE91 为下面 ztest_sap_memory_4 程序创建了 Tcode 为: ztest_sap_memory_4

    REPORT ztest_sap_memory_4 .
    PARAMETERS : p_spa TYPE zdele_1 .
    AT SELECTION-SCREEN OUTPUT . "
    GET PARAMETER ID ’ZPARA1’ FIELD p_spa .

    GET/SET PARAMETER ID 还可以与对话屏幕 Parameter ID 一起使用 ,如通过 MM02 输入物料后,可以在程序中直接读取:

    DATA : material TYPE mara - matnr .
    GET PARAMETER ID 'MAT' FIELD material .
    WRITE : / material .

    19.4. DATABASE

    SHARED BUFFER 并不访问(存储)数据库,而要存放在数据库就应该用 DATABASE

    EXPORT DATABASE 与普通数据库操作的不同之处是,它适合大数据量的操作,系统自动将其拆分成多条记录并存储到数据库中, 比如图片或文档(甚至是程序中的某个内表,请参考后面的实例) ,而用 IMPORT DATABASE 的过程则相反,系统将把这些条相关记录又自动组合起来成为一个整体。

    如果要自定义 INDX 这样的表,需要按以下表结构顺序来定义:

    该数据库表结构要求:

    、可以有也可以无 MANDT 字段

    、除开第一个字段 MANDT (如果有的情况下),下一个字段必须是 RELID ,类型为 CHAR 2 ,它是用来存储 area ID ,系统会根据用户在使用 EXPORT 语句保存数据时指定的 area ID 来填充它。

    、紧接下一个字段是一个任意长度(根据自己的需要定)的 CHAR 字段,名字也可以是随便取的,该字段用为主键的一部分来使用,该字段的值也是在使用 EXPORT 语句保存数据时使用 ID 选项指定的值。

    、下一个字段的名字必须是 SRTF2 ,类型为 INT4 ,用来 存储数据行号 (大数据对象——如图片、文件、程序中的内表对象等,要分成多行来存储)。由于某个数据可能很大,需要多行来存储,理念是可能达到 2**31 行,该字段会自动的由系统填充。

    、在 SRTF2 字段的后面,你可以包括任意数量及类型的数据字段, 这些字段是用来管理大对象的相应信息 (如文件名、文件类型、创建者等),当你在保存数据时系统不会自动的填充这些字段,所以在保存这些字段时,需要通过一个结构传递需要存储的值(即 EXPORT 语句中的 From 选项所带的结构)。

    、倒数第二个字段的名必须为 CLUSTR ,类型为 INT2 ,它 存储了最后一个字段 CLUSTD 所存储数据的长度 (字节数),在使用 EXPORT 语句保存数据时系统会自动填充

    、最后一个字段的名必须是 CLUSTD ,并且数据类型为 LRAW ,其长度表示能最大存储多少个字节的内容,如果大数据对象很大(一行存储不下时),会分成多行来存储,行号就存储在前面的 SRTF2 字段中。

    19.4.1. 将文件存入表中

    注意:上面这个表中的 SRTFD 实际上没用上,因为 Export 时, ID 选项的值实质上存储到了它前面的 ZZKEY 中了,所以可以去掉这个字段(一般会留名为 SRTFD 字段而去掉 ZZKEY 字段)。

    PARAMETERS : p_file TYPE string OBLIGATORY .
    AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file .
    CALL FUNCTION 'WS_FILENAME_GET'
    EXPORTING
    def_filename
    = '*.*'
    def_path
    = 'c:\'
    mask = ',*'
    mode = 'O'
    title = 'File select'
    IMPORTING
    filename
    = p_file .
    START-OF-SELECTION .
    DATA : il_data LIKE solix OCCURS 0 WITH HEADER LINE ,
    l_len
    TYPE i .
    **Upload file
    REFRESH : il_data .
    CLEAR l_len .
    CALL FUNCTION 'GUI_UPLOAD'
    EXPORTING
    filename
    = p_file
    filetype
    = 'BIN'
    IMPORTING
    filelength
    = l_len
    TABLES
    data_tab
    = il_data . "ABAP 没有二进制类型, X 类型代替
    EXPORT il_data TO DATABASE indx ( YY ) ID 'ZZZ' .

    上面是直接将读取到的文件的二进制数据内表存储到簇数据库表中,我也也可通过 SCMS_BINARY_TO_XSTRING 函数将读取的二进制数据内表拼接成只有一行的二进制串,然后再存储这个被转换后的二进制串也可:

    PARAMETERS : p_file TYPE string OBLIGATORY ,
    p_id
    LIKE ybc_file - zzkey OBLIGATORY ,
    p_ftype
    LIKE ybc_file - mimetype OBLIGATORY ,
    p_fname
    LIKE ybc_file - filename OBLIGATORY .
    AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file .
    CALL FUNCTION 'WS_FILENAME_GET'
    EXPORTING
    def_filename
    = '*.*'
    def_path
    = 'c:\'
    mask = ',*'
    mode = 'O'
    title = 'File select'
    IMPORTING
    filename
    = p_file .
    START-OF-SELECTION .
    DATA : il_data LIKE solix OCCURS 0 WITH HEADER LINE ,
    l_len
    TYPE i .
    **Upload file
    REFRESH : il_data .
    CLEAR l_len .
    CALL FUNCTION 'GUI_UPLOAD'
    EXPORTING
    filename
    = p_file
    filetype
    = 'BIN'
    IMPORTING
    filelength
    = l_len
    TABLES
    data_tab
    = il_data . "X 类型内表

    CHECK il_data[] IS NOT INITIAL .
    **Convert data
    DATA : l_xstr TYPE xstring .
    CLEAR l_xstr .
    " 将内表以 X 类型拼接成 XString 字符串
    CALL FUNCTION ' SCMS_BINARY_TO_XSTRING '
    EXPORTING
    input_length
    = l_len
    IMPORTING
    buffer = l_xstr
    TABLES
    binary_tab
    = il_data .
    **Save data
    "wl_file 用于填充 ybc_file 表中 非规定字段
    DATA : wl_file LIKE ybc_file .
    wl_file
    - uname = sy - uname .
    wl_file
    - aedtm = sy - datum .
    wl_file
    - pgmid = sy - cprog .
    wl_file
    - mimetype = p_ftype .
    wl_file
    - filename = p_fname .
    DATA : l_answer TYPE c .
    EXPORT l_xstr = l_xstr TO DATABASE ybc_file ( bc ) FROM wl_file ID p_id .

    19.4.2. 从表中读取文件

    DATA : il_data LIKE solix OCCURS 0 .
    IMPORT il_data FROM  DATABASE indx ( YY ) ID 'ZZZ' .
    CALL METHOD cl_gui_frontend_services => gui_download
    EXPORTING
    " bin_filesize            = l_bytes
    filename
    = 'c:\1.jpg'
    filetype
    = 'BIN'
    CHANGING
    data_tab
    = il_data .

    与存储文件一样,如果存储的是拼接好的二进制串,则要使用 SCMS_XSTRING_TO_BINARY 函数来还原后再下载:
    PARAMETERS : p_key LIKE ybc_file - zzkey .
    DATA : l_xstr TYPE xstring .
    IMPORT l_xstr = l_xstr FROM DATABASE ybc_file ( bc ) ID p_key .
    DATA : l_xstring TYPE xstring ,
    l_xcnt
    TYPE i ,
    l_bytes
    TYPE i .
    TYPES : hex512 ( 512 ) TYPE x .
    DATA : tab_xstring TYPE TABLE OF hex512 WITH HEADER LINE .
    " Xstring X 类型视图存储到内表中
    CALL FUNCTION ' SCMS_XSTRING_TO_BINARY '
    EXPORTING buffer = l_xstr
    TABLES binary_tab = tab_xstring .

    DATA : l_ftype LIKE yhr_attach - mimetype ,
    l_fname
    LIKE yhr_attach - filename .
    " 二进内容需使用 IMPORT 语句中读取,但其他字段除了可使用 IMPORT 语句的 TO 选项来直接读取外,还可以通过 SQL 直接查询
    SELECT SINGLE mimetype filename INTO ( l_ftype , l_fname ) FROM ybc_file
    WHERE relid = 'BC' AND zzkey = p_key .
    DATA : l_file_name TYPE string .
    CONCATENATE 'C:\' l_fname '.' l_ftype INTO l_file_name .
    CALL METHOD cl_gui_frontend_services => gui_download
    EXPORTING
    bin_filesize
    = l_bytes
    filename
    = l_file_name
    filetype
    = 'BIN'
    CHANGING
    data_tab
    = tab_xstring[] .

    19.5. JOB 间数据传递

    有两种方式:

    l SHARED MEMORY/SHARED BUFFER

    l 通过 Cluster Databases

    20. 拾遗

    20.1. Function 调用

    20.1.1. 更新 FM LUW

    CALL FUNCTION update_function IN UPDATE TASK 直到 Commit Work 才运行

    主要用于本地更新(非远程 RFC 调用,如果是远程调用,则采用事务性 RFC 调用方式: IN  BACKGROUND  TASK

    20.1.2. RFC 函数:远程调用

    20.1.2.1. 同步

    CALL FUNCTION func [ DESTINATION dest ] [ˌdestiˈneiʃən]

    DESTINATION 不省略的情况下,且 dest 取值又不为 SPACE ,则函数一定要是 RFC 函数才能采用此方式进行远程同步调用

    20.1.2.2. 异步

    CALL FUNCTION rfm_name STARTING NEW TASK [ DESTINATION dest] taskname PERFORMING return_form ON END OF TASK

    FORM return_form USING taskname .
    ...
    RECEIVE RESULTS FROM FUNCTION rfm_name
    ...
    ENDFORM .

    等待多个异步调用的返回结果 WAIT UNTIL log_exp [ UP TO sec SECONDS ] .

    异步调用时不能有 IMPORTING CHANGE 参数 函数一定要是 RFC 函数才能采用异步调用 ;只要有 STARTING NEW TASK 选项,即为异步调用;如果是异步调用同一目标端的 RFC 函数,则可以省略 DESTINATION

    20.1.2.2.1. 事务性 RFC 调用

    实质上事务 RFC 调用也属于异步调用

    CALL FUNCTION func IN BACKGROUND TASK [ DESTINATION dest ] 并不立即执行,直到主调程序中的 COMMIT WORK 语句(隐式提交不要,一定要使用 COMMIT WORK 显示提交)才一次性执行多个远程函数调用

    函数一定要为 RFC 函数,且要通过 Commit Work 语句显示提交后,才会去执行,否则不会执行;如果是同一目标端的 RFC 函数,则可以省略 DESTINATION ;不能返回参数,即不能使用 RECEIVE 语句来接收返回参数

    20.1.2.3. DESTINATION 取值

    l 目标 NONE :当前程序所在应用服务器作为目标系统, 但调用过程还是 RFC 远程方式来调用 ,这与 SPACE 是不同的

    l 目标 SPACE DESTINATION 选项将会 被忽略 ,被调功能函数将作为 普通函数在本机调用

    l 目标 BACK :用于被远程调用的 RFM 程序内部的 CALL FUNCTION 语句中的目标指定,通过已建立的 RFC 连接反过来调用该函数的主调者系统中的其他功能模块(即主调程序— > 远程系统中的 RFM > 又回调主调程序所在系统中的其他函数)

    20.2. 函数、类

    CL_GUI_FRONTEND_SERVICES

    20.4. 文件读写

    DATA : file TYPE string VALUE `jzjflights.dat` ,
    wa
    TYPE spfli .
    OPEN DATASET file FOR OUTPUT IN BINARY MODE .
    SELECT *
    FROM spfli
    INTO wa .
    TRANSFER wa TO file . "
    ENDSELECT .
    CLOSE DATASET file .

    DATA : file TYPE string VALUE `jzjflights.dat` ,
    wa
    TYPE spfli .
    OPEN DATASET file FOR INPUT IN BINARY MODE .
    DO .
    " 由于没有使用 MAXIMUM LENGTH 选项,所以每次读取的最大字节数由 wa 所占字节数决定
    READ DATASET file INTO wa . "
    IF sy - subrc = 0 .
    WRITE : / wa - carrid ,
    wa
    - connid ,
    wa
    - countryfr ,
    wa
    - cityfrom ,
    wa
    - cityto ,
    wa
    - fltime ,
    wa
    - distance .
    ELSE .
    EXIT .
    ENDIF .
    ENDDO .
    CLOSE DATASET file .

    20.5. Email

    DATA : send_request TYPE REF TO cl_bcs ,
    document
    TYPE REF TO cl_ document _bcs ,
    document
    = cl_document_bcs => create_document ( 创建邮件内容
    CALL METHOD document -> add_attachment 添加附件

    send_request
    = cl_bcs => create_
    persistent ( ). 创建发送请求 [pəˈsistənt] 持续的 ; 持久的
    CALL METHOD
    send_request -> set_sender 设置发送者
    CALL METHOD send_request -> add_
    recipient 设置接收者 [riˈsipiənt]
    CALL METHOD send_request -> set_document ( document
    ) .
    CALL METHOD send_request -> send ( 发送

    20.6. XML

    if_ ixml

    if_ixml_ document
    if_ixml_
    node

    if_ixml_ element

    if_ixml_istream

    if_ixml_ostream

    document element ATTRIBUTE COMMENT TEXT 都属于 Node

    20.6.1. 生成

    version="1.0"?>

    <flow BAPI=" ZBAPI_MM_RK_AFTER_APP " DES=" 广深公司 - 采购订单 " KEY=" gsgs-cgdd "><customform><fd n=" flight ">< V >110000 </ V > </ fd > <fd n=" flight ">< V >090000 </ V > </ fd ></ customform ></ flow >

    TYPE-POOLS : ixml , abap .
    TYPES : BEGIN OF xml_line ,
    data ( 512 ) TYPE x , " 这里的长度设置不会影响输出结果,设置成 1 都可以
    END OF xml_line .
    DATA : l_ixml TYPE REF TO if_ixml ,
    l_streamfactory
    TYPE REF TO if_ixml_stream_factory ,
    l_ostream
    TYPE REF TO if_ixml_ostream ,
    l_renderer
    TYPE REF TO if_ixml_renderer ,
    l_document
    TYPE REF TO if_ixml_document .
    DATA : l_element_flights TYPE REF TO if_ixml_element ,
    l_element_airline
    TYPE REF TO if_ixml_element ,
    l_element_flight
    TYPE REF TO if_ixml_element ,
    l_element_dummy
    TYPE REF TO if_ixml_element ,
    l_value
    TYPE string .
    DATA : l_xml_table TYPE TABLE OF xml_line WITH HEADER LINE ,
    l_xml_size
    TYPE i ,
    l_rc
    TYPE i .
    DATA : lt_spfli TYPE TABLE OF spfli .
    DATA : l_spfli TYPE spfli .

    START-OF-SELECTION .
    SELECT * FROM spfli INTO TABLE lt_spfli UP TO 2 ROWS .
    SORT lt_spfli BY carrid .
    * 生成 XML 数据
    LOOP AT lt_spfli INTO l_spfli .
    AT FIRST .
    *       Creating a ixml factory
    l_ixml
    = cl_ixml => create ( ).
    *       Creating the dom object model
    l_document
    = l_ixml -> create_document ( ).
    *       Fill root node with value flow
    l_element_flights
    = l_document -> create_simple_element (
    name
    = 'flow'
    parent
    = l_document ).
    l_rc
    = l_element_flights -> set_attribute ( name = 'KEY' value = 'gsgs-cgdd' ).
    l_rc
    = l_element_flights -> set_attribute ( name = 'DES' value = ' 广深公司 - 采购订单 ' ).
    l_rc
    = l_element_flights -> set_attribute ( name = 'BAPI' value = 'ZBAPI_MM_RK_AFTER_APP' ).
    l_element_airline
    = l_document -> create_simple_element (
    name
    = 'customform'
    parent
    = l_element_flights ). "parent 为父节点
    ENDAT .
    AT NEW connid .
    l_element_flight
    = l_document -> create_simple_element (
    name
    = 'fd'
    parent
    = l_element_airline ).
    "l_value = l_spfli-connid.
    l_rc
    = l_element_flight -> set_attribute ( name = 'n' value = 'flight' ).
    ENDAT .
    l_value
    = l_spfli - deptime .
    l_element_dummy
    = l_document -> create_simple_element (
    name
    = 'V'
    value = l_value
    parent
    = l_element_flight ).
    ENDLOOP .
    *   Creating a stream factory
    l_streamfactory
    = l_ixml -> create_stream_factory ( ). [stri:m]
    *   Connect internal XML table to stream factory
    l_
    ostream = l_streamfactory -> create_ostream_itable ( table = l_xml_table [] ).
    *   Rendering the document
    l_
    renderer = l_ixml -> create_renderer ( ostream = l_ostream [ˈrendə] 朗读
    document
    =
    l_document ). " l_document 为根节点
    l_rc
    = l_renderer -> render ( ). " 注:执行此句后, l_xml_table 内表里才会有数据
    l_xml_size
    = l_ostream -> get_num_written_raw ( ).
    " 取得 XML 数据大小
    *************************************************************
    **-- xml 数据导出到本地
    * call method cl_gui_frontend_services=>gui_download
    *   exporting
    *     bin_filesize = l_xml_size
    *     filename     = 'd:\flights.xml'
    *     filetype     = 'BIN'
    *   changing
    *     data_tab     = l_xml_table[].
    ************************************************************
    ****************************************************
    **-- XML 数据导入到内表
    *  DATA xmldata TYPE xstring .
    *  DATA: result_xml TYPE STANDARD TABLE OF smum_xmltb .
    *  DATA: return TYPE STANDARD TABLE OF bapiret2 .
    *  DATA: wa_xml TYPE smum_xmltb.
    *  " 如果需要上载 XML 可以用一下方法
    *  CALL FUNCTION 'GUI_UPLOAD'
    *    EXPORTING
    *      filename   = 'd:\flights.xml'
    *      filetype   = 'BIN'
    *    IMPORTING
    *      filelength = l_xml_size
    *    TABLES
    *      data_tab   = l_xml_table.
    *  " 将二进制内表转换(拼接)成一个二进制串
    *  CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'
    *    EXPORTING
    *      input_length = l_xml_size
    *    IMPORTING
    *      buffer       = xmldata
    *    TABLES
    *      binary_tab   = l_xml_table.
    *  CALL FUNCTION 'SMUM_XML_PARSE'" 解析
    *    EXPORTING
    *      xml_input = xmldata
    *    TABLES
    *      xml_table = result_xml
    *      return    = return.
    *  LOOP AT result_xml INTO wa_xml .
    *    WRITE: / wa_xml-hier,wa_xml-type,wa_xml-cname,wa_xml-cvalue.
    *  ENDLOOP.
    ************************************************
    **************************************************
    ** XML 转换成字符串
    *  DATA: w_string TYPE xstring.
    *  DATA ls_xml TYPE string.
    *  FIELD-SYMBOLS: <fs> TYPE string.
    *  CALL FUNCTION 'SDIXML_DOM_TO_XML'
    *    EXPORTING
    *      document      = l_document
    *    IMPORTING
    *      xml_as_string = w_string
    *      size          = l_xml_size
    *    TABLES
    *      xml_as_table  = l_xml_table.
    *
    *  DATA: convin TYPE REF TO cl_abap_conv_in_ce.
    *  " 创建解码对象
    *  convin = cl_abap_conv_in_ce=>create( input = w_string ).
    *  DATA: str TYPE string.
    *  CALL METHOD convin->read
    *    IMPORTING
    *      data = ls_xml.
    *  WRITE: / ls_xml.
    * 将一个二进制串分割存储到二进制内表中
    *  call function 'SCMS_XSTRING_TO_BINARY'
    *    exporting
    *      BUFFER        = W_STRING
    *    importing
    *      OUTPUT_LENGTH = L_XML_SIZE
    *    tables
    *      BINARY_TAB    = L_XML_TABLE.
    " 将二进制内表转换(拼接)成一个字符串
    *  CALL FUNCTION 'SCMS_BINARY_TO_STRING'
    *    EXPORTING
    *      input_length = l_xml_size
    *    IMPORTING
    *      text_buffer  = ls_xml
    *    TABLES
    *      binary_tab   = l_xml_table.
    *  WRITE: / ls_xml.
    ****************************************************************

    20.6.2. 解析


    TYPE-POOLS : ixml .
    DATA : ixml TYPE REF TO if_ixml ,
    document
    TYPE REF TO if_ixml_document ,
    streamfactory
    TYPE REF TO if_ixml_stream_factory ,
    istream
    TYPE REF TO if_ixml_istream ,
    parser
    TYPE REF TO if_ixml_parser ,
    node
    TYPE REF TO if_ixml_node ,
    string
    TYPE string ,
    count          TYPE i ,
    index          TYPE i ,
    totalsize
    TYPE i .
    TYPES : BEGIN OF xml_line ,
    data ( 256 ) TYPE x ,
    END OF xml_line .
    DATA : xml_table TYPE TABLE OF xml_line .
    START-OF-SELECTION .
    CALL FUNCTION 'GUI_UPLOAD'
    EXPORTING
    filename
    = 'd:\flights.xml'
    filetype
    = 'BIN'
    IMPORTING
    filelength
    = totalsize
    TABLES
    data_tab
    = xml_table
    EXCEPTIONS
    OTHERS = 11 .
    IF sy - subrc <> 0 .
    EXIT .
    ENDIF .

    ixml
    = cl_ixml => create ( ).
    document
    = ixml -> create_document ( ).
    streamfactory
    = ixml -> create_stream_factory ( ).
    istream
    = streamfactory -> create_istream_itable ( table = xml_table
    size = totalsize ).
    parser
    = ixml -> create_parser ( stream_factory = streamfactory
    istream
    = istream
    document
    = document ).
    IF parser -> parse ( ) NE 0 .
    IF parser -> num_errors ( ) NE 0 .
    count = parser -> num_errors ( ).
    WRITE : count , ' parse errors have occured:' .
    DATA : pparseerror TYPE REF TO if_ixml_parse_error ,
    i TYPE i .
    index = 0 .
    WHILE index < count .
    pparseerror
    = parser -> get_error ( index = index ).
    i = pparseerror -> get_line ( ).
    WRITE : 'line: ' , i .
    i = pparseerror -> get_column ( ).
    WRITE : 'column: ' , i .
    string
    = pparseerror -> get_reason ( ).
    WRITE : string .
    index = index + 1 .
    ENDWHILE .
    ENDIF .
    ENDIF .

    CALL METHOD istream -> close ( ).
    CLEAR istream .
    node
    = document .
    PERFORM print_node USING node 0 .

    FORM print_node USING p_node TYPE REF TO if_ixml_node deep TYPE i .
    DATA : nodetype TYPE i ,
    attrslen
    TYPE i ,
    attrs
    TYPE REF TO if_ixml_named_node_map ,
    attr
    TYPE REF TO if_ixml_node .
    nodetype
    = p_node -> get_type ( ).
    CASE p_node -> get_type ( ).
    WHEN if_ixml_node => co_node_element . " 这里只处理元素节点
    WRITE : / .
    PERFORM printnodeinfo USING ' 元素 ' deep p_node .
    attrs
    = p_node -> get_attributes ( ).
    attrslen
    = attrs -> get_length ( ).
    DO attrslen TIMES .
    attr
    = attrs -> get_item ( sy - index - 1 ).
    PERFORM printnodeinfo USING ' 属性 ' deep attr .
    ENDDO .
    "WHEN if_ixml_node=>co_node_text.
    "PERFORM printnodeinfo USING ' 文本 ' deep p_node.
    ENDCASE .
    DATA : childs TYPE REF TO if_ixml_node_list ,
    child
    TYPE REF TO if_ixml_node ,
    childslen
    TYPE i .
    childs
    = p_node -> get_children ( ).
    childslen
    = childs -> get_length ( ).
    DATA : deep2 TYPE i .
    deep2
    = deep + 1 .
    DO childslen TIMES .
    child
    = childs -> get_item ( sy - index - 1 ).
    PERFORM
    print_node USING child deep2 .
    ENDDO .
    ENDFORM .

    FORM printnodeinfo USING nodetype TYPE string deep TYPE i node TYPE REF TO if_ixml_node .
    DATA : name TYPE string ,
    value TYPE string ,
    spaces
    TYPE string .
    DO deep TIMES .
    spaces
    = spaces && ` ` .
    ENDDO .
    name
    = node -> get_name ( ).
    value = node -> get_value ( ).
    WRITE : spaces , nodetype , name , value .
    ENDFORM .

    20.7. OLE

    CREATE OBJECT obj_name 'app' . " 创建 APP 应用类的一个对象 obj_name 实例
    SET PROPERTY OF obj_name 'XXX' = f . " 设置对象 OBJ_NAME 属性 xxx 为值 f
    GET PROPERTY OF obj_name 'xxx' = f . " obj_name 的属性 xxx 的值获取赋给 f
    CALL METHOD OF
    obj_name
    'xxx' = f " f 来接收返回值
    EXPORTING
    #1
    = f1 .
    " 调用 Obj_name 的方法 xxx 传入参数 f1…fn
    FREE OBJECT obj_name . " 释放 obj_name.

    * 定义 OLE 变量
    DATA : EXCEL TYPE OLE2_OBJECT ,
    WORKBOOK
    TYPE OLE2_OBJECT ,
    SHEET TYPE OLE2_OBJECT ,
    CELL
    TYPE OLE2_OBJECT .

    创建 excel 对象:

    CREATE OBJECT EXCEL 'EXCEL.APPLICATION' .

    SET PROPERTY OF EXCEL 'VISIBLE' = 1 . " 使 excel 可见

    SET PROPERTY OF EXCEL 'SHEETSINNEWWORKBOOK' = 1 . " 设置 Microsoft Excel 软件打开 时,自动插入到新工作簿中的工作表数目(即初始 sheet 数目,默认名字依次为 Sheet1 Sheet2.....

    创建 workbook

    CALL METHOD OF EXCEL 'WORKBOOKS' = WORKBOOK .

    " 由于 Workbooks 同时为属性,所以可以使用下面语句代替上面语句
    *GET PROPERTY OF EXCEL 'Workbooks' = WORKBOOK .
    CALL METHOD OF WORKBOOK 'ADD' .

    CALL METHOD OF WORKBOOK 'OPEN' EXPORTING #1 = 'c:\1.xlsx' . " 开文件

    添加 sheet

    CALL METHOD OF EXCEL 'sheets' = SHEET .
    CALL METHOD OF SHEET 'Add' .

    SET PROPERTY OF SHEET 'Name' = 'aaa' . " sheet 重命名

    切换 sheet

    CALL METHOD OF EXCEL 'Worksheets' = SHEET EXPORTING #1 = 'sheet3' .
    CALL METHOD OF SHEET 'Activate' .

    给单元格赋值:

    CaLL METHOD OF EXCEL 'CELLS' = CELL EXPORTING #1 = 2 #2 = 2 .
    SET  PROPERTY OF CELL 'value' = xxxx .

    CALL METHOD OF EXCEL 'RUN' EXPORTING #1 = 'ZMACRO2' .

    保存和退出:

    GET PROPERTY OF EXCEL 'ACTIVESHEET' = SHEET . " 激活工作簿
    GET PROPERTY OF EXCEL 'ACTIVEWORKBOOK' = WORKBOOK . " 激活工作区
    CALL METHOD OF WORKBOOK 'SAVEAS' EXPORTING #1 = 'c:\1.xls' #2 = 1 .
    CALL METHOD OF WORKBOOK 'CLOSE' . " 关闭工作区
    CALL METHOD OF EXCEL 'QUIT' . " 退出 excel

    释放资源:

    FREE OBJECT SHEET .
    FREE OBJECT WORKBOOK .
    FREE OBJECT EXCEL .

    20.7.1. 导出 Exel 文件多种方式

    FM 函数 SAP_CONVERT_TO_XLS_FORMAT

    优点是快速,简单;缺点就是不能控制格式,导出的数据看起来不够美观,不能使用公式,宏等

    OLE

    这个方法使用对象 OLE2_OBJECT ,模拟手工来填写 EXCEL 的内容,所以基本上可以实现 Excel 的绝大部分功能,诸如特殊格式、函数、宏、图片等等

    优点是功能强大,能做到用户指定的格式;缺点是复杂,速度慢。

    OLE + Excel 模板:

    这个方法是在纯 OLE 的基础上增加使用 Excel 模板,原理是通过在 Excel 模板里面设定格式,公式等已知的内容,然后使用 OLE 去填充其它数据

    优点是比纯 OLE 速度要快;缺点还是速度慢,虽然比第二种方法有所提高,但是如果数据量比较大的时候,比如超过 1000 行,速度方面还是不尽如人意

    OLE + Excel 模板 + TXT

    这个方法在方法 3 的基础上增加使用 TXT 文本文件,原理是先将数据根据按照 Excel 行列准备好,导出到 TXT 文本文件中,然后在 Excel 模板中使用宏打开文本文件进行填充

    优点是功能强,速度快;缺点是实现起来较为复杂,且需要懂 VBA

    20.8. ABAP 示例代码

    通过 ABAPDOCU DWDM 事务码查看

    20.9. 长文本

    Read_text 中设置一个断点,然后打开相应的 Tcode ,进行对应的长文本界面,程序会停止在 Read_text 函数里,然后按 F7 ,即可回到调用 Read_text 函数的上层主调程序,这样可以找到这传递的参数信息。

    20.9.1. 物料长文本

    20.9.2. 生产定单长文本

    20.9.3. 采购定单长文本

    20.9.4. 销售定单长文本

    20.10. Smart Forms

    主窗口中的数据可以在多个打印页面中可连续输出,即可跨页面(分页显示)每个页面( PAGE )中只能包含一个主窗口,但可以有多个子窗口 (分页情况下,子窗口应该在每页上都会显示,就是页眉页脚一样)。

    一个 FORM 只能定义一个主窗体 不同 PAGE 上的主窗体必须宽度相同,但是高度可以不同

    一个没有主窗体的 PAGE 指向的下一个页面不能为它自己 (但可以被其他 Page 指定为下一页)

    &field(< length>)& 设置输出长度 .

    &field(*)& 如果该字段类型是 abap 数据字典里定义的类型,系统将按照字典定义的长度设置输出长度

    &field(S)& 禁止输出符号位

    &field(.<length >)& 设置显示小数的位数

    &field(Z)& 禁止数字前导 0 的显示

    &field(I)& 禁止显示空值

    &field(K)& 禁止类型系统按数据字典定义的转换函数进行输出转换

    &field(R)& 右对齐 ( 只有在定义了输出长度时才有效 )

    &field(C)& 该设置效果和 ABAP CONDENSE 语句相同。 把金额、数量转换为字符串后显示

    &field(<)& 符号位显示在数据前面

    20.11. BOM

    一个物料,知道物料号,想知道由这种物料组成的一种或数种产成品应该使用 CS15 ;一产成品,知道物料号,想知道这产成品是由哪几种物料组成应该使用 CS03 (显示 BOM )、 CS11 (逐层显示 BOM )、 CS12 (多层 BOM

    BOM Header/Item : STKO/STOP

    Order BOM 展开 CS_BOM_EXPL_KND_V1

    Material BOM 展开 CS_BOM_EXPL_MAT_V2

    CALL FUNCTION ' CS_BOM_EXPL_MAT_V2 ' explosion [ɪkˈspləʊʒn] :爆炸

    EXPORTING
    CAPID = 'PP01' " BOM 应用 DATUV = sy - datum " 有效起始日
    EMENG = '1' " 需求数量 MEHRS = 'X' " 多层展开
    MMORY
    = '1' " 是否使用缓存 MTNRV = imatnr - matnr " 待展开 物料号
    STLAN = '1' " BOM 用途 WERKS = s_werks - low " 物料所在 工厂
    TABLES
    STB
    = stb

    20.12. 传输请求 SE01 SE09 SE10

    20.13. Script Form 传输: SCC1

    需在目标客户端 Client 上操作

    20.14. 权限检查

    AT SELECTION-SCREEN.
    DATA : BEGIN OF lt_bukrs OCCURS 0 ,
    bukrs
    TYPE t001 - bukrs ,
    END OF lt_bukrs .
    SELECT bukrs FROM t001 INTO CORRESPONDING FIELDS OF TABLE lt_bukrs WHERE bukrs IN s_bukrs .
    LOOP AT lt_bukrs .
    AUTHORITY -CHECK OBJECT 'ZDABAP' [ɔ:ˈθɔriti]
    ID '
    VKORG' DUMMY 销售组织
    ID
    'BUKRS' FIELD lt_bukrs - bukrs 公司代码
    ID '
    WERKS' DUMMY 工厂
    ID '
    EKORG' DUMMY 采购组织
    ID '
    KOKRS' DUMMY
    ID '
    GSBER' DUMMY
    ID '
    SEGMENT' DUMMY .
    IF sy - subrc <> 0 . "
    MESSAGE s001 ( 00 ) DISPLAY LIKE 'E' WITH 'You do not have authorization to access company code:' lt_bukrs - bukrs .
    STOP .
    ENDIF .
    ENDLOOP .
    ENDFORM .

    20.15. 允许对表数据维护

    允许通过维护工具数据浏览器(事务 SE16 )和表视图维护(事务 SM30 )显示 / 维护表数据,一般设置此属性后,就可以直接通过 SE16 进行表数据的维护了

    20.16. SE93 创建事务码

    平时我们选择的是第二个(是基于“程序和选择屏幕”来创建)会出现选择屏幕,这里我们选择最后一个(基于“事务码”),并且可以先配置事务码需要的初始化参数,在运行时直接跳过初始屏幕:

    20.17. 表字段初始值、 NULL 等问题

    20.17.1. SE11 表设置中的 Initial Values

    如果一个表是新创建的,数据库中的所有字段都会被设计成非 NULL ,此时与钩不钩上“ Initial Values ”框没有关系,且都会设置默认值,并且所有的主键都会强制将“ Initial Values ”框钩上

    该标示只在修改表结构且在现有表结构增加一个字段时,才起作用,并且只对新增的字段有影响

    如果在给现已有的表中增加一个字段,调整表结构时, 如果新加的字段没有钩上“ Initial Values ”,则对应到数据库表设计中表示该字段则为 NULL ;如果钩上了,则数据库中的相应字段不为 NULL ,并且会设置一个默认值

    20.17.2. 底层数据库表字段默认值

    字符类型的字段默认值大多数(极个别使用空字符串)为一个空格,数字字符串与日期为相应位数的 0 字符中,数字类型为 0

    createdefault [ecc] . [str_default] as ' ' 一个空格

    createdefault [ecc] . [empstr_default] as '' 空字符串

    createdefault [ecc] . [raw_default] as 0x00

    createdefault [ecc] . [numc5_default] as '00000'

    createdefault [ecc] . [numc8_default] as '00000000'

    createdefault [ecc] . [num_default]

    20.17.3. ABAP 初始值、底层数据库表默认值相互转换

    20.17.3.1. 向表中插入初始值

    在通过 ABAP 向数据库中插入数据时,不可能将 NULL 插入到表中,因为 SAP 系统将数据插入到数据库表之前会判断各字段值是否是 ABAP 程序中相应的初始值,如果为 ABAP 程序初始值,则使用相对应 ABAP 词典中的内置类型初始值进行插入; 所以数据库表中字段值为 NULL 只有一种情况,就是调整表结构时(增加字段),没有将 Initial Values 钩上

    DATA : wa_strc LIKE ytest2 . .
    CLEAR : wa_strc .
    INSERT ytest2 FROM wa_strc .

    内存中的数据(其中类型为 P 类型字段的初始值为 000...00C ,最后的 C 表示整数位为 12 位,剩下小数位数为 16 -12 – 1 = 3 位,其中算术式中的 1 表示一个小数点,所以整体来看类型为 P 的字段初始值还是 0 ):

    向表中插入初始行后,字符字段全为一个空格,数字类型为 0

    20.17.3.2. 读取数据

    CLEAR : wa_strc .
    SELECT SINGLE * INTO CORRESPONDING FIELDS OF wa_strc FROM ytest2 .

    得到的结果与上面插入数据一样。即使手动将数据库中的一个空格修改成多个,还是能读取出来恢复成插入时的初始数据。另外,以下 SQL 还是能读出数据:

    SELECT SINGLE * INTO CORRESPONDING FIELDS OF wa_strc FROM ytest2 WHERE
    key1
    eq
    '' and key1 eq ' ' and key1 eq '   ' 分别为一个、两个、三个空格

    但如果加上 key1 is null ,则查询不出数据。

    由此可以看出,数据库中的默认值加载到 ABAP 内存中后,也会转换成相应 ABAP 程序内置类型相应的初始值

    20.17.4. SAP 系统中的表字段不允许为 NULL 的原因

    下面 VAL2 字段值为 NULL 时,使用 val2 <> 33 查询时, VAL2 NULL 值是查询不出来的(标准 SQL 语句就是这样):

    20.18. ABAP 中的 “空”、 INITIAL

    DATA : n ( 4 ) TYPE n VALUE '0000' .
    IF '' = ' ' AND '' = 0 AND ' ' = 0 AND '' IS INITIAL AND ' ' IS INITIAL  AND 0 IS INITIAL AND n IS INITIAL .
    WRITE : 'IS INITIAL' . 以上条件为真
    ENDIF .

    '0000' 数字常量串不能视为初始,下面条件也为真:

    IF '' <> '0000' AND ' ' <> '0000' AND '0000' IS NOT INITIAL .

    当查询某个表时, 如果要判断某个字段是否为空,则要使用是否等于 ' ' 空格( '' 空字符也行)来判断( XX EQ ' ' 如果是数字类型,则需要与 0 进行对比),而不能使用 is NULL 来查询,因为 SAP 中的表字段几乎没有为 NULL 的,基本上都是一个空格,所以不能使用 is NULL 。从 ST05 可以看出: is NULL 会原样写在 SQL 语句中,而 空字符串或空格字符串都会转换成一个空格 ,这正好与数据库字符类型字段的默认字段对应:

    SELECT SINGLE * FROM mara WHERE matnr IS NULL OR matnr = '' OR matnr = ' ' OR matnr = '  ' .

    SELECT WHERE "MANDT" = '210' AND ( "MATNR" IS NULL OR "MATNR" = ' ' OR "MATNR" = ' ' OR "MATNR" = ' ' ) AND ROWNUM <= 1

    另外, 如果是查询条件字段是 Date Numc QUAN 类型时, Where 条件后面的值不会使用引号引起来,而是把它们直接看作是数字类型 ,特别是 Date Numc 类型,所对应的数据库表字段的类型为 Nvarchar ,这样在查询时会先将数据库表字段的值转换为数字类型后再进行比较:

    SELECT WHERE "MANDT" = '210' AND "MATNR" = 'FOOTBALL' AND "ERSDA" = 00000000 AND "LAEDA" = 20120511 AND "BLANZ" = 000 AND "COMPL" = 01 AND "BRGEW" = 0 AND "NTGEW" = 10

    下面是此生成 SQL 的查询界面:

    20.19. 调试工具

    20.19.1. ST05

    hh:mm:ss.ms: 發送數據庫請求的開始時間點 ( 小時 : 分鐘 : : 毫秒 )

    Duration: 數據庫操作的持續時間 , 以毫秒計 ( 根據系統負載而變化 )

    Program: 調用 SQL 語句的程式名

    Object name: SQL 語句中的 table view 的名稱

    Oper 所執行的數據庫操作符 .

    Curs: 數據庫游標的標記

    ArrSz: 數據記錄從 database server application server 傳輸的數據包大小 .

    Rec: 通過數據庫操作數據記錄的傳輸數量

    RC: 從數據庫系統返回的代碼

    Statement:  SQL 語句的文本

    20.20. 程序创建 Job (报表自已设置后台运行,前后台数据共享)

    REPORT ymais_sust .
    TABLES vbap .
    PARAMETERS : p_back .
    SELECT-OPTIONS : s_vbeln FOR vbap - vbeln .
    DATA : l_number TYPE tbtcjob - jobcount ,
    l_name
    TYPE tbtcjob - jobname .
    DATA : run_flg . " 当前程序是否已在后台运行过了
    l_name
    = sy - repid . " 当前程序名
    START-OF-SELECTION .
    DATA : c_tmp ( 20 ).
    DATA : l_jobcount TYPE tbtcm - jobcount ,
    l_jobname
    TYPE tbtcm - jobname .
    " 如果当前程序是在后台运行时,从 SAP 内存中读取前台共享的参数
    IF sy - batch IS NOT INITIAL .
    CALL FUNCTION 'GET_JOB_RUNTIME_INFO' " 获取当前后台 Job 名与 Job 编号
    IMPORTING
    jobcount
    = l_jobcount
    jobname
    = l_jobname .
    CONCATENATE 'YMAIS_SUST' l_jobcount INTO c_tmp RESPECTING BLANKS.
    " 读取从前台传递过来的参数
    IMPORT run_flg FROM SHARED BUFFER indx ( fi ) ID c_tmp .
    IF sy - subrc <> 0 .
    MESSAGE e001 ( 00 ) WITH 'import data unsuccessful' .
    ELSE .
    " 所有输出的 message 可以在假脱机日志里看到
    MESSAGE i001 ( 00 ) WITH ' 从前台读取来的值 run_flg = ' run_flg .
    " 共享内存使用后即时删除,否则要等到服务器重启再消失
    DELETE FROM SHARED BUFFER indx ( fi ) ID c_tmp .
    ENDIF .
    ELSE . " 如果是通过前台运行时
    CALL FUNCTION ' JOB_OPEN '
    EXPORTING
    jobname
    = l_name
    IMPORTING
    jobcount
    = l_number
    EXCEPTIONS
    cant_create_job
    = 1
    invalid_job_data
    = 2
    jobname_missing
    = 3
    OTHERS = 4 .
    IF sy - subrc = 0 .
    " 直接采用 SUBMIT 的方式,让报表程序在后台运行
    SUBMIT ymais_sust
    WITH p_back = p_back "Paramters 参数
    WITH s_vbeln IN s_vbeln "Selection-option 参数
    *     WITH s_bukrs BETWEEN '1106' AND '1111' SIGN 'I'" 如果 Selection-option 只有一行时,可以这样使用,如果是单个值,还可以这样使用:
    *     WITH bukrs eq '1106' SIGN 'I'
    VIA JOB l_name NUMBER l_number AND RETURN .
    " 由于 Shared Buffer 是整个服务器都可以共享的 ,所以 每个后台 Job 需要自己的 Buffer ,所以
    " 使用各自 Job 的编号来区分 。该方式用来在前台程序与后台 Job 之间传递参数
    CONCATENATE 'YMAIS_SUST' l_number INTO c_tmp RESPECTING BLANKS .
    EXPORT run_flg FROM 'X' TO SHARED BUFFER indx ( fi ) ID c_tmp .
    " 也可以通过下面标准函数来提交 Job ,但此标准函数不能直接将前台参数传递给后台 Job 程序(除通过变式
    " 参数 VARIANT 外)。所以只能采用上面 EXPORT ... SHARED BUFFER 语句来共享服务器内存来实现
    *    CALL FUNCTION '
    JOB_SUBMIT '
    *      EXPORTING
    *        authcknam = sy-uname
    *        jobcount  = l_number
    *        jobname   = l_name
    **       PRIPARAMS = ' ' 打印参数
    *        report    = 'YMAIS_SUST'
    **       VARIANT   =  ' ' 可通过变式来传递参数
    CALL FUNCTION '
    JOB_CLOSE'
    EXPORTING
    jobcount
    = l_number
    jobname
    = l_name
    strtimmed
    = 'X' .
    ENDIF .
    ENDIF .
    END-OF-SELECTION .
    " 如果当前程序是在后台运行时
    IF sy - batch IS NOT INITIAL .
    " 会输出到假脱机输出列表中
    WRITE : / ' 后台输出 ' .
    " 后面还可以写在后台运行时需要执行的代码逻辑及输出
    ......
    ELSE .
    MESSAGE ' 当前程序已经通过后台运行 ' TYPE 'I' .
    LEAVE PROGRAM .
    ENDIF .

    20.21. SE78 SWM0

    SE78 也可以上传图片,但那是为 Smart/Script Form 设计使用的; SWM0 除了上传图片外,还可以上传其他一些二进制文件(如上传一些 Excel 模块,供用户下载到 PC 端,再通过 OLE 来操作此文档),通过 DOWNLOAD_WEB_OBJECT 函数下载使用。 SWM0 过程如下:

    选择二进制数据回车

    点“执行”按钮,显示当前系统中已上传的二进制资源。如果待上传的资源文件扩展名不在系统中时,上传会报错,此时需对 MIME 类型进行维护,如上传 GIF 文件时,需要先维护 MIME 类型:

    20.22. 客户端文本文件或 Excel 文件上传与下载

    20.22.1. 读取客户端 Txt Excel 文件到内表: TEXT_CONVERT_XLS_TO_SAP

    TEXT_CONVERT_XLS_TO_SAP 函数可以将本地的文本文件(列与列之间默认使用 TAB 键分开,但也可以指定)或真正的 Excel 文件上传到服务内表中,并且文件转换成内表中的数据是自动完成,不需要手动,这与 ALSM_EXCEL_TO_INTERNAL_TABLE 函数是不一样的

    PARAMETERS : p_file LIKE rlgrap - filename OBLIGATORY .
    DATA : il_raw TYPE truxs_t_text_data .
    DATA : l_obj TYPE REF TO cl_gui_frontend_services .
    DATA : it_file TYPE filetable WITH HEADER LINE .
    DATA : g_rc TYPE i .
    DATA : BEGIN OF i_data OCCURS 0 ,
    c ( 2 ),
    n
    ( 2 ) TYPE n ,
    i TYPE i ,
    d
    TYPE d ,
    END OF i_data .
    AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file . " 弹出选择文件对话框
    CREATE OBJECT l_obj .
    CALL METHOD l_obj -> file_open_dialog
    EXPORTING
    file_filter
    = '*.xls;*.
    xlsx;*.txt'
    initial_directory
    = 'C:\data'
    CHANGING
    file_table
    = it_file[]
    rc
    = g_rc .
    READ TABLE it_file INDEX 1 .
    p_file
    = it_file - filename .
    START-OF-SELECTION .
    CALL FUNCTION 'TEXT_CONVERT_XLS_TO_SAP' " 可以是 Excel 文件,也可以是 Txt 文件
    EXPORTING
    *     I_FIELD_SEPERATOR    = 分隔符,默认为 Tab
    *     i_line_header        = 'X'
    " 文本中的第一行是否是标题头,如果是则不会读取
    i_tab_raw_data
    = il_raw
    " 该参数实际上没有使用到,但为必输参数
    i_filename
    = p_file
    TABLES
    i_tab_converted_data
    = i_data . " 会自动的将 Excel Txt 文件中的数据一行行读取到数据内表中

    20.22.2. 将数据内表导出为 EXCEL 文件: SAP_CONVERT_TO_XLS_FORMAT

    DATA : t100_lines TYPE STANDARD TABLE OF t001 .
    SELECT * FROM t001 INTO TABLE t100_lines .
    CALL FUNCTION 'SAP_CONVERT_TO_XLS_FORMAT'
    EXPORTING
    i_filename
    = 'c:\1.xlsx'
    TABLES
    i_tab_sap_data
    = t100_lines .

    如果 EXCEL 文件已经存在,那么数据会被覆盖

    注:数据内表中的字段类型不能是数字类型,否则会出现意想不到的错,如有数字类型字段,转出前最好先转换为字符类型再输出

    20.23. Unicode 字符串互转

    DATA : c ( 4 ) TYPE c VALUE 'ABCD' .
    FIELD - SYMBOLS <fs1>.
    ASSIGN c TO <fs1> type 'X' . " 将字符串以十六进制的 Unicode 码来表示
    WRITE : / <fs1>. 0041004200430044 这是在 AIX 上测试的结果。注意, SAP 上使用的是 Unicode 码,所以为双字节,在转换为十六进制时,与服务器所在操作系统的字节顺有关 Java 是与平台无关的,在任何平台上都是高字节序),从这里就可以看出 Windows Unix 上的字节序不是一样的。

    "==== 分配时指定类型
    DATA : x ( 8 ) TYPE x . " 这里的 8 表示 8 个字节
    x = <fs1>.
    FIELD -SYMBOLS <fs3> .
    " 将十六进制的 Unicode 码转换为字符串
    ASSIGN x TO <fs3> type 'C' . "C 在这里是一般类型 代指字符串 而不是只一个 C
    WRITE :/ <fs3>. " ABCD


    "==== 通过强转
    FIELD -SYMBOLS <fs4> TYPE c . "C 在这里也是一般类型
    ASSIGN x TO <fs4> CASTING.
    WRITE :/ <fs4>. " ABCD

    20.24. 字符编码与解码

    DATA : xstr TYPE xstring .
    DATA : l_codepage ( 4 ) TYPE n .
    DATA : l_encoding ( 20 ).
    ********** 字符集名与内码转换
    " 将外部字符集名转换为内部编码
    CALL FUNCTION 'SCP_ CODEPAGE _BY_EXTERNAL_NAME'
    EXPORTING
    external_name
    = 'UTF-8'
    IMPORTING
    sap_codepage
    = l_codepage .
    l_encoding
    = l_codepage .
    ********** 编码
    DATA : convout TYPE REF TO cl_abap_conv_out_ce .
    " 创建编码对象
    convout
    = cl_abap_conv_out_ce => create ( encoding = l_encoding ).
    convout
    -> write ( data = ' 江正军 ' ). " 编码
    xstr
    = convout -> get_buffer ( ). " 获取码流
    WRITE : / xstr . " E6B19FE6ADA3E5869B
    ********** 解码
    DATA : convin TYPE REF TO cl_abap_conv_in_ce .
    " 创建解码对象
    convin
    = cl_abap_conv_in_ce => create ( encoding = l_encoding input = xstr ).
    DATA : str TYPE string .
    CALL METHOD convin -> read " 解码
    IMPORTING data = str .
    WRITE : / str . " 江正军

    20.25. ABAP 中的特殊字符列表

    cl_abap_char_utilities=>horizontal_tab           09          TAB

    cl_abap_char_utilities=>CR_LF                            0D0A 回车换行

    cl_abap_char_utilities=>VERTICAL_TAB       0B 垂直制表符

    cl_abap_char_utilities=>NEWLINE                 0A

    cl_abap_char_utilities=>FORM_FEED           0C

    cl_abap_char_utilities=>BACKSPACE            08

    CL_ABAP_CHAR_UTILITIES=>BYTE_ORDER_MARK_LITTLE             (utf-16le) 的文件头

    CL_ABAP_CHAR_UTILITIES=>BYTE_ORDER_MARK_UTF8                (utf-8) 的文件头

    如果是要单独取得回车或者换行 不是回车加换行 ), 可以采用

    cl_abap_char_utilities=>CR_LF(1)
    cl_abap_char_utilities=>CR_LF 1(1)

    空白字符 System. out .println(( int ) ' ' ); //12288

    DATA : gc_result ( 50 ) TYPE c .
    CONSTANTS : c_tab TYPE c VALUE cl_abap_char_utilities => horizontal_tab .
    CONCATENATE 'text01' c_tab 'text02' c_tab 'text03' INTO gc_result .

    20.26. 下载文件

    20.26.1. BIN 二进制下载

    DATA : xstr TYPE xstring .
    DATA : l_codepage ( 4 ) TYPE n .
    DATA : l_encoding ( 20 ).
    ********** 字符集名与内码转换
    " 将外部字符集名转换为内部编码
    CALL FUNCTION ' SCP_CODEPAGE_BY_EXTERNAL_NAME '
    EXPORTING
    external_name
    = 'UTF-8'
    IMPORTING
    sap_codepage
    = l_codepage .
    l_encoding
    = l_codepage .
    ********** 编码
    DATA : convout TYPE REF TO cl_abap_conv_out_ce .
    " 创建编码对象
    convout
    = cl_abap_conv_out_ce => create ( encoding = l_encoding ).
    convout
    -> write ( data = ' 江正军 ' ). " 编码
    xstr
    = convout -> get_buffer ( ). " 获取二进制码流
    WRITE : / xstr . "E6B19FE6ADA3E5869B
    ********** 解码
    DATA : convin TYPE REF TO cl_abap_conv_in_ce .
    " 创建解码对象
    convin
    = cl_abap_conv_in_ce => create ( encoding = l_encoding input = xstr ).
    DATA : str TYPE string .
    CALL METHOD convin -> read " 解码
    IMPORTING data = str .
    WRITE : / str . " 江正军

    TYPES : xx ( 100 ) TYPE x .
    DATA : xtab TYPE STANDARD TABLE OF xx WITH HEADER LINE .
    xtab
    = xstr .
    APPEND xtab .

    CALL FUNCTION 'GUI_DOWNLOAD'
    EXPORTING
    filename
    = 'c:\2.txt'
    filetype
    = 'BIN'
    TABLES
    "data_tab 的类型为 ANY ,所以 xtab 是一列还是多列,都会写到
    " 文件中去,这里还只有一列,而且还没有列名,这也没有关系
    data_tab
    = xtab[] .

    20.26.2. 以字符模式下载

    DATA : BEGIN OF strc OCCURS 0 ,
    c1
    ( 2 ) TYPE c ,
    c2
    ( 1 ) TYPE c ,
    END OF strc .
    strc
    - c1 = ' ' .
    strc
    - c2 = ' ' .
    APPEND strc .
    APPEND strc .

    CALL FUNCTION 'GUI_DOWNLOAD'
    EXPORTING
    *   BIN_FILESIZE          =
    filename
    = 'c:\1.txt'
    filetype
    = 'DAT' " 列与列之间会使用 TAB 分隔
    *   APPEND                = ' '
    *   WRITE_FIELD_SEPARATOR = ' '
    *   HEADER                = '00'
    *   codepage              = '8400' "GBK
    *   codepage              = '8450' "GB2312
    codepage
    = '4110' "utf-8
    *   CODEPAGE              = '4102'"UTF-16BE
    *   CODEPAGE              = '4103'"UTF-16LE
    TABLES
    data_tab
    = strc[] .

    20.27. 将文件上传到数据库表中,并可邮件发送

    以后考虑将 CONTENT_HEX 字段定义为 LRAW 类型,这样直接存储二进制,数据不会膨胀一倍了(也可采用其他方式,请参考 数据共享与传递 ->DATABASE 章节
    "Table: ZJZJPDF 表结构设计如下:
    "Field name     Key     Data element      Datatype       Length
    "MANDT          X       MANDT             CLNT           3
    "ROW_ORDER      X                         NUMC           10
    "CHR_COUNT                                INT4           10
    " CONTENT_HEX                              LCHR           510 " :内类型为 C ,并非 X
    "LCHR 类型的字段前必须有一个 INT4 类型的字段,并且该字段不能是主键,该字段的值为后面
    "CONTENT_HEX 的字符个数,并且需要在插入表时一并进行插入,否则 CONTENT_HEX 字段的值不能 Select 出来

    DATA : BEGIN OF xtab255 OCCURS 0 ,
    x ( 255 ) TYPE x , " 一个字节需使用两个十六进制字符来表示
    END OF xtab255 .
    DATA : ctab255 TYPE STANDARD TABLE OF
    solisti1 WITH HEADER LINE .
    DATA : BEGIN OF pdf_table OCCURS 0 ,
    mandt
    TYPE zjzjpdf - mandt ,
    row_order
    TYPE zjzjpdf - row_order ,
    chr_count
    TYPE zjzjpdf - chr_count ,
    content_hex
    ( 510 ),
    " 实为字符类型
    END OF pdf_table .
    ********========================================== 插入
    CALL FUNCTION 'GUI_UPLOAD'
    EXPORTING
    filename
    = 'c:\1.txt'
    filetype
    = 'BIN'
    TABLES
    data_tab
    = xtab255 .
    *CALL FUNCTION 'GUI_UPLOAD'
    *  EXPORTING
    *    filename = 'c:\1.JPG'
    *    filetype = 'BIN'
    *  TABLES
    *    " 接收上传数据时,按收的内表行类型也可以是 Char 类型内表
    *    " ,尽管是以 BIN 模式上传的,因为该上传函数内部还是以
    *    "X 类型视图来维护 ctab255 内表的
    *    data_tab = ctab255.

    LOOP AT xtab255 .
    pdf_table
    - row_order = sy - tabix .
    pdf_table
    - content_hex = xtab255 - x .
    "chr_count 为内容 content_hex 字段值的长度,一定要设置,否则下次不能 Select 出来
    pdf_table
    - chr_count = strlen ( pdf_table - content_hex ).
    APPEND pdf_table .
    ENDLOOP .
    DELETE FROM zjzjpdf . " 清空数据库表中的所有数据
    MODIFY zjzjpdf FROM TABLE pdf_table[] . " 插入到数据库
    ********========================================== 读取
    CLEAR : pdf_table[] .
    SELECT * INTO TABLE pdf_table FROM zjzjpdf .
    SORT pdf_table BY row_order .
    CLEAR : xtab255[] .
    LOOP AT pdf_table .
    " 将数据库表中字面上的 HEX 字符串转换为真正的 HEX 二进制
    xtab255
    - x = pdf_table - content_hex .
    APPEND xtab255 .
    ENDLOOP .

    " 注意:虽然是原生态下载,但这里生成的文件在字节数上会多于源文本,并且字节数是 255 的整数倍,因为在上传的过程中使用的是 xtab255 类型的内表,当最后一行不满 255 个字节时,也会在后面补上直到 255 个字节,但补的都是 00 这样的字节,所以文件大小还是有区别的,但经过测试 pdf mp3 在码流后面补 00 字节好像没有什么影响唯独是文件大小变大了。但如果是 TXT 文件,打开后发现多了很多空格(按理来说也是什么也没有,因为空格的 ASCII 码为 32 ,但后面补的是 00 字节)
    CALL FUNCTION 'GUI_DOWNLOAD'
    EXPORTING
    filename
    = 'c:\2.txt'
    filetype
    = 'BIN'
    TABLES
    data_tab
    = xtab255 .
    *************************************************************************************
    如果需要将 PDF 以邮件的形式发送出去时,上面的 xtab255 内表接口就已适合于使用 cl_document_bcs 类的 add_attachment 方法来发送邮件的 i_att_content_hex 内表参数接口;但如果使用 SO_DOCUMENT_SEND_API1 函数的方式来发送邮件时,并使用 CONTENTS_BIN 过时(可以使用新参参数 CONTENTS_HEX 内表接口时不需转换为下面的 ctab255 内表接口,也可以直接使用上面的 xtab255 内表接口)的内表参数接口传递 PDF 文件内容时,需要使用下面方式经过转换后的内表 ctab255 ,即需要将上面的 xtab255 内表中每两行转换为 ctab255 (行类型为 Char255 )内表中的一行(因为一个 X 类型为一个字节,但在 Unicode 中一个 C 可存两个字节的内容,所以需二行转换一行操作)
    *************************************************************************************
    FIELD-SYMBOLS : <x> TYPE x .
    DATA : c_tmp1 ( 510 ) TYPE c .
    DATA : c_tmp2 ( 510 ) TYPE c .
    DATA : c_total ( 1020 ) TYPE c .
    DATA : tabix TYPE i , mod TYPE i .
    CLEAR : ctab255[] .
    LOOP AT xtab255 .
    tabix
    = sy - tabix .
    mod = tabix MOD 2 .
    IF mod = 1 .
    CLEAR : ctab255 .
    " 原来是准备使用字段符号 <c> FIELD-SYMBOLS <c> type c 指向 xtab255-x 使用 assign xtab255-x to <c> ,但该语句使用时有限制: xtab255-x 的类型的长度(所定义的字节数)一定要是 4 的倍数,否则编译出错 ,所以换成了现在这样,思路:将赋值双方中不是 X 类型的需要以 X 类型视图来操作,这样 X 类型与 X 类型赋值时不会发生数据的丢失或者是类型的转换,而不是将原本为 X 类型的一方以 C 或者其类型视图来看待,这样在非 X 类型间的赋值可能会导致数据丢失或发生类型转换
    ASSIGN COMPONENT 'LINE' OF STRUCTURE
    ctab255 TO <x> CASTING .
    c_tmp1
    = xtab255 - x .
    ELSE .
    c_tmp2
    = xtab255 - x .
    CONCATENATE c_tmp1 c_tmp2 INTO c_total .
    <x>
    =
    c_total . " 因为 4 个字面上的十六进制字符才能表示一个字符 C ,所以从 c_total 以十六进制方式赋值给 ctab255 时,缩短了 3/4
    APPEND ctab255 .
    CLEAR : c_tmp1 .
    ENDIF .
    ENDLOOP .
    " 可能是奇数后,所以需要判断一下,将最后一行也要附加上去
    IF c_tmp1 IS NOT INITIAL .
    <x>
    = c_tmp1 .
    APPEND ctab255 .
    ENDIF .

    20.28. Append Include 系统表结构增强

    如果表的结构修改后,不能激活或激活失败,此时可以使用 SE14 重新对表进行调整即可

    l Include 方式时,会在透明表中增加一行名为“ .INCLUDE ”的列,而 Append 时,会在末尾增加一列名为“ .APPEND ”的列

    l Include 可以插入到任何位置 ,但 Append 每次只能附加到当前表结构的末尾 (可以附加多个)(但经过多次的修改,最后 Append 进来的结构也可能位于当前表结构的中间)

    l Include 时,结构要事先创建好,但 Append 时,不能引用事先创建好的结构,只能在 Append 过程中创建 当多个表有相同的几个字段时,这时可以 将这些相同的字段抽出来形成一个结构,然后再将这个结构 Include 到表结构中

    l Include 不同的是, Append 可以在不修改 SAP 系统表(编辑状态)的情况下,可以给系统表新增一字段 ,对现有使用该表的程序影响很小。

    l 不能够为 Pooled Cluster 表进行 Append

    l 如果某个表中有长文本字段(类型为 LCHR LRAW )的表,不能够在使用 Append 对它进行扩展,因为长文本字段通常也是只能放在表结构的最后面。

    l Append 的结构名需要以 Z Y 打头

    l Append 结构中的字段名要使用 YY ZZ 打头。

    l Append 时, SE11 不需要切换到编辑模式,但 Include 需要切换到编辑模式下才能使用

    l Append 后, Append 的结构中所字段会全部紧跟着显示在“ .APPEND” 行后面,但 Include 时是不会将被 Include 里的字段显示出来 不是没显示出来,是没有展开( Append Include 其实都是可以展开的):

    l 在复制表时,“ .Append ”会丢失,但 Append 中的字段会被拷贝过来,但 Include 与之不同,包括 .INCLUDE 与其字段都会被拷贝过来,这进一步证实了 .INCLUDE 结构是可以重复使用的,但 Append 结构不能

    20.29. 结构复用( INCLUDE

    TYPES BEGIN OF struc_type .
    TYPES comp ...
    TYPES comp TYPE struc_type BOXED . " 参照另一结构类型
    INCLUDE { { TYPE struc_type} | { STRUCTURE struc} } " 将另一结构包括进来
    [
    AS name [ RENAMING WITH SUFFIX suffix]] .
    TYPES END OF struc_type .

    INCLUDE { { TYPE struc_type} | { STRUCTURE struc} }
    [
    AS name [ RENAMING WITH SUFFIX suffix]] .

    该语句只能用在定义结构的 BEGIN OF END OF 之间。作用是将 结构类型 struc_type 结构变量 struc 的所有组件字段拷贝到当前结构定义的指定位置, INCLUDE 就是将可以重复使用的东西先做好,再包含进来。

    AS name :给包含进来的结构类型(或结构变量)取一个别名,这样就可以通过结构组件符( - )来选取这个结构类型(或结构变量)

    RENAMING WITH SUFFIX suffix :如果 include 进来的结构类型(或结构变量)的组件字段与现有的重复,则可以使用此选项重命名 include 进来的结构类型(或结构变量)的各组件字段名,具体做法只是在原来组件名后加上了指定的后缀 suffix

    TYPES : BEGIN OF t_day ,
    work TYPE c LENGTH 8 ,
    free TYPE c LENGTH 16 ,
    END OF t_day .
    DATA BEGIN OF week .
    INCLUDE TYPE t_day AS monday    RENAMING WITH SUFFIX _mon .
    INCLUDE TYPE t_day AS tuesday   RENAMING WITH SUFFIX _tue .
    INCLUDE TYPE t_day AS wednesday RENAMING WITH SUFFIX _wed .
    ...
    DATA END OF week .

    可以通下面的方式来访问 week 结构变量:

    直接看作是 week 结构变量组件: week-work_mon, week-free_mon, week-work_tue

    由于使用 as 别名,所以还可以这样访问: week-monday-work, week-monday-free, week-tuesday-work

    当程序中多个结构使用共同的字段时,将公用的部分提取出来,使用 INCLUDE 将它们组装起来,编程结构更清晰。 下面是结构对象的复用:

    DATA : BEGIN OF comm1 OCCURS 0 ,
    bukrs
    TYPE bseg - bukrs ,
    END OF comm1 .
    TYPES : BEGIN OF comm2 ,
    blart
    TYPE bkpf - blart ,
    END OF comm2 .
    DATA : BEGIN OF gt_result OCCURS 0 ,
    c1
    TYPE c .
    " 直接定义组件字段,但前面语句后面使用逗号
    INCLUDE STRUCTURE comm1 . " 直接将结构对象包括进来
    INCLUDE TYPE comm2 . " 直接将结构类型包括进来
    DATA : comm LIKE comm1 ,
    " 直接参照
    c2
    TYPE c . " 直接定义组件字段,但前面语句后面使用逗号
    DATA : END OF gt_result .
    gt_result
    - bukrs = '111' .
    gt_result
    - blart = '222' .
    gt_result
    - comm - bukrs = '333' .

    下面是类型的复用:

    TYPES : BEGIN OF street_type ,
    name
    TYPE c LENGTH 40 ,
    no   TYPE c LENGTH 4 ,
    END OF street_type .
    DATA : BEGIN OF comm1 OCCURS 0 ,
    bukrs
    TYPE bseg - bukrs ,
    END OF comm1 .
    TYPES : BEGIN OF address_type ,
    name1
    TYPE c LENGTH 30 . " 直接定义类型,但前面语句需使用逗号
    TYPES : street TYPE street_type , " 参照另一结构类型
    c TYPE c . " 直接定义类型,但前面语句需使用逗号
    INCLUDE STRUCTURE comm1 .
    INCLUDE TYPE street_type .
    TYPES : END OF address_type .

    * 或者是这样
    *TYPES: BEGIN OF address_type,
    *        name1 TYPE c LENGTH 30,
    * street TYPE street_type,
    *        c TYPE c.
    *        INCLUDE STRUCTURE comm1.
    *        INCLUDE TYPE  street_type.
    *TYPES: END OF address_type.
    DATA : name TYPE address_type - street - name .
    DATA : name2 TYPE address_type - name .
    DATA : bukrs TYPE address_type - bukrs .

    20.30. 常用事务码

    21.1.1. 日期、时间验证

    DATE_CHECK_PLAUSIBILITY :检查一个日期是否是有效格式,如果不是有效日期,则报异常:

    CALL FUNCTION 'DATE_CHECK_PLAUSIBILITY'
    EXPORTING
    date = '20110229'
    EXCEPTIONS
    plausibility_check_failed
    = 1
    OTHERS = 2 .
    IF sy - subrc <> 0 .
    ENDIF .

    TIME_CHECK_PLAUSIBILITY :时间有效性检查,与上面日期有效性检查使用方式相同

    21.1.2. 内部转换外部格式

    FORMAT_DATE_4_OUTPUT 将数据库中 8 位的日期( YYYYMMDD )转换为指定的任意格式

    注意:在程序中,日期格式要使用 大写 格式。

    CONVERT_DATE_TO_EXTERNAL 将数据库中的 8 内部 日期( YYYYMMDD )以当前 Client 设置的 外部 日期格式显示:

    21.1.3. 外部转内部格式

    CONVERT_DATE_TO_INTERNAL 将外部日期(要符合 Client 设置的日期格式)转换为数据库内部日期( YYYYMMDD

    INPUT:      02/03/2008    "Should be same as the user's default setting

    OUPUT:     20080203

    CONVERT_DATE_INPUT 将外部日期(要符合 Client 设置的日期格式)转换为数据库内部日期

    DATA : d TYPE d .
    CALL FUNCTION 'CONVERT_DATE_INPUT'
    EXPORTING
    input = '2011.10.18'
    plausibility_check
    = 'X' " 进行日期有效性验证
    IMPORTING
    output = d
    EXCEPTIONS
    plausibility_check_failed
    = 1
    wrong_format_in_input
    = 2
    OTHERS = 3 .

    21.1.4. 获取 Client 格式

    获取当前 Client 端的日期格式与时间格式:

    SELECT datfm INTO lv_datfm FROM usr01 UP TO 1 ROWS WHERE bname = zname . ENDSELECT.

    21.1.5. 日期加减

    RP_CALC_DATE_IN_INTERVAL 加减自然年、自然月,还可以加减天数(一般加多少天直接通过日期类型就加减就可以了,但如果向下面那样需要在 20070101 加上 1 1 个月零 28 天时,就很有用了):

    21.1.6. 转成工厂日期

    DATE_CONVERT_TO_FACTORYDATE :如果输入的是周末与公共节假日,则将它把调整为工厂日历日期(工作日期):

    DATA : date        LIKE scal - date ,
    factorydate
    LIKE scal - facdate ,
    workday
    LIKE scal - indicator .
    CALL FUNCTION 'DATE_CONVERT_TO_FACTORYDATE'
    EXPORTING
    " 如果输入的日期不是工作日,则是该函数会返回离该输入的节假日前或者后面的最近的工作日,是返回前面还是后面由 +/- 来决定 + :返回后面最近的工作日 - :返回前面最近的工作日默认就是 +
    *   CORRECT_OPTION                     = '+'
    date = '20111119'
    " 使用的工厂日历 ID ,可以从 T001W-FABKL 中获取
    factory_calendar_id
    = 'CN'
    IMPORTING
    " 如果输入的是节假日,则返回是经过转换过了的工作日;如果输入的就 是工作时,则返回的是自身,格式为 YYYYMMDD
    date = date
    " 返回的工作日在指定的工作日历中位于第几个工作日,一般对我们没什么作用
    factorydate
    = factorydate
    " 标示返回的 DATE 是否是工作日,如果为空,则是,如果为 + ,表示返回的是输入的节假日后面最近一个工作;如果为 - ,表示返回的是输入的节假日前面最近一个工作日。
    workingday_indicator
    = workday .
    WRITE : / date , factorydate , workday .

    其中工厂日历的代码按工厂号到表 T001W 上取:

    SELECT SINGLE fabkl INTO l_fabkl FROM t001w  WHERE werks = im_mt61d-werks.

    FACTORYDATE_CONVERT_TO_DATE 该函数可以将 DATE_CONVERT_TO_FACTORYDATE 返回的 factorydate (工作日序号:工作日在指定的工作日历中位于第几个工作日)转换成工作日,也可以用在“加几个工作日”的应用中:比如给某个时间加上几个工作日时,可以先使用 DATE_CONVERT_TO_FACTORYDATE 函数将某个时间转换为工厂日期,并获取 factorydate 工作日序号,再在这个工作日序号上加上几个工作日,得到新的工作日序号,然后再将这个新的工作日序号传递给 FACTORYDATE_CONVERT_TO_DATE 函数,得到最终的新的工作日期:

    工厂日历定义表为 TFACD (节假日历定义表 THOCD ),表里存储了工厂日历 ID TFACD-IDENT )与节假日历 ID TFACD-HOCID )的关系,所以只要知道了工厂日历 ID ,则要使用节假日历 ID 则可以查询出来( 注:工厂日历 ID 还可以通过 T001W 来查询得到 ):

    工厂日历相关表是以“ TFA ”开头的表,节假日历相关的表是以“ THO ”开头相关的表

    21.1.7. 日期属性

    DAY_ATTRIBUTES_GET 查看某日期的属性(休息日、节假日、星期几):

    CALL FUNCTION 'DAY_ATTRIBUTES_GET'
    EXPORTING
    FACTORY_CALENDAR
    = 'CN' 引用表字段 TFACD-IDENT
    *   HOLIDAY_CALENDAR                 = ' ' 引用表字段 THOCI-IDENT
    DATE_FROM
    = '20110428'
    DATE_TO
    = '20110510'
    *   LANGUAGE                         = SY-LANGU
    *   NON_ISO                          = ' '
    * IMPORTING
    *   YEAR_OF_VALID_FROM               =
    *   YEAR_OF_VALID_TO                 =
    *   RETURNCODE                       =
    TABLES
    day_attributes
    = attr .

    21.1.8. 节假日

    HOLIDAY_CHECK_AND_GET_INFO 判断某天是否是假日,并且可以返回该日期所对应的节假日信息。

    DATA attr TYPE casdayattr OCCURS 0 .
    DATA : is_hol .
    DATA : THOL TYPE THOL OCCURS 0 .
    CALL FUNCTION 'HOLIDAY_CHECK_AND_GET_INFO'
    EXPORTING
    date = '20100529'
    holiday_calendar_id
    = 'Z1'
    " 需要返回节假日属性内表信息
    with_holiday_attributes
    = 'X'
    IMPORTING
    " 是否节假日( 注:周末不是节假日,但是一种休息日
    holiday_found
    = is_hol
    "Attributes of the found public holidays The table contains
    "the holiday aAttributes, if the specified date is a holiday.
    "The attributes must be passed in a table because several
    "holidays can fall on a date . 即一天有可能有两个节,所以需要使用内表接收
    TABLES
    holiday_attributes
    = THOL .

    21.1.9. 年月选择框

    POPUP_TO_SELECT_MONTH 弹出一个对话框显示月份和年度下拉列表,让用户选择年与月

    ACTUAL_MONTH 当前月份(弹出框中的默认值),必须填写。形式为 YYYYMM
    FACTORY_CALENDAR
    工厂日历,可以省略,默认值为空。更多信息请参考表 TFACD
    HOLIDAY_CALENDAR
    公共假日日历,可以省略,默认值为空。更多信息请参考表 THOCI
    LANGUAGE
    语言,可以省略。用来指定月份名称用哪种语言显示,不指定就是当前登录语言。
    START_COLUMN
    弹出对话框出现的位置,列,可以省略,默认值为 8
    START_ROW
    弹出对话框出现的位置,行,可以省略,默认值为 5
    输出:
    SELECTED_MONTH
    选择的月份,如果按了取消按钮,则值为 000000
    RETURN_CODE
    返回码,如果按了取消按钮,则值为 4 ,否则为 0

    说明:工厂日历和公共假日日历主要用来限制下拉列表中的可选年份范围。如果不指定,就是当前年前后各 50 年,共 100

    21.1.10. 财政年

    GET_CURRENT_YEAR 得到当前的财政年( fiscal year

    21.1.11. 星期翻译对照表

    WEEKDAY_GET 从数据表中获得指定语言每周七天的名称,例如中文就是星期一、星期二 …… 星期日,英文就是 Sunday Monday……Saturday

    输入参数:

    LANGUAGE :指定语言代码,可以省略,如果不填就是当前登录语言。注意,在调用时如果指定某种特定语言,必须用一个字节的语言代码,例如中文是 1 、英文是 E…… ,而不能用 ZH EN ,语言代码参见表 T002
    输出内表: WEEKDAY :结构与透明表 T246 相同,用来存储返回给用户的周日名称。

    21.1.12. 日期所在周末、天 / 周、周 /

    HR_GBSSP_GET_WEEK_DATES 获得某个日期所在周的周六周日、所在周的第几天、所在年的第几周:

    输入参数:
    P_PDATE
    :一个日期,必须填写。
    输出参数:
    P_SUNDAY
    :该周的周日,在 SSP 日期系统中,周日为第一天。
    P_SATURDAY
    :该周的周六,在 SSP 日期系统中,周六为最后一天。
    P_DAY_IN_WEEK
    :输入日期在当周的第几天,周日第 1 天,周一第 2 天,依此类推,周六第 7 天。
    P_WEEK_NO
    :该周是年度的第几周。 6 位数字,格式为 YYYYWW ,前四位是年,后两位是周。
    说明: SSP
    Statutory Sick Pay 的缩写

    该函数包括了以下两个函数的功能:

    DATE_GET_WEEK 获得某个日期所在的周

    WEEK_GET_FIRST_DAY 计算某周的第一天(如下面的 1999 年的第 52 周第一天):