すべてではないですよ。経験上であったり他の方法で知っていた分から、Power Queryでの内部的な動作(処理される順番とか)ってはどうなっているのかをトレースログから推測するなど。"適用したステップ" の順で処理されるわけではないよという話を検証するような内容。ただし、ログ自体の仕様は不明なので絶対ではない感じでよくて、あーそんな感じなのねとわかればよいかなと。
- Power Query ってちょっと不思議なところがあるけど、知っておいた方がいいことがあるよという話をした。- Qiita
- Power Query のトレースログ を Power Query でパースしてみた - Qiita
Power BI Desktop に "クエリ診断" 機能が追加されました
Power Query エディターで診断開始から終了、パースされたファイル(JSON)からログなど参照できるようになりました
Diagnostics.Trace
Diagnostics.Trace
Diagnostics.Trace(traceLevel as number, message as anynonnull, value as any, optional delayed as nullable logical) as any
トレースが有効になっていて値を返す場合は、トレース エントリを記述します。
トレースが有効になっていてvalue
を返す場合は、トレースmessage
を記述します。オプション パラメーターdelayed
は、メッセージがトレースされるまでvalue
の評価を遅らせるかどうかを指定します。traceLevel
には次のいずれかの値を指定できます:
TraceLevel.Critical
TraceLevel.Error
TraceLevel.Warning
TraceLevel.Information
TraceLevel.Verbose
いくつかのケース
Table
Table.AddColumn(カスタム列をテーブルに追加)
いくつかのカスタム列をテーブルに追加するとき、全行にカスタム列を追加が終わってから続いてカスタム列を追加するのではなくて、可能であれば行ごとにすべてのカスタム列を追加していく動作。
let
Source = #table(
type table [Column1 = Int64.Type],
List.Transform({1 .. 5}, each {_})
),
AddedCustom1 = Table.AddColumn(
Source,
"Custom1",
each [Column1] + 1,
Int64.Type
),
AddedCustom2 = Table.AddColumn(
AddedCustom1,
"Custom2",
each [Column1] + 2,
Int64.Type
)
in
AddedCustom2
let
Source = #table(
type table [Column1 = Int64.Type],
List.Transform({1 .. 5}, each {_})
),
AddedCustom1 = Diagnostics.Trace(
TraceLevel.Information,
"AddedCustom1",
()=> Table.AddColumn(
Source,
"Custom1",
each Diagnostics.Trace(
TraceLevel.Information,
"AddedCustom1 #" & Text.From([Column1]),
()=> [Column1] + 1,
true
),
Int64.Type
),
true
),
AddedCustom2 = Diagnostics.Trace(
TraceLevel.Information,
"AddedCustom2",
()=> Table.AddColumn(
AddedCustom1,
"Custom2",
each Diagnostics.Trace(
TraceLevel.Information,
"AddedCustom2 #" & Text.From([Column1]),
()=> [Column1] + 2,
true
),
Int64.Type
),
true
)
in
AddedCustom2
Table.SelectRows(行の選択)
Power Query エディターでの列の値を条件にするフィルタ操作。当然ながら全行を走査する動作
let
Source = #table(
type table [Column1 = Int64.Type, Column2 = Int64.Type],
List.Transform({1 .. 5}, each {_, _})
),
MultipliedColumn = Table.TransformColumns(
Source,
{"Column2", each _ * 10, Int64.Type}
),
FilteredRows = Table.SelectRows(
MultipliedColumn,
each [Column2] < 40
)
in
FilteredRows
let
Source = #table(
type table [Column1 = Int64.Type, Column2 = Int64.Type],
List.Transform({1 .. 5}, each {_, _})
),
MultipliedColumn = Diagnostics.Trace(
TraceLevel.Information,
"MultipliedColumn",
()=> Table.TransformColumns(
Source,
{
"Column2",
each Diagnostics.Trace(
TraceLevel.Information,
"MultipliedColumn2 #" & Text.From(_),
()=> _ * 10,
true
),
Int64.Type
}
),
true
),
FilteredRows = Diagnostics.Trace(
TraceLevel.Information,
"FilteredRows",
()=> Table.SelectRows(
MultipliedColumn,
each Diagnostics.Trace(
TraceLevel.Information,
"FilteredRows #" & Text.From([Column1]),
()=> [Column2] < 40,
true
)
),
true
)
in
FilteredRows
カスタム列の追加が終わってから行を選択する評価ではなく、カスタム列の追加をしつつ行を選択する動作。
Table.FirstN(先頭行の選択)
Table.SelectRows と異なり全行を走査しない。 先頭行から条件が満たされる行のみを選択。
なので。データソース側で行の順番が確定できる場合に使うとよいことがある。
let
Source = #table(
type table [Column1 = Int64.Type, Column2 = Int64.Type],
List.Transform({1 .. 5}, each {_, _})
),
MultipliedColumn = Table.TransformColumns(
Source,
{"Column2", each _ * 10, Int64.Type}
),
FirstRows = Table.FirstN(
MultipliedColumn,
each [Column2] < 40
)
in
FirstRows
let
Source = #table(
type table [Column1 = Int64.Type, Column2 = Int64.Type],
List.Transform({1 .. 5}, each {_, _})
),
MultipliedColumn = Diagnostics.Trace(
TraceLevel.Information,
"MultipliedColumn",
()=> Table.TransformColumns(
Source,
{
"Column2",
each Diagnostics.Trace(
TraceLevel.Information,
"MultipliedColumn2 #" & Text.From(_),
()=> _ * 10,
true
),
Int64.Type
}
),
true
),
FirstRows = Diagnostics.Trace(
TraceLevel.Information,
"FirstRows",
()=> Table.FirstN(
MultipliedColumn,
each Diagnostics.Trace(
TraceLevel.Information,
"FirstRows #" & Text.From([Column1]),
()=> [Column2] < 40,
true
)
),
true
)
in
FirstRows
4行目で条件を満たさなくなり、それ以降の評価は実施されない。
List
リスト(list)の場合もテーブルとほぼ等しくリストアイテムを順に評価していく動作。
List.Contains
let
ListContainsFalse = List.Contains(
{true, false, true, false},
false
),
ConvertedToTable = #table(1, {{ListContainsFalse}})
in
ConvertedToTable
let
ListContainsFalse = Diagnostics.Trace(
TraceLevel.Information,
"ListContains",
()=> List.Contains(
{
Diagnostics.Trace(TraceLevel.Information, "#1 TRUE", ()=> true, true),
Diagnostics.Trace(TraceLevel.Information, "#2 FALSE", ()=> false, true),
Diagnostics.Trace(TraceLevel.Information, "#3 TRUE", ()=> true, true),
Diagnostics.Trace(TraceLevel.Information, "#4 FALSE", ()=> false, true)
},
false
),
true
),
ConvertedToTable = #table(1, {{ListContainsFalse}})
in
ConvertedToTable
ふたつめのリストアイテムで条件を満たしたので、それ以降の評価は実施されない。
List.AllTrue / List.AnyTrue
特殊な List.Contains って感覚ではあったがちょっと違うようだ。
let
ListAnyTrue = List.AnyTrue(
{true, false, true, false}
),
ConvertedToTable = #table(1, {{ListAnyTrue}})
in
ConvertedToTable
let
ListAnyTrue = Diagnostics.Trace(
TraceLevel.Information,
"ListAnyTrue",
()=> List.AnyTrue(
{
Diagnostics.Trace(TraceLevel.Information, "#1 TRUE", ()=> true, true),
Diagnostics.Trace(TraceLevel.Information, "#2 FALSE", ()=> false, true),
Diagnostics.Trace(TraceLevel.Information, "#3 TRUE", ()=> true, true),
Diagnostics.Trace(TraceLevel.Information, "#4 FALSE", ()=> false, true)
}
),
true
),
ConvertedToTable = #table(1, {{ListAnyTrue}})
in
ConvertedToTable
ひとつめのリストアイテムで条件を満たすのだけど、すべてのリストアイテムを評価しているので、List.Contains に書き換えるとよいときがあるかと。
List.Select
Table.SelectRows 同様、すべてのリストアイテムが評価される。
let
ListSelect = List.Select(
{true, false, true, false},
each _ = true
),
ConvertedToTable = Table.FromColumns({ListSelect})
in
ConvertedToTable
let
ListSelect = Diagnostics.Trace(
TraceLevel.Information,
"ListSelect",
()=> List.Select(
{
Diagnostics.Trace(TraceLevel.Information, "#1 TRUE", ()=> true, true),
Diagnostics.Trace(TraceLevel.Information, "#2 FALSE", ()=> false, true),
Diagnostics.Trace(TraceLevel.Information, "#3 TRUE", ()=> true, true),
Diagnostics.Trace(TraceLevel.Information, "#4 FALSE", ()=> false, true)
},
each _ = true
),
true
),
ConvertedToTable = Table.FromColumns({ListSelect})
in
ConvertedToTable
Query Folding
"適用するステップ"は可能な限り折りたたみが行われる。
OData.Feed
とくに特別な動作ではないが、Request 内容からすると、全件を取得する動作はしない。
let
Source = OData.Feed(
"https://services.odata.org/v4/northwind/northwind.svc",
null,
[Implementation="2.0"]
),
Products_table = Source{[Name="Products",Signature="table"]}[Data],
FilteredRows = Table.SelectRows(
Products_table,
each ([Discontinued] = false)
),
ExpandedCategoryName = Table.ExpandRecordColumn(
FilteredRows,
"Category",
{"CategoryName"}, {"CategoryName"}
),
RemovedOtherColumns = Table.SelectColumns(
ExpandedCategoryName,
{"ProductID", "ProductName", "CategoryID", "CategoryName"}
)
in
RemovedOtherColumns
思ったこと🙄
できれだけ "行" 単位で処理が進むようにして、条件がそろっているなら Table.SelectRows や List.Select をではなく代替の関数を使うことで不要な評価を減らす感じなのかな。いろいろあるよねー。