文 / 老汪(汽车零部件财务与商业智能实战派)
专栏:【Excel BI 财务建模实战课讲义(第2阶段)】 · 期数:EP042
核心函数:Table.Max/Table.Min/Table.Group/Table.ExpandRecordColumn
📌 老汪开篇导读:车间质量会议上的“两极法则”与性能杀手
各位财务同仁、质量控制工程师、成本会计与精益生产管理BP:
在咱们汽车零部件制造企业(Tier 1 一级供应商)的月度质量总结与绩效评定会上,总经理和质量总监通常最反感只看“平均良品率”这种中庸指标。因为“平均数”往往会掩盖产线的致命缺陷:
– 压铸车间 DC01 产线平均良品率 98.2%,看似达标,但其中某张工单却因为铝液温度失控导致良品率断崖跌至 92%,造成 4,000 多元的模具损坏与批量报废!
– 机加车间 CNC-B 组通风制动盘看似平稳,但某天夜班的一张工单竟然跑出了 99.9% 的近乎零缺陷标杆记录!
每当面对这种管理诉求,高管的核心问题永远是:
“老汪,别只给我汇报平均值!我就要精准锁定:各车间、各产线表现最好的一张标杆工单(红榜)是哪张?是谁带班生产的?同时,良品率最差、废品损失最严重的一张隐患工单(黑榜)又是哪张?生产的是什么零件?造成了多少损失?!”
很多初级分析员实现这个需求时的常规套路是:
1. 先对整张表按照良品率降序排序(Table.Sort);
2. 然后取前 1 行(Table.FirstN 或 {0});
3. 接着再升序排序一次,取最后 1 行……
🚨 老汪敲黑板(性能与算力常识):
全表排序的算法时间复杂度是 $O(N \log N)$!如果工厂一个月有几十万行工单流水,为了找两个极值而反复把整张大表在内存中洗牌排序两次,会造成巨大的内存卡顿与计算浪费!
在 Power Query M 引擎中,专门提供了两枚直接进行线性单次扫描($O(N)$ 复杂度)的整行提取神器——Table.Max与Table.Min!
今天老汪就带大家吃透它们的核心机理,并揭秘如何将它们与上一节课讲的 Table.Group 强强联合,构建一套极速穿透产线红黑榜的商业智能诊断看板!
🛠️ 第一部分:老法师拆解底层——Table.Max / Table.Min 运算核心
很多同学容易把 Table.Max 和列表求极值函数 List.Max 搞混。记住两者的本质区别:
– List.Max(清单) ➔ 返回一个单一的数值或文本(标量值),比如返回数字 0.998;
– Table.Max(数据表, 比较依据) ➔ 返回的是包含该最大值的整整一行数据(即一条完整的 Record 记录)!
Table.Max(table as table, comparisonCriteria as any, optional default as any) as any
Table.Min(table as table, comparisonCriteria as any, optional default as any) as any
1. 单字段极值比较
最高良品率记录 = Table.Max(工单流水表, "良品率")
- 返回值:一条包含
[工单号="WO-002", 车间="精密压铸", 良品率=0.998, 班组长="张建国"...]的完整行记录(Record); - 提取字段:由于返回的是 Record,直接在后面用中括号即可提取任意字段,如
最高良品率记录[班组长]即可拿到带班人!
2. 多字段加权复合极值比较(高阶秘籍)
如果第一指标出现并列相同值怎么办?Table.Max 支持传入字段列表作为多级比较权重:
最新且最好的工单 = Table.Max(工单流水表, {"完工日期", "良品率"})
- M 引擎会优先比较第一权重(完工日期最新);若日期相同,再在并列行中挑出第二权重(良品率最高)的那一行!
3. 第 3 参数:空表安全兜底(optional default)
如果过滤后的表可能是空表(0 行):
– Table.Max(#table({}, {}), "良品率") 会直接报错崩溃;
– 加上第三参数:Table.Max(可能为空的表, "良品率", null),空表时安全返回 null,保障整个 ETL 自动化流水线绝不中断!
🏭 第二部分:汽车零部件制造质检工单“红黑榜诊断看板”实战
1. 业务场景背景
某汽车底盘控制臂与制动系统制造基地,月末质检部门汇总了压铸、CNC机加、焊接三大车间共 50 余批次工单:
– 包含字段:工单号、车间名称、生产产线、零件图号、零件名称、完工数量(件)、良品率、废品损失(元)、班组长。
– 管理层核心诉求:
按 【车间名称 + 生产产线】 归集,提取各产线的两极对比诊断看板:
1. 【红榜标杆工单】:提取该产线最高良品率工单号、零件名称、最高良品率、带班班组长;
2. 【黑榜隐患工单】:提取该产线最低良品率工单号、零件名称、最低良品率、最大废品损失金额、带班班组长。
2. 核心架构设计:Table.Group 嵌套 Table.Max / Table.Min
直接在 Table.Group 的第三参数(聚合列表)中,利用子表 _ 进行极值记录提取:
{"红榜标杆记录", each Table.Max(_, "良品率"), type record},
{"黑榜隐患记录", each Table.Min(_, "良品率"), type record}
提取完毕后,直接调用 Table.ExpandRecordColumn 展开红榜与黑榜的核心字段,一行代码完成主子表下钻与红黑榜并列对比!
💻 第三部分:随堂课件完整 M 脚本工程实现
💡 老汪敲黑板(免改路径直接刷新秘籍):
平时做财务分析最怕换台电脑一刷新就报错“找不到文件路径”。老汪在给各位学员配套的实操课件【配置】表 A1 单元格中预置了自适应公式,大家把本课案例包解压后直接点击 Excel 的【全部刷新】即可秒级重现,绝不报红字!大家可以把这套自适应架构无缝迁移到公司的日常模型中! 实操指南点此篇文章:《实操指南》
// =========================================================================
// 专栏:【Excel BI 财务建模实战课讲义(第2阶段)】· EP042
// 模块:Table.Max 与 Table.Min 整行极值快速提取与产线红黑榜穿透
// 讲师:老汪(汽车零部件财务与商业智能实战派)
// =========================================================================
let
// ---------------------------------------------------------------------
// 步骤 1:【Q001 自适应路径规范】动态定位当前工作簿同级目录
// ---------------------------------------------------------------------
当前目录 = try
let
rawPath = Text.From(Excel.CurrentWorkbook(){[Name="当前路径"]}[Content]{0}[Column1]),
cleanPath = Text.Trim(rawPath),
fixedPath = if Text.EndsWith(cleanPath, "\") then cleanPath else cleanPath & "\"
in
fixedPath
otherwise
"./",
// ---------------------------------------------------------------------
// 步骤 2:动态读取同级「原始业务数据表.xlsx」中工单生产质检明细流水
// ---------------------------------------------------------------------
数据文件路径 = 当前目录 & "原始业务数据表.xlsx",
源文件 = Excel.Workbook(File.Contents(数据文件路径), null, true),
流水表 = 源文件{[Item="工单生产质检明细流水", Kind="Sheet"]}[Data],
提升的标题 = Table.PromoteHeaders(流水表, [PromoteAllScalars=true]),
更改的类型 = Table.TransformColumnTypes(提升的标题, {
{"工单号", type text},
{"车间名称", type text},
{"生产产线", type text},
{"零件图号", type text},
{"零件名称", type text},
{"完工数量(件)", Int64.Type},
{"良品率", Percentage.Type},
{"废品损失(元)", type number},
{"班组长", type text}
}),
// ---------------------------------------------------------------------
// 步骤 3:核心算子 Table.Group 结合 Table.Max 与 Table.Min 嵌套提取极值记录
// 【老汪深度拆解】:
// 1. Table.Max(子表, "良品率"):直接以 O(N) 线性单次扫描锁定良品率最高的一整行(返回 Record)!
// 2. Table.Min(子表, "良品率"):锁定良品率最低的整行记录;
// 3. 避免了整表高开销排序(Table.Sort),效率高出数倍!
// ---------------------------------------------------------------------
分组极值提取 = Table.Group(
更改的类型,
{"车间名称", "生产产线"},
{
// 提取红榜最高良品率整行记录
{"红榜标杆记录", each Table.Max(_, "良品率"), type record},
// 提取黑榜最低良品率整行记录
{"黑榜隐患记录", each Table.Min(_, "良品率"), type record}
}
),
// ---------------------------------------------------------------------
// 步骤 4:展开红榜标杆工单核心字段
// ---------------------------------------------------------------------
展开红榜 = Table.ExpandRecordColumn(
分组极值提取,
"红榜标杆记录",
{"工单号", "零件名称", "良品率", "班组长"},
{"红榜工单号(标杆)", "红榜零件名称", "最高良品率", "红榜带班长"}
),
// ---------------------------------------------------------------------
// 步骤 5:展开黑榜隐患工单核心字段
// ---------------------------------------------------------------------
展开黑榜 = Table.ExpandRecordColumn(
展开红榜,
"黑榜隐患记录",
{"工单号", "零件名称", "良品率", "废品损失(元)", "班组长"},
{"黑榜工单号(隐患)", "黑榜零件名称", "最低良品率", "最大废品损失(元)", "黑榜带班长"}
),
// ---------------------------------------------------------------------
// 步骤 6:格式化与多列规范排序
// ---------------------------------------------------------------------
更改最终类型 = Table.TransformColumnTypes(展开黑榜, {
{"最高良品率", Percentage.Type},
{"最低良品率", Percentage.Type},
{"最大废品损失(元)", type number}
}),
最终排序 = Table.Sort(更改最终类型, {{"车间名称", Order.Ascending}, {"生产产线", Order.Ascending}})
in
最终排序
📊 第四部分:清洗前后数据效果对照表
1. 原始工单质检明细流水表(部分节选)
| 工单号 | 车间名称 | 生产产线 | 零件图号 | 零件名称 | 完工数量(件) | 良品率 | 废品损失(元) | 班组长 |
|---|---|---|---|---|---|---|---|---|
| WO-202611-002 | 精密压铸车间 | 产线-DC01-500T | P-8101-ARM | 铝合金副车架左控制臂毛坯 | 120 | 98.5% | 280.0 | 张建国 |
| WO-202611-005 | 精密压铸车间 | 产线-DC01-500T | P-8102-ARM | 铝合金副车架右控制臂毛坯 | 125 | 99.8% | 40.0 | 张建国 |
| WO-202611-006 | 精密压铸车间 | 产线-DC01-500T | P-8103-ARM | 副车架后横梁毛坯 | 90 | 94.0% | 1,150.0 | 李海峰 |
| WO-202611-007 | 精密压铸车间 | 产线-DC02-800T | P-8201-CASE | 电池包托盘下壳体压铸件 | 45 | 92.0% | 3,200.0 | 王振华 |
| WO-202611-009 | 精密压铸车间 | 产线-DC02-800T | P-8202-CASE | 电驱动总成铸铝端盖 | 80 | 99.5% | 80.0 | 赵志强 |
| WO-202611-011 | 精密压铸车间 | 产线-DC02-800T | P-8201-CASE | 电池包托盘下壳体压铸件 | 48 | 89.5% | 4,100.0 | 王振华 |
| WO-202611-019 | CNC机加车间 | 产线-CNC-B组 | P-8306-DISC | 制动盘摩擦工作面精车 | 180 | 99.9% | 20.0 | 周立新 |
2. 最终输出:各产线质量红黑榜工单诊断看板(全景透视)
| 车间名称 | 生产产线 | 红榜工单号(标杆) | 红榜零件名称 | 最高良品率 | 红榜带班长 | 黑榜工单号(隐患) | 黑榜零件名称 | 最低良品率 | 最大废品损失(元) | 黑榜带班长 |
|---|---|---|---|---|---|---|---|---|---|---|
| CNC机加车间 | 产线-CNC-A组 | WO-202611-013 | 副车架左臂精密机加件 | 99.8% | 孙伟 | WO-202611-041 | 前轮转向节机加工装成件 | 93.2% | 1,360.8 | 陈勇 |
| CNC机加车间 | 产线-CNC-B组 | WO-202611-019 | 制动盘摩擦工作面精车 | 99.9% | 周立新 | WO-202611-045 | 制动盘摩擦工作面精车 | 94.2% | 1,058.4 | 吴建平 |
| 冲压焊接车间 | 产线-WLD-总成01 | WO-202611-023 | 前副车架总成机器人焊接 | 99.6% | 钱学森 | WO-202611-026 | 前副车架总成机器人焊接 | 91.5% | 2,100.0 | 冯国强 |
| 精密压铸车间 | 产线-DC01-500T | WO-202611-005 | 铝合金副车架右控制臂毛坯 | 99.8% | 张建国 | WO-202611-031 | 副车架后横梁毛坯 | 93.7% | 1,242.0 | 李海峰 |
| 精密压铸车间 | 产线-DC02-800T | WO-202611-009 | 电驱动总成铸铝端盖 | 99.5% | 赵志强 | WO-202611-011 | 电池包托盘下壳体压铸件 | 89.5% | 4,100.0 | 王振华 |
🚦 第五部分:老汪实战避坑指南(三大血泪教训)
- 别再用“全表排序取第一行”替代
Table.Max:
– 全表排序不仅耗费内存,而且在某些数据源或网关刷新时会破坏数据流的稳定性;
–Table.Max与Table.Min在底层直接维护一个单行指针,遍历一次即可交出结果,运行速度是排序的数倍以上! - 警惕文本型数字的“字典序比较陷阱”:
– 很多现场导出的工单流水,良品率或金额被误识别成了文本型(Text);
– 如果对文本执行Table.Max,"9.5%"会比"100%"更大(因为字符"9"的字典序大于"1")!在使用Table.Max之前,必须确保目标列已经正确转换为数值或百分比类型(type number/Percentage.Type)! - 并列最大值的处理边界:
– 如果一张表中有两行并列都是最高良品率99.8%,Table.Max默认返回先出现的那一行;
– 如果业务上需要精确打破平局,必须传入第二个比较字段,如{"良品率", "完工数量"}(并列时优先取产量更大的一行)。
🎁 老汪结语与随堂课件包下载
掌握了 Table.Max 与 Table.Min 与 Table.Group 的合体技法,你的报表就能彻底告别流水账式的平铺直叙,直接上升到“标杆对齐与风险预警”的管理诊断层级。无论是车间红黑榜、供应商考核、还是销售冠军与亏损订单追踪,这套架构都是你简历与实战中最亮眼的利器!
老汪已经把本节课涉及的原始业务数据表、最终完成版带PQ查询工程文件以及学员操作指引指南全部打包上传。大家在文末点击即可免费下载实操演练!















暂无评论内容