文档大纲

Power Query 避坑指南:解决文件夹不存在时 Folder.Files 硬报错问题

在 Excel Power Query 中处理文件夹读取场景时,很多人会遇到一个 "顽固" 问题:明明用了try...otherwise包裹Folder.Files函数,可当目标文件夹路径不存在时,依然会触发DataSource.NotFound硬报错,otherwise分支完全不生效。

在出现这个问题后,我同时向ChatGPT、Gemini、Deepseek和豆包提问解决方式,每个AI会话轮数都超过20次,但没有一个解决了问题,最终发现还是不能偷懒,自力更生,研究出了解决方法。

特别有意思的是ChatGPT,每次都信誓旦旦,一定可以解决问题/完美方案/终极方案,可惜次次令人失望。看来AI在某些方面,还是有很大进步空间。

本文将拆解问题根源,并分享一种 "从确定存在的上级目录入手" 的完美解决方案,让文件夹不存在时优雅返回空表,存在时正常读取数据。

一、问题:为什么 try 包裹 Folder.Files 依然报错?

先看一个典型的 "踩坑" 代码(也是很多人的第一反应):

powerquery-m

let
    目标路径 = "E:\测试\不存在的文件夹\",
    // 试图用try捕获错误,却无效
    源 = try Folder.Files(目标路径) otherwise #table({}, {}),
    结果 = 源
in
    结果

执行后依然会弹出:DataSource.NotFound: File or Folder: 找不到文件夹,这是因为:

  • Excel Power Query 的错误分为两层:M 表达式错误(如语法错、空值引用)可被try捕获;数据源连接错误(如路径不存在、权限不足)发生在 M 表达式执行之前,属于 "连接层硬错误",try完全无法拦截。
  • Folder.Files访问不存在的路径时,触发的是后者 ——Power Query 会先尝试建立数据源连接,连接失败直接报错,根本不会执行try...otherwise逻辑。

二、核心思路:从 "确定存在的上级目录" 验证目标路径

既然直接访问未知路径会报错,那换个思路:先读取 "确定存在" 的上级目录,再从上级目录的文件 / 文件夹列表中,判断目标路径是否存在。

核心逻辑:

  1. 找到一个 "100% 存在" 的上级根目录(比如业务场景中固定的办公文件根目录);
  2. 读取该根目录下的所有文件夹路径列表;
  3. 检查目标文件夹路径是否包含在列表中:
    • 存在:调用自定义函数执行正常的文件读取逻辑;
    • 不存在:直接返回预设的空表,全程不触发硬报错。空表与有数据的表,在Power Pivot中可以完美融合。

三、完美解决方案

以下是经过验证的完整代码,适配所有 Excel Power Query 版本,可根据注释,根据自己的需要修改套用:

powerquery-m

let
    // 步骤1:定义确定存在的根目录(关键!必须是100%存在的路径)
    根目录 = Text.BeforeDelimiter(文件位置, "11客户"), // 业务场景中固定的上级目录
    // 步骤2:定义目标文件夹路径(你要读取的宝贝报表目录)
    宝贝报表目录 = 根目录 & "04万相台无界\宝贝报表\",
    // 步骤3:读取根目录下的所有文件/文件夹(根目录存在,不会报错)
    文件列表 = Folder.Files(根目录),
    // 步骤4:封装正常的数据处理逻辑为自定义函数
    文件转换fx = (X)  =>
    let
        // 正常读取目标文件夹文件(此时路径已验证存在,不会报错)
        源 = Folder.Files(X),
        // 原有数据处理逻辑(按需修改,保持和业务一致)
        添加filepath = Table.AddColumn(源, "filepath", each [Folder Path]&[Name]),
        插入月份文本 = Table.AddColumn(添加filepath, "月", each Text.BetweenDelimiters([Name], "_", ".csv") & "-01", type date),
        更改月类型 = Table.TransformColumnTypes(插入月份文本,{{"月", type date}}),
        DateEndOfMonth = Table.TransformColumns(更改月类型, {"月", each Date.EndOfMonth(_), type date}),
        筛选的行 = Table.SelectRows(DateEndOfMonth, each ([月] >= 数据日期)),
        保留filepath = Table.SelectColumns(筛选的行,{"filepath"}),
        二进制转换 = Table.TransformColumns(保留filepath, {"filepath", each 宝贝报表fx(_)}),
        展开 = Table.ExpandTableColumn(二进制转换, "filepath", {"日期", "计划ID", "计划名字", "主体ID", "主体名称", "花费"}, {"日期", "计划ID", "计划名字", "主体ID", "主体名称", "花费"})
    in
        展开,
    // 步骤5:定义预设空表(列名和类型与业务一致)
    空表 = #table(
            {"日期", "计划ID", "计划名字", "主体ID", "主体名称", "花费"}, // 列名匹配业务需求
            {} // 空行,保证无数据
        ),
    // 步骤6:分支判断(核心!验证目标路径是否存在)
    分支 = if List.Contains(文件列表[Folder Path], 宝贝报表目录) then 
               文件转换fx(宝贝报表目录) // 路径存在,执行正常逻辑
           else 
               空表, // 路径不存在,返回空表
    // 步骤7:统一设置列类型(保证输出格式一致)
    更改类型 = Table.TransformColumnTypes(分支,{{"日期", type date}, {"花费", type number}})
in
    更改类型

四、代码关键解析

1. 根目录的选择(重中之重)

根目录必须是业务场景中 100% 存在的路径(比如示例中的Text.BeforeDelimiter(文件位置, "11客户")),这是整个方案的基础 —— 只有根目录存在,Folder.Files(根目录)才不会报错,才能拿到后续判断的依据。

2. 自定义函数封装业务逻辑

将正常的文件读取、数据处理逻辑封装为文件转换fx函数,好处是:

  • 代码结构清晰,分离 "路径验证" 和 "数据处理" 逻辑;
  • 仅在路径验证通过后才调用函数,避免无效执行。

3. 路径存在性判断

List.Contains(文件列表[Folder Path], 宝贝报表目录)是核心判断:

  • 文件列表[Folder Path]:根目录下所有子文件夹的完整路径列表;
  • List.Contains:检查目标路径是否在列表中,返回true/false(注意:目标路径要以字符"\"结尾);
  • 全程不直接访问目标路径,而是通过 "列表查询" 验证,从根源规避硬报错。

4. 空表的定义

用#table定义空表,保证列名(日期、计划 ID 等)和业务输出完全一致,避免后续引用该查询时因列名 / 结构不一致报错。

五、方案优势

  1. 彻底避错:全程仅访问 "确定存在的根目录",目标路径不存在时不调用Folder.Files,无任何数据源硬报错;
  2. 逻辑完整:路径存在时执行原有业务逻辑,数据读取、处理不受影响;
  3. 适配性强:仅使用 Excel Power Query 原生函数,无 Power BI 专属函数,全版本兼容;
  4. 易维护:自定义函数封装业务逻辑,后续修改数据处理规则只需改函数内部,无需动验证逻辑。

六、总结

Excel Power Query 中Folder.Files的硬报错问题,核心症结是 "直接访问未知路径触发连接层错误",而解决的关键是 "绕开直接访问"—— 从确定存在的上级目录入手,通过列表查询验证目标路径,再分支执行逻辑。

这个方案的核心思想可以总结为:先验证,后执行;验证用已知路径,执行业务逻辑。无论是 "万相台无界" 文件夹不存在,还是 "宝贝报表" 子文件夹不存在,都能优雅返回空表;路径存在时,又能正常读取和处理数据,完美解决了 "报错" 和 "业务逻辑" 的双重需求。

阅读量: 286