030、内表增删改操作
调试一个批导程序时客户说数据少了两行。代码里明明写了INSERT但内表怎么都插不进去。后来发现是INSERT的语法和APPEND不同INSERT默认按关键字排序插入如果表不是SORTED或者没指定INDEX数据就跑到奇怪的位置去了。更隐蔽的是MODIFY直接改工作区内表纹丝不动——这类问题十有八九是忘记MODIFY TRANSPORTING或者没写INDEX。今天这篇就专门把内表的增删改操作掰开揉碎全是实战里踩过的坑。增APPEND、INSERT、COLLECTAPPEND是往表尾追加一行这条最简单。但别忽略APPEND的变体APPEND LINES OF it1 TO it2批量搬数据时常用。这里有个坑如果it1和it2是同一个内表APPEND LINES OF it1 TO it1会死循环因为每追加一行源表长度就变长了。别问我怎么知道的线上程序跑挂了才知道。INSERT是按索引插入。INSERT wa INTO it INDEX 5就是在第5行前插入。如果表本身是标准表执行前必须确保INDEX有效索引超出范围会直接dump。这里踩过坑INSERT时如果不TRANSPORTING整行都会覆盖。更要注意的是INSERT有INITIAL LINE和FROM wa的区别INSERT INITIAL LINE INTO it INDEX 1只在指定位置插入一条空行字段内容全是初始值。你要是想先插入空行再填值别写INSERT wa因为wa未必是空行。COLLECT是按标准键汇总数值字段。这操作精妙但容易误用。如果内表没有数值字段COLLECT的行为和APPEND一致但内部会做哈希查找性能反而更差。还有COLLECT要求字段长度够否则数值溢出直接dump。业务上一个典型的坑金额字段是CURR类型但小数位没正确配置COLLECT一执行就报“字段长度不足”。所以优先确认表结构再决定用不用COLLECT。删DELETE、CLEAR、REFRESH、FREEDELETE可以按索引、按条件、按重复行删除。DELETE it INDEX 5删除第5行但如果表行数不够运行时错误。DELETE it WHERE field X是条件删除这个在循环里用特别爽但要注意DELETE修改了内表行数所以LOOP的时候如果动态删除当前行必须用DELETE it不带条件然后立刻退出循环否则索引错乱。这里有个经典问题LOOP AT it INTO wa. DELETE it. ENDLOOP.你以为只删一行实际上循环会继续然后访问不存在的行。正确做法是DELETE it INDEX sy-tabix.然后RETURN或者EXIT。另外DELETE ADJACENT DUPLICATES FROM it COMPARING field是去重神器但必须确保表按比较字段排序否则只删除相邻重复项删不干净。CLEAR和REFRESH都清空内表的行但保留表头行的初始状态。区别在于CLEAR只清空当前工作区REFRESH专用于内表两者效果相同。FREE除了清空内表还释放内存在超大内表处理完一定要用FREE不然内存一直挂着。REFRESH不释放内存所以频繁APPEND大表又REFRESH内存占用会持续增长应用服务器迟早被拖垮。真实案例一个日报程序每天夜里跑连续运行半年后内存溢出最后查出来就是REFRESH没释放。改MODIFY、TRANSPORTINGMODIFY是重头戏。最简单的MODIFY it FROM wa按表键查找并替换。如果表是标准表且有重复键它只修改第一条匹配的行。这里有大坑如果表没有定义非空唯一键MODIFY可能找不到目标然后静默失败不报任何错。所以用MODIFY前先确认表的键或者用INDEX指定位置。MODIFY it FROM wa INDEX 5 TRANSPORTING field1 field2意思是只修改第5行的field1和field2其他字段保持不变。这个写法避免了把工作区其他字段误写进内表。我在实际项目中见过把整个结构赋给内表行的结果其他字段的值变成初始值数据全毁了。所以记住能用TRANSPORTING就一定带上别偷懒。还有MODIFY和LOOP的配合。LOOP AT it INTO wa. IF wa-flag X. wa-value Y. MODIFY it FROM wa TRANSPORTING value. ENDIF. ENDLOOP.注意这里MODIFY不带INDEX因为LOOP内部sy-tabix就是当前行索引。但也有人喜欢用字段符号LOOP AT it ASSIGNING fs. fs-value Y. ENDLOOP.字段符号修改的是内表本身连MODIFY都省了效率最高。不过字段符号的坑是MODIFY之后引用会失效如果内表在循环中被INSERT或DELETE字段符号可能变成悬空指针。所以用了字段符号就尽量不要在循环里做增删操作。实操常见场景场景一批量更新内表数据源。从数据库读取数据到内表然后根据逻辑修改某些行。建议用LOOP ... ASSIGNING直接改既快又安全。如果非要MODIFY记得用TRANSPORTING限定字段。场景二去重。先SORT it BY key1 key2再DELETE ADJACENT DUPLICATES FROM it COMPARING key1 key2。如果不去重后面COLLECT会报错或者汇总错误。场景三动态条件删除。DELETE it WHERE field IN seltab这个在权限过滤时特别好用不用写循环。场景四给内表插入空行然后填值。INSERT INITIAL LINE INTO it INDEX 1然后LOOP时用sy-tabix定位赋值。注意这里INSERT之后原有行的索引全部后移如果你在后面又有按索引的MODIFY小心索引已经变了。性能观察内表操作性能差距巨大。APPEND最块INSERT在表头插入时标准表会移动所有后续行O(n)复杂度数据量大时明显卡顿。DELETE同理在表头删除一行所有行都要搬。COLLECT内部有哈希索引理论上快但只能用于标准表和哈希表不适用于SORTED表。选择内表类型很关键数据量大且频繁按键访问用SORTED表频繁按顺序处理用标准表偶尔按键查找用哈希表。增删改操作类型决定内表类型别先入为主。字段符号和性能对比MODIFY it FROM wa内部会隐式查找如果表键没建好就是线性扫描。而ASSIGNING字段符号直接定位内存差距可以达到一个数量级。我在一个处理十万行的内表程序里把MODIFY改成ASSIGNING运行时间从8秒降到2秒。所以性能敏感业务能用字段符号就不要用MODIFY。但字段符号修改后内表的索引可能变化比如SORT之后字段符号失效必须重新ASSIGNING。踩坑实录有一次功能需求是“删除内表中备注字段为空的行”。我写了DELETE it WHERE remark 。结果运行后发现非空行也被删了。查了半天原来remark字段是CHAR类型数据库里存的可能是空格而不是初始值。在ABAP中会转换成空格实际上remark 比较时系统把当作一个空格字符串而CHAR字段默认补空格。所以条件永远为真全删了。正确写法是remark space或者remark IS INITIAL。这个教训让我记住了涉及字符字段条件判断先看是CHAR还是STRINGSTRING空串和空格是两回事。另一个问题用INSERT LINES OF it1 TO it2时it1和it2有相同行类型但it2是SORTED表插入时自动按排序字段插入如果你以为INSERT LINES是追加就大错特错。还有COLLECT要求表有非零数值字段如果全是字符字段COLLECT会把相同键的字符字段也合并不会COLLECT只会对数值字段求和字符字段取第一条的值。所以在共享数据导入时用COLLECT做数量汇总但描述字段要保持一致否则结果看运气。最后说点经验不要看着语法简单就上手写。内表增删改操作的核心不是语法而是对索引、键值、内存行为的理解。每写一个操作前问自己三个问题这张表是标准表、排序表还是哈希表我操作的行有没有唯一键循环中修改内表会不会影响sy-tabix?这三个问题想清楚了基本不会写出大bug。调试内表问题不要只盯着代码看。在关键操作前后用BREAK-POINT或者CL_DEMO_OUTPUT打印内表行内容尤其是索引和值。很多时候你以为改了第5行实际因为之前DELETE过行号早变了。写一个LOOP输出sy-tabix和字段值的小工具函数放在包里随时调用省你很多时间。如果程序性能不好优先检查LOOP里有没有SELECT那是另一回事。内表自身的增删改操作只要你没在循环里做低效的DELETE WHERE一般不会太差。最后提一句代码注释一定要写清楚“为什么这么写”比如“这里用DELETE ADJACENT DUPLICATES是因为数据源有重复”而不是“删除重复”。因为后面维护的人多是刚上手ABAP的新人你写个“为什么”他就不敢乱动了。