拓冰建站拓冰建站
首页 / 资讯中心 / 正文

053、ABAP数据字典与ABAP程序联动

053、ABAP数据字典与ABAP程序联动昨晚线上一个报表突然查不出数据用户那边直接炸了。我远程连上去一看程序逻辑没动过数据库表里也有记录但SELECT就是查不到。后来发现是有人在前台顺手改了一个数据元素把域里的输出长度从10改成了12结果这个字段在程序里被当成关键字段去做内表排序排序键变了匹配规则全乱。这种问题不是语法错误不会报错但就是让你半夜爬起来查。从那以后我就特别在意数据字典和ABAP程序之间的那些隐性契约。先聊最基础的一件事你在SE11里建一张表ABAP程序里用TYPES或者DATA直接引用这个结构这叫“活引用”。意思是程序里的变量类型跟着字典对象走字典里字段改了长度、类型你程序里的变量自动跟着变。很多人觉得这样省事但省事的另一面是风险。你自以为程序里用的是char10结果字典里悄悄变成char12你程序里所有用到这个变量的逻辑比如字符串拼接、比较、写屏幕全部换了一套规则。而且编译不报警运行时也不报错只有数据对不上。所以我的习惯是程序里引用表结构没问题但关键逻辑里如果对某个字段有强假设比如必须等长我会在初始化的时候用DESCRIBE或者直接再定义一个固定长度的本地变量把字典值赋进去人为加一道约束免得字典被别人改了程序也跟着抽风。数据字典里的域(Domain)和数据元素(Data Element)是两级定义。域管的是技术属性比如类型、长度、值表数据元素管的是业务语义比如字段标签、文档。很多入门的人分不清直接在建表的时候把域和数据元素混着用导致程序里明明读的是同一个字段名但不同表之间因为引用不同的域长度或类型有细微差异内表关联的时候隐式转换性能一下就下去了。我见过最坑的一次是两张表关联A表字段引用域ZCHAR10B表字段引用域ZCHAR10_NEW外部一看都是10位字符但B表的域实际定义成了NUMBER只是输出长度也是10。ABAP在做等值关联时会自动转换结果把A表里的字母串转成数字转换失败直接dump或者更恶心的是转换成功但值变了比如前导零被吃掉。再说表维护生成器。你在SE11里点“生成维护视图”系统会帮你做一个维护对话框程序。但那个程序一旦生成它就变成了一个独立的程序跟数据字典的连接就断开了。你后面改了表结构加字段、改长度维护程序不会自动同步必须重新激活有时候还要删掉重新生成。很多新手以为表结构和维护程序是绑定的改了表激活一下就完事结果上线后用户去SM30里维护数据屏幕字段还是老样子甚至保存时报字段不存在。这种问题排查起来也简单去看那个维护程序的屏幕描述对比一下表结构不一致就是没同步。但根治的办法是别依赖自动生成自己写一个维护视图或者用事件块控制字段校验虽然工作量大了点但至少改动可控不至于被生成器牵着鼻子走。数据字典里还有个东西叫搜索帮助(Search Help)它和程序联动的坑也不少。最典型的是你在屏幕上的P_xxx字段直接挂一个搜索帮助程序里没有做任何F4处理运行时也能弹出来但你要是想根据屏幕其它字段动态过滤搜索帮助的返回值就得在POV事件里写代码。很多人不知道搜索帮助的返回值是回到屏幕字段的不是直接进你程序变量。你如果想在用户点击F4选中值之后立刻做一些联动比如把其它字段带出来必须用FIELD … MODULE … ON REQUEST或者用搜索帮助的“影响字段”配置。我平时更习惯直接在POV里调用F4IF_INT_TABLE_VALUE_REQUEST自己控制返回表因为表维护里生成的搜索帮助逻辑太隐蔽出了问题不好追。还有锁对象。数据字典里生成锁对象后系统会自动生成两个函数模块ENQUEUE_和DEQUEUE_。你在程序里调用它们来加锁解锁。但注意锁参数是表的非空字段还要严格按字段顺序传值。特别是那些用了SYSTEM缓存机制的表如果你只锁了部分字段别的会话还能改同一行数据。我曾经遇到过一个连续保存多次导致数据重复的问题查到最后是锁对象没建全只锁了公司代码和单据号漏了项目号两个会话同时插入不同项目号的数据就互相没拦住。所以你在设计锁的时候一定要把能唯一标识业务行的所有字段都放进去别嫌麻烦。也别指望ABAP会自动帮你检查锁冲突你得在加锁之前自己试着读一遍锁参数加锁之后再判断返回码SY-SUBRC不等于0就要提示用户。说回程序和数据字典的编译期联动。ABAP里最常见的是INCLUDE STRUCTURE比如INCLUDE STRUCTURE BSEG。这个语句把数据库表的字段引入到局部结构里方便你定义内表或者工作区。但要注意INCLUDE STRUCTURE展开的字段名和表里完全一致如果程序里已经有同名字段会被覆盖。我踩过这个坑在报告程序里定义了一个局部字段叫BUKRS用来存临时值然后INCLUDE STRUCTURE BSEG结果BUKRS被重新定义成BSEG的公司代码临时值全丢了。后来我学乖了所有INCLUDE STRUCTURE之前先把局部变量重命名或者把INCLUDE放在所有数据声明的最前面防止覆盖。另外使用DATABASE TABLE直接定义内表时比如DATA: GT_ITAB TYPE STANDARD TABLE OF BSEG WITH HEADER LINE. 这种写法是把BSEG的结构完整复制到内表包括那些非关键字字段的默认值、转换例程。特别是转换例程这是数据字典和程序联动最隐蔽的地方。比如一个字段在数据元素里挂了CONVERSION_EXIT_ALPHA_OUTPUT或者INPUT你程序里写入内表时如果直接用MOVE那么系统会自动调用转换例程对值做处理。比如物料号外部输入可能是“0000001000”你MOVE进内表后它可能自动变成“1000”因为ALPHA输出转换去掉了前导零。然后你再拿这个内表去数据库查询就会查不到因为数据库里存的是带前导零的。反过来你从数据库读到内表显示时可能自动加了前导零屏幕上一长串用户看着也懵。这种转换在SE16查看数据时看不到因为SE16显示的是原始存储值但程序里访问就会触发转换。解决办法是在程序里用“RAW”方式强制关闭转换或者干脆在SELECT之前用CONVERSION_EXIT_ALPHA_INPUT显式加重前导零。我建议你对所有关键字段都做一次显式转换别依赖隐式行为。数据字典的激活状态也有讲究。一个表或域被修改后如果没有激活程序运行时仍然会使用旧版本的结构。你明明改了字段长度但程序里感觉不到因为程序引用的是激活状态的版本。这经常让我们误以为程序没改对其实字典还没激活。更麻烦的是当字典对象被修改但未激活时代码补全和语法检查会提示字段类型不匹配因为语法检查用的是未激活版本实际上语法检查用激活版本所以不提示。只有运行时才可能因为长度不匹配报异常。所以每次改完字典第一件事就是激活而且激活之后要去检查程序里引用该结构的变量是不是还符合预期。特别是删除字段的时候程序里如果还引用这个字段激活不会报错但程序一运行到MOVE对应字段就会dump。SE03可以查字典对象的使用清单但这个功能有时候索引不全不如用WHERE USED LIST查一下程序列表更可靠。还有一个常被忽略的是文本表。如果你定义的数据库表有文本表SPRAS语言字段那么用FOR ALL ENTRIES IN或LEFT OUTER JOIN关联抓取文本描述时一定要保证文本表的语言字段跟主表的关键字关联。很多业务表有语言无关的字段和语言相关的文本字段你连接时如果忘了SPRAS条件会从所有语言里随机挑一条结果内表里出现德语描述、英语描述混在一起。正确做法是除了关联主键外再加一句AND T_SPRAS SY-LANGU。如果主表本身没有语言属性那就是所有语言共用文本表按主键关联即可。但ABAP在FOR ALL ENTRIES里有个特点它会把空值自动过滤掉如果你文本表里某些语言缺失内表里对应行会被丢弃导致主表数据带不出描述。这时候可以在文本表关联之前先用一个子查询把缺文本的行补齐或者用LEFT OUTER JOIN加NVL处理。我自己更倾向于把描述字段直接冗余在主表里用维护程序去同步虽然数据库范型上不好看但查询性能和数据一致性控制起来简单很多特别是报表场景省去了一堆JOIN和语言判断。再提一个关于类型组(TYPE-POOL)的东西。数据字典里可以定义类型组程序里用TYPE-POOLS语句引用。类型组里的类型常量比如YES/NO、TRUE/FALSE可以在程序里直接使用不用去SE11里查值。但类型组有个坑它一旦被某个程序引用就不能再修改了必须先把引用它的程序全部删除或解除引用才能改。这在开发时特别磨人。所以类型组适合放那些非常稳定、基本不变的定义。如果你的业务经常调整最好只把基本布尔枚举放进去复杂结构还是定义成数据元素或表类型稳妥。数据字典里的表类型(Table Type)和程序内表变量之间的转换也容易出问题。你定义了一个表类型ZZ_TT然后程序里DATA: GT_ITAB TYPE ZZ_TT. 接着你用APPEND往GT_ITAB里加行。看起来没问题但表类型的排序属性是字典里定义好的比如SORTED或者HASHED程序里默认继承这个属性。如果你在程序里对这种表类型用INSERT到指定位置会抛异常因为排序表不允许插入到任意位置。很多人没意识到这一点把表类型当STANDARD用结果运行时出错。反过来如果你把表类型定义成STANDARD程序里却想要求它唯一那就在程序里强制加SORTED BY和DELETE ADJACENT DUPLICATES。不要试图修改字典表类型的属性来适应程序那样会影响所有引用它的程序。不如在程序里定义局部类型更灵活。最后说一个大家都容易忽略的数据字典里的字段是带“域”属性的而域里可以定义输出长度、转换例程、值表。但程序屏幕里显示用的字段长度不一定等于数据库字段长度。你在SE11里看表结构比如字段ZQTY是QUAN类型域是MENG13输出长度是13。但屏幕上可以定义成只显示5位或者用CURRENCY格式加小数位。程序内部取值依然是MENG13但显示成多少位取决于你在屏幕的PROCESS BEFORE OUTPUT事件里怎么格式化。如果屏幕字段类型引用的是数据元素而不是数据库字段那么它和数据字典的耦合更弱。所以调一个屏幕字段显示不对别急着改程序先看屏幕字段的属性是直接引用数据元素还是通过程序变量间接引用。如果是直接引用改数据元素标签和输出长度就能影响屏幕显示如果程序变量那就是变量定义和屏幕字段类型不匹配。我遇到过最迷惑的一个case程序里变量定义参考了某个数据元素屏幕里另外一个字段手动输入了相同的数据元素名结果两边长度不一致程序里MOVE的时候自动做了舍入小数点后两位被截断用户填的金额少了几毛钱。后来我把屏幕字段类型改成和变量完全一致并在PBO里强制CLEAR转换才解决。经验之谈数据字典和程序联动本质上是一堆“约定”的集合。字典改一个属性程序里所有相关逻辑都是连锁反应。写程序之前一定要花十分钟把引用的表结构、数据元素、域、搜索帮助、文本表、锁对象全部过一遍别只盯着字段名一样就开干。改字典的时候改完激活然后去程序里搜索那些字段的MOVE、ASSIGN、SELECT、WHERE、SORT、比较操作逐个核对。尤其注意那些用了“等号”判断字符型字段的地方字符型带尾随空格你用MOVE到另一个长度不同的字段时空格会被截断或补齐比较结果会出乎意料。我一般会在所有关键比较之前用CONDENSE或SHIFT去掉空格或者用CS包含代替EQ。这不算什么高级技巧但能救你半条命。还有一个容易踩的是把数据库表的某个字段改成了可空NULL但程序里没做NULL处理。ABAP数据库表默认是NOT NULL但你可以通过“允许为null”选项修改。一旦允许空值程序里用SELECT单条读取时这个字段如果是NULL用普通变量接收会变成初始值但如果你用字段符号指向它可能拿到的是十六进制FF或者状态码显示为16进制。这时候用IF IS INITIAL判断可能失效因为NULL不是初始值。我建议数据库表设计时所有业务字段都禁止NULL实在允许缺失的话程序里统一用“空字符串”代替NULL别用数据库的NULL语义。写了这么多我是真心觉得数据字典和程序的联动不是“学一个工具”这么简单。它更像一张网每个字典对象都是一个结点程序里的每一行代码都是一根线。你要做的不是把每个结点背下来而是理解线的走向。真正出问题的时候往往不是字典定义错了也不是程序写错了而是某个结点在某次维护中被静默改变了程序还停留在旧的假设里。所以我会定期把数据字典里关键对象的激活时间、修改人导出到表格和程序版本提交记录对照着看一旦线上出问题能快速锁定是哪次变更引发的。给新人的话别只会在SE11里建表然后去SE38里写SELECT。多去注意表字段的“技术属性”页签里那些转换例程和预定义值去注意域的值表是否限制了合法值去注意搜索帮助的返回值到底是哪个字段。这些都在数据字典里但都和程序运行时的行为绑定。你把这些搞懂调试问题的速度至少快一倍。我见过太多人遇到数据不对就SET DEBUG去一行行追程序逻辑查了半天发现是字典里一个标签长度被改了那种感觉真的很冤。所以把数据字典当成程序的一部分来对待而不是一个独立的“存储设计”工具你的ABAP水平才能真正上一个台阶。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门