034、内表嵌套与结构内表
那天同事拉着我过去,说程序跑出来没数据,明明内表在第一遍循环里还看得见东西。我打开调试器,一眼瞅中那个字段的类型栏写着“Table”。刚开始学ABAP的人八成没见过这种场面——点击这个字段,里头居然又冒出一张表。他愣了:“这啥?表套表?”我说,这就是内表嵌套,ABAP里最容易被新手当成“结构体数组”但其实比那多一层的东西。
先讲清楚什么叫“结构内表”。内表的每一行如果是一个结构,那这张表就是结构内表。比如你定义一个人,名字、年龄,然后说“给我一张放很多人的表”,这就是最普通的内表。
TYPES: BEGIN OF t_person, name TYPE string, age TYPE i, END OF t_person. DATA: gt_people TYPE TABLE OF t_person.这种表平铺直叙,字段一目了然,调试的时候不会出幺蛾子。可问题来了——现实业务里经常有父子关系,比如一个订单,抬头是订单号、日期,底下还挂着多个物料行项目。你要是用两张平铺内表,就得自己在代码里维护订单号与行项目的对应关系。更麻烦的是,从RFC或JSON接口拿到数据时,数据本身就是嵌套的,你非把它拆平,反而更痛苦。
ABAP允许你定义“内表行里还带内表”,这就是内表嵌套。具体写法看下面。
* 先定义行项目结构,再定义行项目表 TYPES: BEGIN OF t_item, matnr TYPE matnr, quantity TYPE i, END OF t_item. TYPES: tt_item TYPE TABLE OF t_item. * 订单抬头结构里,放一个内表字段 TYPES: BEGIN OF t_order, order_no TYPE vbeln, date TYPE sy-datum, items TYPE tt_item, " 这就是嵌套,注意别把它写成TABLE OF END OF t_order. DATA: gt_orders TYPE TABLE OF t_order.注意,结构里的items类型是tt_item,不是TABLE OF。这里很多人写错过,差点把结构定义成内表的表,然后编译器直接报错。这里踩过坑,牢记。
给这种嵌套表赋值,得一层一层来。先给行项目工作区,APPEND到外层工作区的内表字段里,最后再把整个外层工作区APPEND到外层表。
DATA: ls_order TYPE t_order, ls_item TYPE t_item. ls_order-order_no = '1000'. ls_order-date = sy-datum. ls_item-matnr = 'MAT-01'. ls_item-quantity = 5. APPEND ls_item TO ls_order-items. " 先往内层塞 ls_item-matnr = 'MAT-02'. ls_item-quantity = 3. APPEND ls_item TO ls_order-items. APPEND ls_order TO gt_orders. " 外层再塞看明白没有?ls_order-items本身就是一张内表,你不能直接ls_order-items = 'xxx',得拿行项目工作区去APPEND。这跟操作普通内表一模一样,只不过这个内表现在住在结构字段里。
访问嵌套数据也一样,双层LOOP。这里我推荐用字段符号,别把整个结构往工作区里搬,否则内表字段会连累一大片数据做深层复制,性能吃不消。
DATA: ls_item TYPE t_item. FIELD-SYMBOLS: <fs_order> TYPE t_order. READ TABLE gt_orders ASSIGNING <fs_order> WITH KEY order_no = '1000'. IF sy-subrc = 0. LOOP AT <fs_order>-items INTO ls_item. WRITE: / ls_item-matnr, ls_item-quantity. ENDLOOP. ENDIF.ASSIGNING比INTO省了一次深层拷贝。深层拷贝这个词可能吓人,但反过来想,如果一笔订单下有几百个行项目,你把整个ls_order从gt_orders里READ INTO出来,等于把这几百行又复制了一遍。在循环里这么搞,内存会疯的。这里踩过坑,别这样写。
还有个大坑跟“清空”有关。你用CLEAR ls_order时,ABAP会把内表字段items一并清空,这点还好。但如果你在循环里重复利用ls_order,忘记CLEAR,上一次残留的行项目会留在items里,下一笔订单会莫名带上旧数据。我亲眼见过有人为了这问题查了一整天,最后发现是CLEAR少写了一行。
那嵌套内表到底能干嘛?最实在的用处是描述层级结构。比如对接外部系统的JSON,数据长这个样子:
{"orderNo":"1000","date":"2025-03-01","items":[{"matnr":"MAT-01","quantity":5},{"matnr":"MAT-02","quantity":3}]}在ABAP里用嵌套内表来接收这种结构,简直无缝衔接。ABAP的JSON转换工具会直接把内表字段映射成JSON数组。你要是用两张平铺表,反而要自己拼结构、对ID,麻烦得多。
但嵌套内表也有不好使的时候。ALV表格控件默认不支持嵌套显示。你试图把一个含内表字段的内表直接塞给ALV,它只会显示一个“Table”字符串或干脆报错。做报表时,老老实实把嵌套展平成一张宽表,把抬头字段重复到每一行,才是正道。所以我的习惯是:接口解析、内存里做复杂业务处理,用嵌套;展示、打印、下载,一定转平铺。
另外,别对嵌套内表使用SORT或DELETE ADJACENT DUPLICATES之类的操作。内表字段没法比较大小,也不能做唯一性判断,编译器可能直接拒绝。就算标准表让你塞进去了,运行时结果也是不可预测的。这里踩过坑,别去挑战ABAP的脾气。
还有一点,嵌套内表的赋值比想象中“贵”。如果你真的需要复制一个嵌套内表,MOVE或=会深拷贝所有内嵌的数据。数据量大时,这一行代码能让你程序卡上好几秒。如果仅仅是想传引用,用字段符号或ASSIGN,甚至考虑动态指针。不过ABAP里没有真正意义的指针,字段符号就是你的好兄弟。
最后说一个调和“结构内表”这词。有些人把“结构内表”理解为“结构里有内表字段”,也就是我上面讲的订单结构;也有人把“结构内表”理解为“行类型是结构的内表”,也就是最普通的gt_people。这两种叫法容易让人晕。我在团队里统一这么说:gt_people叫“标准内表”或“平铺内表”;带内表字段的t_order叫“深层结构”或“嵌套内表”。你写代码时,定义清楚术语,别人读你的文章或代码才不容易懵。
那就这样。学ABAP不用追求花哨,嵌套内表是个工具,不是玩具。我的经验是:能用SQL JOIN解决的,别在内存里折腾嵌套;必须得用嵌套的时候,牢牢记住三层循环的节奏,还有字段符号这个性能救星。调试器里再看到“Table”类型,双击进去,里面藏着另一个世界。你要做的是先搞清楚那一层世界的边界,别一脚踩空。