(Power BI) Mから始めよう #7 〜Power Queryの識別子について

　以下の内容は、Ben Gribaudoのブログを参照して作成しました。
- [Power Query M Primer (Part 4): Variables & Identifiers](https://bengribaudo.com/blog/2018/01/19/4321/power-query-m-primer-part4-variables-identifiers)
- [Power Query M Primer (Part 21): Identifier Scope & Sections](https://bengribaudo.com/blog/2021/07/12/5809/power-query-m-primer-part21-identifier-scope-sections)

# 識別子(Regular Identifiers)
　Power Queryでは、ステップごとに式の左側に変数が作成されます。これは、実行されている操作の値を保存し、次のステップに渡す役割があります。Power Queryでは、この変数を上書きすることはできず、1つのクエリの中で一意となるため、　 **識別子** と呼ばれます。

```:例1
let
    ソース = 1 + 2
in
    ソース
```

　識別子は、ＧＵＩで操作を行うと、操作の種類によって自動で作成されます。通常は、アプリケーションの言語（日本語）で付与されます。Power BI Desktopの「ファイル」⇒「オプションと設定」から「グローバル」の「地域の設定」を選択し、「**常に英語**」とすることで、英語にすることも出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/fbe76a1d-3c15-62eb-1906-9ff29ad033bb.png)
　オプションの「現在のファイル」にある「地域の設定」は、編集中のファイルについてのみ変更することができます。

# 引用符付き識別子(Quoted Identifiers)
　英語で使用していると、ＧＵＩで作成される識別子に「#"」のようなプレフィックス、サフィックスが付いてくることがあります。名前の中にスペースが含まれる場合は **#"Added Custom"** のように、「"」で変数名が囲まれ、頭に「#」がつき、**引用符付き識別子**と呼ばれます。識別子に半角スペースが含まれる場合は、このように記述しなければエラーになります。

　また、識別子に **+-*/** のような文字が入る場合にも、 **#"Price/2"** のように識別子を囲う必要があります。

```:例2
let
    Source = Table.FromRecords({
        [ID = 1, Item = "ハンバーガー", Price = 110.0]
    }),
    #"Take Two" = Table.AddColumn(Source, "Take Two", each [Price] * 2),
    #"Price/2" = Table.AddColumn(#"Take Two", "Price/2", each [Price] / 2)
in
    #"Price/2"
```

　他のプログラム言語では、変数名にスペースを含めることはできません。Power Queryでは自由に記述でき、表記が見やすくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/e1254451-83c3-6366-176e-418eda2b3db5.png)

　引用符の必要ない識別子を引用符付識別子で記述しても、同じものとして扱われます。

```:例3
let
  Weight = 50
in
  #"Weight"
```

　ただし、日本語の設定で使っている場合は、ＧＵＩで自動付与される名前は全て全角文字が使用され、スペースが入ることはありません。

　Power Queryの上級者は、詳細エディタでコードを書き込むため、グローバルの地域の設定を **「常に英語」** にし、GUIで自動付与された英語もスペースを削除して使用しています。その方が、直接記述するときにタイピングしやすく、判読しやすいためです。代表的なスペースの消し方は、以下のようになります。

```
 #"Converted to Table"　→　ConvertedToTable   
 #"Converted to Table"　→　Converted_to_Table 
```

# 一般識別子(Generalized Identifiers)
　識別子は、変数だけでなく、レコードフィールドや列名も識別します。
　列名を参照する場合、識別子が角括弧で囲まれて表記されます。上記の例2でも、式中に **each [Price] * 2** のようにあります。このように角括弧で囲まれた識別子は、間にスペースが入っていても引用符付にする必要はありませんが、「+-*/」などの演算子が入っている場合は、引用符付にする必要があります。

```:例4
let
    Source = Table.FromRecords({
        [ID = 1, Item = "ハンバーガー", Price = 110.0]
    }),
    #"Take Two" = Table.AddColumn(Source, "Take Two", each [Price] * 2),
    #"Price/2" = Table.AddColumn(#"Take Two", "Price/2", each [Price] / 2),
    #"Take Four" = Table.AddColumn(#"Price/2", "Take Four", each [Take Two] / 2),
    #"Added Custom" = Table.AddColumn(#"Take Four", "Price/4", each [#"Price/2"] / 2)
in
    #"Added Custom"
```

# 識別子参照
　Power Queryでは、自分自身の識別子を参照することは出来ません。これを**排他的識別子参照(Exclusive Identifier Reference)**と言います。例えば、以下の例はエラーになります。

```:例5
[
  a = a * 10 
]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/97e7e0e8-6bbc-d481-c6ec-3cec6cc44616.png)

　ただし、再帰処理を行う場合は、＠をつけて自分自身を参照することができます。これを、**包括的識別子参照(Inclusive Identifier References)**と言います。

```:例6
let
  SumConsecutive = (x) => if x <= 0 then 0 else x + @SumConsecutive(x - 1),
  Result = SumConsecutive(4)
in
  Result
```
　下記のように、必要がない場合でも＠をつけて記述できますが、意味はなく、混乱を招きます。必要な場合にのみ包括的参照を使用してください。

```:例6
let
  a = 2,
  b = @a * 10
in
  b
```

# 親子関係
　1つの識別子に2度定義することはできませんが、親子関係になっている場合は可能です。

```:例7
let
    source = [
        a = 10, // 親a
        b = 20, 
        inner = [
            a = a, // 子aは親aの値を参照し、10になります。
            b = 2
        ]
    ]
in
    source
```

　上の例では、最初のa,b,cは親となっており、innerの中のa,b,cは子の関係になります。子aは親aを参照でき、a = a は自己参照ではなく、親の値を参照しています。同じ名前でも親子は違うものになります。ただし、親は子の値を参照することはできません。

# クロスクエリ参照
　クエリは、他のクエリの値を参照できます。次の2つのクエリがあります。

```:Data
10
```

```:例8
let
  Result = Data
in
  Result
```

　例8は、Dataと名付けられた別のクエリの値を参照し、10を返します。しかし、以下のようにすると、

```:例9
let
  Data = 100,
  Result = Data
in
  Result // returns 100
```

　ローカルのDataの値が優先されるため、帰ってくる値は100になります。

# セクションアクセス式

　敢えて、ローカルの値ではなく、Dataクエリの値を使いたい場合は、**Section1** というキーワードを使用して参照することが可能です。

```:例10
let
  Data = 100,
  Result = Section1!Data
in
  Result //returns 10 (previously returned 100)
```
　ローカルの100ではなく、Dataクエリの値 10 が返されます。

# セクション
　各クエリは、１つの親となるコンテナに含まれていることで、共通のスコープを持ちます。それをセクションといい、セクションドキュメント内(Section1)でクエリや関数、パラメータなどの識別子が定義されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/d98b0ff2-9780-725f-d24d-ae45fb2b90ea.png)

　また、そこに定義されている識別子は、グローバル環境(shared)に追加され、標準ライブラリと同様にアクセスできるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/4ebdcbe7-a9de-2851-05f3-78601023160c.png)