Power Query Mのリスト・レコード・テーブルをアクセス記法から理解する

Power Query Mのリスト・レコード・テーブルをアクセス記法から理解する

~[]・{}・?を、現在の値の型から読み解く~

Copyright © 2026 LWP 山中 一弘

本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。

記事要約

 Power Queryの画面では、データはほとんどいつも表に見えます。しかし、M言語の式を一段ずつたどると、途中の値はテーブルだけではありません。テーブルから一行を取り出せばレコードになり、一列を取り出せばリストになります。さらにその中から値を取り出せば、文字列や数値になります。

 Mコードが急に読めなくなる大きな原因は、この「現在の値の型」の変化を見失うことです。[]や{}を記号だけで暗記しても、左側がテーブルなのか、レコードなのか、リストなのかが分からなければ、何を取り出しているのか判断できません。

 この記事では、M言語の三つの複合型であるlist、record、tableを、一つのデータモデルとして整理します。中心となる判断軸は、「今の値は何型か」「一回のアクセスで何型が返るか」「次にどの型の操作を使うか」の三つです。この考え方が身につけば、長いアクセス式も左から一段ずつ読めるようになります。

本記事の対象とゴール

想定読者

  • Power Queryの画面操作はできるが、詳細エディターのMコードを読むと迷う人

  • Excel.CurrentWorkbook(){[Name = "売上表"]}[Content]の記号を説明できるようになりたい人

  • リスト関数、レコード関数、テーブル関数の使い分けを整理したい人

  • Pythonのリストや辞書と比較しながら、Mのデータモデルを理解したい人

本記事で得られること

  1. リスト、レコード、テーブルの違いを、順序・名前・行列という構造から説明できます。

  2. []、{}、?を、左側の値の型に応じて読み分けられます。

  3. テーブルの一行がレコード、一列がリストになることを利用して、目的の値へ到達できます。

  4. 複合型の相互変換で、名前や列情報が維持されるか失われるかを判断できます。

  5. Mコードのエラーを、関数名ではなく途中結果の型から切り分けられます。

1. Power Queryの表だけを見ているとMコードを見失う

 Power Queryエディターのプレビューは、表形式のデータを中心に表示します。そのため、M言語も最初から最後までテーブルだけを扱う言語のように見えます。実際には、Mの式は数値、文字列、真偽値、日付、リスト、レコード、テーブルなど、さまざまな値を返します。クエリの最終結果も、必ずテーブルでなければならないわけではありません。

 空のクエリの詳細エディターへ、次の式を入力してみます。このコードの目的は、クエリ全体が数値を返せることを確認することです。実行結果として20が表示されれば成功です。

let
    元の値 = 10,
    計算結果 = 元の値 * 2
in
    計算結果

 同じように、inの後ろへ{1, 2, 3}を書けばリストが、[A = 1, B = 2]を書けばレコードが表示されます。学習中は最終結果を無理にテーブルへ戻さず、途中の値をそのまま返す方が、データの形を確認しやすくなります。

1.1 値と型を分ける

 値は実際のデータです。型は、その値がどの種類に属し、どの操作を受け付けるかを表します。数値の123と文字列の"123"は、見た目が似ていても別の型です。リストの{10}と、そこから取り出した数値の10も別の型です。

 複合型を扱うときも、値と型を分けます。{1, 2, 3}はリスト値、[A = 1, B = 2]はレコード値です。コードを読むときは、値の内容だけでなく、その時点の型を言葉にします。

1.2 Mの操作は元の値を書き換えない

 Mでは、リストやレコード、テーブルをその場で変更するのではなく、操作結果として新しい値を返します。次のコードでは、元のリストを2倍した新しいリストを作ります。実行後に元と変換後を見比べると、元listは変わっていません。

let
    元list = {100, 200, 300},
    変換後list = List.Transform(元list, each _ * 2)
in
    [元 = 元list, 変換後 = 変換後list]

 Power Queryの「適用したステップ」も同じです。前のステップを直接書き換えるのではなく、前の値を入力として新しい値を作り、次のステップへ渡しています。

2. 三つの複合型を一つのモデルとして捉える

 リスト、レコード、テーブルを別々の構文として暗記すると、テーブルから行や列を取り出した瞬間に混乱します。三つの型は、次の関係で一つにつながっています。

  • リストは、順序を持つ値の並びです。

  • レコードは、名前と値の組です。

  • テーブルは、行・列・列名を持つ値です。

  • テーブルの一行はレコードです。

  • テーブルの一列はリストです。

 記事全体で、次の売上テーブルを共通例として使います。空のクエリの詳細エディターへ入力すると、3行4列のテーブルが表示されます。

let
    売上tbl = #table(
        {"ID", "商品名", "数量", "単価"},
        {
            {101, "りんご", 2, 120},
            {102, "みかん", 5, 80},
            {103, "ぶどう", 1, 300}
        }
    )
in
    売上tbl

2.1 リストは順序で値を識別する

 リストは波括弧{}で作り、要素を順番に並べます。先頭位置は1ではなく0です。

{"A", "B", "C"}

 このリストでは、"A"が位置0、"B"が位置1、"C"が位置2です。要素に名前は付いていないため、基本的には位置を使って取り出します。

2.2 レコードは名前で値を識別する

 レコードは角括弧[]で作り、フィールド名 = 値を並べます。

[ID = 101, 商品名 = "りんご", 単価 = 120]

 この値は、一件分の商品データとしても、名前付きの設定値のまとまりとしても使えます。レコードでは、何番目にあるかではなく、IDや商品名という名前で値を識別します。

2.3 テーブルは行から見ても列から見てもよい

 テーブルには二つの見方があります。行方向に一件を取り出すとレコードになり、列方向に一列を取り出すとリストになります。

 売上テーブルの先頭行は、次のレコードです。

[ID = 101, 商品名 = "りんご", 数量 = 2, 単価 = 120]

 商品名列は、次のリストです。

{"りんご", "みかん", "ぶどう"}

 この関係が、アクセス記法を選ぶ基準になります。

3. `{}`は位置または行を選ぶ

 値{セレクター}という形は、左側がリストなら要素を、テーブルなら行を選びます。同じ波括弧でも、左側の型によって意味と戻り値が変わります。

3.1 リストから一要素を取り出す

 次の式は、リストの位置0にある要素を取得します。空のクエリで実行し、結果が文字列"A"になることを確認します。

{"A", "B", "C"}{0}

 重要なのは、結果が{"A"}ではなく"A"であることです。容器であるリストから一要素を出したため、結果は要素そのものの型へ変わります。

3.2 テーブルから一行を取り出す

 売上テーブルから先頭行を取り出す場合も{0}を使います。

売上tbl{0}

 左側がテーブルなので、結果は一行を表すレコードです。以降はテーブル関数ではなく、レコードのフィールドアクセスが使えます。

3.3 行番号ではなくキーで一行を選ぶ

 並べ替えや絞り込みによって行位置は変わります。業務上のIDで一行を指定したい場合は、波括弧の中へ条件レコードを書きます。

売上tbl{[ID = 102]}

 結果は[ID = 102, 商品名 = "みかん", 数量 = 5, 単価 = 80]です。このキーアクセスは、一致行がちょうど1件のときだけ成功します。0件なら対象がなく、複数件なら一意に決められないため、どちらも通常はエラーになります。最初の一致行を返す記法ではありません。

4. `[]`は名前または列を選ぶ

 値[名前]という形は、左側がレコードならフィールドを、テーブルなら列を選びます。ここでも、記号だけでなく左側の型が意味を決めます。

4.1 レコードからフィールド値を取り出す

 商品レコードから商品名を取り出します。結果は文字列"りんご"です。

let
    商品rec = [ID = 101, 商品名 = "りんご", 単価 = 120]
in
    商品rec[商品名]

 空白や記号を含むフィールド名は、引用符付き識別子#""で明確に指定します。文字列"商品 名"と識別子#"商品 名"は別の役割です。

let
    商品rec = [#"商品 名" = "りんご"]
in
    商品rec[#"商品 名"]

4.2 テーブルから一列を取り出す

 同じ角括弧をテーブルへ使うと、一列分の値がリストで返ります。

売上tbl[商品名]

 結果は{"りんご", "みかん", "ぶどう"}です。列を取り出した時点でテーブルではなくリストになったため、次はList.CountやList.Sumなどのリスト関数を使います。

4.3 列を残したテーブルと、列の値のリストは違う

 Table.SelectColumns(売上tbl, {"商品名"})は、商品名列だけを持つテーブルを返します。一方、売上tbl[商品名]は、商品名の値を持つリストを返します。

 見た目が一列でも、戻り値の型が違います。後続処理で行と列の構造を保ちたいならTable.SelectColumns、列の値を集計・変換したいなら列アクセスを使う、というように目的で選びます。

5. アクセス式は左から一回ずつ読む

 複数のアクセス記法が連結されていても、全体を一度に理解する必要はありません。一回のアクセスで何型が返るかを左から追います。

5.1 行からセルへ到達する

売上tbl{0}[商品名]

 この式は、次の順に評価されます。

  1. 売上tblはテーブルです。

  2. {0}で先頭行を取り出すとレコードになります。

  3. [商品名]でフィールド値を取り出すと文字列になります。

 型の移動はtable -> record -> textです。

5.2 列からセルへ到達する

売上tbl[商品名]{0}

 こちらは、商品名列をリストとして取り出してから、その先頭要素を取得します。型の移動はtable -> list -> textです。最終結果は同じ"りんご"でも、途中経路が違います。

5.3 入れ子になっても同じ規則で読める

 次の値では、レコードの中にリストがあり、そのリストの各要素がレコードです。

let
    受注rec = [
        受注ID = 5001,
        明細 = {
            [商品名 = "りんご", 数量 = 2],
            [商品名 = "みかん", 数量 = 5]
        }
    ]
in
    受注rec[明細]{1}[数量]

 [明細]でリスト、{1}で2件目のレコード、[数量]で数値へ移動します。型の移動はrecord -> list -> record -> numberです。JSONや結合後の入れ子テーブルでも、読み方は変わりません。

6. `?`は存在しないことを許容する

 通常のアクセスは、指定した要素、フィールド、行が存在しなければエラーになります。アクセス記法の直後へ?を付けると、見つからない場合にnullを返せます。

{"A", "B"}{10}?
[ID = 1, 名前 = "山田"][住所]?
売上tbl{[ID = 999]}?

 上の例は、それぞれ存在しない位置、存在しないフィールド、0件のキーアクセスを許容します。ただし、キーに一致する行が複数ある場合は、?を付けても一意性の問題は解消しません。

 ?は便利ですが、何にでも付けるものではありません。住所や備考のような任意項目には適しますが、IDや必須設定の欠落までnullにすると、データ不備を後段へ持ち越します。「存在しなければ処理を止めるべきか」を先に決めます。

7. 固定名のアクセスと動的アクセスを分ける

 フィールド名がコードを書く時点で決まっているなら、商品rec[商品名]のようなアクセスが短く、意図も明確です。実行時にフィールド名が決まる場合は、文字列を受け取る関数を使います。

let
    商品rec = [ID = 101, 商品名 = "りんご", 単価 = 120],
    参照フィールド = "単価"
in
    Record.Field(商品rec, 参照フィールド)

 存在しないフィールドに既定値を返したい場合はRecord.FieldOrDefault、テーブルの列名を動的に指定する場合はTable.Columnを使えます。角括弧の中へ文字列変数を書けば動的アクセスになる、というわけではありません。

8. 関数は「入力型から出力型への移動」として覚える

 関数名を個別に暗記するより、「何型を受け取り、何型を返すか」で整理すると、後続処理を選びやすくなります。

8.1 リストを選択・変換・集計する

 次のコードは、1000以上の金額だけを残し、10%増加させて合計します。空のクエリへ入力し、最終結果が2970になることを確認します。

let
    元list = {1200, 800, 1500, 600},
    選択後list = List.Select(元list, each _ >= 1000),
    変換後list = List.Transform(選択後list, each _ * 1.1),
    合計 = List.Sum(変換後list)
in
    合計

 型はlist -> list -> list -> numberと移動します。each内の_は、現在処理しているリスト要素です。

8.2 レコードを一件分のデータや設定として扱う

 レコードには異なる型の値を一緒に格納できます。Record.AddFieldは新しいフィールドを追加したレコードを返し、Record.SelectFieldsは必要なフィールドだけを持つレコードを返します。

let
    商品rec = [ID = 101, 商品名 = "りんご", 単価 = 120, 税率 = 0.1],
    追加後rec = Record.AddField(
        商品rec,
        "税込単価",
        商品rec[単価] * (1 + 商品rec[税率])
    ),
    出力rec = Record.SelectFields(追加後rec, {"商品名", "税込単価"})
in
    出力rec

 元の商品recは変更されません。結果は[商品名 = "りんご", 税込単価 = 132]という新しいレコードです。

8.3 テーブル関数では各行がレコードとして渡される

 次のコードは、数量が2以上の行を残し、金額列を追加します。実行結果として、りんごとみかんの2行に金額列が追加されれば成功です。

let
    売上tbl = #table(
        {"ID", "商品名", "数量", "単価"},
        {
            {101, "りんご", 2, 120},
            {102, "みかん", 5, 80},
            {103, "ぶどう", 1, 300}
        }
    ),
    行選択後tbl = Table.SelectRows(売上tbl, each [数量] >= 2),
    金額追加後tbl = Table.AddColumn(
        行選択後tbl,
        "金額",
        each [数量] * [単価],
        type number
    )
in
    金額追加後tbl

 Table.SelectRowsやTable.AddColumnのeachへ渡される現在値は、一行を表すレコードです。そのため、[数量]や[単価]というフィールドアクセスが使えます。List.Transformのeachでは現在値がリスト要素、テーブルの行関数では行レコードです。同じeachでも、入力の型が違います。

9. 相互変換では何を失うかを見る

 同じ情報でも、順番に処理したいならリスト、名前で参照したいならレコード、行と列で操作したいならテーブルが向きます。変換関数を選ぶときは、入力型と出力型に加えて、名前・順序・列型のどれが維持されるかを確認します。

9.1 リストとレコードを変換する

 値のリストへフィールド名を対応させると、Record.FromListでレコードを作れます。

Record.FromList(
    {101, "りんご", 120},
    {"ID", "商品名", "単価"}
)

 逆にRecord.ToListを使うとフィールド値のリストになりますが、フィールド名は失われます。名前が必要なら、安易に値だけのリストへ変換しません。

9.2 レコードとName・Valueテーブルを変換する

 Record.ToTableは、フィールド名と値をName列、Value列の二列テーブルへ変換します。設定値を一覧表示したり、項目名自体を行として処理したりするときに便利です。

 逆方向のRecord.FromTableは、Name列とValue列を持つテーブルからレコードを作ります。Name列が重複すると、レコードのフィールド名を一意にできないためエラーになります。

9.3 テーブルはレコードのリストへ変換すると列名を保てる

 Table.ToRecordsは、テーブルの各行をレコードにしたリストを返します。列名は各レコードのフィールド名として維持されます。逆方向はTable.FromRecordsです。

Table.FromRecords({
    [ID = 101, 商品名 = "りんご"],
    [ID = 102, 商品名 = "みかん"]
})

 Table.ToRowsは各行を値のリストにしますが、列名を失います。列名を保つ必要がある処理では、Table.ToRecordsの方が安全です。値の位置関係だけで処理してよい場合に限って、Table.ToRowsを選びます。

10. Excel.CurrentWorkbookの一行を型の移動で読む

 実務でよく見る次の式を、記号の暗記ではなく型の移動として読みます。

Excel.CurrentWorkbook(){[Name = "売上表"]}[Content]

10.1 Excel.CurrentWorkbookはテーブルを返す

 Excel.CurrentWorkbook()は、現在のブック内にあるExcelテーブル、名前付き範囲、名前付きセルなどを一覧にしたテーブルを返します。代表的な列はNameとContentです。

10.2 キーアクセスで一行のレコードを返す

 続く{[Name = "売上表"]}は、Name列が"売上表"である一行を選びます。結果はテーブルではなく行レコードです。

10.3 Contentフィールドから内側のテーブルを返す

 最後の[Content]は、行レコードのContentフィールドを取得します。そのフィールド値が、Excelテーブルの内容を表す別のテーブルです。

 したがって、型の移動はtable -> record -> tableです。一行の式を分解すると、次のように確認できます。

let
    一覧tbl = Excel.CurrentWorkbook(),
    売上行rec = 一覧tbl{[Name = "売上表"]},
    売上tbl = 売上行rec[Content]
in
    [
        一覧の型 = Value.Type(一覧tbl),
        行の型 = Value.Type(売上行rec),
        内容の型 = Value.Type(売上tbl)
    ]

 この確認コードの目的は、各段階の型を実際に表示することです。Excel側に売上表という名前のテーブルを用意し、三つの型が順にテーブル、レコード、テーブルになることを確認します。

11. エラーは直前の値と型から調べる

 Mコードでエラーが出ると、関数名や括弧を先に疑いがちです。しかし、正しい関数へ違う型の値を渡しているだけ、ということも多くあります。

 長い式で迷ったら、アクセスを分けて途中結果に名前を付けます。inの後ろを一覧tbl、売上行rec、売上tblのように順番に切り替え、プレビューとValue.Typeを確認します。

 特に次の違いを確認します。

  • テーブルの一行を取り出した後は、レコードになっている。

  • テーブルの一列を取り出した後は、リストになっている。

  • リストから一要素を取り出した後は、要素本来の型になっている。

  • Table.SelectColumnsはテーブルを返すが、tbl[列名]はリストを返す。

  • 通常アクセスの欠落はエラー、省略可能アクセスの欠落はnullになる。

 「現在の値の型」を確認できれば、次に使うべき演算子や関数も絞れます。

12. Pythonとの対応は入口にとどめる

 Pythonを知っている人は、Mのリストをlist、レコードをdict、テーブルをpandas.DataFrameに近いものとして捉えると、入口では理解しやすくなります。ただし、完全な対応ではありません。

 Mでは、元の値を書き換えるより、操作結果として新しい値を返す考え方が中心です。また、テーブルは単なるリストのリストではなく、列名と行を持つ専用の値です。Pythonの添字や辞書アクセスの記法をそのまま持ち込まず、Mでは左側の型とアクセス演算子の規則を基準にします。

13. まとめ

 M言語の複合型は、三つの独立した暗記事項ではありません。テーブルの一行はレコード、一列はリストという関係でつながっています。

 {}はリストの位置またはテーブルの行を選び、[]はレコードのフィールドまたはテーブルの列を選びます。?は、対象が存在しない場合をnullとして許容します。ただし、どの記号も左側の型を無視して読むことはできません。

 長い式を読むときは、次の三点を習慣にします。

  1. 現在の値の型を確認する。

  2. 一回のアクセスで返る型を予測する。

  3. 目的に合う複合型へ変換し、失われる情報を確認する。

 関数名を大量に覚える前に、このデータモデルを身につけると、Power QueryのMコードを一段ずつ説明できるようになります。

出典メモ

  • 元資料: LWP 山中一弘「M言語のデータモデル――リスト・レコード・テーブルを理解し、目的の値を取り出す」3時間講座用講義資料、2026年8月3日。

  • 元資料の保存場所: C:\Dropbox\LWP\書籍出版\プログラミングM言語\M言語のデータモデル_講義資料.md

  • Microsoft Learn: M Language values - https://learn.microsoft.com/en-us/powerquery-m/m-spec-values

  • Microsoft Learn: M Language types - https://learn.microsoft.com/en-us/powerquery-m/m-spec-types

  • Microsoft Learn: M Language operators - https://learn.microsoft.com/en-us/powerquery-m/m-spec-operators

  • Microsoft Learn: Record.FromList - https://learn.microsoft.com/en-us/powerquery-m/record-fromlist

  • Microsoft Learn: Record.FromTable - https://learn.microsoft.com/en-us/powerquery-m/record-fromtable

  • Microsoft Learn: Record.ToTable - https://learn.microsoft.com/en-us/powerquery-m/record-totable

  • Microsoft Learn: Table.FromRecords - https://learn.microsoft.com/en-us/powerquery-m/table-fromrecords

  • Microsoft Learn: Table.ToRecords - https://learn.microsoft.com/en-us/powerquery-m/table-torecords

  • Microsoft Learn: Table.FromRows - https://learn.microsoft.com/en-us/powerquery-m/table-fromrows

  • Microsoft Learn: Table.ToRows - https://learn.microsoft.com/en-us/powerquery-m/table-torows

  • 確認範囲: 元の講義資料を公開記事向けに再構成した。サンプルは元資料の独自例を基礎とし、読者が空のクエリで確認できる形へ整理した。Excelテーブルを必要とする例は、記事作成時に実ブックでの実行確認を行っていない。