PAYATHON 2026

Power Query 能否从外部文件导入查询或函数定义?

技术老齐

结论

Power Query 没有类似 import、include 或程序包管理器的内置机制,因此无法直接把外部 .pq 或 .txt 文件声明为共享代码模块。

可以用 File.Contents、Text.FromBinary 和 Expression.Evaluate 读取外部文件,并执行其中的 M 表达式。外部表达式只要返回一个 function,加载该文件的 query 就能像普通 Power Query function 一样供其他 queries 调用。

多个 workbooks 可以共用同一个外部文件。文件更新后,每个 workbook 会在下次 refresh 时读取新版本。这属于动态加载方案,并不是 Power Query 内置的函数库或依赖管理系统。

外部函数文件

先创建一个 UTF-8 编码的文本文件,例如:

C:\PowerQueryLibrary\fnNormalizeText.pq

文件内容必须是完整且可以求值的 M 表达式。以下表达式会返回一个 function:

let
    fnNormalizeText =
        (
            value as nullable text,
            optional fallback as nullable text
        ) as text =>
        let
            Trimmed =
                if value = null
                then null
                else Text.Trim(value),

            DefaultValue =
                if fallback = null
                then ""
                else fallback,

            Result =
                if Trimmed = null or Trimmed = ""
                then DefaultValue
                else Trimmed
        in
            Result
in
    fnNormalizeText

外部文件中不要写入 Advanced Editor 显示的 query 名称或其他 UI 信息,只保留合法的 M 表达式。

在 Excel 中加载函数

在 workbook 中新建一个 blank query,将其命名为 fnNormalizeText,并写入以下代码:

let
    FunctionFile =
        "C:\PowerQueryLibrary\fnNormalizeText.pq",

    FunctionCode =
        Text.FromBinary(
            File.Contents(FunctionFile),
            TextEncoding.Utf8
        ),

    ImportedFunction =
        Expression.Evaluate(
            FunctionCode,
            #shared
        ),

    Result =
        if Value.Is(ImportedFunction, type function)
        then ImportedFunction
        else
            error Error.Record(
                "InvalidFunctionDefinition",
                "外部文件没有返回 Power Query function。",
                [File = FunctionFile]
            )
in
    Result

这段代码中:

  • File.Contents 读取文件的 binary 内容。
  • Text.FromBinary 按 UTF-8 将 binary 转换成 text。
  • Expression.Evaluate 将 text 作为 M 表达式求值。
  • #shared 为表达式提供 Power Query 的共享环境,其中包括常用的标准库函数。
  • Value.Is 检查外部表达式返回的值是否为 function。

加载完成后,fnNormalizeText 就是当前 workbook 中可调用的 function。

在其他 query 中调用

let
    Source =
        #table(
            {"Name"},
            {
                {" Alice "},
                {null},
                {""}
            }
        ),

    Normalized =
        Table.AddColumn(
            Source,
            "NormalizedName",
            each fnNormalizeText([Name], "(unknown)"),
            type text
        )
in
    Normalized

加载 query 会在每次 refresh 时重新读取外部文件。指向该文件的 workbooks 会在各自下次 refresh 时使用更新后的函数定义。

避免写死文件路径

如果不同用户使用的目录不同,可以将文件路径放入 Excel named range,例如将其命名为 FunctionLibraryPath,再通过 Power Query 读取:

let
    FunctionFile =
        Excel.CurrentWorkbook()
            {[Name = "FunctionLibraryPath"]}
            [Content]
            {0}
            [Column1],

    FunctionCode =
        Text.FromBinary(
            File.Contents(FunctionFile),
            TextEncoding.Utf8
        ),

    ImportedFunction =
        Expression.Evaluate(
            FunctionCode,
            #shared
        )
in
    ImportedFunction

也可以把文件放在所有用户都能访问的 network share 中,例如:

File.Contents("\\server\PowerQueryLibrary\fnNormalizeText.pq")

如果文件发布在 HTTP 或 HTTPS 地址上,可以使用 Web.Contents:

let
    FunctionCode =
        Text.FromBinary(
            Web.Contents(
                "https://example.com/powerquery/fnNormalizeText.pq"
            ),
            TextEncoding.Utf8
        ),

    ImportedFunction =
        Expression.Evaluate(
            FunctionCode,
            #shared
        )
in
    ImportedFunction

Web.Contents 能否正常 refresh,取决于地址是否稳定、采用哪种身份验证方式、Power Query privacy settings,以及实际执行 refresh 的环境。

外部函数引用其他代码

外部函数如果只使用 Text.Trim、Table.AddColumn 等标准库函数,通常传入 #shared 就够了。

如果函数还需要调用 workbook 中自定义的 helper function,可以将其明确加入求值环境:

let
    FunctionCode =
        Text.FromBinary(
            File.Contents("C:\PowerQueryLibrary\fnProcess.pq"),
            TextEncoding.Utf8
        ),

    EvaluationEnvironment =
        Record.Combine(
            {
                #shared,
                [
                    fnHelper = fnHelper
                ]
            }
        ),

    ImportedFunction =
        Expression.Evaluate(
            FunctionCode,
            EvaluationEnvironment
        )
in
    ImportedFunction

这里需要避免循环依赖。如果 fnProcess 的加载 query 依赖 fnHelper,fnHelper 就不能反过来依赖 fnProcess。

使用时需要注意

只有 refresh 后才会使用新版本

外部文件更新后,不会主动推送到已经打开的 workbook。用户需要执行 refresh,也可以设置为打开 workbook 时自动 refresh。

如果不同 workbook 的刷新时间不同,它们在短时间内仍可能运行不同版本。

外部代码会被执行

Expression.Evaluate 执行的是 M 代码,并非普通配置文本。外部文件应存放在可信且权限受控的位置,不要加载用户可以随意修改的内容。

可能遇到 Formula Firewall

外部函数文件和业务数据来自不同数据源时,Power Query 可能因 privacy levels 或数据源组合方式报告 Formula.Firewall。是否出现这一问题,要看具体的数据源、查询结构和刷新环境。

遇到此类问题,可以先检查:

  • 各数据源的 privacy level 是否设置合理;
  • 函数加载和业务数据读取能否拆成独立的 staging queries;
  • Power Query 是否将文件路径或 URL 识别为动态数据源;
  • Excel Desktop、Power BI Service、gateway 或其他执行环境能否访问该数据源。

不建议为了绕过错误而直接关闭所有隐私检查。

运行环境必须能访问文件

本地路径只适合在能够访问该路径的机器上刷新。使用 Excel Online、Power BI Service 或 gateway 刷新时,通常无法直接访问本地文件,实际支持范围取决于所用产品和刷新环境。

需要跨设备复用时,应选择所有刷新端都能访问的数据源,并提前验证认证方式。

这不是完整的版本管理方案

这种方式只能从外部加载 M 表达式,不会自动处理版本锁定、依赖解析、兼容性检查或回滚。实际使用时,可以用不同的文件名保留各个版本,例如:

fnNormalizeText-v1.pq
fnNormalizeText-v2.pq

需要始终使用最新版的 workbook 可以指向统一文件;需要稳定版本的 workbook 则可以固定指向带版本号的文件。这样便于集中维护,也不会要求所有文件同时升级。

备注:内容仅供参考。