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 则可以固定指向带版本号的文件。这样便于集中维护,也不会要求所有文件同时升级。
备注:内容仅供参考。
