
简介KettlePDI开发中循环读取结果集并传入下一个转换是批量数据处理与动态传参的常见需求这份PDF围绕t1.ktr、var.ktr和j1.kjb三个核心文件完整演示了如何用JavaScript步骤获取前序结果集通过作业变量tables、id、name、size、i实现循环控制与数值传递再由var.ktr将加工结果输出到本地txt。内容包含可复用的脚本片段、变量读写写法、作业与转换的配置细节适合已有一定PDI基础、需要优化多表或批量数据抽取逻辑的ETL开发者。资源以单个PDF文档呈现大小约130KB结构紧凑便于按步骤对照实践目前已有3113人浏览学习说明该场景在实际项目中较为常见。通过该文档读者可掌握结果集遍历与变量传递的组合方式减少重复开发在Kettle中更灵活地构建自动化数据流。1. 面对Kettle结果集循环别急着写脚本先搞懂这个场景到底卡在哪做数据抽取的老手基本都撞过这堵墙源表里有几千上万行主键需要把每一行作为一个查询参数喂给另一个转换去查明细、调接口、生成报表。拿表输入一次把数据全抽出来不难难的是每一行都要触发一次下游处理。Kettle里最顺手的解法就是标题这条路径——循环获取结果集中的数据再把当前行的字段传进转换里执行。这个方案能解决批量ID查明细、逐行调WebService、按单据号补拉增量这种一行一请求的典型需求适合已经能跑通基础Kettle转换、但还没理清Job和Transformation怎么配合的从业者。先说个反直觉的结论循环不写在转换里而是写在作业里转换不是被调用循环包住而是被结果集的每一行触发的。想通这一点后面所有配置都顺了。2. Kettle的分层循环模型Job与Transformation在什么时候各干各的活2.1 Job和Transformation的分工为什么循环必须放在作业层Kettle里有两套执行单元作业Job管流程转换Transformation管数据。作业比作流水线的传送带转换是传送带上的工位。工位内部可以处理一批数据但工位与工位之间怎么衔接、某个工位要不要反复做那是传送带的职责。结果集循环恰恰是同一段流程要反复执行所以必须放在作业层。很多新手会试图在转换内部用表输入 循环来解决问题结果写出来的步骤非常别扭要么用JavaScript步骤硬套循环要么用执行SQL脚本来回折腾。以我在生产环境跑过的项目看正确拆法是作业层有两个转换——第一个转换负责把要循环的ID或主键集合写入Kettle结果集第二个转换在作业循环体里负责接收结果集的当前行字段执行真正要做的抽取或处理。作业里从从结果获取记录这个步骤开始后续的转换会逐行执行。2.2 结果集的传递机制复制行到结果与从结果获取记录的前后关系结果集这个概念很多人没在意它其实是Kettle作业和转换之间的一块共享内存区域。第一个转换里用复制行到结果步骤把当前流里的字段以行记录的形式塞进结果集作业里用从结果获取记录步骤把它读出来。这两个步骤天然配对一个写一个读。关键参数有两个都在复制行到结果的配置里一个是结果集名称默认叫结果可以改成业务语义的名字比如id_result但注意作业里取的时候要对应另一个是结果集大小或结果过期时间默认场景下缓存到内存行数大时要估算内存开销。有一个容易被忽视的点是复制到结果在转换末尾的放置位置——它必须放在流程最后一个步骤否则后面再有步骤还是会把结果追加进同一块共享区域导致循环轮次虚增。我一般会在写结果集前用一个过滤记录或去重步骤做一轮清洗避免把重复ID带进循环。从结果获取记录这个步骤是循环的开关所在。放在作业里后面跟一个转换Kettle会逐行读取结果集每读一行就把该行字段以变量形式暴露给后续转换。作业链是顺序执行的所以每次读到的行都会触发一次后续转换的完整执行。要利用好这个机制有一句口诀作业里第一个转换放复制行到结果循环体开头放从结果获取记录被循环的转换放在它后面。2.3 结果集循环和表输入每一行执行的本质区别有经验的读者可能会想表输入步骤里有个每一行都执行上一个步骤的结果选项是不是也能当循环用能但那是另一套机制。它会把变量逐行替换进SQL语句在同一个转换内部完成每行查一次适合参数直接拼进SQL的简单场景。它的短板是没法在一个转换执行完一批完整逻辑后再换参数重来比如你要调外部WebService、要生成文件、要执行一次存储过程这些都无法用每一行执行可靠地串联。结果集循环的优势在于作业层。循环体里不只放转换还可以放检查表是否存在执行SQL脚本发送邮件等作业步骤。这是标题这套方案真正的价值边界它能循环复杂多步骤流程不只是循环一条SQL。代价是多一层作业调度日志和排错要跨两层看。所以我的选型标准一般是只查数据库用每一行执行就够了要串多个处理环节用结果集循环。3. 搭建作业主循环从造数到结果集的最小可跑通配置3.1 先造一张可循环的测试表再动手配作业按老规矩任何方案先在本地用最小数据集验证。这里先建一张订单表和一张订单明细分表模拟主表查出一条订单ID再逐ID查明细的经典场景。-- 造一张简单订单主表 CREATE TABLE demo_orders ( order_id VARCHAR(20) PRIMARY KEY, order_date DATE, status VARCHAR(10) ); -- 造一张订单明细表 CREATE TABLE demo_order_items ( item_id INT PRIMARY KEY AUTO_INCREMENT, order_id VARCHAR(20), sku VARCHAR(20), qty INT ); -- 插入测试数据让循环能跑出至少三轮 INSERT INTO demo_orders VALUES (ORD001, 2025-01-01, PAID), (ORD002, 2025-01-02, PAID), (ORD003, 2025-01-03, UNPAID); INSERT INTO demo_order_items (order_id, sku, qty) VALUES (ORD001, SKU_A, 2), (ORD001, SKU_B, 1), (ORD002, SKU_A, 3);如果你本机没装MySQL用H2文件库或SQLite也能跑通只要表结构和插入语句等价即可。这里用MySQL语法是因为它在生产环境最常见连接配置不用额外讲。3.2 主作业的关键XML片段把复制行到结果和循环开关一次配好Kettle作业保存为.ktr和.kjb文件本质是XML。下面这段是主作业里配置循环的关键片段省去了画布上的坐标信息。你在Spoon里通过图形界面配置最终生成的XML逻辑与这段等价。job entries entry name抽取订单ID列表/name typeTRANS/type transformation抽取订单ID.ktr/transformation /entry entry name从结果获取记录/name typeGET_ROWS_FROM_RESULT/type single_vmtrue/single_vm clear_result_rowstrue/clear_result_rows send_single_rowtrue/send_single_row /entry entry name按订单ID查明细/name typeTRANS/type transformation按订单ID查明细.ktr/transformation /entry /entries hops hop from抽取订单ID列表/from to从结果获取记录/to /hop hop from从结果获取记录/from to按订单ID查明细/to /hop /hops /job这段XML里最要命的是send_single_row参数它就是循环的开关。置为true表示每次向后续步骤只暴露一行记录后续转换每执行一次只处理一条订单ID如果没有这个参数Kettle会把整个结果集一次性传给下游等于没循环。另一个是clear_result_rows它的作用是每轮读完后清空结果集防止下一轮把历史数据又带进来。3.3 抽取订单ID的转换怎么收尾才能正确写入结果集主作业第一步的抽取订单ID.ktr转换内部很简单表输入读订单表 → 字段选择 → 复制行到结果。但有一个细节就是表输入前尽量先做排序或者去重否则结果集里有重复ID循环会多跑好几轮。这个转换的最终XML大致是transformation step name读取订单ID/name typeTableInput/type sqlSELECT order_id FROM demo_orders WHERE status PAID/sql /step step name复制到结果/name typeCopyRowsToResult/type result_set_nameRESULT/result_set_name /step hop from读取订单ID/from to复制到结果/to /hop /transformationresult_set_name这里用默认的RESULT主作业的从结果获取记录不需要手动指定结果集名它会自动关联当前Job上下文的结果集。如果你建了多个结果集才需要明确指定。这套配置跑通后Kettle会严格按读取全部ID → 逐行触发查明细的顺序执行不会出现下游转换先于结果集准备好的情况因为作业里的hop是有顺序的。4. 结果集数据传入转换变量映射与字段对接的实操配置4.1 结果集的行字段如何变成转换里的可用变量循环通了下一个问题就是结果集的当前行怎么进入转换。Kettle会在从结果获取记录步骤执行时把当前行的每个字段名变成作业级变量命名规则就是字段名本身。比如结果集里有ORDER_ID字段那后续转换里直接写${ORDER_ID}就能拿到当前行的值。字段名大小写敏感Oracle或MySQL返回的字段别名不同变量名也跟着不同这是最常翻车的点建议在源转换里统一用字段选择把别名改成固定小写。但这套机制有个边界变量只在作业的后续作业条目里生效不会自动穿透到转换内部每个步骤。转换里如果用表输入直接写SELECT * FROM demo_order_items WHERE order_id ${ORDER_ID}这时Kettle会做变量替换原则上是能用的。但如果你在同一个转换里换了字段名或者变量里有特殊字符替换就会出幺蛾子。所以更稳的做法是在转换入口加一个获取变量步骤把变量显式映射成流里的字段。step name获取外部变量/name typeGetVariable/type fields field nameorder_id/name variableORDER_ID/variable typeString/type /field /fields /step这个获取变量步骤是结果集数据进入转换流的正式入口。它把作业级变量ORDER_ID读成流里的order_id字段后面任何步骤都能拿order_id当普通字段用。type建议显式指定String、Integer、Date之间不要混用否则数字类型的订单号被当成字符串查SQL时可能命中不了索引。4.2 用设置变量步骤做数据清洗的补充传递有些场景里结果集字段不是你想传的最终参数比如要从ORDER_DATE推导出start_date和end_date或者要把状态码转成业务描述。这时可以在源转换里加设置变量步骤把新算出来的字段一并写进变量区再进结果集。设置变量的作用域要选作业内有效这样才能被作业后续条目读出来。// 在JavaScript代码步骤里做字段加工也可以用计算器替代 var orderDate new Date(order_date.getTime()); var startDate new Date(orderDate.getFullYear(), orderDate.getMonth(), 1); var endDate new Date(orderDate.getFullYear(), orderDate.getMonth() 1, 0);加工完再通过设置变量输出两个新变量START_DATE和END_DATE。这样循环体里的目标转换一笔订单就能拿到该月的起止日期不用自己再算一遍。用JavaScript做日期运算时注意Kettle里的order_date字段类型可能是java.util.Date要调用getTime()等Java方法和纯JavaScript语法略有差异。4.3 在目标转换里验证当前行参数是否生效配置做完别急着跑全量先让目标转换输出一行验证数据。最常见的验证方式目标转换里放一个表输出或文本文件输出把接收到的order_id和qty写出来然后看输出文件里是否有三行数据、每一行是否对应一个订单ID。日志里还可以看到按订单ID查明细 - 开始处理这类步骤日志出现次数等于结果集行数。如果验证发现所有行都用了同一个ID十有八九是send_single_row没设成true。如果是循环体整个没执行看作业日志里从结果获取记录有没有读取到记录——读不到就去查源转换复制行到结果是否真的写入了。Kettle的日志默认是黑匣子建议把日志级别调到Basic以上至少能看到每个作业条目是否执行必要时调到Debug能看到GetVariable步骤读到的实际值。5. 结果集循环的避坑清单死循环、内存溢出、变量玄学与类型错乱5.1 现象作业一直重复执行同一个结果集日志里从结果获取记录永不结束原因多半是结果集没被消费掉。clear_result_rows参数没开每跑一轮结果集里还残留上一轮的行下一轮又从第一行开始读形成一个永不停歇的怪圈。还有一个隐蔽场景就是源转换和目标转换都往结果集里写数据把循环体里的输出又追加回同一个结果集越攒越多。解决clear_result_rows设为true确认只有源转换有复制行到结果目标转换末尾不要放任何写结果集的步骤。如果必须在目标转换里保留中间结果用不同的结果集名称区分别混用默认的RESULT。5.2 现象变量传到了目标转换但值全是空字符串或null原因有两类。一类是作业从结果获取记录和后续转换的hop没有把传变量开关打开。Spoon里的hop是可以配置是否传递变量的鼠标点击hop后下方属性区有这个选项默认可能没勾。另一类是字段类型不匹配——比如源表order_id是整数结果集里默认按字符串存目标转换里按整数变量去匹配就匹配不上。解决先在hop属性里勾选传递执行结果变量再在目标转换的获取变量步骤里把类型设成和源表一致。这里没有银弹老老实实逐个字段核对类型。日期字段尤其容易坑Kettle结果集里的日期是java.util.Date目标库如果是Oracle驱动转换不了时要在源转换先转成字符串。5.3 现象结果集行数不大跑起来却把内存撑爆了原因不一定是结果集本身。从结果获取记录把记录传给目标转换后目标转换里如果有表输入步骤每跑一轮新建连接几千轮跑下来连接池会堆积。内存与连接双高表现就是越来越卡最后OOM。解决源转换和循环体里都别直接用一个裸表输入连数据库先把数据库连接池连接名配置里的连接池开关打开。更稳的做法是循环体转换里用数据库查询步骤替代表输入因为它天然按行缓冲。另外复制行到结果的结果集大小不要设成无限给个上限比如5000行写满就分片循环这是生产环境用过觉得最实用的控制手段。5.4 现象循环体内转换里的SQL执行了但每次用的都是最后一行参数这个现象最迷惑。根因不是在从结果获取记录而是目标转换里用了表输入步骤并且它的SQL里直接写了${ORDER_ID}。Kettle的变量替换生效时机是转换启动时抽取SQL理论上每次执行都是新转换、新参数不该出问题。但有一种情况会让它变成最后一次参数作业里把目标转换定义成了同一个转换实例复用而不是每次新建。Spoon的作业条目上有个每次执行都创建新连接之类的选项没勾的话Kettle可能复用已加载的转换。解决目标转换条目上把每次执行创建新的转换实例勾上或者在目标转换的字段选择步骤里加一个字段值检查打印当前参数到日志眼见为实。这属于典型的Kettle玄学查日志比改配置管用。5.5 现象中文字段名或带空格的表名在变量传递时全部失效原因很简单Kettle变量名解析依赖${}占位符中文字段名、带横杠的字段名解析时会出问题。不是不能用而是解析规则有边界。解决所有要循环的字段在源转换里先经字段选择统一重命名为纯英文小写无空格名称再进结果集。这条建议写进你的开发规范里能省掉后面所有排查时间。还有一种情况是字段名里带-变量名会被截断同样必须改名。Kettle的字段重命名成本极低别懒这一下。6. 进阶用法分批循环、前置检查与一条龙验证技巧6.1 用结果集长度做分批控制避免下游接口被瞬时打爆标题场景里结果集一旦量大比如几十万行主键要逐行调外部接口Kettle默认逐行处理对下游服务很不友好。我常用的做法是在源转换里按固定片长切分结果集比如每500个订单ID切一个包作业循环体里对每个包单独调用转换转换内部再做一轮记录集处理。这样外部接口看到的是500笔一批的流量不是每秒并发几千。具体实现上源转换的SQL里加LIMIT和OFFSET控制每批数量用作业变量${batch_index}推进偏移量。外层用一个循环批号的JavaScript作业条目维护批次号批完就退出不批完就继续读下一批。这个模式可以配合Kettle自带的检查结果步骤检测是否还有剩余数据替代手动维护循环计数器。6.2 在下游转换前加前置检查防止空结果集跑出脏数据结果集循环里最怕一种情况源表当天没有新数据结果集为空循环体里的转换却因为变量为空仍然执行了一次往目标表里插了一条全空ID的记录。加一道防线在作业里从结果获取记录与目标转换之间插入一个检查字段是否有值或校验结果集是否为空的作业步骤可以在Spoon的作业面板里配置条件分支空结果直接跳到结束不执行下游。这条在增量同步场景特别重要。数据源当天没产生新订单是常态不能因为没数据就把下游表搞出一堆垃圾行。加上前置检查以后每次调度跑完你只需要确认作业的最终状态是未执行还是成功不用再去翻目标表里有没有空值记录。6.3 最后收尾一条龙验证循环轮数与数据完整性我把这套方案交付给同事时总会让他们跑一次验证闭环这里分享一个实用的技巧目标转换末尾加一个写日志步骤打印当前处理的order_id。然后把作业日志导出来用下面这行shell命令统计循环轮数grep 开始处理订单 kettle_job.log | wc -l如果统计出的轮数和源结果集行数相等说明循环没有漏跑如果比预期多说明结果集被重复消费。这个验证方法不用写检查程序就是Kettle日志常规操作但能把循环行为立刻变成可见的数据。我在一次真实项目里用这个命令秒查出结果集没有清空循环跑了三万二千轮而源数据只有三千行问题原理一下就清楚了——就是前面说的clear_result_rows没开。从那以后每次交付结果集循环方案我都会把这段日志统计命令行写进交付文档里让接手的人第一时间自己验证运行轮数与数据完整性。这套链路本身不难难的是把结果集生命周期和变量传递规则这两个看不见摸不着的机制真正吃透。希望帮到你。本文还有配套的精品资源点击获取