Power Query Mのリスト・レコード・テーブルをアクセス記法から理解する
~[]・{}・?を、現在の値の型から読み解く~
Copyright © 2026 LWP 山中 一弘
本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。
記事要約
Power Queryの画面では、データはほとんどいつも表に見えます。しかし、M言語の式を一段ずつたどると、途中の値はテーブルだけではありません。テーブルから一行を取り出せばレコードになり、一列を取り出せばリストになります。さらにその中から値を取り出せば、文字列や数値になります。
Mコードが急に読めなくなる大きな原因は、この「現在の値の型」の変化を見失うことです。[]や{}を記号だけで暗記しても、左側がテーブルなのか、レコードなのか、リストなのかが分からなければ、何を取り出しているのか判断できません。
この記事では、M言語の三つの複合型であるlist、record、tableを、一つのデータモデルとして整理します。中心となる判断軸は、「今の値は何型か」「一回のアクセスで何型が返るか」「次にどの型の操作を使うか」の三つです。この考え方が身につけば、長いアクセス式も左から一段ずつ読めるようになります。
本記事の対象とゴール
想定読者
Power Queryの画面操作はできるが、詳細エディターのMコードを読むと迷う人
Excel.CurrentWorkbook(){[Name = "売上表"]}[Content]の記号を説明できるようになりたい人
リスト関数、レコード関数、テーブル関数の使い分けを整理したい人
Pythonのリストや辞書と比較しながら、Mのデータモデルを理解したい人
本記事で得られること
リスト、レコード、テーブルの違いを、順序・名前・行列という構造から説明できます。
[]、{}、?を、左側の値の型に応じて読み分けられます。
テーブルの一行がレコード、一列がリストになることを利用して、目的の値へ到達できます。
複合型の相互変換で、名前や列情報が維持されるか失われるかを判断できます。
Mコードのエラーを、関数名ではなく途中結果の型から切り分けられます。
1. Power Queryの表だけを見ているとMコードを見失う
Power Queryエディターのプレビューは、表形式のデータを中心に表示します。そのため、M言語も最初から最後までテーブルだけを扱う言語のように見えます。実際には、Mの式は数値、文字列、真偽値、日付、リスト、レコード、テーブルなど、さまざまな値を返します。クエリの最終結果も、必ずテーブルでなければならないわけではありません。
空のクエリの詳細エディターへ、次の式を入力してみます。このコードの目的は、クエリ全体が数値を返せることを確認することです。実行結果として20が表示されれば成功です。
let 元の値 = 10, 計算結果 = 元の値 * 2in 計算結果同じように、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 売上tbl2.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}[商品名]この式は、次の順に評価されます。
売上tblはテーブルです。
{0}で先頭行を取り出すとレコードになります。
[商品名]でフィールド値を取り出すと文字列になります。
型の移動は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 金額追加後tblTable.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として許容します。ただし、どの記号も左側の型を無視して読むことはできません。
長い式を読むときは、次の三点を習慣にします。
現在の値の型を確認する。
一回のアクセスで返る型を予測する。
目的に合う複合型へ変換し、失われる情報を確認する。
関数名を大量に覚える前に、このデータモデルを身につけると、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テーブルを必要とする例は、記事作成時に実ブックでの実行確認を行っていない。
