Power BI の通貨換算に関する解説記事は、たいてい二つの陣営のどちらかに属します。DAX 陣営は洗練されたメジャーを見せてくれますが、ExchangeRate テーブルがすでにモデル内に存在することをさらりと前提にしています。API 陣営は Power Query のコードを見せてくれますが、それは Power BI Desktop では見事に動き、発行した瞬間に壊れます。スケジュールされた更新が受け付けないからです。
このガイドは、その両方を順番に扱います。REST API からリアルタイム為替レートを Power BI に取り込み、しかも Power BI サービスへの発行に耐える方法。数字を説明可能なものにするためのレートのモデリング。そしてレポートが本当に必要としているものに応じて、インポート時に換算するか、クエリ時に換算するかの選び方です。
以下の Power Query と DAX のサンプルはすべて、Finexly API のドキュメントに記載されたレスポンス構造に基づいて書かれています。
まず、自分がどの換算問題を抱えているかを決める
「Power BI の通貨換算」は、同じ名前で呼ばれる三つの別々の技術課題です。ここで選択を誤ることが、この記事全体で最も高くつく間違いです。
- 複数の元通貨、単一のレポート通貨。 売上テーブルに EUR、GBP、JPY の行があり、CFO は USD の数字一つを求めている。インポート時に換算してください。レートはトランザクションの属性であって、レポートの属性ではありません。
- 単一の元通貨、複数のレポート通貨。 すべて USD で保存され、ユーザーがスライサーで表示通貨を選ぶ。DAX でクエリ時に換算してください。全通貨を事前計算するのは現実的ではありません。
- 複数の元通貨、複数のレポート通貨。 インポート時に単一の基準通貨へ正規化し、その上にケース 2 を重ねます。一段階で解こうとしないでください。
経験を積んだモデラーが繰り返し言い、ここでも繰り返す価値のある原則はこうです。許される限り早く換算する。 クエリ時に押し込んだ換算はすべて、ビジュアルごと、フィルター変更ごと、スライサーのクリックごとにコストを請求してきます。
Power Query に正しくリアルタイムレートを取り込む
まずレートテーブルから始めます。最も効率のよい呼び出しは複数ペアの一括取得です。通貨ごとに 1 リクエストではなく、必要な通貨すべてを 1 リクエストで取得できます。
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 にします。BaseCurrency、Currency、Rate、RetrievedAt の 4 列テーブルが得られ、そのままリレーションシップやメジャーで使えます。
スケジュール更新を壊す 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 を解決できる必要があります。可変部分を RelativePath と Query に渡せば、まさにそれが満たされます。ベースは https://api.finexly.com のままで、動的な部分はすべてオプション側に置かれます。
パラメーター、日付、他テーブルの値から URL を組み立てるクエリすべてに同じことが言えます。この記事から一つだけ持ち帰るなら、RelativePath にしてください。
API キーをハードコードせずに扱う
ApiKey = "YOUR_API_KEY" をコードに直書きするのは、5 分のテストなら問題ありませんが、共有されるものには不適切です。セマンティックモデルの 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 が推測できない 2 列を追加します。
MinorUnits— その通貨が実際に使う小数桁数。JPY は 0、KWD は 3、多くは 2 です。円の合計を小数 2 桁で表示するのは財務レポートでは一目でわかる誤りであり、誤った段階での丸めがそれを増幅します。通貨の丸めガイドが、誤差の入り込む場所を説明しています。FormatString— 例:"\€#,0.00"、"\¥#,0"。後述の動的書式文字列で必要になります。
どちらも Power BI がネイティブに知っているものではなく ISO 4217 に従います。ISO 4217 リファレンスに完全な表があります。このテーブルをディメンションとしてマークし、FxRates[Currency] と一対多で関連付け、スライサーのソースにはレートテーブルではなくこちらを使ってください。
方法 A:インポート時に換算する(速く、地味で、正しい)
シナリオ 1 ——多通貨が入って 1 通貨が出る——では、作業を Power Query で済ませ、モデルにはきれいな数値を一つだけ保存させます。
- トランザクションのクエリを Power Query に読み込む。
- ホーム → クエリのマージ で
Transactions[Currency]とFxRates[Currency]を結合(左外部)。 - マージした列を展開し、
Rateを残す。 - 列の追加 → カスタム列:
= 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 に換算するときは掛けます。これを取り違えるのは多通貨レポートで 2 番目に多いバグで、レートが 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()にゼロを返させるのではなく、意図して空白を返してください。売上カードのゼロは嘘であり、空白は見える欠損です。
レートが時系列で変わる場合
上のメジャーはデータセット全体に単一の現在レートを適用します。これは「昨年の売上は今日ならいくらか」には正しく、それ以外のほぼすべてには誤りです。レートテーブルが通貨×日で 1 行になっているなら、換算前に日付でグループ化します。
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" )これで ¥ を表示するカードは ¥ を小数桁ゼロで表示します。2 つ目のメジャーは不要です。以前は Analysis Services の計算グループが必要でしたが、メジャーの動的書式文字列によって Power BI 本体で使えるようになりました。
実際にはどのレートを使うべきか
これはダッシュボードと「経理が承認するレポート」を分ける問いであり、どの API も代わりに答えてはくれません。
- 取引日のスポットレート — 個々の取引を記録する用途。精度は最高、レートテーブルは最大。
- 月次または期間平均 — IAS 21 でも ASC 830 でも、損益計算書項目の標準。月内の変動をならし、多くの連結処理で実際に使われています。
- 期末レート — 貸借対照表項目(現金、売掛金、買掛金)向け。
- 予算レート/計画レート — 通年で固定し、差異分析が事業成績と為替変動を切り分けられるようにするためのもの。
本格的なモデルでは、これらのうち 2〜3 種類を同時に持つことがよくあります。別テーブルにするのではなく、同じレートテーブルの別列(SpotRate、AverageRate、ClosingRate)にしてください。レポートが申告書類に流れ込むなら、為替レートと税務報告のガイドが、説明責任を果たせるソースとタイムスタンプについて扱っています。またヒストリカルレートのガイドでは、リアルタイムではなく日付指定のレート取得を解説しています。
合計を静かに壊す「週末の穴」
為替市場は閉まります。ライブ API から作った日次レートテーブルには、土曜も日曜も 12 月 25 日もありません。土曜日付のトランザクションをそのテーブルに結合すれば null が返り、null は空白になり、空白は週末の売上ちょうどぶんだけ小さい合計になります。
これはメジャーではなくレートテーブル側で直します。連続した日付リストを生成し、下方向にフィルします。
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
FilledTable.FillDown は金曜のレートを週末に持ち越します。これは慣行的な扱いであり、そして何より、偶然ではなく明示された扱いです。フィルの前に通貨と日付で並べ替えてください。そうしないと別の通貨のレートを穴に持ち越してしまいます。
プランにヒストリカルのエンドポイントが含まれていない場合は、履歴を前向きに積み上げる方法もあります。更新のたびに当日のレートを保存テーブルに追記する——データフローや Fabric レイクハウステーブルが適しています——と、四半期後には本物の時系列が手に入ります。遡及はできませんが、コストは 1 日 1 回の API 呼び出しだけです。
更新スケジュールとクォータの計算
Power BI Pro はセマンティックモデルあたり1 日 8 回のスケジュール更新を許可し、Premium と Fabric の容量では 48 回です。これが API クォータでカバーすべき数字ですが、計算は多くの人が思うより優しいものです。
上のレートテーブルは 1 回の更新につき 2 コール——/v1/currencies に 1 回、/v1/convert に 1 回です。したがって:
| 更新頻度 | 月あたり更新回数 | 月あたり API 呼び出し | Finexly プラン |
|---|---|---|---|
| 1 日 8 回(Pro の上限) | 約 240 | 約 480 | 無料(1,000/月) |
| 1 日 48 回(Fabric、30 分ごと) | 約 1,440 | 約 2,880 | Starter |
| 1 日 48 回+毎時のデータフロー履歴 | 約 2,160 | 約 4,320 | Growth |
注意すべきは毎分 10 回の上限です。Table.AddColumn の中で通貨ごとに /v1/rate を呼ぶクエリを組むと、20 通貨は数秒で 20 コールとなり、更新の途中で 429 が連発します。複数ペア対応の /v1/convert はまさにそのために存在します。まとめて呼び、キャッシュしてください。キャッシュとエラー処理のガイドにあるバックオフ付きリトライのパターンは、スケジュール更新にもそのまま当てはまります。
ゲートウェイ、Excel、Fabric
それぞれ半日を節約してくれる環境まわりのメモです。
- ゲートウェイは不要。 クラウドの REST API はオンプレミスのソースではないので、オンプレミス データ ゲートウェイは必要ありません。更新に失敗して誰かがゲートウェイの導入を勧めてきたら、それはたいてい動的データソースのエラーが姿を変えたものです。
- Excel も同じエンジン。 Excel の Power Query は上の M をそのまま受け付けます。読者がダッシュボードよりブックの中で仕事をしているなら、Excel でのリアルタイムレート取得ガイドが
WEBSERVICE、LAMBDA、バージョン対応表を扱っています。Google スプレッドシート版もあります。 - 複数のレポートが必要とするなら、レートテーブルの置き場所は Fabric Dataflow Gen2 が適しています。 レートを一度だけ着地させ、すべてのセマンティックモデルに同じテーブルを読ませれば、API 使用量がレポート数に比例して増えることはなくなります。
- 公開前に既知の値と突き合わせてください。 通貨コンバーターから 1 ペアを取り、同じ時点の同じペアについてモデルが示す値と比較します。食い違うなら方向か丸めの問題です。役員会ではなく今この場で見つけたいはずです。
よくある質問
API なしで Power BI は通貨換算できますか? レートを自分で用意すればできます。手作業で保守する表、財務システムからのエクスポート、データベースビューなどです。Power BI に組み込みのレートソースはありません。API が意味を持つのは、「誰かが更新を覚えていなくても」更新されるレートが必要なときです。
換算レポートが Power BI Desktop では更新できるのにサービスで失敗するのはなぜ?
ほぼ必ず動的データソースのエラーです。Web.Contents の呼び出しが文字列連結で URL を組み立てています。可変部分を RelativePath と Query オプションに移してベース URL を静的にし、再発行して資格情報を入力し直してください。
換算は Power Query と DAX のどちらでやるべき? レポートのレポート通貨が一つなら Power Query——速く、単純です。ユーザーが実行時に通貨を選ぶなら DAX。両方必要なら、Power Query で基準通貨に正規化し、その上に DAX メジャーを重ねます。
Power BI の更新 1 回で API リクエストはどれくらい消費しますか?
全ペアを 1 回の /v1/convert にまとめれば、更新あたり 2 回です。Power BI Pro の上限である 1 日 8 回なら月におよそ 480 リクエストで、無料プランの範囲内に収まります。高くつくのは通貨ごとや行ごとに API を呼んだ場合だけです。
日次レートテーブルで週末や祝日はどう扱えばいい? 連続した日付テーブルを作り、レートを左結合し、通貨と日付で並べ替えてから下方向にフィルします。金曜のレートが週末に持ち越されます。重要なのは、その扱いが意図的で文書化されていること——行が黙って消えないことです。
財務報告にはどの為替レートを使うべき? 損益計算書項目には期間平均レート、貸借対照表項目には期末レート。IAS 21 でも ASC 830 でも同じです。同一のレートテーブルの別列として保持すれば、モデルを変更せずにレポート側で切り替えられます。
ダッシュボードの裏側にリアルタイムレートを置く準備はできましたか。Finexly の無料 API キーを取得——クレジットカードは不要です。月 1,000 リクエストから始められ、これは Power BI Pro のワークスペースを最大頻度で更新するのに十分な量です。ヒストリカルデータやより高い更新頻度が必要になったらアップグレードしてください。まだ提供元を比較中なら、比較ページで各社を並べて確認できます。
Explore More
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 →