Power Query へそのゴマ　第１３章　式、関数、識別子、パラメータを用いた高度な操作

Power Queryの中核であるM言語では、式や関数、識別子、パラメータが重要な役割を果たします。本章では、これらの要素を駆使して複雑なロジックを構築する方法を深堀りします。また、`each`キーワードやオプションパラメータを用いた高度なクエリ設計についても解説します。

# 13.1 高度な式と識別子

## 13.1.1 式の評価順序と最適化

M言語の式は、順序に従って評価されますが、特定の条件下では遅延評価が行われます。

- **遅延評価**: 式が参照されるまで計算が遅延するため、効率的なパフォーマンスが期待されます。
- **例: 条件分岐での遅延評価**
  ```
  let
      a = 10,
      b = if a > 5 then a * 2 else a / 0  // "else"の部分は評価されない
  in
      b
  ```

## 13.1.2 識別子のスコープ

識別子は、スコープ（有効範囲）が定義された名前付き値です。

- **ローカルスコープ**: その式内でのみ有効な識別子。
- **グローバルスコープ**: クエリ全体で共有される識別子。
- **識別子の例**:
  ```
  let
      Value1 = 100,            // グローバルスコープ
      Result = let
          Value2 = Value1 * 2  // ローカルスコープ
      in
          Value2
  in
      Result
  ```
  **結果**: `200`

---

# 13.2 無名関数と`each`キーワード

## 13.2.1 `each`キーワードの基本

`each`キーワードは、シンプルな無名関数を記述するための省略形です。


```
Table.AddColumn(
    PreviousTable, 
    "NewColumn", 
    each [Column1] + [Column2]
)
```

`each`は`(_) => _[Column1] + _[Column2]`の省略形です。以下の5つは同じ動作になります。引数に`_`を指定している場合は省略可能です。

```
each [Column1] + [Column2]
each _[Column1] + _[Column2]
(_)=> [Column1] + [Column2]
(_)=> _[Column1] + _[Column2]
(x)=> x[Column1] + x[Column2]
```

以下のような書き方はエラーになります。

```
()=> [Column1] + [Column2]
()=> _[Column1] + _[Column2]
(x)=> [Column1] + [Column2]
(x)=> _[Column1] + _[Column2]
```

## 13.2.2 多層構造での使用

  複雑なロジックでは`each`を入れ子構造で使用できます。

<table border="1">
<tr><th>Column1</th><th>Column2</th></tr>
<tr><td rowspan="2" style="text-align:center">1</td><td style="text-align:center">2</td></tr><tr><td style="text-align:center">3</td></tr>
<tr><td rowspan="2" style="text-align:center">4</td><td style="text-align:center">5</td></tr><tr><td style="text-align:center">6</td></tr>
</table>

このようなテーブルで、`Column2`の`2`と`3`、`5`と`6`はそれぞれリストになっています。

```
let
    Source = 
        Table.FromRows(
            {
                {1, {2, 3}},
                {4, {5, 6}}
            },
            type table [Column1 = number, Column2 = number]
        ),

    Calculate = 
        Table.AddColumn(
            Source,
            "NewColumn", 
            each                  // _ には [Column1]の値と[Column2]のリストが入る
                List.Transform(
                    _[Column2],   
                    each _ * 10   // _ には [Column2」のリストの中身が入る
                )
        )

in
    Calculate  
```

変換結果は以下の様になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/1055776a-57da-7724-a085-7d61b91fd374.png)

<table border="1">
<tr><th>Column1</th><th>Column2</th><th>NewColumn</th</tr>
<tr><td rowspan="2" style="text-align:center">1</td><td style="text-align:center">2</td><td style="text-align:center">20</td></tr><tr><td style="text-align:center">3</td><td style="text-align:center">30</td></tr>
<tr><td rowspan="2" style="text-align:center">4</td><td style="text-align:center">5</td><td style="text-align:center">50</td></tr><tr><td style="text-align:center">6</td><td style="text-align:center">60</td></tr>
</table>

この処理の結果をフラット化するよう、`List.TransformMany`を使って書き直してみました。

```
let
    Source = 
        Table.FromRows(
            {
                {1, {2, 3}},
                {4, {5, 6}}
            },
            type table [Column1 = number, Column2 = number]
        ),
    
    Expand = 
        Table.FromRows(
            List.TransformMany(
                Table.ToRecords(Source),
                each [Column2],
                (row, col2) => 
                    {row[Column1], col2, col2 * 10}
            ),
            type table [Column1 = number, Column2 = number, TenTimes = number]
        )
in
    Expand
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/fa6c1987-d769-6a4f-5adc-c34064eb7eb7.png)


## 13.2.3 多層構造で親の値を参照

もし、入れ子になっている子の関数で親の値を参照したい場合は、以下の様にします。

```
let
    Source = 
        Table.FromRows(
            {
                {1, {2, 3}},
                {4, {5, 6}}
            },
            type table [Column1 = number, Column2 = number]
        ),

    Calculate = 
        Table.AddColumn(
            Source,
            "NewColumn", 
            (x)=>                       // x には [Column1]の値と[Column2]のリストが入る
                List.Transform(
                    x[Column2],
                    each x[Column1] + _ // _ には [Column2] の値が入る
                )
        )

in
    Calculate  
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/59532819-420d-2b46-e889-a51f53acfc1e.png)

<table style="border:1px">
<tr><th>Column1</th><th>Column2</th><th>NewColumn</th</tr>
<tr><td rowspan="2" style="text-align:center">1</td><td style="text-align:center">2</td><td style="text-align:center">3</td></tr><tr><td style="text-align:center">3</td><td style="text-align:center">4</td></tr>
<tr><td rowspan="2" style="text-align:center">4</td><td style="text-align:center">5</td><td style="text-align:center">9</td></tr><tr><td style="text-align:center">6</td><td style="text-align:center">10</td></tr>
</table>


`(x)=>`の様にかっこの中に変数を指定すると、子の関数で親の値を参照することができます。


:::note warn
### `each`と`(x)=>`
`each`を入れ子にする場合は、`_`のスコープに注意しなければなりません。
複雑な変換を行う場合は、`each`ではなく、`(x)=>` のような引数を持つ形にした方が分かりやすくなります。
:::

# 13.3 オプションパラメータ

## 13.3.1 オプションの基本

関数にオプションの引数を追加することで、柔軟な関数を作成できます。省略可能なオプションには`optional`を付けます。

```:関数
(required as text, optional optionalValue as number) as text =>
  
  if optionalValue = null 
      then required 
      else Text.Combine({required, Number.ToText(optionalValue)})
```

この関数を選択すると、以下の様に「省略可能」と表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/63e14d19-1828-1a87-5543-c1f5b0456919.png)

```:呼び出し例
FunctionWithOptional("Test")          // 結果: "Test"
FunctionWithOptional("Test", 123)     // 結果: "Test123"
```

`optional`をつける引数は、必ず後ろから付けていきます。必須オプションの前に配置することはできません。

```
(a, b, optional c)=>          // OK
(a, optional b, optional c)=> // OK
(a, optional b, c)=>          // NG
```

## 13.3.2 省略されたパラメータの扱い

`optional`で省略された場合、変数には`null`がセットされます。従って、関数内では以下の様に扱うことができます。

```
(a as number, optional b as number) as number =>

if (b <> null)
    then a * b
    else a
```

あるいは、`??`を使って以下の様に書くこともできます。

```
(a as number, optional b as number) as number =>

a * b ?? a
```

これは、`b`が`null`の場合、`a * b`は`null`になるので、`a`の値を返すという動作になります。

## 13.3.3 パラメータのデフォルト値

デフォルト値を設定することで、明示的な引数指定が不要になります。

```:デフォルト値の指定
let
    FunctionWithDefault = 
        (x as number, optional y as number = 10) => x + y
in
  FunctionWithDefault(5)  // 結果: 15
```

# 13.4 選択演算子と射影演算子

選択演算子と射影演算子を使用すると、リストとレコードの値からデータを抽出できます。

## 13.4.1 アイテム・アクセス

`{}`の中に数値を入れ、リストの項目を抽出します。

```
// リストの１番目の項目
{ "a", "b", "c", "d" }{ 0 }                         // -> "a"

// リストの２番目の項目
{ {"a", "b"}, {"c", "d"}, {"e", "f"} }{ 1 }   // -> {"c", "d"}
```

`{[]}`の中に数値か、条件を入れレコードを抽出します。

```
// テーブルの１行目のレコード
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ 0 }             // [A=0, B=1]       

// テーブルのA列が２となるレコード
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ [A = 2] }     // [A=2, B=1]
```

上記は、以下のようなテーブルになっています。

<table border="1"><tr><th>A</th><th>B</th></tr>
<tr><td>0</td><td>1</td></tr>
<tr><td>2</td><td>1</td></tr>
</table>


存在しない項目やレコードを抽出しようとするとエラーが発生します。

```
{ "a", "b", "c", "d" }{ 4 }    // エラー「列挙内に十分な要素がない」
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ [A = 5] }  // エラー「キーが一致しない」
```

形式 `x{y}?` を使うと、 `y` がリストまたはテーブル `x` に存在しない場合は `null` を返します。ただし、 **複数の一致がある場合にはエラーが発生**します。

```
// リストの１番目の項目
{ "a", "b", "c", "d" }{ 0 }?    // -> "a"

/// リストの４番目の項目
{ "a", "b", "c", "d" }{ 4 }?    // -> null

// テーブルのA列が２となるレコード
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ [A = 2] }? // [A=2, B=1]

// テーブルのA列が５となるレコード
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ [A = 5] }? // null

// テーブルのB列が１となるレコード
#table( { "A", "B" }, { { 0, 1 }, { 2, 1 } } ){ [ B = 1 ] }? // エラー「キーが複数の行に一致」
```


## 13.4.2 フィールド・アクセス

フィールドアクセス式は、レコードから特定の値を **選択（select）** するため、またはレコードやテーブルから特定のフィールドや列を選択して、より少ないフィールドや列を持つものに変換( **射影（project）**）するために使用されます。

`[]`の中にフィールド名を入れ、項目を抽出します。

```
[A=1,B=2][B]       // 2 
[A=1,B=2][C]       // error 
[A=1,B=2][C]?      // null
```

複数のフィールドをまとめてアクセスする場合には、必須のレコード射影とオプションのレコード射影用の演算子がサポートされています。

- 演算子 `x[[y1],[y2],...]` は、指定されたフィールド `y1, y2, ...` を使ってレコードを新しいレコードに射影（変換）します。この場合、指定されたフィールドが存在しないとエラーが発生します。

- 一方、演算子 `x[[y1],[y2],...]?` は、同じように指定されたフィールド `y1, y2, ...` を使って新しいレコードに射影します。ただし、指定したフィールドが存在しない場合は、エラーではなく `null` になります。

```
// Field1とField2がある新しいレコードを作成
[ a = 1, b = 2, c = 3 ][ [a], [b] ]  // -> [a = 1, b = 2]

// Field1はそのまま使用、Field4が存在しないのでエラー
[ a = 1, b = 2, c = 3 ][ [a], [d] ]  // -> エラー「フィールド d が見つかりません」

// Field1はそのまま使用、Field4は存在しないのでnull
[ a = 1, b = 2, c = 3 ][ [a], [d] ]? // -> [a = 1, d = null] 
```

識別子 `_`（アンダースコア）が使用されている場合、`[y]` および `[y]?`の様に`_`を省略して参照することができます。

```:グローバルスコープで使用
let
    _ = [a = 1, b = 2]
in
    [a]
```

```:関数の引数として使用
let 
    MyFunction = (_) => [a] * 10
in
    MyFunction([a = 2, b = 4])
```

この短縮形式は `each` に置き換えることができます。

```:(_)=> を each で置き換える
let 
    MyFunction = each [a] * 10
in
    MyFunction([a = 2, b = 4])
```

---

https://learn.microsoft.com/ja-jp/powerquery-m/m-spec-operators#selection-and-projection-operators