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

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

- [Power Query ってちょっと不思議なところがあるけど、知っておいた方がいいことがあるよという話をした。- Qiita](https://qiita.com/PowerBIxyz/items/a98a991b301c071632dc)
- [Power Query のトレースログ を Power Query でパースしてみた - Qiita](https://qiita.com/PowerBIxyz/items/d32807275fa73afebd8c)

**Power BI Desktop に "クエリ診断" 機能が追加されました**
Power Query エディターで診断開始から終了、パースされたファイル(JSON)からログなど参照できるようになりました :smile: 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/a7d7e0bf-edb5-bde7-b17c-18da4ea61491.png)

# Diagnostics.Trace
>**Diagnostics.Trace**
Diagnostics.Trace(traceLevel as *number*, message as *anynonnull*, value as *any*, optional delayed as *nullable logical*) as *any*
トレースが有効になっていて値を返す場合は、トレース エントリを記述します。
トレースが有効になっていて <code>value</code> を返す場合は、トレース <code>message</code> を記述します。オプション パラメーター <code>delayed</code> は、メッセージがトレースされるまで <code>value</code> の評価を遅らせるかどうかを指定します。<code>traceLevel</code> には次のいずれかの値を指定できます:
TraceLevel.Critical
TraceLevel.Error
TraceLevel.Warning
TraceLevel.Information
TraceLevel.Verbose

# いくつかのケース
## Table
### Table.AddColumn(カスタム列をテーブルに追加)
いくつかのカスタム列をテーブルに追加するとき、全行にカスタム列を追加が終わってから続いてカスタム列を追加するのではなくて、可能であれば行ごとにすべてのカスタム列を追加していく動作。

```text
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
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/e346af70-a3de-0d30-dbb0-535a78404d3e.png)

```text
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
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/67aafa92-8298-0500-3378-d405083dc51d.png)

### Table.SelectRows(行の選択)
Power Query エディターでの列の値を条件にするフィルタ操作。当然ながら全行を走査する動作

```text: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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/790617ea-2dc5-bda5-98f4-113eda22fd47.png)

```text
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/f238b2e0-2daa-6aee-3271-9b751c1cbd7e.png)
カスタム列の追加が終わってから行を選択する評価ではなく、カスタム列の追加をしつつ行を選択する動作。

### Table.FirstN(先頭行の選択)
Table.SelectRows と異なり全行を走査しない。 先頭行から条件が満たされる行のみを選択。
なので。データソース側で行の順番が確定できる場合に使うとよいことがある。

```text: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
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/4be749c6-bb5d-2a6e-964c-672adbd58809.png)


```text
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/3a04d344-3d52-e606-05f0-c36e385bac4a.png)
4行目で条件を満たさなくなり、それ以降の評価は実施されない。

## List
リスト(list)の場合もテーブルとほぼ等しくリストアイテムを順に評価していく動作。
### List.Contains

```text
let
    ListContainsFalse = List.Contains(
        {true, false, true, false},
        false
    ),
    ConvertedToTable = #table(1, {{ListContainsFalse}})
in
    ConvertedToTable
```

```text
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/e9bba2bb-43ae-38a9-0bac-34f37fe0ba04.png)
ふたつめのリストアイテムで条件を満たしたので、それ以降の評価は実施されない。

### List.AllTrue / List.AnyTrue
特殊な List.Contains って感覚ではあったがちょっと違うようだ。

```text
let
    ListAnyTrue = List.AnyTrue(
        {true, false, true, false}
    ),
    ConvertedToTable = #table(1, {{ListAnyTrue}})
in
    ConvertedToTable
```

```text
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/11fea5c8-191f-ce45-6bbe-2c0d071934f6.png)

ひとつめのリストアイテムで条件を満たすのだけど、すべてのリストアイテムを評価しているので、List.Contains に書き換えるとよいときがあるかと。

### List.Select
Table.SelectRows 同様、すべてのリストアイテムが評価される。

```text
let
    ListSelect = List.Select(
        {true, false, true, false},
        each _ = true
    ),
    ConvertedToTable = Table.FromColumns({ListSelect})
in
    ConvertedToTable
```

```text
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/be2aec45-2bdb-0367-90f5-43e0d3a1acad.png)

## Query Folding
"適用するステップ"は可能な限り折りたたみが行われる。
### OData.Feed
とくに特別な動作ではないが、Request 内容からすると、全件を取得する動作はしない。

```text
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193509/12a850a0-1be1-b551-9c05-e64d09241e28.png)

# 思ったこと🙄
できれだけ "行" 単位で処理が進むようにして、条件がそろっているなら Table.SelectRows や List.Select をではなく代替の関数を使うことで不要な評価を減らす感じなのかな。いろいろあるよねー。

# その他
- [Power BI 勉強会 - connpass](https://powerbi.connpass.com/)
- [Power BI Desktop を使い始めるには](http://qiita.com/PowerBIxyz/items/6c4735cb5b5712e70981)
- [Excel で Power Query を使い始めるには](http://qiita.com/PowerBIxyz/items/52e563037b855a099ca9)