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
を入れ子構造で使用できます。
Column1 | Column2 |
---|---|
1 | 2 |
3 | |
4 | 5 |
6 |
このようなテーブルで、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
変換結果は以下の様になります。
Column1 | Column2 | NewColumn |
---|---|---|
1 | 2 | 20 |
3 | 30 | |
4 | 5 | 50 |
6 | 60 |
この処理の結果をフラット化するよう、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
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
Column1 | Column2 | NewColumn |
---|---|---|
1 | 2 | 3 |
3 | 4 | |
4 | 5 | 9 |
6 | 10 |
(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)})
この関数を選択すると、以下の様に「省略可能」と表示されます。
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 アイテム・アクセス
{}
の中に数値を入れ、リストの項目を抽出します。
// リストの1番目の項目
{ "a", "b", "c", "d" }{ 0 } // -> "a"
// リストの2番目の項目
{ {"a", "b"}, {"c", "d"}, {"e", "f"} }{ 1 } // -> {"c", "d"}
{[]}
の中に数値か、条件を入れレコードを抽出します。
// テーブルの1行目のレコード
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ 0 } // [A=0, B=1]
// テーブルのA列が2となるレコード
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ [A = 2] } // [A=2, B=1]
上記は、以下のようなテーブルになっています。
A | B |
---|---|
0 | 1 |
2 | 1 |
存在しない項目やレコードを抽出しようとするとエラーが発生します。
{ "a", "b", "c", "d" }{ 4 } // エラー「列挙内に十分な要素がない」
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ [A = 5] } // エラー「キーが一致しない」
形式 x{y}?
を使うと、 y
がリストまたはテーブル x
に存在しない場合は null
を返します。ただし、 複数の一致がある場合にはエラーが発生します。
// リストの1番目の項目
{ "a", "b", "c", "d" }{ 0 }? // -> "a"
/// リストの4番目の項目
{ "a", "b", "c", "d" }{ 4 }? // -> null
// テーブルのA列が2となるレコード
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ [A = 2] }? // [A=2, B=1]
// テーブルのA列が5となるレコード
#table( {"A", "B"}, { {0, 1}, {2, 1} } ){ [A = 5] }? // null
// テーブルのB列が1となるレコード
#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
に置き換えることができます。
let
MyFunction = each [a] * 10
in
MyFunction([a = 2, b = 4])