返回博客

如何在 Power BI 中获取实时汇率:Power Query、DAX 与刷新陷阱

V
Vlado Grigirov
September 01, 2026
Currency API Exchange Rates Power BI Power Query DAX Tutorial Finexly

Power BI 中获取实时汇率,是那种看起来早就做完、其实还差得远的任务。你把一个 API URL 粘贴进 获取数据 → Web,汇率表出现了,换算后的收入度量值亮了起来,然后你就发布了。两天后,数据集刷新在 Power BI 服务中失败,报错提到动态数据源;或者刷新成功了,却悄悄地把每一笔历史交易都按今天的汇率换算了一遍。

本指南覆盖完整路径:一段返回干净汇率表的 Power Query M 查询、让它在服务中仍可刷新的 RelativePath 写法、按交易日换算所需的历史汇率函数、支持动态报表币种的 DAX 度量值,以及决定你的刷新计划是否放得进某个 API 套餐的请求次数计算。

下面所有查询都基于 Finexly API 文档中记录的响应结构编写。如果你读过我们那篇在 Excel 中获取实时汇率的指南,这里的 M 代码会很眼熟——但 Power BI 多了一层 Excel 没有的服务端刷新机制,而大多数项目正是在这一层崩掉的。

把汇率接入 Power BI 的三种方式

做法能在服务中刷新吗?API 密钥安全吗?支持历史汇率适合场景
Web 连接器,直接把 URL 粘进对话框通常不行——该 URL 一般会变成动态数据源❌ 密钥暴露在 URL 里五分钟的概念验证
空白查询 + Web.Contents + RelativePath✅ 可以✅ 通过请求头✅ 可以几乎所有真实模型
用数据流(或 Fabric 管道)供给汇率表✅ 可以,且与报表刷新解耦✅ 通过请求头✅ 可以多个报表、大数据量
第二行是默认答案。当不止一个报表需要同一套汇率时,你会升级到第三行。第一行是所有靠截图讲解的教程都在教的做法,也是那么多货币模型在第一次计划刷新时就失败的原因。

方法一:用 Power Query 构建实时汇率表

打开 转换数据 → 新建源 → 空白查询 → 高级编辑器,粘贴以下代码。它会为每个货币对返回一行,并附带 UTC 抓取时间戳。

let
    ApiKey  = "YOUR_API_KEY",
    Base    = "USD",
    Symbols = "EUR,GBP,JPY,CHF,AUD,CAD,SEK,NZD",

    Source = Json.Document(
        Web.Contents(
            "https://api.finexly.com",
            [
                RelativePath = "v1/latest",
                Query        = [ base = Base, symbols = Symbols ],
                Headers      = [ #"Authorization" = "Bearer " & ApiKey ]
            ]
        )
    ),

    Rates    = Record.ToTable( Source[rates] ),
    Renamed  = Table.RenameColumns( Rates, {{"Name", "Quote"}, {"Value", "Rate"}} ),
    AddBase  = Table.AddColumn( Renamed, "Base", each Base, type text ),
    AddStamp = Table.AddColumn( AddBase, "RetrievedUTC", each DateTimeZone.UtcNow(), type datetimezone ),
    Typed    = Table.TransformColumnTypes(
                   AddStamp,
                   {{"Quote", type text}, {"Rate", type number}}
               )
in
    Typed

它调用的接口在命令行下长这样,值得先跑一次,好看清你要解析的数据结构:

curl "https://api.finexly.com/v1/latest?base=USD&symbols=EUR,GBP,JPY" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "success": true,
  "base": "USD",
  "date": "2026-09-01",
  "rates": { "EUR": "…", "GBP": "…", "JPY": "…" }
}

为什么 RelativePath 和 Query 不是可选项

这是全文最重要的一点,所以单独给它一个标题。

如果你把 URL 拼成一整个字符串——"https://api.finexly.com/v1/latest?base=" & Base——Power Query 在查询真正执行之前无法确定目标地址。Microsoft 把这种情况归类为动态数据源,出于安全与隐私原因,动态数据源在 Power BI 服务中不会被刷新。报表在你的笔记本上刷新得好好的,一放上计划刷新就失败。

把路径放进 RelativePath、把参数放进 Query,是官方文档给出的例外。这样 Power BI 就能解析出一个静态的基础 URL——https://api.finexly.com——用于凭据和隐私检查,而可变部分依然可变。由此得出三条规则:

  1. 基础 URL 必须是字面量字符串。 不带参数、不做拼接、里面不能有任何 &
  2. RelativePath 应当是固定的接口路径。"v1/latest",不要写 "v1/latest?base=USD"
  3. 绝不要在 Query 内部做字符串拼接。 传入一个名值对记录,让 Power Query 负责 URL 编码。它还会转义那些否则会破坏请求的字符。

设置凭据

首次运行查询时,Power BI 会询问如何对 https://api.finexly.com 进行身份验证。选择匿名。这看起来不对,但恰恰是正确的:API 密钥走的是你在 M 代码里提供的 Authorization 请求头,而不是 Power BI 的凭据存储。选择 Web API基本会让 Power BI 自行添加请求头,请求随即被拒绝。

在所有数据源上一致地把隐私级别设为公共组织。隐私级别不匹配,是"在 Desktop 能刷、在服务里失败"的第二大常见原因——Power BI 宁可直接阻止查询,也不愿冒着把一个数据源的数据泄漏进另一个数据源请求的风险。

一句实话:密钥现在以明文形式存在查询里。任何人打开 .pbix 都能读到。凡是要在自己机器之外共享的场景,请把密钥提升为 Power Query 参数,并把填好值的版本放在由服务账号拥有的数据流里,这样报表作者能用汇率表却永远看不到凭据。

方法二:按交易日换算所需的历史汇率

实时汇率表回答的是"EUR/USD 现在是多少"。它回答不了"我们一月份的收入折成美元是多少",而拿它去做这件事是整个话题里代价最高的错误——因为模型用新汇率重新换算而导致上季度数字被重述,正是审计最爱盯的情形。如果你的报表要支撑日后需要辩护的数字,请配合阅读我们关于汇率与税务申报的指南。

你需要一张按日期索引的汇率表。下面这个 M 函数封装了时间序列接口,为每个日期、每种货币返回一行:

let
    FxHistory = (base as text, symbols as text, startDate as date, endDate as date) as table =>
        let
            ApiKey = "YOUR_API_KEY",

            Source = Json.Document(
                Web.Contents(
                    "https://api.finexly.com",
                    [
                        RelativePath = "v1/timeseries",
                        Query = [
                            base       = base,
                            symbols    = symbols,
                            start_date = Date.ToText( startDate, [Format = "yyyy-MM-dd", Culture = "en-US"] ),
                            end_date   = Date.ToText( endDate,   [Format = "yyyy-MM-dd", Culture = "en-US"] )
                        ],
                        Headers = [ #"Authorization" = "Bearer " & ApiKey ]
                    ]
                )
            ),

            Days      = Table.RenameColumns( Record.ToTable( Source[rates] ), {{"Name", "RateDate"}} ),
            Expanded  = Table.ExpandRecordColumn( Days, "Value", Record.FieldNames( Days{0}[Value] ) ),
            Unpivoted = Table.UnpivotOtherColumns( Expanded, {"RateDate"}, "Quote", "Rate" ),
            AsDate    = Table.TransformColumns(
                            Unpivoted,
                            {{"RateDate", each Date.FromText( _, [Format = "yyyy-MM-dd", Culture = "en-US"] ), type date}}
                        ),
            AddBase   = Table.AddColumn( AsDate, "Base", each base, type text ),
            Typed     = Table.TransformColumnTypes( AddBase, {{"Quote", type text}, {"Rate", type number}} )
        in
            Typed
in
    FxHistory

其中有两个细节在真正起作用:

  • Date.ToTextDate.FromText 上的 Culture = "en-US" 没有它,一台设为德语或法语区域的机器会把 01.09.2026 当作 start_date 发出去,API 直接拒绝;更糟的是,同事刷新出来的表结构和你的不一样。区域设置是每个跨国 Power Query 项目里那个看不见的变量。
  • Table.UnpivotOtherColumns API 把日期作为记录键返回,值是嵌套的货币记录。逆透视成 RateDate / Base / Quote / Rate 的长表结构后,这张表能干净地关联日期维度,而且每次新增一种货币都不必重做形状。

每次加载调用一次:FxHistory( "USD", "EUR,GBP,JPY", #date(2026,1,1), Date.From( DateTime.LocalNow() ) )

由于过去日期的汇率永远不会变,这张表是增量刷新的教科书案例:按 RateDate 分区,只刷新最近 7 天,其余归档。你的刷新时长不再随历史增长,请求次数同样不再增长。

不要逐行调用 API

真正拖垮这类模型的写法,是在事实表上把汇率函数做成自定义列。一万笔交易就意味着每次刷新一万个 HTTP 请求、一次四十分钟的刷新,以及午饭前就送到的配额账单。

动手前先算账。Power BI Pro 允许每个数据集每天 8 次计划刷新;Premium Per User 和 Fabric 容量允许 48 次。一次 /v1/latest 调用覆盖你需要的全部货币,按 Pro 的上限刷新,成本是每天 8 次请求——大约每月 240 次,稳稳落在 1,000 次的免费额度内。同样的计划换成逐行函数则毫无上限。即便按 PPU 的 48 次上限,一次合并调用也只有大约每月 1,440 次请求,那是一个小额付费套餐,而不是逐行式的灾难。需要精确测算时,我们的价格方案列出了各档阈值。

当不止一个报表需要这些汇率时,把查询搬进数据流。数据流按自己的计划调用 API 并把结果物化;下游每个数据集都从存储读取,而不再各自打 API。同一个数据流下的五个报表,只产生一份请求,而不是五份。

金额换算:Power Query 合并 还是 DAX 度量值

应用换算有两个合理的位置,它们回答的是不同的问题。

方案 A:在 Power Query 中合并

如果你的报表币种是固定的——一切都以美元列报,没有例外——那就在加载时做联接。

  1. 把事实表加载进 Power Query。
  2. 对汇率表执行合并查询,按货币代码日期匹配。按住 Ctrl,在两张表中以相同顺序选择列。
  3. 只展开 Rate 列,并设为固定的小数类型。
  4. 选中 AmountRate,然后 添加列 → 标准 → 乘
  5. 若没有其他对象引用汇率表,就禁用它的加载。

这样很快,只物化一次,也不会被切片器影响。最后这一点正是它的取舍所在。

方案 B:带可选报表币种的 DAX 度量值

如果用户需要把整份报表在美元、欧元、英镑之间切换,换算就必须发生在查询时。添加一张断开关系的 Reporting Currency 表,只放一列 ISO 代码,放进切片器,然后写:

Revenue (Reporting Currency) =
VAR ReportingCurrency = SELECTEDVALUE( 'Reporting Currency'[Code], "USD" )
RETURN
SUMX (
    'Sales',
    VAR TxCurrency = 'Sales'[CurrencyCode]
    VAR TxDate     = 'Sales'[OrderDate]
    VAR Rate =
        CALCULATE (
            MAX ( 'FX Rates'[Rate] ),
            REMOVEFILTERS ( 'FX Rates' ),
            'FX Rates'[Base]     = TxCurrency,
            'FX Rates'[Quote]    = ReportingCurrency,
            'FX Rates'[RateDate] = TxDate
        )
    RETURN 'Sales'[Amount] * Rate
)

三点值得注意:

  • 那几行 VAR 是承重结构。CALCULATE 之前把 TxCurrencyTxDate 捕获进变量,能把它们钉死在当前的 SUMX 行上。直接在筛选参数里引用列,会招来上下文转换的 bug,产出看着合理却错误的合计。
  • REMOVEFILTERS( 'FX Rates' ) 阻止任何传入汇率表的筛选把查找范围收窄。
  • 缺失汇率返回 BLANK(),而 Amount * BLANK() 也是 BLANK() 这正是你想要的行为。一行若悄悄退回到未换算的原始金额,就等于按当时的汇率把你的合计凭空放大。

关系陷阱

不要仅凭货币代码在事实表和汇率表之间建立物理关系。在按日期索引的汇率表里,货币代码并不唯一,于是 Power BI 会提议多对多关系;而多对多加上双向筛选,会痛快地把你的行数扇出、把收入翻倍。要么在 Power Query 里做复合键合并,要么让汇率表保持断开、按上面的方式用 DAX 查找。

五个不报错却算错数的坑

  1. 拼接式 URL。 Desktop 能刷新,服务里失败并提示"此数据集包含动态数据源"。用 RelativePathQuery 修复。
  2. 货币对方向搞反。 base=USD&symbols=EUR 返回的是 USD→EUR。如果事实表存的是欧元金额,你需要的是倒数。在相信任何合计之前,先手算核对一个已知货币对。若拿不准哪个代码该放哪边,可参考我们的 ISO 4217 参考手册
  3. 用今天的汇率算历史行。 数字每次刷新都在变,直到有人拿同一张报表的两份导出做对比,才会有人发现。
  4. 按区域格式化的日期和小数。 逗号小数分隔符会把 1,0842 变成文本,Table.TransformColumnTypes 返回一个被模型当作空值的错误,受影响的行就从合计里消失了。
  5. 在错误的步骤上做四舍五入。 只在展示层、乘完之后舍入一次。先把汇率舍到四位小数再去乘一个七位数金额,会与会计系统产生肉眼可见的差额。

常见问题

Power BI 能自动刷新汇率吗?

可以。已发布的数据集在 Power BI Pro 上最多可安排每天 8 次刷新,在 Premium Per User 或 Fabric 容量上每天 48 次。Premium 的 XMLA 端点允许外部工具在这些限制之外触发刷新。汇率查询本身不需要额外处理,只要用 Web.Contents + RelativePath 构建,服务就会接受。

在 Power BI 里如何安全传递 API 密钥?

把它放进 Web.ContentsHeaders 记录,写成 Authorization = "Bearer " & ApiKey,并在 Power BI 提示时选择匿名身份验证。这能让密钥不进 URL、不进代理日志、不进浏览器历史。但它并不会加密 .pbix 内部的密钥——共享模型请把密钥放在由服务账号拥有的数据流里,报表只读取物化后的表。

为什么我的数据集提示"此数据集包含动态数据源"?

因为 URL 是在代码里拼出来的,Power BI 在查询运行前无法验证目标地址,于是拒绝在服务中刷新它。改用静态基础 URL 加 RelativePathQuery 选项重建调用,重新发布,并为该基础 URL 重新录入凭据。

如何按交易日汇率换算金额?

从时间序列接口加载一张按日期索引的汇率表,然后同时按货币日期匹配——可以用 Power Query 里的复合键合并,也可以像上文那样在 SUMX 内用 CALCULATE 查找。绝不要只按货币联接;那样你会在无声无息中取到排序后碰巧排第一的那行汇率。

有没有能配合 Power BI 使用的免费汇率 API?

有。Finexly 的免费货币 API 套餐每月包含 1,000 次请求且无需信用卡,足以覆盖一个 Pro 授权、每天刷新八次的数据集,并且余量充足。如果你正在按刷新限制、历史深度或货币覆盖面挑选供应商,不妨先对比各家货币 API,再把它接进一个你要维护多年的模型。


开始使用 Finexly

准备好把实时汇率装进你的 Power BI 报表了吗?获取免费的 Finexly API 密钥——无需信用卡。每月 1,000 次免费请求起步,随业务增长再升级。170 多种货币的实时与历史汇率,来自同一个 REST API——无论你是从 Power Query、Python 还是支付服务调用,它的行为都完全一致。

Vlado Grigirov

Senior Currency Markets Analyst & Financial Strategist

Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...

View full profile →