DAXクエリでPower AutomateからPower BIデータセットを読む

背景

Power BIで色々見える化する際に、単にレポートを公開するだけでなく、定期レポートのような形でメールなりチャットなりで現在の状況を端的に通知したいというニーズがあった。
Power BIレポートからデータを取得しようと思うと、以下のような方法があるが、前者はテキストデータを取得できない(図表を画像で取得することはできる)、後者はデータ取得のために色々不要なオブジェクトを作る必要があるなど、使い勝手がいまいちだった。

レポートではなくその源流にあるデータセットに対してクエリを実行することができることを知ったので、やり方を調べてみた。

最終的に実現したいこと

Power Automateでデータ更新間隔に応じて日次・月次などのスケジュールで動くフローを作成し、定期レポートを配信する。

DAXクエリを試す環境の準備

Power Automate上でクエリを書いてトライアンドエラーを繰り返すのは何とも効率が悪い。特に慣れない言語を使うときには高速でトライアンドエラーを回せる環境を整えるのがとても大事。今回はMSの公式ドキュメントで言及されていたDAX Studioを使ってみる。
ローカルのPower BI Desktopでpbixファイルを開いていれば、DAX Studioを使ってpbixファイルの中のデータセットに接続してクエリを試せるらしい。
というわけで以下のようにしてクエリを試す環境を整えた。

  1. 公式サイトからDAX Studioのインストーラーを入手してインストール
  2. Power BIサービスからクエリを発行したいデータセットをダウンロード
  3. ダウンロードしたpbixファイルをPower BI Desktopで開く
  4. DAX Studioを開いてデータセットに接続

DAXクエリを書く

DAXクエリの基本

DAX クエリ - DAX | Microsoft Learnに書いてあるが、要点は以下の通り。

  • DAXクエリの返り値はテーブル
  • EVALUATE <テーブルを返すDAX式>が最も基本的な構文
  • DEFINEステートメントを使って変数を定義することができる

クエリについて学習する前に、DAX の基礎を十分に理解しておくことが重要です。 まだ十分に理解していない場合は、必ず「DAX の概要」を確認してください。

とのこと。いつも雰囲気でDAX式を使っているので、この機会に勉強しなおすことにする。

DAX式の基本

DAX関数

Excel関数によく似たDAX関数を組み合わせて目的の値を得るための式を構築することが基本になる。

データ型

DAX関数の引数や返り値で使うデータの型。公式ドキュメントがうまくまとまっていないので整理してみる。
一般に型と言えば、数値や文字列などの最小のデータ単位を表現するための基本型と、複数の基本型のデータを組み合わせて構造を持たせた複合型(言語によって呼び方が違う)があるが、DAX関数のドキュメントは以下のような状況。

  • 基本データ型は見つけやすいところに記載がある
  • 複合データ型が分かりやすく説明されたページが見つからないので自力で理解するしかない…
DAX式で使える複合データ型

色々見ながら私が理解した内容を記載しています。ということなので間違っている可能性があります。

テーブル

型(基本型)を持った列によって定義された行の集まりによって構成される表構造。要は普通のRDBのテーブルをイメージすればよい。
DAXクエリでは返り値をテーブル型にする必要があるので、EVALUATEステートメントの直下に記述する関数はテーブル型を返すことになる。

式の中ではシングルクオート'で囲み、'テーブル名'のようにして参照できる。 式の中でリテラルでテーブルを書くこともでき、ここに説明があって、DAXクエリでは以下のように試せる。

DEFINE TABLE '複数列から成るテーブル' = {("row1", 1), ("row2", 2), ("row3", 3)}
EVALUATE '複数列から成るテーブル'
Value1 Value2
row1 1
row2 2
row3 3
リスト(列が1つのテーブル)

どうやら厳密に言うとリスト型とういうものがDAXにはなく、列が1つのテーブルを使う様子。書き方は上と同じページに説明がある

DEFINE TABLE '1列から成るテーブル' = {1,2,3}
EVALUATE '1列から成るテーブル' 
Value
1
2
3

例えばIN演算子を書くときに使う。
ネストされたリストも作れるらしく、例えばDATATABLE関数の引数になるが、それ以外の使い道は不明。

Tableの中の単一の列に対する参照であり、実際に処理される際には行コンテキスト(後述)によって特定される基本型の具体的な値として評価される。
'Table'[Column]のようにして[ ]で囲んで指定する。
関数の引数として列そのものを指定したり、引数として指定するDAX式の中で使う。

コンテキスト

DAXを理解するキモになるのがおそらくコンテキスト。
DAX関数に渡す引数は"Value"のようにリテラルで定数を記述する場合を除き、テーブルや列になる。
[Column1] + [Column2]あるいはSUM('Table'[Column])のような式を書いたときに、「計算に含まれるのがどの行の値なのか」を決めるのがコンテキスト。
列名による参照のスコープと言い換えても良いかもしれない。
例えば以下のような場合にコンテキストが重要になる。

  • 累計値を得るために<該当行の日時以前の日時を持つすべての行の値の合計>を計算する
  • 販売シェア(%)を得るために<自社製品の販売個数の合計値> / <全製品の販売個数の合計値>を計算する
行コンテキスト

正確な解説はここが詳しい。
テーブルの行を一行ずつ扱う処理において、列名による参照を「現在処理されている行」に束縛するのが行コンテキスト。
行コンテキストが存在する場合、[Column1] + [Column2]の計算に使われる値は、現在処理されている行のColumn1とColumn2の値となる。

では「行コンテキストが存在する場合」とはどのような場合かというと、以下の2パターンがある。

  1. あるテーブルに作られた「計算列」(例:[Column1] + [Column2])のDAX式が処理されるときには、テーブルの各行が順番に処理されていき、DAX式の中で指定された列の参照先は行コンテキストによって束縛される(つまり、現在処理されている行の該当列の値が参照される)
  2. テーブルを引数として受け取るDAX関数(SUMX、FILTERなど)は与えられたテーブルの全行を順番に処理していく。多くは「処理の内容」を2つ目以降の引数で受け付けるようになっている(例えばFILTERはフィルタ条件のDAX式を第二引数で受け取る)が、「処理の内容」の中で記載された列名の参照先は行コンテキストによって束縛される(つまり第1引数で与えられたテーブルの現在処理されている行の該当列の値が参照される)
行コンテキストのネスト

理解が難しかったのが行コンテキストのネスト。ある行を処理しているときに「複数の行を含むテーブル」を参照しながら値を作る必要がある場合に行コンテキストのネストが発生する。
もう少し具体的に言うと、上記の2のパターン(テーブルを引数として受け取るDAX関数)が行コンテキストの中で現れた時、現在の行コンテキストの中で新しく「テーブルの全行を順番に処理する」ことになり、すなわち新しい行コンテキストが入れ子状に発生する。

例えば、累計値を計算したい場合には「現在の行の日時以前の日時を持つすべての行」を含むテーブルを参照する必要がある。
そのような処理を例えばforループで記述するときにはループがネストするはずである。

for (let row_context of ) {
  const current_datetime = row_context.datetime;
  let cumulative_val = 0;
  for (let nested_row_context of table) {
    if (nested_row_context.datetime <= current_datetime) {
      cumulative_val += nested_row_context.val;
    }
  }
}

このような処理を想定したとき、最初のループの中で1つの行コンテキストを処理している最中に、新しいループができて新しいループの中の複数の行コンテキストが処理されるという流れになる。
DAXにはネストされた行コンテキストの中から外側の行コンテキストの値を参照する仕組み(上記の実装例の中のcurrent_datetimeに相当)が用意されている。EARLIER関数がそれで、ここここが詳しい。

EARLIER関数の典型的なユースケースが累計の計算であり、上記の処理と同じことをDAX関数では以下のように記述できる。

SUMX(FILTER('Table', 'Table'[datetime] <= EARLIER('Table'[datetime])), 'Table'[val])
フィルターコンテキスト

公式ドキュメントではDAXのコンテキストは行コンテキスト・クエリコンテキスト・フィルターコンテキストの3つだと書いてあるが、どうやらクエリコンテキストはフィルターコンテキストの亜種らしいのと、Power BIレポートでは重要だがDAXクエリには関係なさそうなので省略。
例によってここが詳しい。

DAX式の中でDAX関数の引数などでテーブル名を指定したとき、テーブル名による参照を「あるフィルタ条件を満たす行」のみに束縛するのがフィルターコンテキスト。
要はあるDAX式が評価されるときにはテーブルにどのようなフィルタがかかっているか というのがフィルターコンテキスト。
フィルタは列に対する条件(例:[Column1] >= 30)であり、リレーションシップが定義されたテーブル間で伝播する。

DAXではフィルターコンテキストを明示的に操作する関数が用意されており、CALCULATE, CALCULATETABLEフィルター関数がそれにあたる。
例えば自社製品の販売シェアを得るために以下のように書ける。

SUMX(FILTER('Sales', [Company] = "My Company"), [Sales Amount]) / SUMX('Sales', [Sales Amount])

DAXクエリの作成(まだ書いてない)

DAXクエリのPower Automateへの組み込み(まだ書いてない)

まとめ(まだ書いてない)