Lazy loaded imageEP021:List.Transform遍历转换函数和代码版汇总文件夹下Excel工作簿

type
status
date
slug
summary
tags
category
icon
password
notion image

(阅读需要15分钟左右)
这节EP021课,我们继续讲解List函数族的函数,主要讲解 M 语言中一个极其强大且常用的函数:List.Transform

一、 核心功能:遍历与转换

List.Transform 的作用类似于 Excel 里的“批量处理”或者编程中的“循环(Loop)”。
  • 官方定义:通过将转换函数 transform 应用到列表 list 来返回值的新列表。
  • 通俗理解:把一个列表里的每一个元素(无论是数字、文本,还是表格)都拿出来,按照你设定的规则“加工”一遍,然后把加工好的新元素放回一个新的列表里。

1. 基本语法

2. 简单应用案例

  • 数字加一
    • 代码:List.Transform({7, 8, 9}, each _ + 1)
    • 过程:拿出7 -> 加1得8;拿出8 -> 加1得9;拿出9 -> 加1得10。
    • 结果:{8, 9, 10}
  • 自定义变量参数写法
    • 代码:List.Transform({7, 8, 9}, (x) => x + 1)
    • 说明:这与上面的 each _ + 1 效果完全一样,x 只是给当前元素起的一个临时名字(变量名)。
  • 文本连接
    • 代码:List.Transform({"周一", "周二", "周三"}, each _ & "上班")
    • 结果:{"周一上班", "周二上班","周三上班"}

二、 实战案例:通过函数代码批量汇总文件夹下Excel工作簿

这是本节课的重头戏,向大家展示并传授 List.Transform 在处理复杂数据结构时的威力。
场景:你需要从一个文件夹里读取多个 Excel 文件,每个文件里都有数据,你需要把它们全部合并成一张大表——这就是我们在第一阶段界面操作课EP011里的《从文件夹追加查询》的函数操作版了,大致的步骤如下。
解决步骤(使用高级编辑器编写):
  1. 获取文件列表
      • 源 = Folder.Files("文件夹路径")
      • 此时得到一张表,其中 [Content] 列包含了所有文件的二进制内容。
  1. 提取 Content 列
      • 源[Content]
      • 这步操作把 [Content] 列变成了一个 列表 (List),里面是一个个 Binary 文件。
  1. 遍历转换(核心步骤)
      • 目标:把 Binary 文件变成 Excel 的 Table。
      • 代码
        • 结果:现在列表里的元素不再是 Binary,而是一个个包含 Sheet 信息的 Table
    1. 二次遍历与钻取(深化)
        • 上一步得到的 Table 里,我们通常只需要第一个 Sheet 的数据(位于 Data 列的第 0 行)。
        • 代码优化
          • 结果:现在列表里的元素,就是我们真正需要的数据表 (Table) 了。
      1. 合并列表
          • 最后,我们需要把这个包含多个 Table 的列表,合并成一张大表。
          • 代码Table.Combine(上一步的列表)

      下面就以EP011:《讲透追加查询以及初识M函数(汇总同一文件夹下多个工作簿)》的例子来复用讲解一下。

      首先,我们回到EP011那一节课教案的“删除的其他列”这一步。
      notion image
       
      当前这一步的状态是:已经出现一个 Table(表),但表里只有一列 Content(装满二进制文件的列)如上图所示。
      我们的目标是:不动用鼠标点那个“合并文件”按钮,完全用 M 函数 List.Transform 把它们变成一张大表。
      notion image
      请点击 【高级编辑器】,可以用三行代码(对应逻辑上的三步)接管后续的操作。

      操作步骤

      在高级编辑器中,请在 #"删除的其他列" 这一行后面加个逗号,然后粘贴以下三步代码(注意:我把逻辑拆得很细,方便你理解:
      最后别忘了修改 in
      结果,与EP011课所讲的方法差5行。
      notion image

      原因:

      ⚔️ 两种方法的逻辑差异

      EP021方法:代码流(行数变少的那个)

      在于遍历转换步骤中的这个代码: Excel.Workbook(_, true){0}[Data]
      • 逻辑:它不管文件里有几个 Sheet,它只抓取排在第 1 位的那一个 ({0})
      • 结果:它抓了 Sheet1(有数据),无视了 Sheet2 和 Sheet3。所以数据是干净的 9 行。

      EP011方法:手动流(行数更多的那个)

      操作:你点击了“展开”按钮。
      • 逻辑:它把文件里所有的 Sheet 都展出来了!
      • 结果
        • 它读取了 Sheet1(正常数据)。
        • 它也读取了 Sheet2(可能是个空表,导致全是 null)。
        • 它还读取了 Sheet3(可能里面哪怕只有一个单元格有内容,比如那个落单的 P-102,也会被读进来)。
      因为,我的源文件里确实存在,一个工作簿下,不仅仅只有一个工作表需要合并的情况,而是有多个工作表的情况,那要如何修改代码呢?以下就是修改后的完整函数:

      💡 关键细节解读

      我在代码里偷偷加了一个 Table.SelectRows(..., each [Kind]="Sheet")
      为什么要加这个?
      这又是“排雷”!Excel文件里除了 Sheet(工作表),有时候还会有 DefinedName(定义名称)或者 Table(超级表)。如果不加这个筛选,Power Query 可能会把同一份数据重复读两遍(一遍读Sheet,一遍读Print_Area)。加上这个筛选,确保 “我只读工作表”,数据不重复、不报错。

      ⚠️ 一个潜在的副作用(Sheet名名称丢失)

      这种“代码流”写法虽然极其简洁,但有一个缺点:它只提取了数据(Data),丢掉了 Sheet 的名字(Name),如果你的表明有隐藏的信息,比如区域、月份或者其他标识,那么通过这种方法就丢失了,就得使用EP011的手动展开法,那个是可以选择保留的。
      • 如果你的数据表里本身有“区域”列,那没问题。
      • 如果你的数据表里没有“区域”列,而你是靠 Sheet 名(如“华东”、“华北”)来区分区域的,那么这种写法会导致你分不清哪行数据属于哪个区域
      如果你需要保留 Sheet 名,代码会稍微复杂一点(需要保留 Table 结构再展开)。这里就不过多展开写“保留Sheet名版”的代码了。如果不需要Sheet名,上面的代码就是最高效的方案!在真实的财务/商业 BI 项目中,90% 的情况我们都需要保留数据来源(Source Name)
      • 如果是 Table.Combine ,想要保留文件名,如上面所说,必须在 List.Transform 里写更复杂的嵌套函数(把文件名塞进内层表)。
      • 而EP011的 “展开法”,天然就保留了文件名。
      目前行业内的现状是:
      1. 做快速一次性分析:用EP021的 Table.Combine ,快准狠。
      1. 做长期自动化报表:用EP011的“动态展开法”。因为只有保留了文件名,数据才是可追溯、可审计的。

      自动剪除空白行问题

      notion image
      如上图,经过这样修改函数之后的结果变成11行,与我原来手动展开的14行仍然差了3行,我查了下那三行是空白行,也就是说EP021新方法这三个空白行被直接排除了。为什么呢?
      在使用新方法(代码版),当你写下这句代码时:
      each Table.SelectRows(Excel.Workbook(_, true), each [Kind]="Sheet")[Data]
      Power Query 在后台做了这几件事:
      1. 打开文件:读取 Excel 文件。
      1. 解析 Sheet:它去查看每个 Sheet 的 UsedRange(已使用区域)
      1. 提升标题 (true)
          • 关键点来了!当 PQ 试图“提升第一行作为标题”时,它会触发一个 自动修剪(Auto-Trim) 机制。
          • 它会认为:“既然第一行是标题,那么标题下方如果没有数据,或者全是空行,那这些空行肯定不是‘表格’的一部分。”
          • 结果:它在读取阶段,就直接把 Sheet 末尾那些无意义的空行给 “切” 掉了。它只把“有效数据矩形”交给了你。
      notion image
      结论:在代码版里,那些空行根本就没机会进入你的 [Data] 列表,它们在门口就被拦截了。而当你使用手动 UI 操作(点击 Data 旁边的展开按钮)时,Power Query的UI逻辑为了保证预览的完整性,UI 的默认行为往往是 “宁可错杀,不可放过”。读取 Sheet时它虽然也可能提升了标题,但 UI 生成的步骤(通常是 Table.ExpandTableColumn )在处理某些边缘情况(比如 Sheet 里残留了格式但没内容的行)时,判定标准比函数直接调用要宽松,从而把空行也保留了下来。
      那3行消失的空白行,本来就是数据里的“噪音”。
      • 如果你的目的是做销售分析,那3行全空的null对你毫无价值,甚至会干扰计数和求和。
      • 新代码不仅帮你合并了数据,还免费赠送了你一次 “除噪服务”
      所以,这11行的结果才是高质量的数据。

      关于动态列表题的问题

      notion image
      还记不记得,在EP011那节课里,通过从文件夹合并的时候,我们在在展开data这一步之前加了一个获取动态列标题的步骤?步骤函数代码如下:
      这样做是为了防止,在被合并的Excel文件工作表里面,如果有人添加了新的列标题信息之后,合并表取不到新列的信息,而EP021这节课讲的 Table.Combine 用法,新加一列, Table.Combine 能抓到吗?
      答案是:不会漏。因为Table.Combine的机制是“全集并集”
      • 如果表 A 有 [姓名, 年龄]
      • 如果表 B 有 [姓名, 年龄, 手机号] (新增了列)
      • Table.Combine({表A, 表B}) 的结果会自动变成 [姓名, 年龄, 手机号]。它会自动对齐,缺的地方填 null。
      所以,在“动态获取新列”这一点上,EP021讲的代码法和EP011的动态标题法,打成平手,它们都是动态的。
       

      【扫码识别下发二维码,获取本文使用到的及历史以来提及、发布的相关精选和原创资料,和我们一起充电,加入后可享受每月6次免费咨询提问,帮助解决您的特定问题!】

      如果本篇文章对您有帮助或启发,请帮我们点赞、转发、推荐、关注,让更多想转型财务BP、锻造数据分析和可视化洞察能力的财务同行们看到,关注【老汪洞察】,不迷路!
      notion image
      notion image
       
      温馨提示
      🙏🏻
      如果您不想错过【老汪洞察】的文章,请将我们设为"星标",这样每次最新文章推送才会第一时间出现在您的订阅列表里。 方法:点击文章页面左上角蓝色文字“老汪洞察”进入主页,点击关注后,再点主页右上角"...",然后选择"设为星标",即可完成,感谢您的支持。
       
      上一篇
      7个财务经营分析指标计算的陷阱与解决方案(附Excel函数和M函数公式底稿)
      下一篇
      EP020:知识点综合实战之动态交互查询(上下文嵌套、筛选、精准钻取值、动态参数)
      Loading...
      文章列表
      让财税成为经营的力量
      管理报表
      从Power Query到Power BI,入门到精通
      699课程讲义
      VBA小工具
      电脑与网络
      知识运用