3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Power Query へそのゴマAdvent Calendar 2024

Day 13

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

Last updated at Posted at 2024-12-12

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

このようなテーブルで、Column22356はそれぞれリストになっています。

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

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

image.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

Column1 Column2 NewColumn
1 2 3
3 4
4 5 9
6 10

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

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

呼び出し例
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

これは、bnullの場合、a * bnullになるので、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 に置き換えることができます。

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

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?