ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ABAP批量设置SAP后台JOB:三个FM搞定自动化调度

ABAP批量设置SAP后台JOB:三个FM搞定自动化调度 如果你在SAP项目上待过几年一定遇到过这样的需求业务部门的同事拿着一张Excel清单上面列着二三十个程序要求“每天凌晨3点跑这几个月初1号跑那几个每周五再跑另外几个”。第一反应是用SM36逐个建后台JOB但你很快会发现这活儿干到第十个就开始头疼了——重复点鼠标、容易漏参数、想比对都无从下手。更不用说是上百个作业一起铺到生产机上的情况。我写这篇东西就是想把“ABAP批量设置后台JOB”这件事彻底讲透。不管你是ABAP开发、BASIS运维还是被业务需求砸晕的FICO/MM顾问只要能看懂一点点ABAP代码就能照着我下面的方案搭一套批量建作业的程序顺便把后期批量修改、监控、排查的坑都补上。1. 批量设置后台JOB的整体思路与方案选型1.1 先搞清楚哪些场景需要批量建后台JOB很多刚接触SAP后台作业的人觉得“建个作业嘛SM36点几下不就行了”。确实三五个作业手工点一点没问题但批量建作业的需求一旦出现基本都是二十个起步。我归纳下来最常见的批量场景无非这么几类月结场景多个公司代码的物料账、CO月结、资产折旧、批量导入后转总账……一个月结项目动辄十几个甚至几十个程序每个都要按工厂、版本、财年参数跑一遍。如果每个程序都手工建作业还要选变式、填日期、设周期光点鼠标就能点废一只手。数据交换场景每天晚上要和外围系统交换数据的程序数量可以到几十个。尤其上了接口平台之后很多ABAP程序其实是“定时把数据导成文件、推到对方目录”的这种程序往往一个接口一个不批量建根本扛不住。报表推送场景按部门推送销售报表、库存报表到不同邮箱一个部门一个变式加起来就是几十个作业。这种还经常要“每周一早上8点推”“每个月最后一天推”调度逻辑还不一样。系统迁移场景S/4升级、系统分割、新环境上线新环境的作业体系要和旧环境一一对应。这时候靠人工去SM36里搬根本搬不完而且搬错了还没法快速复查。看清这些场景之后结论其实很自然批量设置后台JOB不是“要不要做”的问题而是“用什么方式做得又快又不出错”的问题。1.2 方案选型手工、BDC、写程序到底选哪个做这件事之前我先把市面上的方案拉出来对比了一遍大致有三条路方案优点缺点适用场景人工SM36逐条创建直观、零开发量十级以上体验极差、易漏易错、无法审计5个以内的一次性作业BDC录制回放不用写ABAP也大概能做屏幕字段变动就崩、审计困难、维护成本高少量且GUI界面长期稳定的作业ABAP程序调用标准FM参数可批量、可反复执行、有日志可审计、易扩展需要写一段ABAP初次搭建耗时任何数量尤其适合中大批量作业的管理我最后选的是第三条路写一个ABAP批处理程序内部调用SAP标准作业管理的函数模块Function Module。为什么这么选因为批量建作业这件事真正的价值不在于“建一次”而在于“之后能批量改、能重新铺、能拿日志对账”。用代码来做每次执行都有返回码、有消息日志作业建完还能把这批作业的清单再导出来这在生产环境做变更评审时是硬通货。反过来说BDC录制看起来省事但SAP升级后屏幕字段一变录制的脚本就废了。而标准FM从老版本到现在接口一直稳定这才是可以长期依赖的方案。2. 吃透三个核心FMJOB_OPEN、JOB_SUBMIT、JOB_CLOSE2.1 后台JOB的三层结构在写代码之前必须先理解SAP后台作业的模型。管作业就像管一份值班表它天生分三层作业头Job Header相当于值班表的标题区域记录作业叫什么名字、属于哪个用户、当前处于什么状态已保存/已释放/执行中/已完成。对应SAP表是TBTCO。作业步骤Job Step相当于值班表里每一行任务记录要执行哪个ABAP程序、用什么变式、要不要传参数。一个作业可以有多个步骤按顺序执行。对应SAP表是TBTCS。开始条件Start Condition相当于值班安排记录这个作业什么时候启动、多久重复一次、有没有结束日期。这些信息也挂在作业头上。理解了这三层你就理解了为什么SAP要把“建作业”拆成三个函数模块JOB_OPEN负责开一个空的作业头JOB_SUBMIT负责往作业头里塞步骤JOB_CLOSE负责设置开始条件并把作业“释放”。这样拆开的好处是一个作业可以挂多个步骤——比如先跑数据抽取再跑数据转换最后发邮件每一步都是一个JOB_SUBMIT调用。2.2 核心FMJOB_OPEN和JOB_SUBMIT先看第一个调用DATA: lv_jobname TYPE tbtco-jobname, lv_jobcount TYPE tbtco-jobcount, lv_subrc TYPE sysubrc. lv_jobname Z_MM_DAILY_INV_001. CALL FUNCTION JOB_OPEN EXPORTING jobname lv_jobname jobgroup space 作业组可选方便SM37里分组管理 IMPORTING jobcount lv_jobcount EXCEPTIONS cant_create_job 1 invalid_job_data 2 jobname_missing 3 OTHERS 4.重点在于JOB_OPEN只是“占了个坑”返回的JOBCOUNT非常关键。SAP允许同名作业存在同一个名字在同一秒可能建多次靠JOBCOUNT来区分。这个JOBCOUNT在后续的JOB_SUBMIT和JOB_CLOSE都必须原样传回去否则系统不知道你在操作哪个作业。然后是JOB_SUBMIT它的作用是给作业添加一个ABAP程序步骤DATA: lt_seltab TYPE TABLE OF rsparams. CALL FUNCTION JOB_SUBMIT EXPORTING jobcount lv_jobcount report ZMMR001 要执行的ABAP程序 variant lv_variant 变式名可空 authority_check X 执行前检查作业所有者的权限 TABLES seltab lt_seltab 直接传选择屏幕参数 EXCEPTIONS jobcount_missing 1 joblock 2 job_not_found 3 report_missing 4 OTHERS 5.这里的SELTAB参数值得单独说。后台作业里跑ABAP程序本质上等于有人打开SE38输入程序后按F8执行。执行时程序选择屏幕上的值从哪来两个来源一是变式Variant二是SELTAB直传。我后面专门讲这两个方式怎么取舍。2.3 核心FMJOB_CLOSE与调度周期参数JOB_CLOSE是整个流程的收口动作它负责设置开始条件、释放作业CALL FUNCTION JOB_CLOSE EXPORTING jobcount lv_jobcount jobname lv_jobname strtimmed space X立即执行优先级高于日期时间 startdate lv_startdate 首次执行日期 starttime lv_starttime 首次执行时间 enddate lv_enddate 结束日期可空 endtime lv_endtime 结束时间可空 prddays lv_prddays 每N天执行一次 prdhours lv_prdhours 每N小时执行一次 prdweeks lv_prdweeks 每N周执行一次 prdmonths lv_prdmonths 每N月执行一次 TABLES predjob_tab lt_predjob 前驱作业表用于作业链 EXCEPTIONS jobcount_missing 1 jobname_missing 2 joblock 3 job_not_found 4 OTHERS 5.这里有一个新手最容易踩的坑JOB_CLOSE不调用作业就一直是“已保存”状态调度器永远不会去跑它。很多人写完JOB_OPEN和JOB_SUBMIT就觉得完事了跑完批量程序回头一看SM37里全是“已保存”的幽灵作业。关于周期参数SAP的频率控制不是“每天”而是“每N天”。比如每2小时一次就传PRDHOURS2每3天一次就传PRDDAYS3。如果只传PRDDAYS1就是每天。只传日期时间不传周期就是单次作业跑完拉倒。2.4 变式与选择屏幕参数直接传参和变式方案的取舍到这一步批量建作业的核心逻辑已经很清晰了。但还有一个关键选择作业执行程序时运行参数怎么给方案一是变式。比如给ZMMR001建两个变式ZMMR001_001和ZMMR001_002分别代表不同工厂的参数组合。批量建作业时每个作业指定自己的变式代码最简单也最贴近业务习惯。缺点是每多一种参数组合就要先去SE38维护一个变式变式多了之后管理起来比较麻烦。方案二是JOB_SUBMIT的SELTAB直传。这个方案好处是参数全部写在清单里不需要事前维护变式缺点是代码稍麻烦每个选择条件都要按RSPARAMS结构构造一条记录。RSPARAMS的结构长这样DATA: ls_seltab LIKE LINE OF lt_seltab. ls_seltab-selname S_WERKS. 选择屏幕字段名 ls_seltab-kind S. S选择表P参数 ls_seltab-sign I. I包含E排除 ls_seltab-option EQ. 操作符 ls_seltab-low 1000. 低值 ls_seltab-high . 高值 APPEND ls_seltab TO lt_seltab.打个比方变式就是“一份配好的菜谱”直接传参就是“临时按今天手头的食材调整”。批量场景里如果程序参数特别固定用变式如果参数会随作业动态变化用SELTAB直传。实际项目里我见过混合用的变式管静态部分SELTAB管动态部分两种方式可以同时叠加SAP会合并生效。3. 实操从零写一个批量建后台JOB的ABAP程序3.1 程序功能设计选择屏幕与清单来源批量建作业的程序核心就是“一遍循环替代一百次手工点击”。设计上建议做四部分选择屏幕接收作业名前缀、程序名、变式、开始日期、开始时间、周期频率等公共参数。清单来源作业清单放哪最灵活的做法是放在一个内表里手动维护或者从服务器上的TXT/CSV文件读取。为了降低门槛我这里以内表为例你看完改成读文件也不难。主循环遍历清单逐个创建作业。日志输出把每个作业的创建结果作业名、JOBCOUNT、返回码、消息收集起来最后统一展示。选择屏幕我建议这么做PARAMETERS: p_jobpfx TYPE tbtco-jobname OBLIGATORY, 作业名前缀 p_date TYPE sy-datum DEFAULT sy-datum, 首次执行日期 p_time TYPE sy-uzeit DEFAULT 030000, 首次执行时间 p_prddays TYPE int4 DEFAULT 0, 每N天0单次 p_imm AS CHECKBOX. 是否立即执行 SELECT-OPTIONS: s_prog FOR raldbdir-report, 要执行的程序名多选 s_var FOR raldbdir-variant. 变式名可空这里我把清单简化为“从选择屏幕多选程序名”每个程序建一个作业。如果同一个程序需要建多个不同参数的作业那就把清单换成内表维护或者读配置文件。生产上我更推荐做成“作业配置表”的方式建一张自建表存作业名、程序名、变式、调度时间程序读表循环。这样以后批量改作业直接改配置表重新跑一遍生成程序就行。3.2 主流程代码循环、开作业、塞步骤、关作业下面这段是标准的批量建作业主体我尽量写得接近生产可用的样子DATA: lt_log TYPE TABLE OF zjob_log, 日志表字段见3.4 ls_log LIKE LINE OF lt_log, lv_jobname LIKE tbtco-jobname, lv_jobcount LIKE tbtco-jobcount, lv_subrc TYPE sysubrc. LOOP AT s_prog INTO DATA(ls_prog). CLEAR: lv_jobcount, lv_subrc. 用前缀序号生成作业名SAP作业名最长32位 CONCATENATE p_jobpfx sy-tabix INTO lv_jobname SEPARATED BY _. CONDENSE lv_jobname. 1. 打开作业 CALL FUNCTION JOB_OPEN EXPORTING jobname lv_jobname IMPORTING jobcount lv_jobcount EXCEPTIONS cant_create_job 1 invalid_job_data 2 jobname_missing 3 OTHERS 4. IF sy-subrc 0. ls_log-jobname lv_jobname. ls_log-rc sy-subrc. ls_log-msg JOB_OPEN失败. APPEND ls_log TO lt_log. CONTINUE. ENDIF. 2. 塞入程序步骤这里演示带变式和SELTAB直传结合 CALL FUNCTION JOB_SUBMIT EXPORTING jobcount lv_jobcount report ls_prog-low variant lv_variant 变式可为空 authority_check X TABLES seltab lt_seltab EXCEPTIONS jobcount_missing 1 joblock 2 job_not_found 3 report_missing 4 OTHERS 5. IF sy-subrc 0. ls_log-jobname lv_jobname. ls_log-rc sy-subrc. ls_log-msg JOB_SUBMIT失败跳过JOB_CLOSE. APPEND ls_log TO lt_log. CONTINUE. ENDIF. 3. 设置开始条件并释放 CALL FUNCTION JOB_CLOSE EXPORTING jobcount lv_jobcount jobname lv_jobname strtimmed p_imm startdate p_date starttime p_time prddays p_prddays EXCEPTIONS jobcount_missing 1 jobname_missing 2 joblock 3 job_not_found 4 OTHERS 5. IF sy-subrc 0. ls_log-jobname lv_jobname. ls_log-rc sy-subrc. ls_log-msg JOB_CLOSE失败作业可能已保存但未释放. APPEND ls_log TO lt_log. ELSE. ls_log-jobname lv_jobname. ls_log-jobcount lv_jobcount. ls_log-rc 0. ls_log-msg 创建成功. APPEND ls_log TO lt_log. ENDIF. ENDLOOP.注意两个细节一是JOBCOUNT变量必须在每一轮循环里重新获取千万不要在循环外定义一次就反复用否则所有JOB_SUBMIT和JOB_CLOSE都会指向第一个作业二是如果JOB_SUBMIT失败就不要再去调JOB_CLOSE了不然会关掉一个只有作业头、没有步骤的残缺作业。3.3 周期作业与“每月最后一天”的处理周期作业听起来简单实际配置的时候坑不少。最常见的是“每月最后一天执行”这种需求。SAP的每月周期调度基于“日期号数”如果你配每月31日那2月、4月、6月这些没有31日的月份这个作业根本不会触发。很多业务人员不懂这个上来就说“每月最后一天跑”你要是不加处理漏数据了就是他来找你。工程上推荐两种做法变通调度把作业调度为“每日运行”在程序内部判断“今天是不是该月的最后一个自然日或工作日”不是就直接EXIT。这样无论大小月都能覆盖。拆成多个作业如果业务逻辑允许就拆成“每月1日跑上月数据汇总”这种形式避开“最后一天”的歧义。我见过因为“每月31日”调度习惯导致2月数据漏处理的真实事故所以批量建作业时凡是涉及月末的调度我都会额外在日志里标注“请确认是否用每日判断逻辑兜底”。另外周期作业和单次作业的释放状态也不同。单次作业跑完就变成“已完成Finished”周期作业跑完一次后会自动生成下一次运行计划状态一直保持“已释放”。批量程序如果建的是周期作业要提醒用户不要看到作业列表里“已释放”就以为它异常点进周期条件里看下一次运行时间才是正确姿势。3.4 结果输出一个可以复盘的自检清单批量程序跑完以后如果没有一份清晰的日志那跟没做一样。我习惯给日志表设计这些字段序号、作业名、JOBCOUNT、程序名、变式、开始日期、开始时间、返回码、消息文本、是否周期。最后用ALV输出或者简单直接WRITE。如果你懒得写ALV用WRITE也完全没问题。但注意把“作业名JOBCOUNT”打出来这是后续去SM37定位问题的钥匙。JOBCOUNT不要只放在内部变量里一定要落到日志否则同名作业出现问题时你根本不知道是哪一次创建留下的。还可以顺手加个“测试模式”开关程序只打印要创建的作业清单不真正调用JOB_OPEN。这个开关在刚上线时特别有用可以先对一遍配置再真正执行。别小看这个功能批量操作最怕的就是“跑完了才发现清单里有个程序名打错”。4. 批量作业创建后常见问题与排查技巧4.1 作业建了但到点没跑这是批量建作业之后最常被问的问题。排查顺序有讲究看状态SM37里查这个作业是“已保存”还是“已释放”。如果是“已保存”说明JOB_CLOSE没走对作业没有被释放调度器根本不知道它存在。看时区SAP后台作业的时间用的是服务器时区不是你的电脑时区。你觉得“凌晨3点”服务器上可能是另一个时间。看调度条件如果建的是周期作业检查有没有设“结束日期”。比如作业从今天开始、昨天结束这种倒挂条件会让作业立即失效。看作业所有者权限JOB_SUBMIT时如果开了AUTHORITY_CHECK作业执行时会检查所有者对程序的使用权限。如果所有者的权限对象不全作业虽然显示了“已释放”实际运行时可能秒失败。看作业日志SM37选中作业点“作业日志”红色行就是失败点。这一步能解决一大半“作业没跑”的疑问。4.2 同名作业与JOBCOUNT的坑SAP允许同名作业存在但同一名字下用JOBCOUNT区分。很多批量程序在循环里用的是同一个工作区变量第二轮循环开始前没有重新获取JOBCOUNT最终导致JOB_CLOSE关错了作业。我调试过类似问题表面看着“作业都建了”实际前几个作业被改得乱七八糟。正确做法是每一轮循环都重新调用JOB_OPENJOBCOUNT变量在每轮循环都被重新覆盖并且JOB_SUBMIT、JOB_CLOSE的传入值必须用“当前轮的JOBCOUNT”。另外如果业务上不允许同名作业建作业前最好查一下TBTCO发现同名作业且状态为“已释放”就直接跳过避免覆盖。4.3 变式、权限、释放三连问这里把常见报错整理成一张速查表方便排查报错/异常可能原因处理方式JOB_OPEN返回CANT_CREATE_JOB权限不足或作业与现有对象冲突检查S_JOB、S_BTCH_*权限对象检查作业名JOB_SUBMIT返回REPORT_MISSING程序不存在或未激活在SE38里确认程序状态激活后再跑JOB_SUBMIT返回JOBLOCK作业正被其他进程锁住用SM36查看作业释放锁后重试作业状态一直是“已保存”JOB_CLOSE未成功执行检查JOB_CLOSE返回码确认STRTIMMED/日期时间参数变式相关报错变式不存在或归属用户不对在SE38的变式维护里确认注意区分“全局变式”和“用户变式”作业执行失败但状态是“已完成”程序本身运行出错看作业日志检查程序变式参数是否有效变式这个点要特别强调SAP的变式是有“归属用户”概念的。如果JOB_SUBMIT指定了变式但这个变式是某个用户私有的作业执行时如果所有者不对可能直接报“变式不存在”。所以批量建作业前先把变式统一改成全局变式或者明确指定用哪个用户的变式。4.4 批量修改已有作业的方法“批量建完成了但业务又说程序换了、时间要改”——这种需求太常见了。修改已有作业最安全的方式不是直接UPDATE表而是“重建”。因为你手工去UPDATE TBTCO和TBTCS很容易破坏作业内部的一致性而SAP标准作业管理接口并没有提供“批量更新步骤”的API。我的做法是建一张作业配置表ZJOB_CFG里面存作业名、程序名、变式、频率、开始时间。批量建作业程序读这张表生成作业。要修改时先在SM37里把旧作业释放掉然后改ZJOB_CFG再跑一遍生成程序。这样既满足变更可控又能保留变更痕迹比直接改表稳得多。4.5 创建周期作业后怎么验证“下一次运行时间”批量建完周期作业别急着走。验证的黄金标准是SM37里按作业名前缀筛选逐个看“周期作业”页签里的“下一次开始时间”。如果时间合理这批作业才算真正落地。如果你要在ABAP程序里做自动校验可以用标准函数BP_JOB_SELECT查询作业状态也可以直接读TBTCO的SDATE、STIME、PERIODIC字段。API方式和表直读各有优劣BP_JOB_SELECT会处理作业状态逻辑但性能一般TBTCO直读速度快但需要你自己理解字段含义。我的建议是人工确认用SM37程序校验用BP_JOB_SELECT两者结合最稳妥。5. 批量后台JOB的最佳实践与进阶玩法5.1 作业命名规范与模板化批量管理的核心是“一致性”。作业命名如果不讲规矩过两个月谁也看不懂谁是谁。我在项目里推的命名规范是Z_模块_业务_频率_序号 Z_MM_DAILY_INV_DAY_001 Z_FI_MONTHLY_ACC_CLOSE_002为什么要这么干因为SM37本身支持按作业名前缀筛选你也可以在批量程序里按“Z_MM_%”这种条件把某个模块的作业全部捞出来统一处理。如果当初建作业时用了“001”“TEST1”这种名字后面批量维护就是灾难。更进一步可以把作业模板化建一张作业注册表放过“作业名、程序名、变式、频率、开始时间、所有者、是否启用”所有作业都通过这个表生成。这样作业变更就走配置表审批而不是有人偷偷在SM36里手工加了个作业。5.2 作业链让作业按顺序跑批量建作业到一定阶段你一定会遇到“程序A跑完才能跑程序B”的需求。SAP作业链的标准做法是在JOB_CLOSE阶段指定前驱作业。前驱作业执行成功后后继作业才会被触发。批量程序里实现作业链思路也很清晰先建前驱作业把前驱的JOBNAME和JOBCOUNT记住再建后继作业时把前驱信息放进PREDJOB_TAB。注意前驱作业的JOBCOUNT也要传否则系统不知道你指的是哪一个同名作业。不过要提醒一句传统作业链中后驱作业的触发依赖于前驱作业的运行状态。如果前驱作业运行了但返回了异常退出后驱可能不会触发。这个行为在不同版本里细节有差异上线前一定先拿两个测试程序验证。5.3 监控与告警人不能一直盯着SM37批量作业建完只是第一步真正体现运维水平的是“监控”。最简单的落地方案是再建一个“作业健康检查”的每日作业读TBTCO和作业日志找出当天运行失败的作业清单用SAP发送邮件给运维组。这个健康检查程序本身也是一个后台作业等于“用我们批量建的作业去监控我们批量建的作业”。再往上走可以用CCMS告警RZ20、Spool列表检查等方式。如果项目上了Solution Manager也有现成的作业管理监控功能。但不管用哪套核心思路都一样作业失败要有自动化通知而不是用户发现数据不对了才来问你。5.4 一个完整的落地流程建议最后把我经验里比较顺的落地流程整理出来你可以直接照着走梳理作业清单程序名、变式、调度频率、所有者、参数成配置表。开发批量建作业程序先做“测试模式”验证清单。在开发/测试环境跑全量SM37按前缀筛选核对作业数和状态。挑1个最小作业单独跑一遍确认“建完能正常执行、能产生预期结果”。生产环境按配置表一键铺开保留执行日志。日常用健康检查作业巡检失败自动告警。变更时修改配置表重新生成作业替代人工手工批改。这套流程最大的好处是批量建作业不再是“一次性脚本”而是变成一套可以长期使用的运维能力。往后任何人接手只要会读配置表、会跑生成程序就能管理成百上千个后台作业。我个人在实际操作中的体会是批量建后台JOB最容易被忽视的两件事一是变式的归属用户问题二是周期时间跨时区后的校验。我第一次在生产上批量铺三十个作业时就因为没验证“下次执行时间”结果有一批作业在系统时间凌晨零点就开始狂跑把测试机拖得半死。后来养成了一个习惯不管建多少个作业永远先挑一个最小的作业跑通全链路再批量执行并且每次批量后用SM37按作业名前缀筛一遍把“已释放”和“下次运行时间”逐个截图留档。毕竟后台作业是系统里最自动化的部分也是最需要纪律的部分。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进