返回博客

Power BI 货币换算:通过 API 获取实时汇率(2026 指南)

V
Vlado Grigirov
August 27, 2026
Currency API Exchange Rates Power BI Power Query DAX Tutorial Finexly

关于 Power BI 货币换算 的教程通常分成两派。DAX 派给你一个优雅的度量值,却默认你的模型里已经存在一张 ExchangeRate 表。API 派给你一段 Power Query 代码,它在 Power BI Desktop 里跑得漂亮,一发布就挂——因为计划刷新根本不接受它。

这篇指南按顺序讲完这两半:如何用 REST API 把实时汇率拉进 Power BI,并且发布到 Power BI 服务后仍能刷新;如何为这些汇率建模,让你的数字经得起追问;以及根据报表的实际需要,选择在导入时换算还是在查询时换算。

下面所有 Power Query 与 DAX 示例,都是按照 Finexly API 文档中的响应结构编写的。

先确定你面对的是哪一类换算问题

"Power BI 里的货币换算"其实是三个完全不同的工程问题共用了一个名字,选错方向是这篇文章里代价最高的错误。

  1. 多种源币种,一种报表币种。 销售表里有 EUR、GBP、JPY 的行,而财务总监只要一个 USD 数字。请在导入时换算。汇率是交易的属性,不是报表的属性。
  2. 一种源币种,多种报表币种。 所有数据都以 USD 存储,用户通过切片器选择显示币种。请用 DAX 在查询时换算。预先算好每种币种并不现实。
  3. 多种源币种,多种报表币种。 先在导入时统一到一种枢轴币种,再在其上套用第 2 种方案。不要试图一步到位。

有经验的建模者反复强调、这里也值得再说一遍的经验法则是:能多早换算就多早换算。每一次被推迟到查询时的换算,都会在每个视觉对象、每次筛选变化、每次切片器点击时向你收费。

用正确的方式把实时汇率拉进 Power Query

从汇率表开始。最高效的调用方式是一次多货币对查询,用一个请求拿到你关心的全部币种,而不是一个币种一个请求。

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.finexly.com/v1/convert?q=USD_EUR,USD_GBP,USD_JPY"
{
  "USD_EUR": { "rate": 0.9215 },
  "USD_GBP": { "rate": 0.7892 }
}

下面是对应的 Power Query M 查询。新建一个空白查询(主页 → 新建源 → 空白查询 → 高级编辑器)并粘贴:

let
    ApiKey = "YOUR_API_KEY",
    Pairs  = "USD_EUR,USD_GBP,USD_JPY,USD_CAD,USD_AUD,USD_CHF",
    Source = Json.Document(
        Web.Contents(
            "https://api.finexly.com",
            [
                RelativePath = "v1/convert",
                Query        = [ q = Pairs ],
                Headers      = [ #"Authorization" = "Bearer " & ApiKey ]
            ]
        )
    ),
    ToTable  = Record.ToTable(Source),
    Expanded = Table.ExpandRecordColumn(ToTable, "Value", {"rate"}, {"Rate"}),
    Split    = Table.SplitColumn(
                   Expanded, "Name",
                   Splitter.SplitTextByDelimiter("_", QuoteStyle.None),
                   {"BaseCurrency", "Currency"}
               ),
    Typed    = Table.TransformColumnTypes(
                   Split,
                   {{"BaseCurrency", type text}, {"Currency", type text}, {"Rate", type number}}
               ),
    Stamped  = Table.AddColumn(Typed, "RetrievedAt", each DateTimeZone.UtcNow(), type datetimezone)
in
    Stamped

把它命名为 FxRates。你会得到一张四列表——BaseCurrencyCurrencyRateRetrievedAt——可以直接用于关系和度量值。

那个会让计划刷新失败的 Web.Contents 错误

注意上面这段查询没有做的事:它从不把参数拼接进 URL 字符串。这正是货币换算报表"桌面能跑、云端就死"的头号原因。

如果你写成这样:

// Do NOT do this
Source = Json.Document(
    Web.Contents("https://api.finexly.com/v1/convert?q=" & Pairs)
)

……Power BI Desktop 会照常刷新,而 Power BI 服务会拒绝它,并提示 "此数据集包含动态数据源。不支持刷新。" 服务需要在分析阶段解析出一个静态基址 URL,才能把凭据绑定上去。把可变部分交给 RelativePathQuery,正好满足这一点:基址始终是 https://api.finexly.com,所有动态内容都放在选项里。

任何根据参数、日期或另一张表的值来拼 URL 的查询都适用同样的规则。如果这篇文章你只记住一件事,请记住 RelativePath

不要把 API 密钥硬编码进去

ApiKey = "YOUR_API_KEY" 写在代码里,做五分钟的测试没问题,用于任何共享场景都不行。语义模型的 M 代码,对任何拥有生成权限的人都是可见的。

两种可行做法:

  • 使用 Power Query 参数(主页 → 管理参数),然后写成 ApiKey = KeyParam。它仍然随模型保存,但集中管理、便于轮换,还能通过部署管道按环境覆盖。
  • 使用 Web API 凭据类型。 在 Power BI 服务中进入 语义模型设置 → 数据源凭据 → 编辑凭据,选择 Web API 并在那里填入密钥。服务会自行注入 Authorization 标头,你可以把 M 里的 Headers 选项完全删掉。这样密钥就不会留在模型定义中。

无论选哪种,都请使用仅限报表用途的密钥,并在有人离职时轮换。如果密钥出现在 URL 查询字符串而不是标头里,它可能被记录在代理和服务器访问日志中——Finexly 文档明确提醒了这一点,而这对任何提供商都成立。

建一张货币维度表,而不只是一个汇率列表

只有汇率表,你得到的只是数字。它给不了你正确的标签、正确的小数位,也给不了一个排序合理的切片器。把货币清单作为独立维度拉进来:

let
    ApiKey = "YOUR_API_KEY",
    Source = Json.Document(
        Web.Contents(
            "https://api.finexly.com",
            [
                RelativePath = "v1/currencies",
                Headers      = [ #"Authorization" = "Bearer " & ApiKey ]
            ]
        )
    ),
    ToTable = Table.FromList(Source, Splitter.SplitByNothing(), {"Currency"}),
    Typed   = Table.TransformColumnTypes(ToTable, {{"Currency", type text}})
in
    Typed

然后补上 Power BI 无法替你推断的两列:

  • MinorUnits —— 该币种实际使用的小数位数。JPY 是 0,KWD 是 3,多数是 2。把日元总额格式化成两位小数,在财务报表里是一眼可见的错误,而在错误的步骤上做四舍五入还会让误差累积。货币舍入指南讲清了误差是从哪里进来的。
  • FormatString —— 例如 "\€#,0.00""\¥#,0"。后面做动态格式字符串时会用到。

这两列都遵循 ISO 4217 标准,而不是 Power BI 原生知道的东西;ISO 4217 参考里有完整对照表。把这张表标记为维度,与 FxRates[Currency] 建立一对多关系,并用它——而不是汇率表——作为切片器的来源。

路线 A:在导入时换算(快、无聊、正确)

对于场景 1——多币种进、单币种出——把工作放在 Power Query 里做,让模型只存一个干净的数字。

  1. 把交易查询加载到 Power Query。
  2. 主页 → 合并查询,用 Transactions[Currency] 关联 FxRates[Currency](左外部)。
  3. 展开合并列,保留 Rate
  4. 添加列 → 自定义列
= if [Currency] = "USD" then [Amount]
  else if [Rate] = null then null
  else [Amount] / [Rate]

注意那个 null 分支。当汇率表缺少某个币种时,左外部联接会产生 null,而 Power Query 的算术运算遇到 null 会安静地返回 null 而不是报错——它最终变成视觉对象里的空白,以及一个悄悄偏小的总计。把缺口显式化,你才能筛出它、看见它。

也请注意那个除法。USD_EUR = 0.9215 表示 1 USD 兑 0.9215 EUR,所以把 EUR 金额换成 USD 要除。把 USD 金额换成 EUR 才是乘。方向搞反是多币种报表里第二常见的 bug,而当汇率接近 1.0 时它几乎看不出来——EUR/USD 数字上 3% 的误差看起来就像舍入差异,直到有人去对账。

路线 B:用 DAX 在查询时换算

对于场景 2——存储币种固定、显示币种由用户选择——换算必须发生在度量值里。

朴素写法对每一行做一次 LOOKUPVALUE,数据超过几十万行就会很慢。先聚合,再换算一次:

Sales (Reporting Currency) =
VAR SelectedCurrency = SELECTEDVALUE ( Currency[Currency], "USD" )
VAR Rate =
    CALCULATE (
        SELECTEDVALUE ( FxRates[Rate] ),
        FxRates[Currency] = SelectedCurrency
    )
VAR Result =
    IF (
        SelectedCurrency = "USD",
        [Sales Amount],
        [Sales Amount] * Rate
    )
RETURN
    IF ( ISBLANK ( Rate ) && SelectedCurrency <> "USD", BLANK (), Result )

两个比看上去更重要的细节:

  • 带默认值的 SELECTEDVALUE 没有 "USD" 兜底,只要没有选中币种,度量值就返回空白——而报表打开时正是这个状态。
  • 显式的空白保护。 如果某个币种没有汇率,请刻意返回空白,而不是让 [Sales Amount] * BLANK() 返回零。收入卡片上的零是谎言;空白才是看得见的缺口。

当汇率随时间变化

上面的度量值对整个数据集只用一个当前汇率。这对"去年的收入按今天算值多少"是对的,对其他几乎所有场景都是错的。如果你的汇率表是每币种每天一行,请先按日期分组再换算:

Sales (Historical Rates) =
SUMX (
    VALUES ( 'Date'[Date] ),
    VAR DayRate =
        CALCULATE (
            SELECTEDVALUE ( FxRates[Rate] ),
            FxRates[Currency] = SELECTEDVALUE ( Currency[Currency], "USD" )
        )
    RETURN
        [Sales Amount] * DayRate
)

迭代 VALUES('Date'[Date]) 而不是事实表,可以让迭代器保持很小——按天数,而不是按交易笔数。

动态格式字符串

$ 前缀写死在换算结果上,比不加符号更糟。在 Power BI 中把度量值的格式设为动态,并提供表达式:

SELECTEDVALUE ( Currency[FormatString], "#,0.00" )

现在显示 ¥ 的卡片就会显示 ¥、零位小数,而且不需要第二个度量值。过去这需要 Analysis Services 的计算组;度量值的动态格式字符串把它带进了 Power BI 本身。

你到底该用哪一种汇率?

这个问题决定了你做的是一个仪表板,还是一份财务愿意签字的报表——而没有任何 API 能替你回答。

  • 交易日即期汇率 —— 用于记录单笔交易。精度最高,汇率表也最大。
  • 月度或期间平均汇率 —— IAS 21 与 ASC 830 下利润表项目的标准做法。它平滑月内波动,也是多数合并报表实际使用的方式。
  • 期末收盘汇率 —— 用于资产负债表项目:现金、应收、应付。
  • 预算汇率或计划汇率 —— 全年固定不变,使差异分析能把经营表现与汇率波动分开。

一个严肃的模型往往需要同时并存两三种,作为同一张汇率表上的不同列(SpotRateAverageRateClosingRate),而不是拆成多张表。如果你的报表最终会进入申报材料,汇率与税务申报指南讲了你需要能够举证的数据来源与时间戳,历史汇率指南则讲了如何取带日期的汇率而非实时汇率。

那个悄悄破坏总计的周末缺口

外汇市场会休市。用实时 API 构建的每日汇率表没有周六、没有周日,也没有 12 月 25 日。把一笔周六的交易联接到这张表上,你会得到 nullnull 变成空白,而空白让总计正好少了一个周末的销售额。

请在汇率表里修,而不是在度量值里修。生成完整日期列表并向下填充:

let
    Dates = List.Dates(#date(2026,1,1), Duration.Days(Date.From(DateTime.LocalNow()) - #date(2026,1,1)) + 1, #duration(1,0,0,0)),
    DateTable = Table.FromList(Dates, Splitter.SplitByNothing(), {"Date"}),
    Typed = Table.TransformColumnTypes(DateTable, {{"Date", type date}}),
    Joined = Table.NestedJoin(Typed, {"Date"}, RateHistory, {"Date"}, "r", JoinKind.LeftOuter),
    Expanded = Table.ExpandTableColumn(Joined, "r", {"Currency", "Rate"}),
    Filled = Table.FillDown(Expanded, {"Currency", "Rate"})
in
    Filled

Table.FillDown 会把周五的汇率延续到整个周末,这是惯例做法;更重要的是,它是一个被明确声明的处理方式,而不是意外结果。填充前请先按币种和日期排序,否则你会把另一个币种的汇率带过缺口。

如果你的套餐不含历史接口,也可以向前积累历史:每次刷新时把当天汇率追加到一张存储表里——数据流或 Fabric Lakehouse 表都很合适——一个季度后你就有了真实的时间序列。它不能回溯,但每天只花一次 API 调用。

刷新计划与配额算术

Power BI Pro 对每个语义模型允许每天 8 次计划刷新;Premium 与 Fabric 容量允许 48 次。这就是你的 API 配额需要覆盖的数字,而这笔账比多数人想象的宽松。

上面的汇率表每次刷新是两个调用:一个 /v1/currencies,一个 /v1/convert。于是:

刷新频率每月刷新次数每月 API 调用Finexly 套餐
每天 8 次(Pro 上限)~240~480免费(1,000/月)
每天 48 次(Fabric,每 30 分钟)~1,440~2,880Starter
每天 48 次 + 每小时数据流历史~2,160~4,320Growth
免费套餐是每月 1,000 次请求、每分钟 10 次上限,足以覆盖一个以最高频率刷新的 Pro 工作区。当前限额与历史数据可用性见定价页面

真正需要留意的是每分钟 10 次这个上限。如果你在 Table.AddColumn 里对每个币种调用一次 /v1/rate,二十个币种就意味着几秒内二十次调用,刷新中途会撞上一连串 429。多货币对的 /v1/convert 正是为此而存在。批量请求并缓存:缓存与错误处理指南里的退避重试模式同样适用于计划刷新。

网关、Excel 与 Fabric

几条各能替你省下一个下午的环境提示:

  • 不需要网关。 云端 REST API 不是本地数据源,所以这件事不需要本地数据网关。如果刷新失败而有人建议你装一个,那多半是动态数据源错误换了个样子。
  • Excel 用的是同一个引擎。 Excel 里的 Power Query 完全接受上面那段 M 代码。如果你的受众活在工作簿而不是仪表板里,Excel 实时汇率指南讲了 WEBSERVICELAMBDA 和版本对照;还有一份 Google Sheets 版本
  • 一旦不止一个报表需要汇率表,Fabric Dataflow Gen2 是更好的落脚点。 汇率只落地一次,所有语义模型读同一张表,你的 API 用量就不再随报表数量增长。
  • 发布前先用一个已知数字做校验。货币换算器取一个货币对,与你的模型在同一时间点的同一货币对比对。如果对不上,说明存在方向或舍入问题——你希望现在发现它,而不是在董事会上。

常见问题

Power BI 不用 API 能做货币换算吗? 可以,前提是汇率由你自己提供:手工维护的表、财务系统导出,或者一个数据库视图。Power BI 没有内置汇率源。当你需要"不靠人记得去更新"的汇率时,API 才有意义。

为什么我的换算报表在 Power BI Desktop 能刷新,到服务里就失败? 几乎总是动态数据源错误。你的 Web.Contents 调用是用字符串拼接生成 URL 的。把可变部分移到 RelativePathQuery 选项里,让基址 URL 保持静态,重新发布并重新填写凭据。

换算该放在 Power Query 还是 DAX? 报表只有一种报表币种时放 Power Query,更快也更简单。用户在运行时选币种就放 DAX。两者都需要时,先在 Power Query 归一到一种枢轴币种,再在其上叠加 DAX 度量值。

一次 Power BI 刷新会用掉多少 API 请求? 如果你把所有货币对合并成一次 /v1/convert 调用,每次刷新两次。按 Power BI Pro 每天 8 次的上限算,大约每月 480 次请求,免费套餐即可覆盖。只有当你按币种或按行调用 API 时才会变贵。

每日汇率表里的周末和公共假期怎么处理? 生成连续日期表,把汇率左联接上去,按币种和日期排序,然后向下填充。周五的汇率会延续过周末。关键在于这个处理是有意为之且写明的,而不是让数据行悄悄消失。

财务报告该用哪种汇率? 利润表项目用期间平均汇率,资产负债表项目用期末收盘汇率——IAS 21 与 ASC 830 都是如此。把它们作为同一张汇率表上的不同列保存,报表就能在不改模型的情况下切换。


准备好让实时汇率支撑你的仪表板了吗?免费获取 Finexly API 密钥——无需信用卡。从每月 1,000 次请求开始(足够让一个 Power BI Pro 工作区以最高频率刷新),需要历史数据或更高频率时再升级。如果你还在比较供应商,对比页面把各家并排列了出来。

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 →