ゴールのイメージを先にそろえる
今回のテーマは「CSVのヘッダー行が途中にある(しかも場合によっては何回も出てくる)データを、Power Queryでキレイに取り込む実務テンプレ」です。
よくあるパターンは大きく二つです。
1つ目は「最初の数行がタイトルや説明で、本当のヘッダーは3行目以降にある」パターン。
2つ目は「複数CSVをフォルダから結合した結果、ファイルごとにヘッダー行が途中に何度も出てくる」パターンです。
この二つを、プログラミング初心者でも扱えるレベルまでかみ砕いて、Mコード付きでテンプレ化していきます。
パターン1:ヘッダーが最初の行ではなく途中にある
例題データのイメージ
まずは、こんなCSVをイメージしてください。
売上レポート 2025年1月分
出力日: 2025/02/01
日付,商品名,数量,金額
2025/01/01,りんご,10,1200
2025/01/02,みかん,5,500
...
最初の2〜3行は「タイトル」「出力日」などの情報で、本当に欲しいヘッダーは「日付,商品名,数量,金額」の行です。
この場合、「上の不要行を飛ばしてから、ヘッダー昇格する」というのが基本パターンになります。
手動操作での考え方(Power Query画面)
Power Queryの画面上でやるときの流れは、ざっくりこうです。
上位の行を削除 → 本当のヘッダー行を一番上に持ってくる → 「最初の行をヘッダーとして使用」
これをMコードで書くと、Table.Skip と Table.PromoteHeaders の組み合わせになります。
Mコードでのテンプレ(固定行数をスキップ)
「ヘッダーは必ず4行目にある」と分かっているケースのテンプレです。
let
Source = Csv.Document(
File.Contents("C:\Data\incoming\sales.csv"),
[
Delimiter = ",",
Encoding = 65001,
QuoteStyle = QuoteStyle.Csv
]
),
// 上から3行をスキップ(4行目が先頭になる)
SkippedTop = Table.Skip(Source, 3),
// 先頭行をヘッダーに昇格
Promoted = Table.PromoteHeaders(
SkippedTop,
[PromoteAllScalars = true]
)
in
Promoted
ここで重要なのは「どこまでを“ゴミ行”とみなすかを、行数で決めている」という点です。
タイトル行の構造が毎回同じなら、これだけで十分実務テンプレとして使えます。
行数が変わる場合の考え方(ヘッダー行を探す)
もう少し現実的なパターンとして、「タイトル行の行数が日によって微妙に違うけれど、ヘッダーの文字列は毎回同じ」というケースがあります。
たとえば、ヘッダー行が必ず「日付,商品名,数量,金額」という文字列で始まるなら、その行を探してそこまでスキップする、というロジックにできます。
イメージはこうです。
- いったん全部テーブルとして読み込む
- 「日付」「商品名」など、ヘッダー候補の行を探す
- その行番号より上を全部スキップする
- 残ったテーブルの先頭行をヘッダーに昇格する
Mコードの一例です。
let
Source = Csv.Document(
File.Contents("C:\Data\incoming\sales.csv"),
[
Delimiter = ",",
Encoding = 65001,
QuoteStyle = QuoteStyle.Csv
]
),
// 各行をリストとして扱うために、インデックスを付与
Indexed = Table.AddIndexColumn(Source, "Index", 0, 1, Int64.Type),
// ヘッダー行を探す(ここでは1列目が「日付」の行をヘッダーとみなす)
HeaderRow = Table.SelectRows(
Indexed,
each [Column1] = "日付"
),
// 見つかったヘッダー行のインデックスを取得
HeaderIndex = HeaderRow{0}[Index],
// ヘッダー行より上をスキップ
SkippedTop = Table.Skip(Indexed, HeaderIndex),
// もう一度インデックス列を削除して元の形に戻す
RemovedIndex = Table.RemoveColumns(SkippedTop, {"Index"}),
// 先頭行をヘッダーに昇格
Promoted = Table.PromoteHeaders(
RemovedIndex,
[PromoteAllScalars = true]
)
in
Promoted
ここでの重要ポイントは、「ヘッダー行を“文字列で探す”」という発想です。
これにより、「タイトル行の行数が変動する」という現場あるあるにも対応できます。
パターン2:途中に何度もヘッダー行が出てくる(フォルダ結合など)
例題データのイメージ
次は、フォルダ内の複数CSVを結合したときによく起きるパターンです。
たとえば、1つ1つのCSVがこうなっているとします。
日付,商品名,数量,金額
2025/01/01,りんご,10,1200
2025/01/02,みかん,5,500
これが3ファイルあって、フォルダから一括取り込みすると、結合結果はこんな感じになります。
日付,商品名,数量,金額
2025/01/01,りんご,10,1200
2025/01/02,みかん,5,500
日付,商品名,数量,金額
2025/01/03,バナナ,3,450
...
つまり、「途中にヘッダー行が何度も出てくる」状態です。
このままだと集計時に邪魔なので、「途中のヘッダー行だけを削除する」必要があります。
一番シンプルな考え方:ヘッダーと同じ行を削除する
一番分かりやすいロジックは、「1行目のヘッダーと同じ内容の行を、データ部分から削除する」です。
手順を言葉で書くとこうなります。
最初の行をヘッダーに昇格する → データ部分の中で「日付」などヘッダーと同じ値を持つ行を削除する
Mコードの例です。
let
// フォルダから結合した後のテーブルを想定
Source = ...,
// 先頭行をヘッダーに昇格
Promoted = Table.PromoteHeaders(
Source,
[PromoteAllScalars = true]
),
// 1列目が「日付」の行はヘッダー行とみなして削除
RemovedHeaderRows = Table.SelectRows(
Promoted,
each [日付] <> "日付"
)
in
RemovedHeaderRows
ここでのポイントは、「ヘッダーと同じ文字列を持つ行をフィルタで落とす」というシンプルさです。
1列目の列名が「日付」で固定されているなら、これだけでかなり実務的に使えます。
もう少し厳密に判定したい場合(複数列で比較)
もし「日付」列にたまたま「日付」という文字列が入る可能性がある、という場合は、複数列を組み合わせて判定する方が安全です。
たとえば、「日付」「商品名」「数量」「金額」の4列すべてがヘッダーと同じなら、その行はヘッダー行とみなす、というロジックにできます。
let
Promoted = ...,
RemovedHeaderRows =
Table.SelectRows(
Promoted,
each not (
[日付] = "日付" and
[商品名] = "商品名" and
[数量] = "数量" and
[金額] = "金額"
)
)
in
RemovedHeaderRows
ここでの重要ポイントは、「ヘッダー行を“条件で表現する”」という考え方です。
条件さえ書ければ、途中に何回出てきても、すべてまとめて削除できます。
重要ポイントの深掘り
Table.Skip と「上位の行の削除」の関係
Power QueryのUIで「行の削除 → 上位の行の削除」をすると、裏側では Table.Skip が使われます。
Table.Skip(テーブル, 行数) は、「先頭から指定した行数だけ飛ばして残りを返す」関数です。
固定行数を飛ばすだけなら、UI操作で十分ですが、
「ヘッダー行の位置が変わる」「条件で行を探したい」といった場合は、
Mコードで Table.Skip を組み合わせる方が柔軟になります。
Table.PromoteHeaders のタイミング
Table.PromoteHeaders は「先頭行をヘッダーに昇格する」関数です。
大事なのは、「本当にヘッダーにしたい行が先頭に来てから実行する」ことです。
ヘッダーより上にゴミ行が残っている状態で Table.PromoteHeaders を実行すると、
ゴミ行が列名になってしまい、その後の処理がすべてやりにくくなります。
実務テンプレとしては、
不要行を削除 or スキップ → ヘッダー行を先頭にそろえる → Table.PromoteHeaders
という順番を、頭の中の「型」として持っておくと安定します。
「途中ヘッダー削除」は最後の方でやるのが楽
フォルダ結合などで途中にヘッダー行が混ざる場合、
削除処理は「ヘッダー昇格のあと、型変換の前」くらいに入れるのが扱いやすいです。
理由はシンプルで、「列名が確定していた方が条件を書きやすい」からです。
列名が「Column1」「Column2」のままだと、どれが日付なのか分かりにくくなります。
実務テンプレとしてのまとめイメージ
ここまでの話を、実務テンプレの流れとしてまとめると、こんな感じになります。
ヘッダーが途中にある場合は、「ヘッダー行を探してそこまでスキップ → ヘッダー昇格」。
途中に何度もヘッダーが出てくる場合は、「ヘッダー昇格 → ヘッダーと同じ行を条件で削除」。
どちらも、「ヘッダー行をどう見つけるか」「どう表現するか」が肝です。
一度、自分のCSVのヘッダー文字列を眺めて、「どんな条件なら一意に判定できるか」を言葉で書き出してみると、Mコードに落とし込むのが一気に楽になります。
