
本文介绍在不支持窗口函数或 cte 的旧版数据库(如 pervasive sql)中,通过 group by 配合条件聚合(case + sum)和字符串拼接逻辑,将同一工单的多工序记录合并为一条汇总记录,并保留各工序关键字段的结构化汇总结果。
本文介绍在不支持窗口函数或 cte 的旧版数据库(如 pervasive sql)中,通过 group by 配合条件聚合(case + sum)和字符串拼接逻辑,将同一工单的多工序记录合并为一条汇总记录,并保留各工序关键字段的结构化汇总结果。
在实际生产数据报表开发中,常遇到“一对多”关系导致的重复主键问题:例如一个工单(Job + suffix + part)对应多个工序操作(不同 workcenter、hours_estimated、hours_actual)。原始查询即使使用 DISTINCT,也无法消除因工序维度差异产生的多行——因为每行的 workcenter 或 seq 值不同。而业务需求往往要求每个工单仅返回一行,同时保留所有相关工序信息的可读性汇总,如将多个 workcenter 拼接为逗号分隔字符串,或将工时按车间类别分别求和。
由于提问环境使用的是 Pervasive SQL(不支持 STRING_AGG、GROUP_CONCAT 或 CTE),无法直接用现代 SQL 的聚合字符串函数实现字段拼接(如 STRING_AGG(workcenter, ','))。此时应优先在 SQL 层完成语义化聚合,而非依赖 PHP 数组处理——既减少网络传输量,又提升执行效率与逻辑一致性。
✅ 推荐方案:条件聚合(Conditional Aggregation)+ 分组汇总
核心思路是:不拼接原始字符串,而是按业务规则对数值型字段(如 hours_actual)进行分类汇总,对字符型字段(如 workcenter)则通过预定义映射归类到固定列。例如:
- 将 workcenter IN ('0705','0710','0715') 统一归为 "Laser" 类别;
- 对该类别下所有 hours_actual 求和,得到 Laser 总工时;
- 同理定义 WaterJet、Prep、Fab 等列,实现横向展开。
这样既满足“每工单一行”的要求,又使结果具备明确业务含义,比纯字符串拼接(如 "0710,0805,0901,1520")更易后续分析与前端展示。
以下是优化后的标准 SQL 模板(兼容 Pervasive、SQL Server、MySQL 5.7+ 等):
SELECT
v_job_header.job,
v_job_header.suffix,
v_job_header.part,
v_job_header.product_line AS PL,
v_job_header.qty_order AS qty,
-- 按车间类别汇总实际工时(数值聚合)
SUM(CASE WHEN v_job_operations_wc.workcenter = '0750' THEN v_job_operations_wc.hours_actual ELSE 0 END) AS WaterJet,
SUM(CASE WHEN v_job_operations_wc.workcenter IN ('0705','0710','0715') THEN v_job_operations_wc.hours_actual ELSE 0 END) AS Laser,
SUM(CASE WHEN v_job_operations_wc.workcenter IN ('0600','0610','1006','0650','1315') THEN v_job_operations_wc.hours_actual ELSE 0 END) AS Prep,
SUM(CASE WHEN v_job_operations_wc.workcenter IN ('1310','0755') THEN v_job_operations_wc.hours_actual ELSE 0 END) AS Machining,
SUM(CASE WHEN v_job_operations_wc.workcenter IN ('1515','1000','1002','1003','0901','1270') THEN v_job_operations_wc.hours_actual ELSE 0 END) AS Fab,
-- 汇总预估总工时(全局求和)
SUM(v_job_operations_wc.hours_estimated) AS total_hours_estimated,
-- 关联表字段(需加入 GROUP BY,避免歧义)
gab_source_cause_codes.source,
gab_source_cause_codes.cause
FROM v_job_header
LEFT JOIN v_job_operations_wc
ON v_job_operations_wc.job = v_job_header.job
AND v_job_header.suffix = v_job_operations_wc.suffix
LEFT JOIN gab_source_cause_codes
ON gab_source_cause_codes.job = v_job_operations_wc.job
AND gab_source_cause_codes.suffix = v_job_operations_wc.suffix
AND gab_source_cause_codes.seq = v_job_operations_wc.seq
WHERE
v_job_header.product_line = '01'
AND v_job_header.date_closed < '2019-01-01'
AND v_job_operations_wc.LMO = 'L'
AND v_job_operations_wc.seq < '99000'
GROUP BY
v_job_header.job,
v_job_header.suffix,
v_job_header.part,
v_job_header.product_line,
v_job_header.qty_order,
gab_source_cause_codes.source,
gab_source_cause_codes.cause;⚠️ 注意事项与最佳实践
- GROUP BY 必须包含所有非聚合字段:如 job, suffix, part, PL, qty 及关联表中的 source/cause。遗漏会导致语法错误或不可预测聚合。
- NULL 处理:CASE 表达式中建议显式写 ELSE 0(数值)或 ELSE ''(字符串),避免 SUM(NULL) 返回 NULL。
- 性能提示:确保 v_job_operations_wc.job + suffix + seq 和 v_job_header.job + suffix 上有复合索引,大幅提升 JOIN 效率。
- 若必须字符串拼接(如导出明细):Pervasive 不原生支持 GROUP_CONCAT,但可通过客户端(PHP)二次处理——先用 ORDER BY job,suffix,part,seq 获取有序结果,再用 array_reduce() 或 foreach 按主键分组拼接,但此方式不推荐用于大数据集(900+ 行已属临界点)。
- 扩展性建议:将 workcenter → category 映射抽离为独立配置表(如 wc_category_map),使 SQL 更灵活、易维护。
综上,在受限数据库环境下,放弃“字符串拼接幻想”,转向基于业务逻辑的条件聚合建模,是兼顾正确性、性能与可维护性的最优解。


















