Metadataをたまには使ってみる

先日の勉強会にて取り上げた題材をもとに再構成したものです。
Metadataを積極的に使うべきとは思いませんが、全く知らないというのは勿体ない。

#Metadataとは
ごく簡単に言えば、それぞれの値の隠し情報がMetadataです。
M言語仕様によれば、どの値にもMetadataの場所があるとされています。（中身が入っているとは限らない）
まずはどう使われているか見てみましょう。

##システム上の利用例
こんな具合でパラメータを作ったとします。
![パラメータ設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/e70e3036-d9ce-64b2-9089-ca64d4d8238d.png)
それを詳細エディタで開くと、コードはこんな具合になっております。
![パラメータの詳細エディタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/19265d24-ce11-9aca-f450-d70f026cd6f7.png)


上図の`meta`以降のrecord部分――`[`と`]`で括られている部分――がMetadataです。ここにはパラメータの各種設定が記述されていて、こことPower Queryエディタのパラメータ用のUIは連動しているようです。
他にも、`#shared`にある関数の説明書や、ファイルから得た`binary`にもMetadataが入っていることが分かっています。

#Metadataを記述する
こうしたMetadataはユーザが自由に設定できますし、読み取ることができます。
システム側でしか使えないもの、ではないです。
定義する際には、先のシステムが生成したコードのように、`meta`の後に入れたい中身をrecordとして記述します。

```
//定義したいとき
song1 ="mela!" meta [artist ="緑黄色社会",cm="パルティ"],
//読み取りたいとき
Song1Artist = Value.Metadata(song1)[artist] //"緑黄色社会"
```
`song1`自体はただのtextですが、そこからMetadataが取り出せていることが分かると思います。
※スクショの関係でrecordで記述していますが、recordでなくても使えます。

![定義例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/dbd0d6c5-699c-94b1-78a7-85be0d70c8d2.png)

##Metadataは引き継がれない
ある値（Value1としましょう）にMetadataを入れた後に、Value1の表の値に何らかの処理をした場合、処理後の値にはMetadataは引き継がれません。
こんな具合です。

```
x 　 = "元旦" meta [date=#date(2021,1,1)],//Metadataを定義
test = Value.Metadata(x)[date],//#date(2021,1,1)
y    = x & "★",//xを改変してyに入れる
z    = Value.Metadata(y) =[] //Metadataが空っぽということ
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/6606654b-9261-82d1-eaa8-adf84bda6b86.png)


##Metadataが残る時
構造化された値（table、record、list）は値の中にも値がある、ということになります。
なので、構造化された値の中の特定の値にMetadata入れていた場合、構造化された値の他の箇所をいじってもMetadataは生き残る、ということになります。

<details><summary>tableでの例(クリックするとコードを展開)</summary><div>

```
let
    //「データの入力」で作ったテーブル
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMjRSitUBUsYQygRCmSrFxgIA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [列1 = _t]),
    AddMetadata = Table.TransformColumns(Source,{{"列1",each _ meta [msg = _&"日"],type text}}),
    列1をいじらない処理 = Table.AddColumn(AddMetadata, "カスタム", each null),
    列1 = Value.Metadata( 列1をいじらない処理{0}[列1] )
in
    列1
```

</div></details>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/5df51974-31b8-74d7-7489-ecb252d99a57.png)






#番外編：Metadataにクエリの途中のステップを入れる
クエリの完成時に、ステップの途中経過をMetadataに入れておくことで、別クエリから取り出すということも可能です。
例えば、このようにクエリを書いておくと。

```pq:クエリ2
let
    //「データの入力」で作ったテーブル
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMjRSitUBUsYQygRCmSrFxgIA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [列1 = _t]),
    型変換 = Table.TransformColumnTypes(Source,{{"列1", Int64.Type}}),
    処理1 = Table.TransformColumns(型変換, {{"列1", each _ * 100, type number}}),
    処理2とMetadata入れ = Table.FromColumns(List.Repeat({処理1[列1]},5)) meta [base = 処理1[列1]]
in
    処理2とMetadata入れ
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/05482672-4660-0247-1605-ef3b39c6f698.png)

こんな具合で参照できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/807a0688-dae1-e91a-902c-fcbf90a7e88f.png)


#注意点
クエリの実行速度という面では下記のような問題があることに触れておきます。

- Metadataを定義する際、Metadataに入れる値が評価されてしまう。
- ある値Value1のMetadataだけを呼び出すときでも、Value1自体は評価されてしまう。

##診断で確かめる
Power BI Desktopの診断機能で実証してみましょう。
例えば、こんなクエリを作ります。`Source`の中身が評価されると、診断結果にその旨が書き込まれるようにしています。
※`Diagnostics.Trace`関数については @PowerBIxyz さんの記事が詳しいです。
https://qiita.com/PowerBIxyz/items/e1f5abad9257adc7c517

```pq:診断実験1
let
    fx_ForTrace =(arg as any)=>
        Diagnostics.Trace(
                        TraceLevel.Information,
                        Text.Format("#{0}を計算した",{arg}),
                        arg
        ),
    Source = [
                a = fx_ForTrace("a"),
                b = fx_ForTrace("b"),
                c =[test= fx_ForTrace("c")]
    ],
    dummy1 =0,dummy2 =2,dummy3=4,dummy6=10,
    Custom1 = dummy1
in
    Custom1
```
最後のステップを右クリックして「診断」を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/93ce1092-4564-cc36-846f-a181fb2ef3d0.png)

僕のPCですと3秒程度で診断結果のクエリが作成されます。
`Diagnostics.Trace`関数で仕込んだ結果は、このクエリの`Additional Info`列（左から13列目）に出力されますので、展開してやります。この例では`Message`フィールドがないので、`Source`の中身は全く評価されなかったということです。
`Custom1`を評価するうえで、`Source`は関係ないですから、この診断結果は想定通りということになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/ce41ee8a-32ce-a730-1008-403b371d7872.png)

では、診断実験1クエリの最後のステップ`Custom1`に、Metadataとして`Source`を入れてやるとどうなるか。

```
Custom1 = dummy1 meta Source
```

ステップ診断結果は下記の通り。なんと、`a`,`b`どころかrecordに括っている`c`すらも評価されてしまうのです。
これは特徴のある動きです。
ただ単に最終出力を`Source`とした場合は、`c`は評価されないからです。
※`a`,`b`はプレビューが出るので評価されるが、`c`はプレビューされないため。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370992/5c3fa601-7c4f-8e8a-d33f-c1642d87e995.png)

これを回避するには、Metadataに入れる値を関数にしまう、というのがスマートなやり方だと思います。（@PowerBIxyz さん、ありがとうございます）
例えば、診断実験1クエリの最後のステップ`Custom1`をこのように書き換えてやれば、`Source`は評価されなくなります。

```
Custom1 = dummy1 meta [Temp=each Source]
```
※その他、二重のrecordやlistに括っても評価を免れますが、もはや取り出しが面倒なので割愛しました。

#参考
[Metadata - PowerQuery M | Microsoft Docs](https://docs.microsoft.com/en-us/powerquery-m/metadata)

Metadataを操作する関数
https://docs.microsoft.com/en-us/powerquery-m/value-functions#__toc360789761


#テスト環境
Power BI Desktop：2.93.981.0 64-bit (2021年5月)
Excel：MS365のExcel バージョン2105（Power Query 2.93.422.0 64 ビット）
※2010アドイン版から既にあったと思います。