【参考資料】パワークエリでCOBOL帳票を処理する全テクニック
― 自作関数なしでどこまでできるか + MIDB自作関数の実装 ―
エクセルでCOBOL帳票や固定長データを整形したい方向けに、Excel関数・Power Query・MIDBなどを使って列分割、不要行除去、帳票データ変換を進める実務手順を扱います。
💡 この資料の目的
パワークエリ編では「MIDBの壁」を最大の問題として紹介しました。しかし実際には、帳票のデータ構造次第ではパワークエリの標準機能だけで切り出しまで完結するケースが多くあります。
この資料では、まず前半で「自作関数なしでどこまでできるか」を5つのパターンに分けて整理し、後半で「どうしても自作関数が必要なケース」の具体的な実装を示します。自分の帳票がどちらに該当するかをこのページで判定してください。
Power Query(M言語)は CSV やテーブル形式のデータを取り込んで整形することを前提に設計されています。「バイト位置で文字列を切り出す」という操作は設計思想の外にあり、Excel の MIDB / LENB に相当する標準関数が存在しません。そのため COBOL 帳票のような固定長テキストを扱うには、データの特徴に応じた工夫が必要です。このページではその工夫を体系的にまとめています。
「自作関数が必要かどうか」は、帳票の中に"半角と全角が混在するフィールド群を区切り文字なしでバイト位置だけで切り分ける必要があるか"で決まります。それ以外の処理(不要行除去、フィルダウン、2行→1行変換、目印ベースの切り出しなど)はすべて標準機能だけで対応できます。
以下のパターンA〜Eは「自作関数なしでどこまでできるか」を知るためのテクニック集です。しかし COBOL 帳票の多くは全角・半角混在で区切り文字もないため、結局 fx_MIDB(自作関数)が必要になるケースが大半です。確実性・汎用性の観点から、まず fx_MIDB(自作関数)の導入を検討し、標準機能だけで済むかどうかは判定表で確認するという順序をおすすめします。
💡 前半と後半は「別物」ではありません ― 組み合わせて使うのが実務の定番
このページでは便宜上「自作関数なしでできること(前半)」と「自作関数が必要なケース(後半)」に分けていますが、実際の処理では両方を組み合わせるのが普通です。
たとえば、fx_MIDB(自作関数)でバイト位置の切り出しを行った後に、前半で紹介する if Text.Middle(...) で行種を判定してフィルタリングしたり、フィルダウンで親情報を埋めたり、インデックス+行ずらしで2行を1行にまとめたりします。
前半のテクニックは「fx_MIDB(自作関数)の前処理・後処理」としても活躍します。自作関数なしで完結するかの判定材料であると同時に、自作関数と一緒に使うパーツでもあると理解してください。
前半:自作関数なし(標準機能だけ)でできること
以下の5パターンは、M言語のカスタム関数を一切書かずに、パワークエリのGUI操作と標準関数だけで処理できます。また、fx_MIDB(自作関数)によるメインの切り出し処理と組み合わせて前処理・後処理としても使えます。
パターンA 半角のみの固定長データ → Text.Middle で直接切り出し
帳票の全フィールドが半角英数字だけで構成されている場合は、バイト位置 = 文字位置になります。パワークエリ標準の Text.Middle がそのままExcelの MIDB と同じ感覚で使えます。
カスタム列の式(Text.Middle は0始まり)
あるいは、GUI操作だけで完結する方法もあります。[ホーム] → [列の分割] → [位置] を選び、7,27,37 と入力すれば、文字位置7・27・37の手前でそれぞれ分割されます。
ExcelのMIDB(A1, 8, 20) に相当するのは Text.Middle([Column1], 7, 20) です。開始位置を1引くだけ。半角だけのデータならこの換算だけで使えます。
パターンB 全角文字混在でも「文字数」が固定 → Text.Middle で対応可能
見落としがちですが重要なパターンです。全角文字が含まれていても、各フィールドの「文字数」(バイト数ではなく)が固定であれば、Text.Middle(文字数ベース)で正しく切り出せます。
COBOLのデータ定義で言えば、日本語項目が PIC N(10) のように「文字数」で定義されている帳票がこれに該当します。
Text.Middle で文字数ベースの切り出し
各フィールドが「全角だけ」か「半角だけ」で構成されているなら、文字数への換算は簡単です。全角フィールドは「バイト数÷2=文字数」、半角フィールドは「バイト数=文字数」。この換算をすれば Text.Middle の開始位置になります。
各フィールドの中身が「全角のみ」または「半角のみ」のどちらか一方であること。1つのフィールドの中で半角と全角が混在する場合は(例:「ABC新宿店」)、同じバイト数でも文字数が変動するため使えません。
応用:Text.Middle で行種を判定してフラグ列を作る
COBOL帳票には「ヘッダー行」「明細行」「合計行」など複数の行種が混在しています。Text.Middle は切り出しだけでなく、先頭の数文字を取得して行種を判定するフラグ列の作成にも頻繁に使います。これは全角・半角に関係なく使えるテクニックです。
フラグ列を作った後は、フィルターで「明細」だけを残し、パターンAやパターンBの切り出し、あるいはfx_MIDB(自作関数)でフィールド分割するのが定石です。Text.StartsWith でも同様の判定ができます。
応用:if Text.Middle(...) で特定位置の文字を判定して処理を分岐する
Text.Middle で取り出した文字列を if で判定し、行ごとに異なる処理を適用するのは M言語の基本テクニックです。行種判定だけでなく、特定の位置にある文字が何かによって切り出し方を変えることもできます。
このように if Text.Middle(...) = "何か" then ... のパターンは、行種判定から条件付き切り出しまで幅広く使えます。fx_MIDB(自作関数)との組み合わせでも「先にText.Middleで行種を判定 → 明細行だけfx_MIDBで分解」という流れが定番です。
パターンC 不要行の除去・フィルダウン → 標準機能で一瞬
ヘッダー行・罫線行・ページ区切り行の除去、および親情報の空白埋め(フィルダウン)は、全角混在であっても標準機能だけで完璧に処理できます。fx_MIDB(自作関数)でフィールド分解する前の前処理として、またfx_MIDB(自作関数)で切り出した後の仕上げとしても使います。
不要行の除去
列ヘッダーの [▼] フィルターで不要パターンのチェックを外すだけです。M言語で書く場合も標準関数のみです。
フィルダウン(親情報の空白埋め)
関数版(超絶技巧編)で最も難易度が高かった処理が3クリックで完了します。
① ヘッダー行の値が入っている列を選択 → ② [変換] → [フィル] → [下方向]
パターンD 2行にまたがるデータを1行に結合 → インデックス+行ずらし
COBOL帳票では印字桁数の制限から、1件のデータが2行以上に分かれることがよくあります。パワークエリではインデックス列を使った「行ずらし結合」で自作関数なしに標準操作だけで対応できます。1行にまとめた後で fx_MIDB(自作関数)によるフィールド分解を行う流れです。
手法①:インデックス列 + カスタム列で1行下を参照
Step 1:[列の追加] → [インデックス列] → [0から] で連番を振る。
Step 2:[列の追加] → [カスタム列] で以下の式を入力。
この式は「1行下のデータ」を横に展開します。
Step 3:インデックスの2の剰余が0の行だけをフィルターで残す。または1行目特有のキーワードでフィルター。
| Index | Column1(元データ) | 2行目(カスタム列) |
| 0 | 店舗ID:1001 店舗名:新宿本店… | 住所:東京都新宿区〇〇… |
| 1 | 住所:東京都新宿区〇〇… | 店舗ID:1002…(不要) |
| 2 | 店舗ID:1002 店舗名:渋谷支店… | 住所:東京都渋谷区△△… |
| 3 | 住所:東京都渋谷区△△… | (不要) |
手法②:インデックス列 + 自己結合(マージ)
{[インデックス]+1} と {[インデックス]+2} の2つのカスタム列を追加し、3の剰余が0の行だけを残します。各行の行種が先頭文字等で判別できる場合は、剰余でなく行種でフィルターするほうが安全です。
⚠ インデックス+行ずらしの限界:「N行固定」が絶対条件
この手法は「1件あたりの行数が完全に固定されている」ことが大前提です。
崩壊パターン①:不要行が途中に混入する
ページ区切りのヘッダーや罫線が挟まるとインデックスがずれ、その先の全データの組み合わせが崩壊します。行ずらしの前に不要行をフィルターで完全に除去する必要があります(パターンCとの組み合わせ)。
崩壊パターン②:伝票ごとに明細行数がバラバラ
インデックスの算数では対応不可。パイプライン方式(フラグ+フィルダウン+抽出)が正しいアプローチです。
崩壊パターン③:1明細あたりの行数自体が不定
「商品Aは2行、商品Bは3行」のようなケースではインデックス+行ずらしは完全に通用しません。行種判定+条件付きグループ化が必要で、難易度が大幅に上がります。
✅ 1件あたりの行数は完全に固定か?
✅ ページ区切り等のゴミ行は事前に除去したか?
✅ 帳票全体を通して行の並び順は常に一定のサイクルか?
1つでもNoなら、パターンCのフィルター+フィルダウンを使ってください。
パターンE 「目印」を頼りに可変長フィールドを切り出す
ここまでのパターンA~Dは「フィールドの開始位置と長さが完全に固定」であることを前提としていました。しかし実務では、帳票のバイト枠は確保されているのに、中身の有効データの長さが行ごとに違うケースが頻繁に発生します。
典型例は「氏名」「住所」のような自然言語フィールドです。COBOLのPICTURE句で30バイトの枠が確保されていても、「山田太郎」なら8バイト、「アレクサンドル・ペトロフ」なら20バイト以上と、実際に入っているデータの長さは毎行異なります。レイアウト定義書がない場合、隣のフィールドとの境界がスペースだけでは判別困難です。
ケース①:レイアウト定義書がある場合 → fx_MIDB(自作関数)+ Text.Trim で確実
レイアウト定義書が手元にあれば、「氏名は1バイト目から20バイト」「郵便番号は21バイト目から8バイト」「住所は29バイト目から40バイト」のようにバイト位置が確定しています。素直にfx_MIDB(自作関数)で切り出して Text.Trim で後ろのスペースを除去すれば、有効データだけが残ります。
「中身のデータ長が毎行違う」ことと「フィールドのバイト枠が固定」であることは矛盾しません。枠が固定なので fx_MIDB(自作関数)+ Text.Trim で必ず正しく取り出せます。
ケース②:レイアウト定義書がない → データ中の「目印」で切り出す
バイト位置がわからなければfx_MIDB(自作関数)は使えません。この場合は、データの中にある記号やパターンを「目印」として頼りに切り出します。パワークエリには目印ベースの切り出しに使える標準関数が揃っています。
目印ベースで使える標準関数
実例:郵便番号のハイフンを目印に氏名・郵便番号・住所を切り出す
考え方:郵便番号には必ず「-」(ハイフン)が含まれます。このハイフンを起点に、その前3文字+ハイフン+後4文字が郵便番号。ハイフンより前の部分から郵便番号の先頭3文字を除いたものが氏名。ハイフンより後の郵便番号を除いた残りが住所です。
バイト位置を一切使っていません。「-」の文字位置を起点に相対的に切り出すため、全角・半角が混在していても正しく動作します。Text.PositionOf は「文字数」で位置を返し、Text.Middle も「文字数」で切り出すため、バイト数の不一致問題が発生しません。目印さえあればfx_MIDB(自作関数)は不要です。
住所の後ろに電話番号があり、その電話番号にもハイフンが含まれるような場合は、
Text.PositionOf の第3引数で「最後に出現するハイフン」を探したり、Text.AfterDelimiter の出現回数指定を使って対処できます。複数の目印がある場合は、最も確実に位置が特定できる目印を起点に選ぶのがコツです。
⚠ 目印ベース切り出しの重大なリスク:目印文字がデータ中に出現する
目印として使ったハイフン - が、氏名や住所の中にも出現する場合、全フィールドが誤分割されます。
Text.PositionOf([Column1], "-") は最初に見つかったハイフンの位置を返します。氏名中にハイフンがある行では、郵便番号のハイフンではなく氏名中のハイフンが検出されてしまい、氏名・郵便番号・住所のすべてが誤った位置で分割されます。
同様に、住所に「北1条西2-3」のようにハイフンが含まれるケースでは、2番目以降のハイフンとの区別が困難になります。
対策①:Text.PositionOf の検索開始位置を指定して「N文字目以降の最初のハイフン」を探す。ただし氏名の長さが行ごとに異なるため、適切な開始位置の指定が難しい場合があります。
対策②:M言語には正規表現がないため、「数字3桁+ハイフン+数字4桁」のパターンマッチングは標準関数の組み合わせで擬似的に実装する必要があり、複雑になります。
対策③(最も確実):レイアウト定義書を入手して fx_MIDB(自作関数)でバイト位置指定する。目印ベースはあくまで「定義書がない場合の応急処置」です。
判定表:自分の帳票はどのパターンか?
| 帳票の特徴 | 使える手法 | 自作関数 | パターン |
|---|---|---|---|
| 全フィールドが半角のみ | Text.Middle / 列の分割(位置) | 不要 | A |
| 全角あり。各フィールドは全角のみ or 半角のみ(フィールド内混在なし) | Text.Middle(文字数換算)/ if判定 | 不要 | B |
| 不要行の除去 / フィルダウン | フィルター / フィル下方向 | 不要 | C |
| N行固定で1件のデータを1行にまとめたい | インデックス+行ずらし | 不要 | D |
| レイアウト定義書はないが、ハイフンや記号など目印がデータ中にある | Text.PositionOf + Text.Middle 等 | 不要 | E |
| 全角あり。1フィールド内で半角/全角混在、区切り文字なし。レイアウト定義書のバイト位置で切り分ける | fx_MIDB(自作関数) | 必要 | 後半 |
| レイアウト定義書もなく、目印もない(全角文字が区切りなく連続) | 関数的手法では原理的に対応不可。定義書の入手が必須 | ― | 対応不可 |
① データ中にハイフン・カンマ・スペース等の目印がないか? → あればパターンE
② 各フィールドは「全角だけ」「半角だけ」のどちらか? → そうならパターンB
③ レイアウト定義書のバイト位置がわかるか? → わかればfx_MIDB(自作関数)
④ 上のどれにも当てはまらない → まずレイアウト定義書の入手を最優先に
後半:自作関数が必要なケース ― M言語でMIDB相当を作る
前半の判定表で「自作関数が必要」に該当した場合、ここから先の手法を使います。核心技術は Text.ToBinary + Binary.Range + Text.FromBinary の3段階変換です。前半のパターンB〜Dで紹介した行種判定・不要行除去・行ずらし等と組み合わせて使ってください。
核心技術:バイナリ経由のバイト位置切り出し
M言語で文字列をShift_JIS(コードページ932)のバイナリに変換すると、半角=1バイト・全角=2バイトの世界になります。このバイナリをバイト位置で切り出して文字列に戻せば、ExcelのMIDBと完全に同じ結果が得られます。
Shift_JIS バイナリに変換 → Binary.Range
バイト位置で切り出し → Text.FromBinary
文字列に復元
Text.ToBinary(text, 932) ― 文字列をShift_JISバイナリに変換Binary.Length(binary) ― バイナリのバイト数を返す(LENB相当)Binary.Range(binary, offset, count) ― バイト位置で切り出し(0始まり)Text.FromBinary(binary, 932) ― バイナリをShift_JISとして文字列に復元
Binary.Length( Text.ToBinary("テスト文字列", 932) ) で Shift_JIS 基準のバイト数(LENB相当)を取得できます。独立した自作関数にする必要はなく、必要な箇所でこの式をインラインで使えば十分です。
fx_MIDB(自作関数)― バイト位置で切り出す(本丸)
このページで紹介する自作関数はこの fx_MIDB(自作関数)1つだけです。ExcelのMIDBと同じ「1始まり」のバイト位置指定で、全角・半角混在の文字列からバイト単位で正確に切り出します。
fx_MIDB("AB新宿本店CD", 1, 2) → "AB" /
fx_MIDB("AB新宿本店CD", 3, 8) → "新宿本店" /
fx_MIDB("AB新宿本店CD", 11, 2) → "CD"
先頭からNバイト取得は
fx_MIDB([Col], 1, N)、末尾からNバイト取得は fx_MIDB([Col], fx_LENB相当 - N + 1, N) で代用できます。バイト長は Binary.Length(Text.ToBinary([Col], 932)) で取得してください。独立した関数を増やすよりも、fx_MIDB(自作関数)1つで統一するほうがシンプルです。
fx_MIDB_Safe(自作関数)― エラー処理付き安全版
元データに null や短い行(ヘッダー行、空行など)が混在する場合にエラーを回避する安全版です。必要に応じて fx_MIDB(自作関数)の代わりに使用してください。
⚡ パフォーマンス改善:一括バイナリ変換パターン
fx_MIDB(自作関数)を1行あたり複数回呼ぶと、毎回 Text.ToBinary が実行されてパフォーマンスが低下します。カスタム列でレコード型を返せば、バイナリ変換は1行1回で済みます。
レコード型の列が追加されるので、展開ボタン(⇔)で各列に展開します。Text.ToBinary が行あたり1回になり、最大4倍高速です。
カスタム関数の登録手順
手順
① [データ] → [データの取得] → [その他のデータソースから] → [空のクエリ]
② [ホーム] → [詳細エディター] → 内容をすべて削除してコードを貼り付け → [完了]
③ 右側「クエリの設定」でクエリ名を fx_MIDB 等に変更
④ テスト値で動作確認。確認後の呼び出しクエリは削除OK
エンコーディングとコードページ
| エンコーディング | コードページ | 全角1文字 | 備考 |
|---|---|---|---|
| Shift_JIS | 932 | 2バイト | 日本のCOBOL帳票の大多数。本資料の前提。 |
| UTF-8 | 65001 | 3バイト | 最近のシステムで増加中。3バイト計算になるため注意。 |
| EUC-JP | 20932 | 2バイト | UNIX系COBOL帳票で稀に使用。 |
🔧 よくあるトラブル
文字化けする
全角文字の途中を切り出しています。全角項目は偶数バイト位置から偶数バイト数で切り出してください。
Expression.Error(範囲外)
明細行より短い行にMIDB分解が適用されています。先にフィルターで不要行除去、またはfx_MIDB_Safe(自作関数)を使用。
半角カタカナがおかしい
Shift_JISでは1バイト、UTF-8では3バイト。エンコーディングとコードページの不一致が原因。
Excel関数 ↔ パワークエリ 対応表
| 処理 | Excel関数 | パワークエリ |
|---|---|---|
| バイト位置切り出し | MIDB(A1,6,14) | fx_MIDB(自作関数)([Col],6,14) |
| 文字位置切り出し | MID(A1,6,14) | Text.Middle([Col],5,14)【標準】 |
| 目印ベース切り出し | MID+FIND | Text.PositionOf + Text.Middle【標準】 |
| 行種判定フラグ | IF(LEFT(...),...) | if Text.Middle(...) = ... then ...【標準】 |
| 前後スペース除去 | TRIM(...) | Text.Trim(...)【標準】 |
| 数値変換 | VALUE(...) | Number.FromText(...)【標準】 |
| 先頭一致判定 | IF(LEFTB(...),...) | Text.StartsWith(...)でフィルター【標準】 |
| フィルダウン | IF(条件,新値,直上参照) | [変換]→[フィル]→[下方向]【標準】 |
| 不要行の除去 | FILTER / INDEX+SMALL | フィルター▼クリック【標準】 |
| 2行→1行結合 | INDEX+ROW算数 | インデックス+行ずらし【標準】 |
| 複数ファイル結合 | 不可能 | [フォルダーから]取得【標準・PQ独自】 |
🎯 まとめ
パワークエリでCOBOL帳票を処理する際、自作関数が必要になるのは「半角と全角が混在するフィールドを区切り文字なしでバイト位置だけで切り分ける」ケースだけです。それ以外の処理 ― 半角のみデータの切り出し、文字数ベースの切り出し、行種判定フラグ、目印ベースの相対切り出し、不要行除去、フィルダウン、2行→1行結合、複数ファイル結合 ― はすべて標準機能で対応できます。
ただし、Power Query はそもそも固定長テキストの分解を想定した設計ではないため、COBOL帳票の処理には工夫が必要です。そして前半のテクニックと後半の fx_MIDB(自作関数)は別物ではなく、組み合わせて使うのが実務の基本です。行種判定(パターンB)で明細行だけに絞り、不要行除去(パターンC)でゴミ行を消し、fx_MIDB(自作関数)でフィールド分解する ― この一連の流れが COBOL 帳票処理のパワークエリにおける定番パイプラインです。
自作関数なしでできること & fx_MIDB と組み合わせて使えること(前半)
パターンA ― 半角のみ → Text.Middle / 列の分割(位置)
パターンB ― 全角あり・フィールド内混在なし → Text.Middle(文字数換算)+ if判定で行種フラグ
パターンC ― 不要行除去・フィルダウン → フィルター / フィル下方向
パターンD ― N行固定の縦持ち結合 → インデックス+行ずらし ※行数固定が条件
パターンE ― 目印(記号)がデータ中にある → Text.PositionOf + Text.Middle で相対切り出し
自作関数が必要なケース(後半)
1フィールド内で半角と全角が混在し、区切り文字がない → fx_MIDB(自作関数)(Text.ToBinary + Binary.Range + Text.FromBinary)
一度作れば使い回し可能。一括バイナリ変換パターンで高速化も可能。前半のパターンB〜Dと組み合わせて前処理・後処理に活用。
【完全版】MIDB切り出し・浄化・型変換 … 固定長データの基本的な切り出しと型変換。
【応用編】不要行の除外と横展開 … ROWフラグによる行フィルタリング。
【超絶技巧編】ヘッダー紐付け・フィルダウン … 親情報のフィルダウンとMIDB分解パイプライン。
【不規則明細・完結編】行数バラバラの正規化 … 不規則明細とマルチラインレコード。
【パワークエリ編】不要行除去と一括結合 … MIDBの壁と越えた後の優位性。関数との使い分け。
【参考資料】パワークエリ全テクニック(本ページ) … 自作関数なしでできること+MIDB自作関数の実装。
参考情報
参考:区切り文字がある場合 → 列の分割(区切り記号)で全角混在でもそのまま分割可能
フィールド間にカンマ、タブ、スペース連続、パイプ(|)などの区切り文字がある場合は、パワークエリの「列の分割(区切り記号)」だけで全角混在でも処理できます。バイト位置の計算自体が不要になるためです。
GUI操作:[ホーム] → [列の分割] → [区切り記号による分割] → 区切り記号を選択(タブ / カンマ / スペース等)。スペース区切りの場合は「連続する区切り記号を1つとして扱う」オプションにチェックを入れると、複数スペースをまとめて処理できます。
COBOL帳票の出力は基本的に固定長でフィールドを隙間なく詰めるのが標準です。タブやカンマやパイプで区切って出力されるケースはかなり稀であり、該当するのはホスト側でCSV/TSV形式に変換された後のデータや、中間処理で区切り文字を付与して出力し直されたデータに限られます。COBOL帳票の「生データ」としてはまず見かけないため、本編のパターンには含めていません。