
本文介绍如何通过 sql 聚合(group by + 字符串拼接/条件求和)将同一作业(job+suffix+part)的多条工艺记录合并为一条,保留所有 workcenter、hours_estimated 和 hours_actual 的关联信息,适用于 pervasive 等不支持窗口函数或 cte 的旧版数据库。
本文介绍如何通过 sql 聚合(group by + 字符串拼接/条件求和)将同一作业(job+suffix+part)的多条工艺记录合并为一条,保留所有 workcenter、hours_estimated 和 hours_actual 的关联信息,适用于 pervasive 等不支持窗口函数或 cte 的旧版数据库。
在制造业 ERP 或工单系统中,一个作业(Job)常对应多个工序(operations),每道工序分配至不同工位(workcenter)并记录预估/实际工时。原始查询返回的是“一对多”明细结果(如 A02043 有 4 条工序记录),但报表或前端展示往往需要“一对汇总”结构:每个 Job-suffix-part 仅占一行,而将关联的工位、工时等字段以逗号分隔字符串或按类别聚合的形式呈现。
由于提问者明确指出所用数据库为 Pervasive(不支持现代 SQL 特性如 STRING_AGG()、CTE 或窗口函数),因此不能依赖 ROW_NUMBER() 或 GROUP_CONCAT 等标准方案。此时,核心解决思路是:放弃逐字段拼接字符串,转而采用语义化聚合(Semantic Aggregation)——即按业务逻辑对工时进行分类求和,并确保 GROUP BY 覆盖所有非聚合字段。
✅ 正确做法:使用 CASE + SUM 实现工位分类汇总
原查询中 DISTINCT 无效,是因为它无法消除因 seq、workcenter 等差异导致的行级唯一性。真正有效的方式是:
- 移除明细级字段(如 seq, LMO, flag_closed);
- 明确聚合维度:Job, suffix, part, PL, qty_order 是天然分组键(注意:示例答案中将 job 与 suffix 拼接为 Job 别名,属可选优化);
- 对数值型字段做条件聚合:用 SUM(CASE WHEN ... THEN hours_actual END) 按工位编码归类累加实际工时;
- 对字符串型字段暂不拼接(因 Pervasive 缺乏 LISTAGG),若必须输出逗号分隔的 workcenter 列,需在应用层(如 PHP)处理——但本例中更推荐用分类求和替代,提升可读性与性能。
以下是优化后的标准写法(兼容 Pervasive 及多数 ANSI SQL 数据库):
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 IN ('0705','0710','0715') THEN v_job_operations_wc.hours_actual ELSE 0 END) AS Laser_Hours,
SUM(CASE WHEN v_job_operations_wc.workcenter IN ('0600','0610','0650','1315') THEN v_job_operations_wc.hours_actual ELSE 0 END) AS Prep_Hours,
SUM(CASE WHEN v_job_operations_wc.workcenter = '0901' THEN v_job_operations_wc.hours_actual ELSE 0 END) AS Fab_Hours,
SUM(CASE WHEN v_job_operations_wc.workcenter = '1520' THEN v_job_operations_wc.hours_actual ELSE 0 END) AS Crating_Hours,
-- 总预估工时(跨工序累加)
SUM(v_job_operations_wc.hours_estimated) AS Total_Estimated_Hours,
-- 关联原因代码(若存在一对多,此处可能需额外处理,如取 MAX 或 MIN)
MAX(gab_source_cause_codes.source) AS source,
MAX(gab_source_cause_codes.cause) AS cause
FROM v_job_header
LEFT JOIN v_job_operations_wc
ON v_job_operations_wc.job = v_job_header.job
AND v_job_operations_wc.suffix = v_job_header.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 <h3>⚠️ 注意事项与最佳实践</h3>
- GROUP BY 必须完整覆盖非聚合字段:source 和 cause 出现在 SELECT 中且未聚合,就必须加入 GROUP BY;若其与 Job 无强一对一关系,建议改用 MAX()/MIN() 包裹,避免意外笛卡尔膨胀。
- NULL 安全处理:CASE WHEN ... THEN value END 在条件不匹配时返回 NULL,SUM(NULL) 结果为 NULL —— 应显式写成 ELSE 0 保证数值型字段始终有值。
- 避免 PHP 层后处理低效方案:虽然可用 array_reduce() 或 foreach 在 PHP 中合并数组,但 900+ 行数据在应用层聚合会增加内存开销与网络传输量;优先在数据库层完成聚合。
- 性能提示:确保 v_job_operations_wc 表在 (job, suffix, seq) 上有复合索引,显著加速 JOIN 与过滤。
综上,面对老旧数据库限制,不应执着于“字符串拼接”的表象需求,而应回归业务本质:用户真正需要的是可分析、可汇总、可分类的工时分布视图。通过 CASE + SUM 实现语义化聚合,既兼容性高、性能优,又比逗号拼接更利于后续统计与可视化。











