【参考資料】パワークエリでCOBOL帳票を処理する全テクニック
― 自作関数なしでどこまでできるか + MIDB自作関数の実装 ―

エクセルでCOBOL帳票や固定長データを整形したい方向けに、Excel関数・Power Query・MIDBなどを使って列分割、不要行除去、帳票データ変換を進める実務手順を扱います。

💡 この資料の目的

パワークエリ編では「MIDBの壁」を最大の問題として紹介しました。しかし実際には、帳票のデータ構造次第ではパワークエリの標準機能だけで切り出しまで完結するケースが多くあります

この資料では、まず前半で「自作関数なしでどこまでできるか」を5つのパターンに分けて整理し、後半で「どうしても自作関数が必要なケース」の具体的な実装を示します。自分の帳票がどちらに該当するかをこのページで判定してください。

⚠ 前提として:Power Query は固定長テキストの分解を想定していません
Power Query(M言語)は CSV やテーブル形式のデータを取り込んで整形することを前提に設計されています。「バイト位置で文字列を切り出す」という操作は設計思想の外にあり、Excel の MIDB / LENB に相当する標準関数が存在しません。そのため COBOL 帳票のような固定長テキストを扱うには、データの特徴に応じた工夫が必要です。このページではその工夫を体系的にまとめています。
💬 判定の結論を先に言うと:
「自作関数が必要かどうか」は、帳票の中に"半角と全角が混在するフィールド群を区切り文字なしでバイト位置だけで切り分ける必要があるか"で決まります。それ以外の処理(不要行除去、フィルダウン、2行→1行変換、目印ベースの切り出しなど)はすべて標準機能だけで対応できます。
💡 迷ったら fx_MIDB(自作関数)を最初に検討してください:
以下のパターン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 と同じ感覚で使えます。

// 帳票例(全フィールド半角・1行50バイト) 1234567EXCEL CORP 000015000020260318 │1-7 │8-27(20文字) │28-37(10桁)│38-45(8桁)│

カスタム列の式(Text.Middle は0始まり)

// 顧客番号(バイト1~7 → 0始まりで位置0、長さ7) Text.Middle([Column1], 0, 7)
// 会社名(バイト8~27 → 0始まりで位置7、長さ20) Text.Trim( Text.Middle([Column1], 7, 20) )
// 売上金額(バイト28~37 → 0始まりで位置27、長さ10) Number.FromText( Text.Middle([Column1], 27, 10) )

あるいは、GUI操作だけで完結する方法もあります。[ホーム] → [列の分割] → [位置] を選び、7,27,37 と入力すれば、文字位置7・27・37の手前でそれぞれ分割されます。

💡 Text.Middle の引数はMIDBと違い「0始まり」です:
ExcelのMIDB(A1, 8, 20) に相当するのは Text.Middle([Column1], 7, 20) です。開始位置を1引くだけ。半角だけのデータならこの換算だけで使えます。

パターンB 全角文字混在でも「文字数」が固定 → Text.Middle で対応可能

見落としがちですが重要なパターンです。全角文字が含まれていても、各フィールドの「文字数」(バイト数ではなく)が固定であればText.Middle(文字数ベース)で正しく切り出せます。

COBOLのデータ定義で言えば、日本語項目が PIC N(10) のように「文字数」で定義されている帳票がこれに該当します。

// 帳票例:全フィールドが「文字数」で一定 1001新宿本店  0050000山田太郎  │4文字│6文字(全角)│7文字│5文字(全角)│ // ※バイト数では 4+12+7+10=33だが、文字数では 4+6+7+5=22文字

Text.Middle で文字数ベースの切り出し

Text.Middle([Column1], 0, 4) // 店舗ID Text.Trim( Text.Middle([Column1], 4, 6) ) // 店舗名 Number.FromText( Text.Middle([Column1], 10, 7) ) // 売上金額
💡 「バイト数」ではなく「文字数」で考えるのがコツ:
各フィールドが「全角だけ」か「半角だけ」で構成されているなら、文字数への換算は簡単です。全角フィールドは「バイト数÷2=文字数」、半角フィールドは「バイト数=文字数」。この換算をすれば Text.Middle の開始位置になります。
⚠ このパターンが成立する条件:
各フィールドの中身が「全角のみ」または「半角のみ」のどちらか一方であること。1つのフィールドの中で半角と全角が混在する場合は(例:「ABC新宿店」)、同じバイト数でも文字数が変動するため使えません。

応用:Text.Middle で行種を判定してフラグ列を作る

COBOL帳票には「ヘッダー行」「明細行」「合計行」など複数の行種が混在しています。Text.Middle は切り出しだけでなく、先頭の数文字を取得して行種を判定するフラグ列の作成にも頻繁に使います。これは全角・半角に関係なく使えるテクニックです。

// 帳票例:先頭2文字で行種が決まる HD20260315 第3営業部 月次売上報告書 ← ヘッダー行 DT1001新宿本店  山田太郎 0050000 ← 明細行 DT1002渋谷支店  佐藤花子 0120000 ← 明細行 TL合計 0170000 ← 合計行
// カスタム列「行種」を追加 if Text.Middle([Column1], 0, 2) = "HD" then "ヘッダー" else if Text.Middle([Column1], 0, 2) = "DT" then "明細" else if Text.Middle([Column1], 0, 2) = "TL" then "合計" else "不明"

フラグ列を作った後は、フィルターで「明細」だけを残し、パターンAやパターンBの切り出し、あるいはfx_MIDB(自作関数)でフィールド分割するのが定石です。Text.StartsWith でも同様の判定ができます。

// Text.StartsWith を使う場合(より簡潔) if Text.StartsWith([Column1], "DT") then "明細" else "その他"

応用:if Text.Middle(...) で特定位置の文字を判定して処理を分岐する

Text.Middle で取り出した文字列を if で判定し、行ごとに異なる処理を適用するのは M言語の基本テクニックです。行種判定だけでなく、特定の位置にある文字が何かによって切り出し方を変えることもできます。

// 例1:7文字目から1文字取得し、ハイフンかどうかで分岐 if Text.Middle([Column1], 7, 1) = "-" then "郵便番号あり" else "郵便番号なし"
// 例2:特定位置の文字によって切り出し範囲を変える if Text.Middle([Column1], 0, 1) = "*" then Text.Middle([Column1], 1, 20) // 先頭が * なら1文字ずらして取得 else Text.Middle([Column1], 0, 20) // 通常はそのまま取得
// 例3:fx_MIDB(自作関数)で切り出した結果に対して判定する // → 前半のテクニックと後半の自作関数を組み合わせる実例 let 区分 = Text.Middle([Column1], 0, 2), 本体 = if 区分 = "DT" then let bin = Text.ToBinary([Column1], 932), 店舗名 = Text.Trim(Text.FromBinary(Binary.Range(bin, 2, 14), 932)), 売上 = Text.FromBinary(Binary.Range(bin, 16, 10), 932) in [店舗名=店舗名, 売上=売上] else null in 本体

このように if Text.Middle(...) = "何か" then ... のパターンは、行種判定から条件付き切り出しまで幅広く使えます。fx_MIDB(自作関数)との組み合わせでも「先にText.Middleで行種を判定 → 明細行だけfx_MIDBで分解」という流れが定番です。

📌 この行種判定は後続のすべてのパターン(C・D・E および fx_MIDB(自作関数))の前処理として使えます。先にフラグ列で行種を分類し、フィルターで必要な行だけを残してから切り出し処理を行うのが効率的です。

パターンC 不要行の除去・フィルダウン → 標準機能で一瞬

ヘッダー行・罫線行・ページ区切り行の除去、および親情報の空白埋め(フィルダウン)は、全角混在であっても標準機能だけで完璧に処理できます。fx_MIDB(自作関数)でフィールド分解する前の前処理として、またfx_MIDB(自作関数)で切り出した後の仕上げとしても使います。

不要行の除去

列ヘッダーの [▼] フィルターで不要パターンのチェックを外すだけです。M言語で書く場合も標準関数のみです。

Table.SelectRows(前のステップ, each not Text.StartsWith([Column1], "===") and not Text.StartsWith([Column1], "---") and Text.Trim([Column1]) <> "" )

フィルダウン(親情報の空白埋め)

関数版(超絶技巧編)で最も難易度が高かった処理が3クリックで完了します。

① ヘッダー行の値が入っている列を選択 → ② [変換] → [フィル] → [下方向]

📌 パターンBの行種判定フラグと組み合わせると効果的です。フラグ列で「明細」以外を除外し、フィルダウンで親情報を埋める ― という流れが定番です。

パターンD 2行にまたがるデータを1行に結合 → インデックス+行ずらし

COBOL帳票では印字桁数の制限から、1件のデータが2行以上に分かれることがよくあります。パワークエリではインデックス列を使った「行ずらし結合」で自作関数なしに標準操作だけで対応できます。1行にまとめた後で fx_MIDB(自作関数)によるフィールド分解を行う流れです。

// 帳票例(1件=2行構成) 店舗ID:1001 店舗名:新宿本店 売上:50,000 ← 1件目の1行目 住所:東京都新宿区〇〇 電話:03-xxxx-xxxx ← 1件目の2行目 店舗ID:1002 店舗名:渋谷支店 売上:120,000 ← 2件目の1行目 住所:東京都渋谷区△△ 電話:03-yyyy-yyyy ← 2件目の2行目

手法①:インデックス列 + カスタム列で1行下を参照

Step 1:[列の追加] → [インデックス列] → [0から] で連番を振る。

Step 2:[列の追加] → [カスタム列] で以下の式を入力。

追加されたインデックス[Column1]{[インデックス]+1}

この式は「1行下のデータ」を横に展開します。

Step 3:インデックスの2の剰余が0の行だけをフィルターで残す。または1行目特有のキーワードでフィルター。

イメージ:インデックス+行ずらしの結果
IndexColumn1(元データ)2行目(カスタム列)
0店舗ID:1001 店舗名:新宿本店…住所:東京都新宿区〇〇…
1住所:東京都新宿区〇〇…店舗ID:1002…(不要)
2店舗ID:1002 店舗名:渋谷支店…住所:東京都渋谷区△△…
3住所:東京都渋谷区△△…(不要)

手法②:インデックス列 + 自己結合(マージ)

// Step 1:インデックス列「通し番号」を追加(0始まり) = Table.AddIndexColumn(前のステップ, "通し番号", 0, 1, Int64.Type)
// Step 2:「通し番号+1」のカスタム列を追加 = Table.AddColumn(前のステップ, "結合キー", each [通し番号] + 1)
// Step 3:自己結合(マージ) = Table.NestedJoin(前のステップ, {"結合キー"}, 前のステップ, {"通し番号"}, "次の行", JoinKind.LeftOuter)
// Step 4:結合結果を展開 = Table.ExpandTableColumn(前のステップ, "次の行", {"Column1"}, {"2行目データ"})
💡 3行で1件の場合:
{[インデックス]+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バイト以上と、実際に入っているデータの長さは毎行異なります。レイアウト定義書がない場合、隣のフィールドとの境界がスペースだけでは判別困難です。

// 実務でよく遭遇する帳票例 山田太郎     123-4567東京都新宿区西新宿1丁目         アレクサンドルペトロフ987-6543北海道札幌市中央区北1条西2丁目3番4号 │← 氏名(20B) →│←郵便番号→│← 住所(40B) →│

ケース①:レイアウト定義書がある場合 → fx_MIDB(自作関数)+ Text.Trim で確実

レイアウト定義書が手元にあれば、「氏名は1バイト目から20バイト」「郵便番号は21バイト目から8バイト」「住所は29バイト目から40バイト」のようにバイト位置が確定しています。素直にfx_MIDB(自作関数)で切り出して Text.Trim で後ろのスペースを除去すれば、有効データだけが残ります。

Text.Trim( fx_MIDB([Column1], 1, 20) ) // → "山田太郎" Text.Trim( fx_MIDB([Column1], 21, 8) ) // → "123-4567" Text.Trim( fx_MIDB([Column1], 29, 40) ) // → "東京都新宿区西新宿1丁目"
💡 レイアウト定義書があるなら問題は発生しません。
「中身のデータ長が毎行違う」ことと「フィールドのバイト枠が固定」であることは矛盾しません。枠が固定なので fx_MIDB(自作関数)+ Text.Trim で必ず正しく取り出せます。

ケース②:レイアウト定義書がない → データ中の「目印」で切り出す

バイト位置がわからなければfx_MIDB(自作関数)は使えません。この場合は、データの中にある記号やパターンを「目印」として頼りに切り出します。パワークエリには目印ベースの切り出しに使える標準関数が揃っています。

目印ベースで使える標準関数

Text.PositionOf([Column1], "-") // 最初の「-」の位置(0始まり) Text.BeforeDelimiter([Column1], "-") // 最初の「-」の前のテキスト Text.AfterDelimiter([Column1], "-") // 最初の「-」の後のテキスト Text.AfterDelimiter([Column1], "-", 1) // 2番目の「-」の後のテキスト Text.Middle([Column1], 位置, 長さ) // 位置起点の切り出し Text.Middle([Column1], 位置) // 位置以降を末尾まで全部取得

実例:郵便番号のハイフンを目印に氏名・郵便番号・住所を切り出す

考え方:郵便番号には必ず「-」(ハイフン)が含まれます。このハイフンを起点に、その前3文字+ハイフン+後4文字が郵便番号。ハイフンより前の部分から郵便番号の先頭3文字を除いたものが氏名。ハイフンより後の郵便番号を除いた残りが住所です。

// カスタム列でレコードを返す方式 let hp = Text.PositionOf([Column1], "-"), // 氏名:先頭から郵便番号の直前まで 氏名 = Text.Trim(Text.Start([Column1], hp - 3)), // 郵便番号:ハイフン前3文字 + ハイフン + ハイフン後4文字 = 8文字 郵便番号 = Text.Middle([Column1], hp - 3, 8), // 住所:郵便番号の終了位置以降を全部取得 住所 = Text.Trim(Text.Middle([Column1], hp + 5)) in [氏名=氏名, 郵便番号=郵便番号, 住所=住所]
💡 この手法のポイント:
バイト位置を一切使っていません。「-」の文字位置を起点に相対的に切り出すため、全角・半角が混在していても正しく動作します。Text.PositionOf は「文字数」で位置を返し、Text.Middle も「文字数」で切り出すため、バイト数の不一致問題が発生しません。目印さえあればfx_MIDB(自作関数)は不要です。
💬 住所の「後ろ」にも目印がある場合:
住所の後ろに電話番号があり、その電話番号にもハイフンが含まれるような場合は、Text.PositionOf の第3引数で「最後に出現するハイフン」を探したり、Text.AfterDelimiter の出現回数指定を使って対処できます。複数の目印がある場合は、最も確実に位置が特定できる目印を起点に選ぶのがコツです。

⚠ 目印ベース切り出しの重大なリスク:目印文字がデータ中に出現する

目印として使ったハイフン - が、氏名や住所の中にも出現する場合、全フィールドが誤分割されます

// 誤分割が起きる例 井ノ上-太郎    123-4567東京都新宿区西新宿1丁目 ↑ ここが最初のハイフン → Text.PositionOf は氏名中のハイフンを返す!

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(自作関数)
④ 上のどれにも当てはまらない → まずレイアウト定義書の入手を最優先に
📌 パターンB〜Dは fx_MIDB(自作関数)との併用が前提になることも多いです。判定表は「自作関数なしで完結するか」の判定用ですが、実務では fx_MIDB(自作関数)でメインのフィールド分解を行い、行種判定(B)→ 不要行除去・フィルダウン(C)→ 行ずらし結合(D)を前後処理として組み合わせるのが一般的な流れです。

後半:自作関数が必要なケース ― M言語でMIDB相当を作る

前半の判定表で「自作関数が必要」に該当した場合、ここから先の手法を使います。核心技術は Text.ToBinary + Binary.Range + Text.FromBinary の3段階変換です。前半のパターンB〜Dで紹介した行種判定・不要行除去・行ずらし等と組み合わせて使ってください。

核心技術:バイナリ経由のバイト位置切り出し

M言語で文字列をShift_JIS(コードページ932)のバイナリに変換すると、半角=1バイト・全角=2バイトの世界になります。このバイナリをバイト位置で切り出して文字列に戻せば、ExcelのMIDBと完全に同じ結果が得られます。

文字列(text) Text.ToBinary
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(自作関数)― ExcelのMIDB相当(Shift_JIS基準・1始まり) (targetText as text, startByte as number, numBytes as number) as text => let bin = Text.ToBinary(targetText, 932), sliced = Binary.Range(bin, startByte - 1, numBytes), result = Text.FromBinary(sliced, 932) in result
検証: fx_MIDB("AB新宿本店CD", 1, 2)"AB" /  fx_MIDB("AB新宿本店CD", 3, 8)"新宿本店" /  fx_MIDB("AB新宿本店CD", 11, 2)"CD"
⚠ Binary.Range は0始まり、fx_MIDB(自作関数)は1始まり。ExcelのMIDB(A1, 5, 2) と fx_MIDB([Col], 5, 2) は同じ結果です。レイアウト定義書のバイト位置をそのまま使えます。
💡 LEFTB / RIGHTB が必要な場合:
先頭から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_Safe(自作関数)― null・範囲外でもエラーにならない安全版 (targetText as nullable text, startByte as number, numBytes as number) as nullable text => let safeText = if targetText = null or targetText = "" then null else let bin = Text.ToBinary(targetText, 932), totalLen = Binary.Length(bin), adjStart = startByte - 1, adjCount = Number.Min({numBytes, totalLen - adjStart}), result = if adjStart >= totalLen or adjCount <= 0 then "" else Text.FromBinary(Binary.Range(bin, adjStart, adjCount), 932) in result in safeText

⚡ パフォーマンス改善:一括バイナリ変換パターン

fx_MIDB(自作関数)を1行あたり複数回呼ぶと、毎回 Text.ToBinary が実行されてパフォーマンスが低下します。カスタム列でレコード型を返せば、バイナリ変換は1行1回で済みます。

// カスタム列でレコードを返し、後で展開する方式 let bin = Text.ToBinary([Column1], 932), 店舗ID = Text.FromBinary(Binary.Range(bin, 0, 4), 932), 店舗名 = Text.Trim(Text.FromBinary(Binary.Range(bin, 5, 14), 932)), 売上金額 = Number.FromText(Text.FromBinary(Binary.Range(bin, 20, 7), 932)), 担当者名 = Text.Trim(Text.FromBinary(Binary.Range(bin, 28, 8), 932)) in [店舗ID=店舗ID, 店舗名=店舗名, 売上金額=売上金額, 担当者名=担当者名]

レコード型の列が追加されるので、展開ボタン(⇔)で各列に展開します。Text.ToBinary が行あたり1回になり、最大4倍高速です。

📌 目安:数千行…意識不要。数万行…先にフィルターで不要行除去。5万行以上…一括変換推奨。

カスタム関数の登録手順

手順

[データ] → [データの取得] → [その他のデータソースから] → [空のクエリ]

[ホーム] → [詳細エディター] → 内容をすべて削除してコードを貼り付け → [完了]

右側「クエリの設定」でクエリ名を fx_MIDB 等に変更

テスト値で動作確認。確認後の呼び出しクエリは削除OK

エンコーディングとコードページ

エンコーディングコードページ全角1文字備考
Shift_JIS9322バイト日本のCOBOL帳票の大多数。本資料の前提。
UTF-8650013バイト最近のシステムで増加中。3バイト計算になるため注意。
EUC-JP209322バイト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+FINDText.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自作関数の実装。

参考情報

参考:区切り文字がある場合 → 列の分割(区切り記号)で全角混在でもそのまま分割可能

フィールド間にカンマ、タブ、スペース連続、パイプ(|)などの区切り文字がある場合は、パワークエリの「列の分割(区切り記号)」だけで全角混在でも処理できます。バイト位置の計算自体が不要になるためです。

// 区切り文字がある場合の例(タブ区切り・全角混在) 1001 新宿本店 0050000 山田太郎 1002 渋谷支店 0120000 佐藤花子

GUI操作:[ホーム] → [列の分割] → [区切り記号による分割] → 区切り記号を選択(タブ / カンマ / スペース等)。スペース区切りの場合は「連続する区切り記号を1つとして扱う」オプションにチェックを入れると、複数スペースをまとめて処理できます。

⚠ ただしCOBOL帳票では稀なケースです:
COBOL帳票の出力は基本的に固定長でフィールドを隙間なく詰めるのが標準です。タブやカンマやパイプで区切って出力されるケースはかなり稀であり、該当するのはホスト側でCSV/TSV形式に変換された後のデータや、中間処理で区切り文字を付与して出力し直されたデータに限られます。COBOL帳票の「生データ」としてはまず見かけないため、本編のパターンには含めていません。