EP041 Table.Group:表格多维分组聚合与指标多重汇总(多字段统计与明细子表嵌套)

文 / 老汪(汽车零部件财务与商业智能实战派)
专栏:【Excel BI 财务建模实战课讲义(第2阶段)】 · 期数:EP041
核心函数:Table.Group / List.Sum / Table.RowCount / Number.Round


📌 老汪开篇导读:车间月度成本结算与多维分组聚合的工业刚需

各位财务同仁、成本会计、车间统计员与经营分析BP:

在咱们汽车零部件制造企业(Tier 1 一级供应商)月末开月度生产经营分析会的时候,财务部和制造部最怕面对的就是一堆毫无归集的“长明细账”:
– 压铸车间有几百张工单在交替压铸铝合金控制臂和电池壳体;
– 机加车间的 CNC 产线 A 组、B 组夜以继日地跑削切工时;
– 冲压焊接线更是几十道工序频繁切换。

每到月末结账,工厂总经理和生产副总总会把报表往桌上一扔:

“老汪,别给我看几千行的流水账!我就要看:每个车间、每条产线,这个月一共结了多少个工单?总产量是多少?实际耗用了多少工时?到底超耗了多少标准工时?哪条线产生的废品损失最严重?而且——当我看到某条线数据异常时,你能不能立刻把该线的原始明细完整给我下钻穿透看出来?!”

很多习惯在 Excel 里用“数据透视表”的伙伴可能会说:“老汪,这不就是个透视表的事儿吗?”
平时做单机临时分析确实可以随手拉个透视表,但在构建端到端全自动化的财务 ETL 管道中,数据透视表存在致命短板:
1. 无法在管道内存中继续流转:透视表是界面的终点产物,不能作为下游清洗步骤的输入源;
2. 无法原生保留明细子表(Sub-table):透视表汇总后明细就没了,无法实现“一行汇总指标 + 一个内置子表对象”的高级结构嵌套;
3. 无法应对复杂的复合指标运算:比如在汇总的同时计算 [实际工时] - [标准工时] 的净差异率、或者按局部连续状态分组。

在 Power Query M 函数库中,承载所有多维统计与指标汇聚的核武器级函数,正是——Table.Group!


🛠️ 第一部分:老法师拆解底层——Table.Group 五大参数全景解密

很多初学者在界面上点击“分组依据”按钮,只能做非常简单的单列单指标求和。想要随心所欲驾驭多字段、多指标并行统计与子表嵌套,必须吃透它的完整 M 语法签名:

Table.Group(
    table as table,                      // 1. 目标数据表
    key as any,                          // 2. 分组字段(单列名 或 多列复合键)
    aggregatedColumns as list,           // 3. 聚合计算定义列表(核心核心!)
    optional groupKind as nullable number,// 4. 可选:分组模式(全局分组 vs 局部连续分组)
    optional comparer as nullable function// 5. 可选:自定义比较逻辑
) as table

1. 第 2 参数:单列 vs 多列复合键(Composite Key)

  • 单字段分组:直接写字符串,如 "车间名称";
  • 多字段复合分组:用大括号列表传入多个字段,如 {"车间名称", "生产产线"}。M 引擎会自动以两者的笛卡尔唯一组合作为统计主键!

2. 第 3 参数:聚合列表结构与神级标识符 each _

这是 Table.Group 的灵魂所在!它是一个由列表构成的嵌套列表:

{
    {"工单笔数", each Table.RowCount(_), Int64.Type},
    {"完工总产量", each List.Sum([#"完工数量(件)"]), Int64.Type},
    {"明细子表", each _, type table}
}

💡 老汪独家内功心法:
在 each 表达式后面的下划线 _,代表什么?
它代表当前这一组数据筛选出来的全部行构成的独立子表(Sub-table)!
– 如果你想算这组有多少行,就对子表求行数:Table.RowCount(_);
– 如果你想算子表某列的总和,就对子表的列做列表求和:List.Sum([#"完工数量(件)"])([列名] 实际上是 _[列名] 的语法糖);
– 如果你想把这组原始数据原封不动封存在一个单元格里,就直接写:each _!这一招能瞬间把表格变成“主子表嵌套结构”,在后续报表穿透和高级过滤中堪称神技!

3. 第 4 参数:GroupKind(全局分组 vs 局部连续分组)

  • GroupKind.Global(默认,取值 0):全局归集。只要分组字段相同,不管行散落在哪里,全表归为一组(相当于 SQL 的 GROUP BY);
  • GroupKind.Local(取值 1):局部连续分组!仅当相邻连续行的字段相同时才分为一组,一旦中间出现不同值就开启全新组!
    (制造实战:用于计算产线“连续停机批次”、“连续废品发生次数”,遇到正常批次中断,立即重开新组!这是传统 SQL 极难写而 PQ 举重若轻的高阶功能)。

🏭 第二部分:汽车零部件制造工单“月度经营看板”实战

1. 业务场景背景

某汽车底盘零部件制造基地,月末收集到各车间 50 余笔工单流水(涉及精密压铸车间、CNC机加车间、冲压焊接车间):
– 包含字段:工单号、车间名称、生产产线、零件图号、零件名称、完工数量(件)、标准工时(h)、实际工时(h)、废品损失(元)。
– 管理要求:
按 【车间名称 + 生产产线】 复合分组,同步输出 6 大经营指标:
1. 工单笔数(该产线月度开单频次);
2. 完工总产量(件)(产线总产出);
3. 总标准工时(h)(产线理论应耗工时);
4. 总实际工时(h)(车间实际打卡与报工工时);
5. 工时超耗差异(h)(实际工时 - 标准工时,超耗为正,节约为负);
6. 累计废品损失(元)(品质缺陷损失考核金额)。


💻 第三部分:随堂课件完整 M 脚本工程实现

💡 老汪敲黑板(免改路径直接刷新秘籍):
平时做财务分析最怕换台电脑一刷新就报错“找不到文件路径”。老汪在给各位学员配套的实操课件【配置】表 A1 单元格中预置了自适应公式,大家把本课案例包解压后直接点击 Excel 的【全部刷新】即可秒级重现,绝不报红字!大家可以把这套自适应架构无缝迁移到公司的日常模型中! 实操指南点此篇文章:《实操指南》

// =========================================================================
// 专栏:【Excel BI 财务建模实战课讲义(第2阶段)】· EP041
// 模块:Table.Group 表格多维分组聚合与指标多重汇总
// 讲师:老汪(汽车零部件财务与商业智能实战派)
// =========================================================================

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}, 
        {"标准工时(h)", type number}, 
        {"实际工时(h)", type number}, 
        {"废品损失(元)", type number}
    }),

    // ---------------------------------------------------------------------
    // 步骤 3:核心算子 Table.Group 执行多维度复合分组与 6 重指标并行聚合
    // 【老汪深度拆解】:
    // 1. 分组键:{"车间名称", "生产产线"}(双字段复合主键)
    // 2. 聚合列表:嵌套列表,每个子项定义 {列名, each 计算表达式, 类型}
    // 3. each 内部的下划线 _ 代表该分组对应的“子数据表”!
    // ---------------------------------------------------------------------
    分组聚合结果 = Table.Group(
        更改的类型, 
        {"车间名称", "生产产线"}, 
        {
            // 指标 1:工单笔数(计算当前分组子表的总行数)
            {"工单笔数", each Table.RowCount(_), Int64.Type},

            // 指标 2:完工总产量(从子表中提取完工数量列并求和)
            {"完工总产量(件)", each List.Sum([#"完工数量(件)"]), Int64.Type},

            // 指标 3:总标准工时
            {"总标准工时(h)", each Number.Round(List.Sum([#"标准工时(h)"]), 1), type number},

            // 指标 4:总实际工时
            {"总实际工时(h)", each Number.Round(List.Sum([#"实际工时(h)"]), 1), type number},

            // 指标 5:工时超耗差异(实际工时 - 标准工时,正数代表超耗,负数代表节约)
            {"工时超耗差异(h)", each Number.Round(List.Sum([#"实际工时(h)"]) - List.Sum([#"标准工时(h)"]), 1), type number},

            // 指标 6:累计废品损失金额
            {"累计废品损失(元)", each Number.Round(List.Sum([#"废品损失(元)"]), 1), type number},

            // 指标 7(老法师高阶特技):保留子表数据,供后续穿透钻取明细!
            {"分组明细表", each _, type table}
        }
    ),

    // ---------------------------------------------------------------------
    // 步骤 4:移除明细子表列(仅保留汇总看板交付经营层,亦可按需保留)
    // ---------------------------------------------------------------------
    移除子表列 = Table.RemoveColumns(分组聚合结果, {"分组明细表"}),

    // ---------------------------------------------------------------------
    // 步骤 5:按车间与产线规范排序
    // ---------------------------------------------------------------------
    最终排序 = Table.Sort(移除子表列, {{"车间名称", Order.Ascending}, {"生产产线", Order.Ascending}})
in
    最终排序

📊 第四部分:清洗前后数据效果对照表

1. 原始工单生产与工时流水表(50余行明细节选)

工单号 车间名称 生产产线 零件图号 零件名称 完工数量(件) 标准工时(h) 实际工时(h) 废品损失(元)
WO-202611-002 精密压铸车间 产线-DC01-500T P-8101-ARM 铝合金副车架左控制臂毛坯 120 36.0 38.5 450.0
WO-202611-003 精密压铸车间 产线-DC01-500T P-8102-ARM 铝合金副车架右控制臂毛坯 110 33.0 32.0 0.0
WO-202611-004 精密压铸车间 产线-DC02-800T P-8201-CASE 电池包托盘下壳体压铸件 45 45.0 52.0 1280.0
WO-202611-006 CNC机加车间 产线-CNC-A组 P-8101-ARM-M 副车架左臂精密机加件 100 50.0 48.0 0.0
WO-202611-007 CNC机加车间 产线-CNC-A组 P-8102-ARM-M 副车架右臂精密机加件 95 47.5 51.0 320.0
WO-202611-008 CNC机加车间 产线-CNC-B组 P-8305-DISC 通风制动盘卡钳支架 200 60.0 58.5 0.0
WO-202611-010 冲压焊接车间 产线-WLD-总成01 P-8901-SUB 前副车架总成机器人焊接 80 40.0 42.0 850.0

2. 最终输出:车间产线月度经营汇总看板

车间名称 生产产线 工单笔数 完工总产量(件) 总标准工时(h) 总实际工时(h) 工时超耗差异(h) 累计废品损失(元)
CNC机加车间 产线-CNC-A组 10 1,075 536.2 534.6 -1.6 1,120.0
CNC机加车间 产线-CNC-B组 10 2,100 627.0 650.7 23.7 2,240.0
冲压焊接车间 产线-WLD-总成01 10 915 453.8 448.2 -5.6 2,975.0
精密压铸车间 产线-DC01-500T 10 1,270 379.5 380.7 1.2 1,575.0
精密压铸车间 产线-DC02-800T 10 525 522.5 548.1 25.6 4,480.0

🚦 第五部分:老汪实战避坑指南(三大血泪教训)

  1. 别用错聚合函数作用域,区分 Table 函数与 List 函数:
    – 在 Table.Group 的第三参数中,_ 是一整张子表。如果要对列求和,绝不能写 Table.Sum(PQ里没有这个函数!),必须写 List.Sum([列名])!因为取出来的列本身是一个列表(List);
  2. 多列复合分组时,大括号层级别嵌套错:
    – 分组键必须包在一层大括号内:{"车间名称", "生产产线"};
    – 聚合定义必须是双重嵌套:{ {"列1", ...}, {"列2", ...} };如果漏掉一层大括号,M 引擎会报类型不匹配错误(We cannot convert a value of type Record to type List);
  3. 空值(null)对求和与平均的影响:
    – List.Sum 遇到 null 会自动忽略并正常计算,但如果整列全部都是 null,它会返回 null 而不是 0!如果需要参与加减差额运算,务必套上 List.Sum(...) ?? 0 或 try...otherwise 0 防御。

🎁 老汪结语与随堂课件包下载

掌握了 Table.Group 的深度用法,你就真正拥有了在 Power Query 内存管道中随意开辟“多维汇总维度”的顶级内功。无论是月度成本中心分摊、供应商对账核销、还是产线效率看板,你都可以在纯 M 代码中以毫秒级的极速一次性搞定!

老汪已经把本节课涉及的原始业务数据表、最终完成版带PQ查询工程文件以及学员操作指引指南全部打包上传。大家在文末点击即可免费下载实操演练!

[评论可见]隐藏内容 – 管理员可见
📦

EP041 随堂实操课件与 M 脚本原码包

本压缩包内含本节讲解使用的 Excel 随堂课件、M 脚本源码及清洗前后对照表。
已配置绝对自适应相对路径,下载解压后即可直接一键刷新演练!

📁 文件名:EP041_Table_Group_表格多维分组聚合与指标汇总_课件包.zip


🚀 立即下载随堂课件包 (.zip)

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容