[Power BI] TMDLビューを使って「IsAvailableInMDX」プロパティを変更して効果を確認してみる

# はじめに
PBIJP Semantic Data Modeling workout #8　で発表した内容を纏めました。

https://powerbi.connpass.com/


Power BIデスクトップにTMDLビューが搭載されるとの発表がありました。

https://powerbi.microsoft.com/ja-jp/blog/deep-dive-into-tmdl-view-for-power-bi-desktop-preview/

今までTabular EditorやAPIなどでしか変更出来なかったセマンティックモデルのプロパティがPower BIデスクトップからも変更できるようになります。
その記事の中で **「IsAvailableInMDX」** の例が載っていました。今までBest Practice Analyzerで度々みていたので存在は知っていたものの、深く理解していなかったのでこれを機に試してみました。

# IsAvailableInMDXとは？
その前に **MDX** （Multi Dimensional eXpressions）ですが、多次元データベース（キューブ）に対するクエリ言語です。Microsoft以外でも広く使用されている業界標準といってもよい言語だと思います。昔々、DAX登場以前に習得してみようかと思ったもののすぐに挫折をした記憶があります…。

それはともかく、Power BIの世界では **「Excelで分析」** 機能がありますが、ここではExcelのPivotテーブルとセマンティックモデルとの対話は **MDX** を介して行われています。
MDXで会話を行う為に、Power BIセマンティックモデルでは **属性階層** を作成しています。インポートモデルではその分、処理時間とモデルサイズに影響を与えるので、ExcelのPivotテーブルで行・列に使用しない列には「IsAvailableInMDX」をfalseにすることで属性階層が作成されなくなり、 **処理時間の短縮** 及び **モデルの軽量化** ができるということになるはずです。

:::note info
 **Direct Lake** モードのセマンティックモデルでも、「IsAvailableInMDX」のプロパティは存在しており、 **「Excelで分析」機能が使用できる** ものの、モデル上に属性階層での容量を確認することはできませんでした。
:::

# どの列の「IsAvailableInMDX」をfalseにすべきか
Power BIのモデリングでは **スタースキーマ** モデリングがベストプラクティスとされており、明示的なメジャーを作成し、スライスはディメンションテーブルを使用し、使用しない列は非表示にするのがよいとされていますが、これをちゃんとやるとファクトテーブルにはメジャーしか残らなくなります。当然、Excelでもディメンションテーブル列とメジャーだけあればよいので、 **ファクトテーブルのすべての列を「IsAvailableInMDX」をfalseにするのがよい** と思いました。

:::note info
ちなみにBest Practice Analyzerでは以下の記載でした。列で並べ替えに使用していたり、ユーザ階層に使用しているカラムはfalseに変更して保存しようとするとエラーになりました。Direct Lakeモデルには関係ないことも記載されておりますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/e65ea9db-652b-0a58-19e9-0b74c161fbf0.png)
:::

# 試してみた
単純化するためにContosoモデルから３つのテーブルのみでインポートモードのセマンティックモデルを作成しました。Salesテーブルはすべて非表示にしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/4d2438a1-e7c6-386f-e22d-bf52ac0bf854.png)


## 本当にExcel PivotテーブルがMDXを使用している？
DAX StudioのALL Queriesを使いExcelで分析のクエリをトレースしてみました。MDXが発行されていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/2394c3ce-8477-ddec-4589-d41351685a7d.png)

```sql
SELECT
FROM
    [Model]
WHERE
    ([Measures].[Sales Amount]) CELL PROPERTIES VALUE,
    FORMAT_STRING,
    LANGUAGE,
    BACK_COLOR,
    FORE_COLOR,
    FONT_FLAGS
```

## TMDLビューで属性を変更する
TMDLビューからSalesテーブルをドラッグするとスクリプトが挿入されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/a468bd5c-dbd1-1306-d259-fafc51dde77b.png)

ですが、「IsAvailableInMDX」が表示されません💦
念のためTabular Editorでデフォルト値を確認します。trueですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/6a5d4638-f943-5e7b-fdbd-43a2657a0094.png)

TMDLビューに戻り属性名を打ち始めるとインテリセンスが効いてサジェストされました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/5086e355-4f55-a3f7-a220-a4edcd5b7e17.png)

それぞれのカラムに属性を打ち込んで…適用を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/330af12e-8537-3dc0-c7dc-904ef475e66f.png)

適用されたようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/7b0f07d0-263f-c6f0-8526-d973697d6ebb.png)




## モデルサイズと処理時間の比較
DAXスタジオでプロパティ変更前後の統計を比較しました。

デフォルト（「IsAvailableInMDX」が **true** )
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/aa8234a0-b0c2-489b-31a4-0a7e4f33bf62.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/9c2e68a7-5943-5854-6185-32b76d68a87c.png)


Salesテーブルのすべての列の「IsAvailableInMDX」を **false** に変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/53060545-d192-ebda-c50d-9e4b1b050c08.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/2748e868-3999-46fa-2f5e-31a696d96537.png)

処理時間は12分から11分とわずかに短縮されました。
また、属性階層のサイズがゼロになり、Salesテーブルのサイズが縮小されたのが確認できました。



# 副作用

SQL BIの記事に以下がありました。

https://docs.sqlbi.com/dax-internals/optimization-notes/isavailableinmdx-property

属性階層には列の統計情報が格納されており、フィルターコンテキストが存在しない場合に、列をスキャンすることなく値をとることができます。

:::note warn
「IsAvailableInMDX」をfalseにすることの副作用として、フィルターコンテキストがない列の情報もScanして取得するようになります。
:::
以下のDAXクエリーを試してみます。

```
EVALUATE
{
 DISTINCTCOUNT(Customer[City])
が
```
「IsAvailableInMDX」が **true** の場合はScanが発生せずに結果を取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/fc8514da-5ffd-2f1e-abea-87b1b551ae8b.png)

「IsAvailableInMDX」を **false** に変えると…、Scanが発生しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/7371fc3b-ded7-a0b8-73ed-56cb5acef3d4.png)

また、 **Direct Lakeモード** では、「IsAvailableInMDX」が **true** でもScanが発生しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/18988044-ae5c-0633-eaf1-4cd02db73e14.png)



## おわりに
副作用については、実際の現場ではフィルターコンテキストがない状態というのは考えにくいので、あまり気にしなくてもいいように思いました。フィルターコンテキストがない状態では列の統計情報から結果を得ることができるのは知っていましたが、それが「IsAvailableInMDX」に依存してるのは新しいｍでした。
一方で、Direct Lakeモデルは属性階層が無いようにみえるけどMDXは使えるという謎が残りました…。