先日の勉強会にて取り上げた題材をもとに再構成したものです。
Metadataを積極的に使うべきとは思いませんが、全く知らないというのは勿体ない。
#Metadataとは
ごく簡単に言えば、それぞれの値の隠し情報がMetadataです。
M言語仕様によれば、どの値にもMetadataの場所があるとされています。(中身が入っているとは限らない)
まずはどう使われているか見てみましょう。
##システム上の利用例
こんな具合でパラメータを作ったとします。
それを詳細エディタで開くと、コードはこんな具合になっております。
上図の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でなくても使えます。
##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が空っぽということ
##Metadataが残る時
構造化された値(table、record、list)は値の中にも値がある、ということになります。
なので、構造化された値の中の特定の値にMetadata入れていた場合、構造化された値の他の箇所をいじってもMetadataは生き残る、ということになります。
tableでの例(クリックするとコードを展開)
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
#番外編:Metadataにクエリの途中のステップを入れる
クエリの完成時に、ステップの途中経過をMetadataに入れておくことで、別クエリから取り出すということも可能です。
例えば、このようにクエリを書いておくと。
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入れ
#注意点
クエリの実行速度という面では下記のような問題があることに触れておきます。
- Metadataを定義する際、Metadataに入れる値が評価されてしまう。
- ある値Value1のMetadataだけを呼び出すときでも、Value1自体は評価されてしまう。
##診断で確かめる
Power BI Desktopの診断機能で実証してみましょう。
例えば、こんなクエリを作ります。Source
の中身が評価されると、診断結果にその旨が書き込まれるようにしています。
※Diagnostics.Trace
関数については @PowerBIxyz さんの記事が詳しいです。
https://qiita.com/PowerBIxyz/items/e1f5abad9257adc7c517
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
僕のPCですと3秒程度で診断結果のクエリが作成されます。
Diagnostics.Trace
関数で仕込んだ結果は、このクエリのAdditional Info
列(左から13列目)に出力されますので、展開してやります。この例ではMessage
フィールドがないので、Source
の中身は全く評価されなかったということです。
Custom1
を評価するうえで、Source
は関係ないですから、この診断結果は想定通りということになります。
では、診断実験1クエリの最後のステップCustom1
に、MetadataとしてSource
を入れてやるとどうなるか。
Custom1 = dummy1 meta Source
ステップ診断結果は下記の通り。なんと、a
,b
どころかrecordに括っているc
すらも評価されてしまうのです。
これは特徴のある動きです。
ただ単に最終出力をSource
とした場合は、c
は評価されないからです。
※a
,b
はプレビューが出るので評価されるが、c
はプレビューされないため。
これを回避するには、Metadataに入れる値を関数にしまう、というのがスマートなやり方だと思います。(@PowerBIxyz さん、ありがとうございます)
例えば、診断実験1クエリの最後のステップCustom1
をこのように書き換えてやれば、Source
は評価されなくなります。
Custom1 = dummy1 meta [Temp=each Source]
※その他、二重のrecordやlistに括っても評価を免れますが、もはや取り出しが面倒なので割愛しました。
#参考
Metadata - PowerQuery M | Microsoft Docs
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アドイン版から既にあったと思います。