フリーランサー向けGoogle Sheets経費管理の自動化
Google Sheetsで自動更新する経費分類システムを構築。動的な列マッピング、ベンダールールテーブル、P&Lやバーンモデルに連携する出力まで徹底解説。
このガイドでは、Google Sheetsで自動化された経費分類システムを構築する手順を解説します。銀行や決済サービスのエクスポートデータを正規化し、メンテナンスしやすいルールテーブルでベンダー文字列をカテゴリにマッピングし、整理された月次実績をP&L構造に流し込む仕組みを作ります。バーンモデルの参照元として、あるいは投資家への説明資料として、実際の実務で使えるレベルのものを目指します。
必要なもの
- GoogleアカウントとGoogle Sheetsへのアクセス権
- 銀行、Stripe、またはその他会計ツールからのCSVエクスポートデータ
- SUMIFS、INDEX/MATCH、配列数式の基本的な知識
- 実際のP&L項目に対応したカテゴリリスト(ソフトウェア、給与、マーケティングなど)——税務申告の科目ではなく、管理会計ベースで
- (任意)2,000行を超えるデータセットを扱う場合はGoogle Apps Scriptへのアクセス権
ステップバイステップ
3タブ構成を作成する
データフローをImport、Expenses、Summaryの3つのタブに分けます。当たり前に聞こえるかもしれませんが、経費トラッカーがうまく機能しなくなる原因の多くは、CSVのエクスポートデータを既存の数式の上に直接貼り付けてしまうことです。その瞬間に数式が消え、データは生のまま残り、集計が一切機能しなくなります。
- Import**:データを貼り付けるための生データゾーン。ここには数式を一切置かないこと。CSVデータはここに貼り付けて、そのまま保持します。
- Expenses**:正規化レイヤー。このタブの各行はImportからデータを取得する数式で構成され、クリーニングとカテゴリ分類が行われます。
- Summary**:純粋な集計層。ExpensesタブへのSUMIFSで、カテゴリと月ごとに集計します。
- Rules**:ベンダーのキーワードパターンをカテゴリ名にマッピングする専用タブ。数式ロジックとは独立してメンテナンスします。
Pro Tip
データ → シートと範囲を保護 を使って、Importタブを誤編集から守りましょう。気づかないうちにセルに入力してしまい、貼り付けゾーンが壊れるケースは意外と多いです。動的な列検索機能を構築する
銀行のエクスポート、Stripeの送金データ、会計ツールのダンプは、毎月同じ列順で届くとは限りません。Mercuryでは日付がA列にありますが、お使いの会計ソフトではD列かもしれません。エクスポート設定を変更したり、新しいデータフィールドを追加したりすると、その順序がずれることもあります。
解決策は、位置ではなく名前で列を特定するヘッダー行ルックアップです。Expensesタブに、必要な各フィールドの列インデックスを格納する3つのヘルパーセルを定義します:
=MATCH("Date", Import!$1:$1, 0)
=MATCH("Description", Import!$1:$1, 0)
=MATCH("Amount", Import!$1:$1, 0)
Name Boxでこれらのセルをcol_date、col_desc、col_amountと名前付けします。次にExpensesの各行で、動的にデータを取得します:
=INDEX(Import!$A:$Z, ROW(A2), col_date)
=INDEX(Import!$A:$Z, ROW(A2), col_desc)
=INDEX(Import!$A:$Z, ROW(A2), col_amount)
来月のエクスポートでDescriptionがB列ではなくC列になっていても、もう何も壊れません。ヘッダー行ルックアップが自動的に検出し、すべての下流の数式が自動的に調整されます。
Pro Tip
ソースによって位置が変わる列には、4つ目のMATCHを追加しましょう。「Memo」「Reference」「Counterparty」などは、銀行によって異なる位置に表示されます。ベンダー文字列を正規化する
銀行のエクスポートに含まれる生のベンダー文字列は扱いにくいものです。末尾のスペース、大文字・小文字の混在、OCRフィールドの余分な空白、エンコードの乱れなど。同じベンダーの2件の取引でも、余分なスペースが一つあるだけでSEARCH数式は別々のものとして認識します。
ExpensesタブにVendor_Clean列を追加して、3つの変換を順番に適用します:
=TRIM(UPPER(SUBSTITUTE(C2, " ", " ")))
TRIMは先頭と末尾の空白を除去します。UPPERは大文字・小文字の不一致をなくします。SUBSTITUTEはエクスポートされたメモフィールドに時々現れる二重スペースを一つにまとめます。以降のすべての数式はこの列を参照します。元の生インポートの説明文は別の列に残し、監査証跡として保持します。
日付の正規化にはDate_Clean列を追加します。複数のソースからデータを引き込む場合、MM/DD/YYYY、YYYY-MM-DD、「2026年4月16日」など、混在した日付形式が頻繁に発生します。すべてを標準的な値に変換します:
=IF(ISNUMBER(D2), D2, DATEVALUE(D2))
Pro Tip
Date_Cleanは「標準」ではなく「日付」形式でフォーマットしてください。DATEVALUEは日付書式を適用するまで「46000台」のシリアル番号として表示されます。ベンダールールテーブルを構築する
Rulesタブには、KeywordをA列、CategoryをB列とする2列のテーブルを作成します。新しいベンダーが現れたり、カテゴリを変更したいときに編集するのはここだけです。
| Keyword | Category |
|---|---|
| ZOOM | Software |
| GOOGLE CLOUD | Software |
| AWS | Software |
| NOTION | Software |
| FACEBOOK ADS | Marketing |
| Marketing | |
| STRIPE FEE | Payment Processing |
| GUSTO | Payroll |
| UNITED AIRLINES | Travel |
重要な設計上のルールがいくつかあります。キーワードはベンダー文字列の中で最も特徴的な部分に合わせてください。「GOOGLE」では広すぎて(Google Drive、Google Ads、Google Cloudが同じカテゴリになってしまう)、「GOOGLE CLOUD」や「GOOGLE ADS」のように具体的にしてください。各カテゴリのルール数に人工的な制限はありません——精度を確保するために必要なだけ設定してください。同じベンダーで7種類の文字列バリエーションが生じる決済サービスであれば、7行のキーワード行が必要です。
各カテゴリ内のキーワードは、具体性の高い順に並べてください。分類ツールは最初に一致したものを返すため、テーブルに「FACEBOOK」も「FACEBOOK ADS」も含まれている場合、「FACEBOOK ADS」を先に記載する必要があります。
Pro Tip
3列目にNotes(メモ)列を追加して、そのルールが存在する理由を記録しましょう。6ヶ月後にこのテーブルに戻ってきたとき、「なぜ"AMZN MKTPLACE"がMiscellaneousではなくSoftwareになっているのか」と首をかしげずに済みます。INDEX/MATCH分類式を作成する
この数式は、クリーニングされたベンダー文字列の中に出現するキーワードをRulesテーブルからスキャンします。ExpensesタブにCategory列を追加します:
=IFERROR(INDEX(Rules!$B$2:$B$100, MATCH(TRUE, ISNUMBER(SEARCH(Rules!$A$2:$A$100, E2)), 0)), "Uncategorized")
Ctrl+Shift+Enterで入力して配列数式にします(新しいバージョンのSheetsでは、内側の式をARRAYFORMULAでラップする方法でも可能です)。数式はクリーニングされたベンダー文字列の中に出現する、最初のカテゴリのキーワードを返します。部分一致に対応しているため、「GOOGLE CLOUD STORAGE」は「GOOGLE CLOUD」のルールに正しくヒットします。
"Uncategorized"のフォールバックは見た目以上に重要です。未マッチの行に""を返すと、その取引はSUMIFSの合計から静かに消えてしまいます——SUMIFSは空白セルをスキップするためです。明示的な文字列を使うことで、未マッチの経費がSummaryに独自のカテゴリとして表示され、まだマッピングされていない取引を一目で確認できます。この可視性こそが、Rulesテーブルの更新が必要なタイミングを教えてくれます。
Pro Tip
数式を実行したら、Category列で「Uncategorized」をフィルタリングして金額の降順に並べ替えましょう。未マッチの金額上位の取引が、最初に追加すべきルールを教えてくれます。SUMIFSサマリーグリッドを構築する
Summaryタブではカテゴリと月ごとに合計を集計します。A列にカテゴリを縦に並べ、1行目に月を横に並べたグリッドを作成します(各月の初日を日付として入力します。例:2026-01-01)。グリッドの各セルには以下を入力します:
=SUMIFS(Expenses!$F:$F, Expenses!$G:$G, $A2, Expenses!$H:$H, ">="&B$1, Expenses!$H:$H, "<"&EDATE(B$1, 1))
F列はAmount、G列はCategory、H列はDate_Cleanです。EDATE(B$1, 1)によって、終了日をハードコードせずに日付範囲をカレンダー月に限定します。
下部にTotal行(各月の合計)と、カテゴリごとの予算額をハードコードするBudget行を追加します。予実差異はActualsからBudgetを引くだけです:
=B3 - B2
予実差異セルに条件付き書式を適用します。予算超過は赤、予算内は緑にします。これは便利なチェック機能ですが、最終的なアウトプットではなく、入力レイヤーとして位置づけてください。
Pro Tip
列全体の参照には注意が必要です。Expenses!$F:$Fは実用上問題ありませんが、実際に200行しかないデータに対して100万行をスキャンするのは、大きなスプレッドシートでの再計算に時間がかかります。データ量が確定したらExpenses!$F$2:$F$5000のように範囲を限定してください。SummaryをP&L構造に接続する
これはほとんどの経費トラッカーガイドが省略しているステップです。Step 6のSummaryグリッドは個人の帳簿管理には役立ちますが、財務モデルではありません——ただのフラットなテーブルです。バーンモデルや投資家向けP&Lへの入力として活用するには、Summaryを実績レイヤーとして扱う専用のP&Lタブが必要です。
P&LタブのA列に、実際の損益計算書の並びに合わせた全ライン項目を設定します:
| ライン項目 | 1月 | 2月 | 3月 |
|---|---|---|---|
| 売上高 | |||
| 売上原価 | |||
| — ホスティング・インフラ | |||
| — 決済手数料 | |||
| 売上総利益 | |||
| 営業費用 | |||
| — ソフトウェア・サブスクリプション | |||
| — 給与 | |||
| — マーケティング | |||
| — 出張費 | |||
| 営業費用合計 | |||
| EBITDA |
P&Lの各実績セルは、直接参照またはP&Lライン項目をSummaryカテゴリにマッピングするSUMIFSでSummaryから取得します:
=SUMIFS(Summary!$B:$B, Summary!$A:$A, "Software")
複数のSummary行をP&Lライン項目にまとめる場合(例:「ホスティング・インフラ」がAWS、Google Cloud、Cloudflareの各Summaryを合算する)は、SUMIFS複数式を組み合わせます:
=SUMIFS(Summary!B:B,Summary!A:A,"Software") + SUMIFS(Summary!B:B,Summary!A:A,"Hosting")
これがアーキテクチャです。分類ツールがSummaryを埋め、SummaryはActuals入力レイヤーとなり、P&LタブがモデルになりますRevenue はP&Lに直接入力するか、専用のフィードから引き込みます。この構成により、月次実績がバーンレートやランウェイ計算に自然と流れ込みます——その構造についてはスタートアップのランウェイ計算方法とスタートアップバーンレートダッシュボードをご参照ください。
Pro Tip
P&LのBudget行は、ハードコードした数値ではなく、別の前提条件タブを参照するようにしてください。P&Lの予実差異は=Actuals - Budgetになり、予算を修正する場合も前提条件タブの1セルを変えるだけで済みます。Apps Scriptでデータ取り込みを自動化する
Step 5の配列数式は、再計算のたびにN × Mの比較を実行します——Expensesの全行がRulesテーブルの全行に対してスキャンされます。500行の経費データと50行のRulesテーブルでは、再計算ごとに25,000回のルックアップが発生します。10,000行になると50万回になり、セルを変更するたびにスプレッドシートが数秒間フリーズするのを感じるでしょう。
2,000行を超えるデータセットでは、分類処理をApps Scriptに移してください。ロジックは同じですが、一度だけ実行し、結果を一括で書き込みます:
function categorizeExpenses() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const expenses = ss.getSheetByName('Expenses');
const rules = ss.getSheetByName('Rules');
const expData = expenses.getDataRange().getValues();
const ruleData = rules.getRange(2, 1, rules.getLastRow() - 1, 2).getValues();
const categories = [];
for (let i = 1; i < expData.length; i++) {
const vendor = String(expData[i][4]).toUpperCase(); // Vendor_Clean列
let matched = 'Uncategorized';
for (const [keyword, category] of ruleData) {
if (vendor.includes(String(keyword).toUpperCase())) {
matched = category;
break;
}
}
categories.push([matched]);
}
// 一括書き込み — ループ内での書き込みは絶対にしないこと
expenses.getRange(2, 6, categories.length, 1).setValues(categories);
}
重要なルール:setValue()をループ内で呼び出してはいけません。個別の書き込みのたびにSheets APIへの往復通信が発生します。10,000行でループ内に書き込むと、15分以上かかることがあります。最後のsetValues()による一括書き込みなら、同じ処理が10秒以内に完了します。
Extensions → Apps Script → Triggers でトリガーを設定して、編集時に自動実行させるか、Importタブに変更があったときに起動するよう設定してください。2026年4月時点で、Apps Scriptの実行時間は無料アカウントで6分、Workspaceアカウントで30分が上限です。一括書き込みパターンを使えば、Google Sheetsの最大1,000万セルでも両方の制限内に余裕を持って収まります。
Pro Tip
複数の一括書き込みを連続して行う場合は、setValues()の後にSpreadsheetApp.flush()を追加してください。これがないと、Sheetsが変更をバッファリングして順序がずれて適用されることがあります。まとめ
構築したのはトラッカーではなく、データパイプラインです。Importはどんなエクスポート形式にも対応する貼り付けゾーンです。Expensesが正規化と分類を担います。Summaryが集計します。P&Lが消費します。各レイヤーは一つの役割を担い、次のレイヤーに渡します——だから銀行がCSV形式を変更しても、新しいベンダーカテゴリを追加しても、一箇所を変えるだけで残りが自動的に更新されます。
ほとんどの経費管理が陥りがちな失敗は、Summaryグリッドを最終アウトプットとして扱ってしまうことです。そうではありません。Summaryは実績フィードです。売上と適切なライン項目構造を持つP&LタブにSummaryを接続した瞬間、これは帳簿管理の作業ではなく、財務モデルになります。
ModelMonkeyのプランを選ぶ——Google SheetsとExcelの両方で動作し、1回の会話で分類、数式の接続、P&L構造の設定まで対応できます。
よくある質問
INDEX/MATCH分類式にCtrl+Shift+Enterが必要なのはなぜですか?
この数式はRulesテーブルの全キーワード行を同時にチェックするためISNUMBER(SEARCH(...))を使用しており、配列評価が必要です。Ctrl+Shift+Enter(または明示的なARRAYFORMULAラッパー)なしでは、SheetsはRulesテーブルの最初の行だけを対象にスカラー演算として評価します。実際の症状として:分類ツールがすべてのベンダーに対して最初のカテゴリだけを返す場合、配列数式として入力し忘れています。
数式を何行まで拡張すべきですか?
個人事業主であれば5,000行、複数のソースから取得したり1年分をまとめて処理する場合は20,000行まで拡張してください。SUMIFSとP&Lの数式はいずれにせよ境界付き範囲を使用するため、Expensesの下部にある空行が合計を膨らませることはありません。1,000,000行への拡張は避けてください——大きな数式セットで列全体を参照すると、編集のたびに再計算が遅くなります。
同じベンダーが5種類の異なる文字列で表示される場合はどうすれば良いですか?
5種類のバリエーションをすべてRulesテーブルに個別の行として追加し、それぞれ同じCategoryにマッピングしてください。バリエーションに共通する特徴的な部分文字列がある場合(例:すべてに「STRIPE」が含まれる)、その部分文字列で一つのルールがすべてをカバーします。共通する文字列がなければ、バリエーションごとに1行必要です。決済サービスやPOSシステムはベンダー文字列が一貫しないことが多いため、これは想定内のことです。上流でクリーニングしようとするより、ルールテーブルで対応するほうが堅牢です。
複数の通貨が混在する経費に対応できますか?
数式のロジックは通貨に依存しません——通貨にかかわらずベンダー文字列でカテゴリ分類を行います。複数通貨のトラッキングには、ExpensesにCurrency列と、レポート通貨(例:円)に換算するFX_Rate列を追加してください。SummaryのSUMIFSは生のAmount列ではなく、換算後の列(=Amount * FX_Rate)を参照してください。為替レートはリアルタイムなら=GOOGLEFINANCE("CURRENCY:USDJPY")で取得するか、報告期間に対してハードコードすることもできます。
Rulesテーブルが肥大化しないようにするにはどうすれば良いですか?
四半期ごとに見直してください。Category列で「Uncategorized」をフィルタリングし、まだ分類されていない月間支出が5,000円以上のベンダーにルールを追加してください。使わなくなったベンダーのルールは削除しましょう——不要なルールは配列数式のスキャンを無駄に遅くします。一般的なフリーランス事業のルールテーブルは30〜80行が適切な規模です。200行に近づいてきた場合、重複しているルールや冗長なルールが統合できる可能性があります。