Edited at

Power Query のトレースログを見て、どのように処理されているか推測するなど

すべてではないですよ。経験上であったり他の方法で知っていた分から、Power Queryでの内部的な動作(処理される順番とか)ってはどうなっているのかをトレースログから推測するなど。"適用したステップ" の順で処理されるわけではないよという話を検証するような内容。ただし、ログ自体の仕様は不明なので絶対ではない感じでよくて、あーそんな感じなのねとわかればよいかなと。


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 エディターでの列の値を条件にするフィルタ操作。当然ながら全行を走査する動作


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}
),
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 と異なり全行を走査しない。 先頭行から条件が満たされる行のみを選択。

なので。データソース側で行の順番が確定できる場合に使うとよいことがある。


Table.FirstN

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 をではなく代替の関数を使うことで不要な評価を減らす感じなのかな。いろいろあるよねー。


その他