第4课:内表与结构体操作¶
45分钟 | 阶段:基础篇 | 建议边读边做
前置依赖¶
问题引入¶
SFLIGHT 有几千行,SELECT SINGLE 一次取一条显然不现实。怎么把数据一次性"装起来",再分组、排序、查找?答案就是内表(Internal Table)——ABAP 的内存容器,也是这门语言几十年来的核心数据结构。本课同时引入一批现代写法(FOR / GROUP BY / REDUCE),它们会让你的内表代码从"过程式循环"进化为"表达式"。
时间安排¶
| 时段 | 内容 | 时长 |
|---|---|---|
| 场景引入 | 逐条查 vs 批量处理 | 3 分钟 |
| Demo 跟做 | 运行 zac_internal_table,看四段输出 | 5 分钟 |
| 代码拆解 | 结构体/内表类型/常用操作/新语法 | 29 分钟 |
| 知识总结 | 三种内表选型表、操作速查 | 6 分钟 |
| 课后思考 | 练习 | 2 分钟 |
本课目标¶
完成本课你将能够:
- 用
TYPES ... BEGIN OF定义自己的结构体和表类型; - 说出 STANDARD / SORTED / HASHED 三种内表的差异并正确选型;
- 熟练使用 APPEND / SORT / LOOP / READ / MODIFY / DELETE;
- 用
FOR、FOR GROUPS、REDUCE、CORRESPONDING写现代内表代码。
Demo:批量处理航班数据(分步跟做)¶
程序 zac_internal_table 已随仓库下发,SE38 直接运行。它做四件事,输出对照着看:
REPORT zac_internal_table.
START-OF-SELECTION.
" ① 批量读取:一次 SELECT 把 SFLIGHT 全部装进内存
SELECT * FROM sflight INTO TABLE @DATA(lt_sflight).
" ② FOR 表达式——提取不重复的航空公司(SORTED TABLE 自动去重)
DATA(lt_carrids) = VALUE SORTED TABLE OF s_carr_id(
FOR ls IN lt_sflight
NEXT ( ls-carrid )
).
WRITE: / |航空公司数量: { lines( lt_carrids ) }|.
" ③ FOR GROUPS——按航空公司分组统计航班数
DATA(lt_summary) = VALUE SORTED TABLE OF sflight(
FOR GROUPS grp OF ls IN lt_sflight
GROUP BY ( carrid = ls-carrid )
LET cnt = COUNT( * ) IN
( carrid = grp-carrid seatsocc = cnt )
).
LOOP AT lt_summary INTO @DATA(ls_grp).
WRITE: / |{ ls_grp-carrid }: { ls_grp-seatsocc } 条航班|.
ENDLOOP.
" ④ REDUCE——全表累加总已占座位
DATA(lv_total) = REDUCE i(
INIT sum = 0
FOR ls IN lt_sflight
NEXT sum = sum + ls-seatsocc
).
WRITE: / |总已占座位: { lv_total }|.
你会看到什么: 第一行是航空公司数量(十几家);随后每家航空公司一行"xx: N 条航班";最后一行是全表座位占用总数。第⑤节逐段拆解。
知识点¶
1. 结构体:内表里的"一行"¶
TYPES: BEGIN OF ty_summary,
carrid TYPE s_carr_id,
count TYPE i,
END OF ty_summary.
DATA ls_summary TYPE ty_summary. " 一行
DATA lt_summary TYPE STANDARD TABLE OF ty_summary " 一容器行
WITH EMPTY KEY.
- 结构体 = 若干字段的组合;内表 = 结构体的可重复集合;
- 现代写法别忘了
WITH EMPTY KEY——不带键的标准表在严格语法检查下要求显式声明键,EMPTY KEY表示"这表没键,随便排"; - DDIC 表也可以直接当结构体/表类型用:
TYPE sflight、TYPE STANDARD TABLE OF sflight。
2. 三种内表类型:选型是本课的灵魂¶
| 类型 | 排序 | 键 | READ 性能 | 适用场景 |
|---|---|---|---|---|
STANDARD TABLE |
保持插入序 | 可无键 | 线性 O(n) | 顺序遍历、临时收集、结果集 |
SORTED TABLE |
自动按键排序 | 唯一/非唯一 | 二分 O(log n) | 需要有序或按键频繁查 |
HASHED TABLE |
无序 | 仅唯一键 | 哈希 O(1) | 大表按键精确查找 |
DATA lt_std TYPE STANDARD TABLE OF sflight WITH EMPTY KEY.
DATA lt_srt TYPE SORTED TABLE OF sflight WITH NON-UNIQUE KEY carrid.
DATA lt_hash TYPE HASHED TABLE OF sflight WITH UNIQUE KEY carrid connid fldate.
选型直觉: 拿来就遍历 → STANDARD;边插边要求有序/按键二分 → SORTED;几万行按键随机查 → HASHED。(查找复杂度背后的数据结构原理,感兴趣的同学可延伸阅读资料库里 Hello 算法——数组/有序表/哈希表三连。)
WITH HEADER LINE 已死
DATA lt TYPE ... WITH HEADER LINE(表名既是表又是工作区)是上个时代的写法,官方已不推荐且在 OO 上下文中不可用。新代码一律:表 + 显式工作区(ls_)或 FIELD-SYMBOLS。
3. 常用操作速查¶
" 装载与追加
APPEND ls TO lt. " 标准表尾部追加
INSERT ls INTO TABLE lt. " 通用(排序/哈希表按键定位)
COLLECT ls INTO lt. " 数值字段按键累加(旧式汇总)
" 排序与统计
SORT lt BY carrid ASCENDING seatsocc DESCENDING.
DATA(lv_lines) = lines( lt ). " 行数(现代写法,替代 DESCRIBE TABLE)
" 循环
LOOP AT lt INTO DATA(ls) WHERE carrid = 'AA'. " 只读遍历
LOOP AT lt ASSIGNING FIELD-SYMBOL(<fs>). " 就地修改(见下)
ENDLOOP.
" 查找
READ TABLE lt INTO ls INDEX 1. " 按行号
READ TABLE lt INTO ls WITH KEY carrid = 'AA'. " 按内容(线性)
READ TABLE lt INTO ls WITH TABLE KEY carrid = 'AA'. " 按表键(SORTED/HASHED 走算法)
IF sy-subrc = 0. ... ENDIF.
" 修改与删除
MODIFY lt FROM ls INDEX 5 TRANSPORTING seatsocc. " 只搬指定字段
DELETE lt WHERE carrid = 'AA'.
CLEAR lt. " 清空(保留内存)
FREE lt. " 清空并释放内存
BINARY SEARCH 的隐形炸弹
READ TABLE ... BINARY SEARCH 要求表已按查找字段排序。不排序它照样"认真"二分——返回随机错误行且无任何警告。要么先 SORT,要么直接用 SORTED/HASHED 表 + WITH TABLE KEY,把正确性交给类型系统。
4. 循环中改数据:ASSIGNING vs INTO¶
" INTO:拷贝一行到 ls,改 ls 不影响原表,需要 MODIFY 回写
LOOP AT lt INTO ls.
ls-seatsocc = ls-seatsocc + 1.
MODIFY lt FROM ls TRANSPORTING seatsocc. " 又一步,啰嗦
ENDLOOP.
" ASSIGNING:FIELD-SYMBOL 直接指向原表行,改了就是改了
LOOP AT lt ASSIGNING FIELD-SYMBOL(<fs>).
<fs>-seatsocc = <fs>-seatsocc + 1.
ENDLOOP.
大数据量表循环修改,ASSIGNING 少一次行拷贝,是默认选择。
5. 新语法四件套(Demo 逐段拆解)¶
① FOR ... IN:把循环变成表达式
左边是 VALUE 目标类型( 内容 ),内容由 FOR 逐行投喂——一个表达式完成"遍历+投影+装载",SORTED 类型顺手把重复值去掉了。
② FOR GROUPS:ABAP 层的分组统计
FOR GROUPS grp OF ls IN lt_sflight
GROUP BY ( carrid = ls-carrid )
LET cnt = COUNT( * ) IN
( carrid = grp-carrid seatsocc = cnt )
GROUP BY定义分组键,grp代表组键、组内成员可再FOR IN GROUP grp展开;LET ... IN在表达式里声明临时变量(这里是组内行数COUNT(*));- 对应 SQL 的
GROUP BY——数据已在内存时用它,别再倒回数据库。
③ REDUCE:折叠成单值
DATA(lv_total) = REDUCE i(
INIT sum = 0 " 折叠起点
FOR ls IN lt_sflight " 逐行
NEXT sum = sum + ls-seatsocc ). " 折叠动作
④ CORRESPONDING:同名字段自动搬运
DATA ls_flight TYPE sflight.
DATA ls_sum TYPE ty_summary.
ls_sum = CORRESPONDING #( ls_flight ). " 只搬两边同名的字段(carrid)
结构不同但有公共字段时免掉逐字段赋值;进阶参数 MAPPING/EXCEPT 第19课专题展开。
💡 实战经验¶
大数据量表先想键,再想循环
几万行内表要在循环里按键查另一张内表?把被查表建成 HASHED TABLE WITH UNIQUE KEY,O(1) 命中——嵌套双层 STANDARD 循环是性能杀手排行榜第一名。
lines( ) 替代 DESCRIBE TABLE
取行数用内置函数 lines( lt ),可内联进表达式:IF lines( lt ) > 0.,比 DESCRIBE TABLE lt LINES lv_n 干净得多。
REDUCE vs LOOP 累加:性能等价,选可读性
两者编译后差别可忽略;REDUCE 让"折叠意图"一目了然。团队规范二选一统一即可。
📖 延伸阅读¶
- ABAP Keyword Documentation——
VALUE / FOR / REDUCE / CORRESPONDING各条目; - 标准表/排序表/哈希表的查找复杂度差异,本质是数据结构问题,延伸理解见参考资料库背景资料区。
课后思考¶
把你的回答写在页面底部评论区,注明题号,一起讨论。
- 三种内表各适合什么场景?给 SFLIGHT 做"按 carrid+connid+fldate 查一个航班"的内存缓存,你选哪种?
READ TABLE ... BINARY SEARCH的前提是什么?违反了会发生什么(为什么说它是"隐形炸弹")?- 用
FOR GROUPS改写:统计每家航空公司的平均票价(提示:组内再 REDUCE 或两次分组)。 LOOP AT ... ASSIGNING相比INTO的优势在哪?什么情况下必须小心<fs>的生命周期?