4、ABAP内表操作与性能优化:内表类型(STANDARD/SORTED/HASHED)、内表排序与二分查找、LOOP AT与AT NEW/ENDAT 分组处理

内表操作,可以说是ABAP开发里最家常便饭的事了。我做了十几年SAP项目,几乎每个程序都离不开它。但说实话,很多人写了几年代码,对内表的理解还停留在「能跑就行」的阶段。今天咱们就好好聊聊内表的那些门道。

4.1 内表类型:选对了,性能翻倍

ABAP有三种内表类型:STANDARD、SORTED和HASHED。每种都有自己的脾气秉性。选错了,程序跑起来就像老牛拉车;选对了,嗖嗖的。

4.1.1 STANDARD TABLE(标准表)

这是最常用的内表类型。它的特点是:

  • 主键索引是线性的,系统会维护一个逻辑索引
  • 可以按任意顺序添加记录
  • 查找时默认是顺序查找,除非你手动排序后使用二分查找

我在项目中遇到过这样一个场景:有个报表要从物料主表里取几万条数据,然后逐条去匹配另一个内表。一开始用的就是标准表,结果跑了快10分钟还没出结果。后来一查,问题就出在查找方式上。

适用场景:数据量不大(几千条以内),或者需要频繁追加数据的情况。

DATA: lt_standard TYPE TABLE OF mara.
APPEND wa_mara TO lt_standard.  " 直接追加,没问题

4.1.2 SORTED TABLE(排序表)

排序表,顾名思义,数据会按照主键自动排序。你插入一条记录,系统会自动把它放到正确的位置上。

  • 主键是排序字段,系统自动维护排序顺序
  • 查找时自动使用二分查找,速度很快
  • 插入和删除操作相对慢一些,因为要维护排序

我个人习惯在需要频繁按主键查找的场景下使用排序表。比如做数据校验时,用排序表比标准表快得多。

小技巧:如果你确定数据量很大(上万条),而且主要操作是读取而不是写入,排序表是首选。

DATA: lt_sorted TYPE SORTED TABLE OF mara WITH UNIQUE KEY matnr.
READ TABLE lt_sorted INTO wa_mara WITH KEY matnr = '100-100'.  " 自动二分查找

4.1.3 HASHED TABLE(哈希表)

哈希表是性能怪兽。它通过哈希算法直接定位数据,理论上查找速度是O(1)。但代价是:

  • 必须定义唯一主键
  • 不能按非主键字段排序或查找
  • 内存占用比排序表大

我曾经在一个接口程序里,需要根据物料号快速查找对应的描述信息。数据量大概20万条,用标准表查一次要好几秒,换成哈希表后,基本感觉不到延迟。

注意:哈希表只能按完整主键查找。如果你需要按非主键字段查找,或者需要范围查找,哈希表就不合适了。

DATA: lt_hashed TYPE HASHED TABLE OF makt WITH UNIQUE KEY matnr.
READ TABLE lt_hashed INTO wa_makt WITH TABLE KEY matnr = '100-100'.  " 必须用TABLE KEY

4.2 内表排序与二分查找

说到性能优化,排序和二分查找是绕不开的话题。很多新手写代码,上来就是LOOP套LOOP,数据量一大就崩了。

4.2.1 为什么要排序?

标准表默认是顺序查找。什么意思呢?就是你要找一条数据,系统会从第一条开始,一条一条往下比。如果数据在最后一条,那就得比完所有记录。数据量小还好,数据量大了,这就是灾难。

二分查找就不一样了。它每次把数据分成两半,判断目标在哪一半,然后继续分。比如100万条数据,最多20次比较就能找到。你想想看,这差距有多大?

4.2.2 二分查找的正确姿势

使用二分查找有两个前提:

  1. 内表必须按查找字段排序
  2. 查找字段必须是排序字段的一部分
" 错误示范:没排序就二分查找
READ TABLE lt_standard INTO wa_data WITH KEY matnr = '100-100' BINARY SEARCH.
" 结果:可能找不到,或者找到错误的数据

" 正确做法:先排序,再二分
SORT lt_standard BY matnr.
READ TABLE lt_standard INTO wa_data WITH KEY matnr = '100-100' BINARY SEARCH.

我记得有一次帮同事排查性能问题,他的程序跑了半小时还没完。我一看代码,好家伙,在LOOP里用了READ TABLE,但没加BINARY SEARCH。数据量也就5万条,但LOOP了5000次,每次都是顺序查找。改成二分查找后,程序30秒就跑完了。

性能对比:

数据量 顺序查找(平均) 二分查找(平均)
1,000 500次比较 10次比较
10,000 5,000次比较 14次比较
100,000 50,000次比较 17次比较

4.3 LOOP AT与AT NEW/ENDAT 分组处理

分组处理是ABAP里一个很实用的功能。说白了,就是按某个字段的值,把数据分成一组一组的,然后对每组进行处理。

4.3.1 AT NEW / ENDAT 的基本用法

这个语法要求内表必须按分组字段排序。系统会在分组字段值变化时触发AT NEW,在下一组开始时触发AT ENDAT。

SORT lt_data BY werks matnr.  " 必须先排序

LOOP AT lt_data INTO wa_data.
  AT NEW werks.
    WRITE: / '工厂:', wa_data-werks.
    CLEAR: gv_total.
  ENDAT.
  
  gv_total = gv_total + wa_data-menge.
  
  AT END OF werks.
    WRITE: / '总数量:', gv_total.
  ENDAT.
ENDLOOP.

嗯,这里要注意:AT NEW和AT ENDAT的触发时机。AT NEW是在分组字段值变化时触发,也就是新一组的第一条记录。AT ENDAT是在当前组的最后一条记录处理完后触发。

4.3.2 常见的坑

我曾经在项目里看到过这样的代码:

" 错误:没有排序就使用AT NEW
LOOP AT lt_data INTO wa_data.
  AT NEW werks.  " 结果:每个工厂都会出现多次
    ...
  ENDAT.
ENDLOOP.

如果不排序,AT NEW会在每次字段值变化时触发。但因为没有排序,相同工厂的数据可能分散在各处,导致AT NEW被多次触发。这显然不是我们想要的结果。

我的建议:使用AT NEW/ENDAT之前,一定要先按分组字段排序。这是铁律,别偷懒。

4.3.3 分组处理的性能考量

分组处理比在LOOP里自己写IF判断要高效得多。为什么?因为AT NEW/ENDAT是系统级别的优化,它直接利用了内表的排序特性,不需要额外的比较操作。

我一般会在以下场景使用分组处理:

  • 按工厂汇总物料数量
  • 按采购订单分组显示行项目
  • 按客户分组计算销售总额

说白了,只要是需要按某个字段做汇总或分组输出的,AT NEW/ENDAT都是首选方案。

4.4 性能优化实战建议

最后,我总结几条实战经验:

  1. 选对内表类型:频繁读取用SORTED或HASHED,频繁写入用STANDARD
  2. 能用二分查找就别顺序查找:数据量超过1000条,二分查找的优势就很明显了
  3. 分组处理前一定要排序:这是新手最容易犯的错误
  4. 避免在LOOP里嵌套LOOP:能用READ TABLE就用,别偷懒写内循环
  5. 善用COLLECT:做汇总时,COLLECT比LOOP+ADD快得多

我记得有一次优化一个月结报表程序,就是把一个嵌套了3层LOOP的代码,改成了排序+二分查找+分组处理。结果运行时间从45分钟降到了3分钟。你想想看,这优化效果有多明显。

好了,内表操作这块就聊到这儿。下一章咱们讲讲ABAP中更高级的数据处理技巧,比如字符串操作和正则表达式。这些东西在实际项目中用得好,能省不少事。