如何用 JCL 和 DFSORT 将记录均衡拆分到 N 个文件并保留学生分组?
可以实现,先确定分组边界
可以通过 JCL 编排处理步骤,再由 DFSORT 输出文件。记录数和 N 都可能变化时,可以先统计每个 Student number 的记录数,计算允许的拆分位置,再生成 DFSORT 控制语句。
不能直接依靠 SPLIT、SPLITBY 或 SPLIT1R 保证学生分组完整。这些参数按记录分配输出,不会自动把同一个 Student number 的全部记录视为不可拆分的单位。相关行为见 IBM OUTFIL 文档。
JCL 负责调用程序和连接数据集,边界的选择需要在处理逻辑中明确。
先把“尽量均衡”定义为具体规则
示例共有 26 条数据记录,各学生的记录数为:
| Student number | 记录数 | 累计记录数 |
|---|---|---|
| AA000001 | 4 | 4 |
| AA000002 | 8 | 12 |
| AA000003 | 1 | 13 |
| AA000004 | 1 | 14 |
| AA000005 | 1 | 15 |
| AA000006 | 1 | 16 |
| AA000007 | 9 | 25 |
| AA000008 | 1 | 26 |
每个文件的平均目标是 26 / 5 = 5.2 条。AA000007 有 9 条记录,只要不拆开这个学生,就一定有一个文件至少包含 9 条记录。
如果希望像示例一样,让各输出文件依次保存原文件中的连续区间,可以对每个理想累计位置选择最近的学生组结束位置。距离相同时,固定选择较早的位置。
这个规则能复现题目中的结果,但不保证在所有数据分布下都让各文件的记录数差异最小。
根据累计记录数选择切分点
设总记录数为 T,输出文件数为 N,第 j 个理想切分位置为:
q_j = j × T / N,其中 j = 1, 2, ..., N - 1。
允许切分的位置只能是学生组的末尾;另外加入位置 0,表示文件开头。示例中的候选位置为:
0, 4, 12, 13, 14, 15, 16, 25, 26
选择结果为:
| 理想累计位置 | 最近的分组边界 |
|---|---|
| 5.2 | 4 |
| 10.4 | 12 |
| 15.6 | 16 |
| 20.8 | 25 |
五个输出文件对应的记录区间为:
OUT1: 1 - 4
OUT2: 5 - 12
OUT3: 13 - 16
OUT4: 17 - 25
OUT5: 26 - 26
记录数分别为 4、8、4、9、1,与期望输出一致。这里的记录号指数据记录在输入流中的位置,不依赖示例末列的 Rec Seq Number 字段。
使用 DFSORT 输出这些区间
输入已经排序,并且只包含示例中的 26 条数据记录时,可以使用以下 DFSORT 控制语句:
OPTION COPY
OUTFIL FNAMES=OUT1,STARTREC=1,ENDREC=4
OUTFIL FNAMES=OUT2,STARTREC=5,ENDREC=12
OUTFIL FNAMES=OUT3,STARTREC=13,ENDREC=16
OUTFIL FNAMES=OUT4,STARTREC=17,ENDREC=25
OUTFIL FNAMES=OUT5,STARTREC=26,ENDREC=26
STARTREC 和 ENDREC 指定该 OUTFIL 接收的相对记录号范围,包含两个端点。相对记录号从 1 开始。具体定义见 IBM OUTFIL 控制语句说明。
在 JCL 中,需要将 SORTIN 指向输入数据集,将 OUT1 到 OUT5 分别定义为输出 DD,并通过 SYSIN 提供上述控制语句。输出数据集的分配参数应根据实际记录格式和站点要求填写。
这组控制语句只适用于当前样例。输入数量或学生分布变化后,必须重新计算边界。
推广到一般的 N
可将作业分成三个处理步骤:
- 顺序扫描已排序的输入,在 Student number 发生变化时结束上一组,记录组大小和累计记录数。
- 使用辅助程序计算边界,生成本次运行所需的 DFSORT 控制语句。辅助程序可以采用站点支持的 COBOL 或 REXX 等语言实现。
- 再次读取同一份输入,由 DFSORT 按生成的区间输出。
边界计算逻辑可以写成以下伪代码:
require N >= 1
boundaries = [0] + cumulative_group_counts
T = boundaries[-1]
cuts = [0]
for j = 1 to N - 1:
choose b from boundaries minimizing abs(N * b - j * T)
if tied, choose the smaller b
append b to cuts
append T to cuts
for k = 1 to N:
start = cuts[k - 1] + 1
end = cuts[k]
if start <= end:
generate an OUTFIL range for OUTk
else:
mark OUTk as an empty output
使用 abs(N * b - j * T) 可以避免浮点除法。实现时要确保数值类型能够容纳乘法结果。
不同理想位置可能选中同一个边界,从而出现空文件,这符合题目的许可条件。空区间应单独处理,不能生成 STARTREC > ENDREC 的控制语句;作业仍需按约定创建对应的空输出数据集。
实施时需要确认的细节
- 统计和最终拆分应读取同一份数据,否则按记录号计算的边界可能落入某个学生组内部。
- 上述 26 条记录不包含示例中的说明行和列标题。如果实际文件存在表头或尾记录,需要明确其处理方式。
- Student number 必须按完整字段识别。示例是 8 个字符,不能只比较后面的 6 位数字。实际字段位置、
RECFM和LRECL尚未提供,统计程序需要根据这些信息读取键值。 - 范围依据 OUTFIL 接收到的记录流。如果另外加入过滤、去重或改变记录数量的处理,边界统计必须采用相同的数据口径。
- 该方案均衡的是记录数量,记录数只是负载的近似。若不同记录的处理成本差异很大,还需要按业务成本定义权重。
运行后,应核对输出记录总数是否仍为 26,并检查每个 Student number 是否只出现在一个输出文件中。对于动态输入,应使用本次统计得到的总数进行核对。
备注:内容仅供参考。
