Power Queryで複数データを一つにまとめる設計パターン

LWP | Power Queryで複数データを一つにまとめる設計パターン

LWP TECHNICAL ARTICLE | 163

Power Queryで複数データを
一つにまとめる
設計パターン

フォルダー結合からmap・flatMap・fold・unfold・スキーマ設計までを一つの判断軸で理解する

Copyright © 2026 LWP 山中 一弘

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

記事要約

Power Queryでフォルダー内の複数CSVを縦に結合する方法は一つではありません。フォルダーテーブルへ子テーブルを追加して展開する方法、各CSVを同格のテーブルへ変換して最後にTable.Combineする方法、Recordへ分解して再構築する方法、状態をList.Accumulateで引き継ぐ方法などがあります。

重要なのは、関数の種類を増やすことではありません。「何を処理の主語にするか」「どのデータ型を中間表現にするか」「何を引き継ぐか」「何を一つとみなすか」「最後にどの演算を行うか」を分けて考えることです。

通常の業務CSVでは、ファイル単位の読込・正規化関数を作り、由来情報を付け、同じ完成スキーマへそろえたテーブルのリストを最後に一度だけTable.Combineする形が第一候補になります。一方、前回状態が次へ影響するならList.Accumulate、次の入力を状態から作るならList.Generate、階層を残すならTable.Group、別の実体情報を付けるならJoinを選びます。

本記事の対象とゴール

想定読者

  • Power QueryでCSVやExcelファイルを結合した経験がある方

  • Table.AddColumnと展開以外の設計も理解したい方

  • List、Record、関数を実務の部品として使いたい方

  • 列増減、形式差、エラー、由来情報を含めて壊れにくく設計したい方

本記事で得られること

  1. Expand、Combine、Join、Groupの役割をデータの形から区別できます。

  2. map、flatMap、fold、unfoldをM言語の具体的な処理へ対応づけられます。

  3. 単純結合、状態遷移、入力生成、階層保持、異形式統合を使い分けられます。

  4. 完成スキーマ、由来情報、エラー方針を先に決めた実務的な統合処理を設計できます。

目次

  • はじめに 「一つにまとめる」を分解する

  • 第1章 すべてのパターンに共通する読込関数

  • 第2章 通常のフォルダー結合を三つの形で比べる

  • 第3章 中間表現を変える

  • 第4章 状態と入力を生成する

  • 第5章 階層・スキーマ・キー・規則で統合する

  • 第6章 11の設計パターンを選び分ける

  • 第7章 実務で壊れにくくする

  • 第8章 通常の業務CSVに対する推奨構成

  • まとめ 関数ではなく主語とデータの形を選ぶ

はじめに 「一つにまとめる」を分解する

0.1 同じ結果でも設計の主語は異なる

次の3ファイルを1つの明細テーブルへまとめる処理を考えます。

売上_東京.csv
売上_大阪.csv
売上_名古屋.csv
        ↓
すべての売上明細を1つのテーブルへまとめる

この処理は、少なくとも次のように書けます。

  • フォルダー表へ解析結果のTable列を追加し、その列を展開する

  • 各CSVをTableへ変換し、Tableのリストを最後に結合する

  • 各行をRecordへ変換し、Recordのリストから完成表を作る

  • 入力を順に処理し、状態Recordへ結果とエラーを蓄積する

どれも「複数CSVをまとめる」処理ですが、主語と中間表現が違います。

0.2 共通の変換モデル

複数ソースの統合は、次の段階へ分解できます。

入力を列挙する
↓
入力ごとに解釈する
↓
共通の形へ正規化する
↓
入力元の情報を付ける
↓
目的に合う演算で一つにする
↓
エラーや処理履歴を返す

短く表すと、次の構造です。

Result = Assemble(Contextualize(Normalize(Parse(Source))))

設計時には、次の5項目を別々に決めます。

  1. 入力単位: ファイル、行、APIページ、会社、年度など

  2. 中間表現: Binary、Text、Table、List、Record、状態Recordなど

  3. 引き継ぐ文脈: ファイル名、パス、更新日時、連番、前回残高、エラーなど

  4. 完成単位: 全明細、会社別明細、同一顧客、データとエラーを含むRecordなど

  5. 最終演算: Expand、Combine、Join、Group、Accumulate、Generateなど

第1章 すべてのパターンに共通する読込関数

1.1 CSV解析を分離する

比較する処理からCSV解析の詳細を切り離すため、Binaryを受け取ってTableを返す関数fnReadCsvを用意します。

let
    fnReadCsv =
        (Binary as binary) as table =>
            let
                Parsed =
                    Csv.Document(
                        Binary,
                        [
                            Delimiter = ",",
                            Encoding = 65001,
                            QuoteStyle = QuoteStyle.Csv
                        ]
                    ),
                Promoted =
                    Table.PromoteHeaders(
                        Parsed,
                        [PromoteAllScalars = true]
                    )
            in
                Promoted
in
    fnReadCsv

この関数の責任は、次の3点だけです。

  1. BinaryをCSVとして解釈する。

  2. 先頭行をヘッダーへ昇格する。

  3. Tableを返す。

フォルダーの列挙、ファイル名の付与、列名の正規化、全体の結合は別の処理へ分けます。

1.2 ヘッダーはファイルごとに処理する

複数CSVのBinaryや行を先に連結し、最後に一度だけヘッダーを昇格すると、2ファイル目以降のヘッダーがデータ行として残る可能性があります。

CSV Aのヘッダー
CSV Aのデータ
CSV Bのヘッダー  ← データとして残る
CSV Bのデータ

原則として、CSVごとに解析とヘッダー昇格を完了し、その後で結果Tableを統合します。

第2章 通常のフォルダー結合を三つの形で比べる

2.1 親子展開型:外側の文脈を保って平らにする

Folder.Filesの結果を親テーブルとし、各ファイル行へ解析後の子テーブルを追加します。

let
    Files =
        Folder.Files(FolderPath),
    CsvFiles =
        Table.SelectRows(
            Files,
            each
                Text.Lower([Extension]) = ".csv"
                and [Attributes]?[Hidden]? <> true
        ),
    WithData =
        Table.AddColumn(
            CsvFiles,
            "Data",
            each fnReadCsv([Content]),
            type table
        ),
    ExpandColumns =
        {"Date", "CustomerCode", "Amount"},
    Result =
        Table.ExpandTableColumn(
            WithData,
            "Data",
            ExpandColumns,
            ExpandColumns
        )
in
    Result

この設計の主役は、外側のフォルダー表です。子テーブルを展開すると、Name、Folder Path、更新日時などの親の列が、子テーブルの各行へ引き継がれます。

ファイルAの文脈 + 明細1
ファイルAの文脈 + 明細2
ファイルBの文脈 + 明細1

これは概念的にはflatMapに近い処理です。親の1行を子の複数行へ変換し、親の文脈を保ったまま平らな表にします。

この形は、ファイル属性を残したい場合、展開前にファイル単位の結果を確認したい場合、列構成が固定されている場合に向きます。

2.2 部品合成型:同格のTableを最後に結合する

各CSVを独立したTable部品へ変換し、Tableのリストを最後に一度だけTable.Combineします。

let
    Files =
        Folder.Files(FolderPath),
    CsvFiles =
        Table.SelectRows(
            Files,
            each
                Text.Lower([Extension]) = ".csv"
                and [Attributes]?[Hidden]? <> true
        ),
    Sources =
        Table.ToRecords(CsvFiles),
    Tables =
        List.Transform(
            Sources,
            (Source) =>
                let
                    Parsed =
                        fnReadCsv(Source[Content]),
                    WithSource =
                        Table.AddColumn(
                            Parsed,
                            "Source.Name",
                            each Source[Name],
                            type text
                        )
                in
                    WithSource
        ),
    Result =
        Table.Combine(Tables)
in
    Result

この設計は、概念的にはmapしてからunionする形です。

Source A → Table A
Source B → Table B
Source C → Table C
              ↓
         Table.Combine

Table.Combineは、columns引数を省略した場合、入力Tableの列型の和集合を基に結果を作ります。あるTableにしかない列は、ほかのTableに由来する行ではnullになります。

ただし、列集合が自動的に合わさることと、意味が正規化されることは別です。金額、売上金額、Amountは、Power Queryから見れば異なる列名です。

2.3 中間形:フォルダー表を台帳として残し、Combineする

親テーブルへData列を追加しながら、最後は展開せず、Data列に含まれるTableを結合できます。

let
    Files =
        Folder.Files(FolderPath),
    CsvFiles =
        Table.SelectRows(
            Files,
            each Text.Lower([Extension]) = ".csv"
        ),
    WithData =
        Table.AddColumn(
            CsvFiles,
            "Data",
            each
                let
                    Parsed =
                        fnReadCsv([Content]),
                    FileName =
                        [Name],
                    WithSource =
                        Table.AddColumn(
                            Parsed,
                            "Source.Name",
                            each FileName,
                            type text
                        )
                in
                    WithSource,
            type table
        ),
    Result =
        Table.Combine(WithData[Data])
in
    Result

ファイルの選別と検査はフォルダー表、ファイルごとの変換はData列、最終統合はTable.Combineという役割分担です。管理性と列変動の許容を両立しやすい実務的な形です。

第3章 中間表現を変える

3.1 レコード再構築型:1件の事実を集める

Tableを完成部品とせず、1件の事実を表すRecordまで分解できます。

let
    Files =
        Folder.Files(FolderPath),
    CsvFiles =
        Table.SelectRows(
            Files,
            each Text.Lower([Extension]) = ".csv"
        ),
    Sources =
        Table.ToRecords(CsvFiles),
    RecordLists =
        List.Transform(
            Sources,
            (Source) =>
                let
                    Parsed =
                        fnReadCsv(Source[Content]),
                    Records =
                        Table.ToRecords(Parsed),
                    WithSource =
                        List.Transform(
                            Records,
                            (Row) =>
                                Record.Combine(
                                    {
                                        Row,
                                        [#"Source.Name" = Source[Name]]
                                    }
                                )
                        )
                in
                    WithSource
        ),
    AllRecords =
        List.Combine(RecordLists),
    Result =
        Table.FromRecords(AllRecords)
in
    Result

処理の形は次のとおりです。

CSV → Table → List of Record
全ファイルのRecordリストを連結
最後にTable.FromRecords

JSON、APIレスポンス、入れ子Record、行ごとに項目が少し異なる半構造化データでは、Tableへ早く固定するよりRecordのリストとして加工する方が自然な場合があります。

3.2 Tableだけがデータではない

Power Queryでは、List、Record、関数も第一級の値です。

  • Table: 同じ列構造を持つ行集合

  • List: 同じ順序で処理する値の集合

  • Record: 名前付きフィールドの集合

  • Function: 入力から出力を作る変換規則

中間表現を変えると、同じ統合処理でも追加しやすい情報や分岐の置き場所が変わります。

第4章 状態と入力を生成する

4.1 状態遷移型:List.Accumulateはfoldである

List.Accumulateは、前回までの状態と現在の入力から次の状態を作ります。

単純に結合済みTableへ毎回Table.Combineすることもできますが、通常の縦結合では第一候補にしません。反復のたびに、それまでの結合結果を含むTableを再構成する形になりやすいためです。

List.Accumulateを使うなら、状態をRecordとして設計すると目的が明確になります。

let
    Files =
        Folder.Files(FolderPath),
    Sources =
        Table.ToRecords(Files),
    InitialState =
        [
            Tables = {},
            Errors = {},
            FileNumber = 0
        ],
    FinalState =
        List.Accumulate(
            Sources,
            InitialState,
            (State, Source) =>
                let
                    Attempt =
                        try fnReadCsv(Source[Content]),
                    NextFileNumber =
                        State[FileNumber] + 1
                in
                    if Attempt[HasError] then
                        [
                            Tables = State[Tables],
                            Errors =
                                State[Errors]
                                    & {
                                        [
                                            Name = Source[Name],
                                            Error = Attempt[Error]
                                        ]
                                    },
                            FileNumber = NextFileNumber
                        ]
                    else
                        [
                            Tables =
                                State[Tables] & {Attempt[Value]},
                            Errors = State[Errors],
                            FileNumber = NextFileNumber
                        ]
        ),
    Data =
        Table.Combine(FinalState[Tables]),
    ErrorTable =
        Table.FromRecords(FinalState[Errors])
in
    [
        Data = Data,
        Errors = ErrorTable
    ]

このクエリは、完成Tableだけでなく、正常データとエラー一覧を含むRecordを返します。

状態には、前ファイルの最終残高、全体連番、発見した列名、成功件数、警告一覧、処理ログなども保持できます。入力順序に意味がある場合や、前回結果が次の処理へ影響する場合に適します。

4.2 生成型:List.Generateはunfoldである

入力一覧が最初から存在せず、現在の結果から次の入力を決める場合はList.Generateが適します。代表例はAPIのページ送りです。

let
    Pages =
        List.Generate(
            () =>
                [
                    Page = fnGetPage(null),
                    Continue = true
                ],
            each [Continue],
            (State) =>
                let
                    NextToken =
                        State[Page][NextToken],
                    HasNext =
                        NextToken <> null,
                    NextPage =
                        if HasNext then
                            fnGetPage(NextToken)
                        else
                            null
                in
                    [
                        Page = NextPage,
                        Continue = HasNext
                    ],
            each [Page][Data]
        ),
    Result =
        Table.Combine(Pages)
in
    Result

List.AccumulateとList.Generateの違いは、入力の所在です。

List.Accumulate
= 与えられた入力を順番に状態へ畳み込む
 
List.Generate
= 状態から次の候補を作り、条件を満たす間だけ値を生み出す

CSV処理でも、年月を進めながらファイルパスを作る、連番ファイルが存在する間だけ読む、フォルダー階層を条件付きでたどる、といった場面へ応用できます。

第5章 階層・スキーマ・キー・規則で統合する

5.1 分割統治型:Table.Groupで業務上の階層を残す

全ファイルをすぐ1枚の表にせず、会社、年度、拠点、帳票種類などでグループ化し、グループ内だけを結合できます。

let
    Files =
        Folder.Files(FolderPath),
    WithCompany =
        Table.AddColumn(
            Files,
            "Company",
            each fnGetCompany([Name]),
            type text
        ),
    Grouped =
        Table.Group(
            WithCompany,
            {"Company"},
            {
                {
                    "Files",
                    each _,
                    type table
                }
            }
        ),
    WithData =
        Table.AddColumn(
            Grouped,
            "Data",
            each
                let
                    Sources =
                        Table.ToRecords([Files]),
                    Tables =
                        List.Transform(
                            Sources,
                            each fnReadCsv(_[Content])
                        )
                in
                    Table.Combine(Tables),
            type table
        )
in
    WithData

この結果では、会社ごとのData列にネストしたTableが残ります。後から展開することも、会社単位の別処理へ渡すこともできます。

5.2 スキーマ先行型:完成形を契約として決める

実務で最も重要なのは、入力の列をそのまま集めることではなく、完成テーブルが持つ列名と型を先に決めることです。

Date            date
CustomerCode    text
Amount          number
SourceName      text

各入力を、この契約を満たすTableへ変換してから結合します。

let
    ContractColumns =
        {"Date", "CustomerCode", "Amount", "SourceName"},
    Normalize =
        (Source as record) as table =>
            let
                Parsed =
                    fnReadCsv(Source[Content]),
                Renamed =
                    Table.RenameColumns(
                        Parsed,
                        {
                            {"売上日", "Date"},
                            {"得意先コード", "CustomerCode"},
                            {"売上金額", "Amount"}
                        },
                        MissingField.Ignore
                    ),
                WithSource =
                    Table.AddColumn(
                        Renamed,
                        "SourceName",
                        each Source[Name],
                        type text
                    ),
                Shaped =
                    Table.SelectColumns(
                        WithSource,
                        ContractColumns,
                        MissingField.UseNull
                    ),
                Typed =
                    Table.TransformColumnTypes(
                        Shaped,
                        {
                            {"Date", type date},
                            {"CustomerCode", type text},
                            {"Amount", type number},
                            {"SourceName", type text}
                        }
                    )
            in
                Typed,
    Files =
        Folder.Files(FolderPath),
    Sources =
        Table.ToRecords(Files),
    NormalizedTables =
        List.Transform(Sources, each Normalize(_)),
    Result =
        Table.Combine(NormalizedTables)
in
    Result

年度、部署、取引先ごとに列名が異なる場合や、旧形式と新形式、CSVとExcelとAPIを同じ完成表へ統合する場合に有効です。

5.3 キー統合型:同じ実体の列を増やす

売上明細と顧客マスターは、上下に積むデータではありません。顧客コードをキーとして同じ顧客の情報を結び付けます。

let
    Sales =
        fnReadSales(),
    Customers =
        fnReadCustomers(),
    Joined =
        Table.NestedJoin(
            Sales,
            {"CustomerCode"},
            Customers,
            {"CustomerCode"},
            "Customer",
            JoinKind.LeftOuter
        ),
    Result =
        Table.ExpandTableColumn(
            Joined,
            "Customer",
            {"CustomerName", "Area"},
            {"CustomerName", "Area"}
        )
in
    Result

ここでも展開を使いますが、親子展開とは意味が違います。親子展開は1つの入力から生まれた複数行を平らにし、キー統合は別データにある同じ実体の属性を列として追加します。

5.4 規則駆動型:解析関数をデータとして選ぶ

CSV、TSV、JSON、旧形式が混在する場合、ファイル種類に応じて解析関数を切り替えます。

let
    Parsers =
        [
            Csv =
                (Source as record) as table =>
                    fnReadCsv(Source[Content]),
            Tsv =
                (Source as record) as table =>
                    let
                        Parsed =
                            Csv.Document(
                                Source[Content],
                                [
                                    Delimiter = "#(tab)",
                                    Encoding = 65001,
                                    QuoteStyle = QuoteStyle.Csv
                                ]
                            )
                    in
                        Table.PromoteHeaders(Parsed),
            Json =
                (Source as record) as table =>
                    Table.FromRecords(
                        Json.Document(Source[Content])
                    )
        ],
    ReadSource =
        (Source as record) as table =>
            let
                Parser =
                    Record.Field(
                        Parsers,
                        Source[ParserName]
                    )
            in
                Parser(Source)
in
    ReadSource

関数をRecordのフィールドとして保持し、ParserNameで取り出しています。区切り文字、文字コード、読み飛ばす行数、ヘッダー行、正規化関数名なども制御テーブルへ持たせられます。

5.5 バイナリ先行型:限定条件でのみ使う

複数BinaryをBinary.Combineし、最後に一度だけCsv.Documentする発想もあります。しかし、一般的な業務CSVの標準解にはしません。

問題になりやすいのは、次の点です。

  • 2ファイル目以降のヘッダー

  • ファイル末尾の改行不足

  • UTF-8 BOMが途中へ現れること

  • 文字コード、区切り文字、引用符規則の混在

  • 引用符内改行の境界

全ファイルが厳密に同一形式で、ヘッダー、BOM、改行、文字コードを制御できる場合に限って検討します。

第6章 11の設計パターンを選び分ける

6.1 比較表

設計 主役 中間表現 最終操作 向いている場面
親子展開型 フォルダー表 Table内のTable Expand 外側のファイル情報を残す
部品合成型 個々の結果 List of Table Table.Combine 単純な縦結合
中間形 フォルダー表と結果Table Table列 Table.Combine 管理性と列変動の両立
レコード再構築型 1件の事実 List of Record Table.FromRecords 半構造化データ
状態遷移型 処理状態 Record List.Accumulate 前回結果を次へ渡す
生成型 次の入力 状態Record List.Generate ページ送り、未知件数
分割統治型 業務区分 グループ内Table Group+Combine 階層を残す
スキーマ先行型 完成形の契約 正規化済みTable Table.Combine 異形式を統合する
キー統合型 同じ実体のキー ネスト結合結果 Join+Expand マスター属性を付ける
規則駆動型 メタデータ 関数Record 動的関数呼出し 複数形式を扱う
バイナリ先行型 ファイル内容 Binary Binary.Combine 厳密に同一形式の場合のみ

6.2 map・flatMap・fold・unfoldで読む

M言語にflatMapやfoldという名前の関数があるわけではありません。一般的な変換の型として対応づけると、個別関数の関係を理解しやすくなります。

map
= 各入力を1つの結果へ変換する
= List.Transform、Table.AddColumn
 
flatMap
= 各入力を複数行へ変換し、その結果を平らにする
= Table列を追加してTable.ExpandTableColumn
 
fold
= 入力を順番に処理し、1つの状態へ畳み込む
= List.Accumulate
 
unfold
= 現在の状態から次の候補と次の状態を作り続ける
= List.Generate
 
join
= 共通キーで別集合を対応づける
= Table.NestedJoin、Table.Join
 
group
= 共通区分で小さな集合へ分ける
= Table.Group

6.3 標準の「ファイルの結合」を分解する

Power Queryの画面から「ファイルの結合」を実行すると、サンプルファイル、変換関数、パラメーターなどの補助クエリが生成されます。

内部構造は概ね次の組み合わせです。

代表ファイルで変換手順を作る
↓
変換手順を関数化する
↓
各ファイルへ関数を適用する
↓
戻り値のTableを展開する

つまり、関数適用と親子展開型の組み合わせとして読めます。自動生成クエリも、主語と中間表現へ分解すれば修正箇所を判断しやすくなります。

第7章 実務で壊れにくくする

7.1 由来情報を早い段階で残す

結合後に異常行が見つかっても、元ファイルが分からなければ調査できません。少なくとも次のいずれかを完成表へ残します。

  • Source.Name

  • Folder Path

  • ファイル順または業務上の取込番号

  • 取込日時

7.2 列名の統合と意味の正規化を区別する

Table.Combineは、列名から業務上の意味を推測しません。同じ意味の列が異なる名前なら、結合前に改名します。逆に、同じ列名でも意味や単位が異なるなら、安易に同一列へ結合してはいけません。

7.3 型変換の場所を決める

型変換には二つの代表的な方針があります。

各ファイルで型を確定してから結合する

この方法は、どのファイルで変換に失敗したかを特定しやすくなります。

各ファイルでは文字列中心で読み、結合後に最終型を設定する

この方法は、全体へ一貫した型を適用しやすくなります。どちらの場合も、暗黙の型推測へ任せず、完成スキーマとエラー方針を決めます。

7.4 Fail FastとQuarantineを選ぶ

1ファイルの異常で全体を停止するか、正常分と異常一覧を分けるかは業務要件です。

Fail Fast
= 1ファイルでも異常なら全体をエラーにする
 
Quarantine
= 正常ファイルは統合し、異常ファイルは別一覧へ隔離する

欠損を許さない確定処理ではFail Fast、日次取込の早期確認ではQuarantineが適する場合があります。

7.5 List.Accumulateを高速化関数とみなさない

List.Accumulateは状態遷移を表現する関数です。使用しただけでストリーム処理になったり、メモリ使用量が小さくなったりするわけではありません。

単純結合なら、Tableのリストを作って最後に一度だけTable.Combineする形を先に検討します。性能差を主張する場合は、Power Query診断や同一データによる実測を使います。

7.6 Table.Bufferを習慣的に追加しない

Table.Bufferは、テーブルをメモリへ読み込み、評価中の外部変化から分離します。しかし、読み込みと保持のコストが増え、後続のクエリフォールディングを妨げることがあります。

速くなる場合も遅くなる場合もあるため、再評価の有無や診断結果を確認せず、フォルダー結合へ機械的に追加しません。

7.7 空と境界を設計する

正常な複数CSVだけでなく、次の状態を設計対象にします。

  • 対象CSVが0件

  • ヘッダーだけでデータ行が0件

  • 必須列が存在しない

  • 0バイトファイル

  • 文字コードが異なる

  • 読み取り中の一時ファイル

  • 列が追加または削除された

エラーにするのか、空Tableを返すのか、警告へ隔離するのかを決めます。

第8章 通常の業務CSVに対する推奨構成

8.1 第一候補となる流れ

通常の業務CSVでは、次の順序を第一候補にできます。

Folder.Files
↓
拡張子、隠し属性、ファイル名で対象を絞る
↓
Table.ToRecordsでファイルをRecordのリストにする
↓
ファイル単位の関数で解析、ヘッダー昇格、列名正規化を行う
↓
Source.Nameを結果へ追加する
↓
完成スキーマへそろえる
↓
Table.Combineを一度だけ実行する

これは、部品合成型、スキーマ先行型、由来情報付与の組み合わせです。

8.2 例外があるときの切り替え

条件 選ぶ設計
フォルダー属性を広く引き継ぐ 親子展開型
前ファイルの結果が次へ影響する 状態遷移型
入力一覧が最初には分からない 生成型
会社や年度の階層を残す 分割統治型
CSVごとに形式が異なる 規則駆動型+スキーマ先行型
マスター情報を加える キー統合型
半構造化された行を扱う レコード再構築型

8.3 設計レビューで確認する質問

実装前またはレビュー時には、次の質問を使えます。

  1. 1つの入力とは何か。

  2. 解析結果はTable、List、Recordのどれか。

  3. 元ファイルの文脈はどこへ保持するか。

  4. 完成表の列名と型は決まっているか。

  5. 列差分は許容するのか、異常とするのか。

  6. 1ファイルの失敗で全体を止めるのか。

  7. 入力順序や前回状態に意味があるか。

  8. 最後に行を増やすのか、列を増やすのか、階層を残すのか。

  9. 空フォルダー、空CSV、0バイトをどう扱うか。

  10. 性能に関する主張を実測したか。

まとめ 関数ではなく主語とデータの形を選ぶ

複数データを一つにまとめる方法に、唯一の正解はありません。ただし、選択の基準は整理できます。

外側の文脈を残す
→ AddColumnしてExpandする
 
個々の結果を同格の部品として扱う
→ List.TransformしてTable.Combineする
 
1件の事実へ分解して再構築する
→ Recordを集めてTable.FromRecordsする
 
前回の状態を引き継ぐ
→ List.Accumulateする
 
次の入力を状態から作る
→ List.Generateする
 
業務上のまとまりを残す
→ Table.Groupで分けてからCombineする
 
完成形を中心に設計する
→ 共通スキーマへ正規化してからCombineする
 
同じ実体の情報を合わせる
→ キーでJoinする

Power Queryを画面操作や関数カタログとして覚えるだけでは、設計は画一的になります。入力単位、中間表現、引き継ぐ文脈、完成形、最終演算を独立して選ぶと、同じCSV結合からTable、List、Record、関数、状態、階層、スキーマというM言語の主要概念が見えてきます。

一つにまとめるとは、すべてを早い段階で平らにすることではありません。業務上の意味を失わない段階まで構造を保ち、最終的に必要な形へ変換することです。

参考資料

  • Microsoft Learn「Power Query M formula language」

  • Microsoft Learn「Folder.Files」

  • Microsoft Learn「Table.AddColumn」

  • Microsoft Learn「Table.ExpandTableColumn」

  • Microsoft Learn「Table.Combine」

  • Microsoft Learn「Table.ToRecords」

  • Microsoft Learn「Table.FromRecords」

  • Microsoft Learn「List.Transform」

  • Microsoft Learn「List.Combine」

  • Microsoft Learn「List.Accumulate」

  • Microsoft Learn「List.Generate」

  • Microsoft Learn「Table.Group」

  • Microsoft Learn「Table.NestedJoin」

  • Microsoft Learn「Record.Combine」

  • Microsoft Learn「Table.Buffer」

出典メモ

  • 元原稿: PowerQuery_複数ソースを一つにまとめる設計パターン.md

  • 元原稿の28節に含まれる11の設計パターン、コード、比較、注意点を省略せず、公開記事として「主語・中間表現・文脈・完成形・最終演算」という一本の設計軸へ再構成した。

  • Power Query M関数の仕様は、2026年8月9日にMicrosoft LearnのM言語リファレンス、Table.Combine、List.Combine、List.Accumulate、List.Generate、Record.Combine、Table.Bufferなどのページで確認した。

  • 性能はデータ量、データソース、評価、キャッシュ、フォールディングによって変わるため、List.Accumulate、Table.Combine、Table.Bufferの優劣を実測なしに断定していない。