VBAで経費精算表を集計してみよう(総合演習)
ここまでの VBA レッスンで学んだことを全部つないで、動くものを1つ完成させます。新しい構文は出てきません。Sub・Function・ユーザー定義型・配列・For Each・Select Case・With・Range と Cells——すでに練習したものだけで組み立てます。
作るのは経費精算表の集計です。シートから読み取り、費目ごとに合計し、上限を超えた申請を見つけ、承認待ちが長引いているものを知らせます。
このページだけは、実行結果を実際に走らせて得ていません。VBAはExcelの中でしか動かないため、他のカテゴリのようにサーバー側で走らせることができないからです。
載せている「Excelで実行したときの表示」は、コードを読んで書き起こしたものです。金額の合計は計算し直して確かめてありますが、実際にExcelで動かした画面そのものではありません。この点だけ、他のカテゴリと違います。
だからこの演習は「お手本と同じコードを書けたか」を確かめる形にしています。書き写して完成させる体験はできますが、本当に動くところを見るには、ご自分のExcelに貼り付けて実行してください。手順はステップ1で説明します。
シートを壊す操作は1つも書いていません。読み取りと表示だけで、DeleteもClearも使いません。手元のブックで試しても、元のデータは変わりません。
このページの結果は、次の「経費」シートを読んだ場合のものです。同じ表を作れば、同じ結果になります。
A(申請日) B(申請者) C(費目) D(金額) E(承認) 1 申請日 申請者 費目 金額 承認 2 2026/7/28 中島 交通費 12800 3 2026/8/1 田村 会議費 34500 4 2026/8/5 山本 備品 62000 済 5 2026/8/10 佐藤 交通費 8200 済 6 2026/8/12 山本 備品 24900 済 7 2026/8/14 田村 会議費 12200 済 8 2026/8/16 中島 備品 11500 済 9 2026/8/17 佐久間 交通費 6880
E列が空欄なら承認待ち、「済」と入っていれば承認済みという扱いです。1行目は見出しなので読み飛ばします。
完成イメージ
最後まで進むと、こう表示されるところまで作ります。下の実行結果は、このページを作るときに実際にコードを走らせて得たものです。
===== 経費精算の集計 ===== 件数: 8件 上限超過: 田村 会議費 34,500円(上限 30,000円) 上限超過: 山本 備品 62,000円(上限 50,000円) --- 費目別 --- 交通費: 3件 27,880円 会議費: 2件 46,700円 備品: 3件 98,400円 --- 承認待ち --- 中島 7/28 申請から21日 田村 8/1 申請から17日 -------------------------------------- 合計: 172,980円 承認待ち: 3件 / 上限超過: 2件 上限を超えた申請の確認が必要です。
STEP 1Option Explicitとユーザー定義型から始める
🎯 このステップのゴール: 変数の宣言を強制し、経費1件の形を決める
まずOption Explicitです。VBAを書くなら、必ず1行目に書いてください。
これが無いと、変数を宣言せずに使えてしまいます。totalと書くつもりでtotlaと打ってもそのまま通ります。新しい変数が勝手に作られていつまでも0のまま——「合計が合わない」の原因として最も多いのがこれです。
VBEの設定で自動的に入れられます。「ツール」→「オプション」→「変数の宣言を強制する」にチェックを入れると、新しいモジュールに自動で書かれます。
次にデータの形です。経費1件が持つのは「申請日・申請者・費目・金額・承認済みか」の5つ。Private Type Expenseでユーザー定義型を作ります。
5つの変数を別々に持ち回るより、ずっと安全です。「3件目の金額」と「3件目の申請者」がばらばらの配列に入っていると、並べ替えたときに対応が崩れます。1つにまとめておけば、そんなことは起きません。
型の指定にも意味があります。As Longを使い、As Integerは使わないでください。Integerは32767までしか入りません——金額なら簡単に超えます。超えるとオーバーフローで止まります。
VBAで整数を扱うならLong——これは覚えてしまってよい決まりです。
#8/18/2026#というシャープで囲む書き方が日付リテラルです。月/日/年の順——日本の順序ではないので注意してください。
試すには: ExcelでAlt+F11を押してVBEを開き、「挿入」→「標準モジュール」で貼り付けます。実行はF5、結果はCtrl+Gで開くイミディエイトウィンドウに出ます。
申請日: 2026/8/5 申請者: 中島 費目: 交通費 金額: 12,800円 承認: 待ち 基準日: 2026/8/18
STEP 2シートの最終行を求めて読み取る
🎯 このステップのゴール: End(xlUp)で行数を数え、Withで書き方を短くする
シートから読み取ります。まず「何行あるか」を知る必要があります。
ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
読み方は「いちばん下のセルから、上に向かってCtrl+↑を押す」です。手作業でやることを、そのままコードにしています。
ws.Rows.Countと書いているのが要点です。1048576と直接書いてはいけません。古い形式のブック(.xls)では65536行しかないので、直接書くとそちらで落ちます。シートに数えさせれば、どちらでも動きます。
UsedRangeで数えないでください。一度データを入れて消したセルも「使った範囲」に含まれるので、実際より大きな行数が返ることがあります。End(xlUp)のほうが確実です。
ReDim items(1 To lastRow - 1)で配列の大きさを決めます。見出し行を除くので-1です。
1 Toと書いているのは、添字を1から始めるためです。VBAは既定で0から始まりますが、行番号と対応させるなら1からのほうが分かりやすくなります。
読み取りにはWithを使っています。With wsと書けば、中では.Cells(...)と先頭のドットだけで書けます。
短くなるだけではありません。ws.Cellsと毎回書くと、そのたびにシートを探しに行きます。Withなら一度で済むので、行数が多いときに目に見えて速くなります。
.Cells(r, 5).Value = "済"という比較の結果をそのままBooleanに入れているのにも注目してください。Ifを書かずに真偽値が作れます。
読み取った件数: 8件 1件目: 中島 交通費 12,800円 最後: 佐久間
STEP 3Select Caseで費目ごとの上限を決める
🎯 このステップのゴール: 分岐を1か所にまとめ、上限の無い費目も扱う
費目ごとに金額の上限を決めます。
Select Case category——Ifを並べるより、ずっと読みやすくなります。「この1つの値を、何と比べるか」がはっきりするからです。
VBAのSelect CaseにはBreakが要りません。C言語やJavaのように、書き忘れて次の分岐に流れ込む事故が起きない設計です。
Case Else: LimitOf = 0を必ず書いてください。「そのどれでもなかったとき」を受け止める場所です。これが無いと、知らない費目のときLimitOfが0のまま返ります——結果は同じですが、意図してそうしたのかどうかが読む人に伝わりません。
0を「上限なし」の印として使っているのが、この設計の要点です。だから使う側でIf limit > 0 And 金額 > limit Thenと2つの条件を見ています。
この一手間を省くと壊れます。If 金額 > limit Thenだけだと、上限を決めていない費目が全部「超過」になります(0円を超えているので)。
VBAのAndには注意点があります。——短絡評価をしません。If limit > 0 And 何か Thenと書くと、limitが0でも右側が必ず評価されます。他の言語なら左が偽なら右を見ませんが、VBAは両方見ます。
だから右側でエラーが起きうるとき(0で割る、存在しないものを触る)は、Ifを入れ子にしてください。ここでは単純な比較なので問題ありません。
上限を変えたくなったらLimitOfの中だけを直します。判定する側は触りません。
交通費: 上限 20,000円 会議費: 上限 30,000円 備品: 上限 50,000円 その他: 上限なし --- 判定の違い --- 1000円 > 上限0 だけで見ると: 超過 上限ありを確かめると: 範囲内
STEP 4For Eachで費目別に集計する
🎯 このステップのゴール: 配列を回して合計し、DateDiffで日数を数える
費目ごとの合計を出します。外側で費目を回し、内側で全件を見るという二重ループです。
For Each cat In Array("交通費", "会議費", "備品")——Array()でその場で配列が作れます。
catをAs Variantで宣言しているのに注目してください。For Eachで回す変数はVariantでなければなりません。As Stringにするとコンパイルエラーになります——VBAでよく引っかかるところです。
だから文字列として使うときはCStr(cat)で変換します。そのままでも動くことが多いのですが、型をはっきりさせておくほうが安全です。
表示する順番をArray()で自分で決めているのも要点です。データに現れた順でも五十音順でもなく、見せたい順に並べています。
日数の計算はDateDiff("d", 開始, 終了)です。"d"が「日単位で数える」という指定で、"m"なら月、"yyyy"なら年になります。
自分で引き算しないでください。VBAの日付は内部では数値なので終了 - 開始でも日数が出ますが、DateDiffのほうが意図がはっきりします。月や年で数えたくなったときも、第1引数を変えるだけです。
承認済みなら0を返すようにしているのも大事なところです。「待っていない」を0日として扱えば、使う側でIfを書かずに済みます。
LBoundとUBoundで配列の範囲を取ります。0 Toと決めつけないでください——ステップ2で1 Toにしているので、0から回すと1件目で落ちます。
7/28申請・未承認: 21日待ち 8/15申請・未承認: 3日待ち 7/28申請・承認済: 0日待ち 知らせる基準: 14日以上
STEP 5シートが無いときに備える
🎯 このステップのゴール: On Error GoTo で受け止め、すぐ元に戻す
ThisWorkbook.Worksheets("経費")——この名前のシートが無いと、実行時エラーで止まります。
マクロを人に渡すなら、ここを放っておけません。シート名を変えられただけで、意味の分からないエラーダイアログが出ます。
VBAの備え方はOn Error GoTo ラベルです。
On Error GoTo NoSheet
Set ws = ThisWorkbook.Worksheets("経費")
On Error GoTo 0
1行目で「エラーが起きたらNoSheetへ飛べ」と指定し、3行目ですぐ元に戻しています。
このOn Error GoTo 0を省かないでください。これが無いと、その先で起きたエラーも全部NoSheetへ飛びます。「シートが見つかりません」と表示されるのに、本当の原因はまったく別——原因を追う手がかりが消えます。
「囲みたい範囲だけを囲み、すぐ戻す」——これがVBAのエラー処理の基本です。
On Error Resume Nextは、さらに気をつけてください。「エラーを無視して次の行へ進む」という意味で、使いどころを誤ると、間違った結果が出たまま最後まで走ります。使うなら1〜2行を囲んで、直後にOn Error GoTo 0です。
ラベルの前にExit Subを書いているのも大事です。これが無いと、正常に終わったときもエラー処理の中へ流れ込みます。
データが0件のときも見ています。If UBound(items) < LBound(items) Then——空の配列では上限が下限より小さくなるので、これで判定できます。
そのシートは見つかりません。シート名を確認してください。
STEP 6ひとつのSubにまとめて仕上げる
🎯 このステップのゴール: ReportExpensesにまとめ、Formatで金額を整える
最後の仕上げです。ここまでの部品をReportExpensesという1つのSubにまとめます。
金額の表示はFormat(値, "#,##0")です。3桁ごとにカンマが入ります。
#と0の違いを知っておくと役に立ちます。#は「値があれば出す、無ければ出さない」、0は「無くても0を出す」。だから"#,##0"は「3桁区切りで、最低1桁は出す」——0円がきちんと0と表示されます。
"#,###"にすると0円が空欄になります。金額の書式は"#,##0"と覚えてください。
日付はFormat(値, "m/d")で7/28のように短く出しています。
String(38, "-")で区切り線を引きます。「その文字を38個」という意味の関数です。
長い行はアンダースコアで折り返しています。Debug.Print "..." & _——行末の_が「次の行へ続く」という印です。アンダースコアの前に半角スペースが要ります——詰めて書くとエラーになります。
締めはIf overLimit = 0 Thenで分けます。数字を並べて終わりにせず、「それで、どうなの?」に答える——道具として使えるかどうかは、たいていここで決まります。
完成です。ぜひご自分のExcelで動かしてみてください。「経費」という名前のシートを作り、1行目に見出し(申請日・申請者・費目・金額・承認)、2行目から実際のデータを入れます。Alt+F11→標準モジュールに貼り付けてF5、Ctrl+Gで結果が見られます。
ALERT_DAYSを7に変えれば、承認待ちの知らせが増えます。1か所直すだけで判定が変わる——定数に切り出しておいた効果が確かめられます。
===== 経費精算の集計 ===== 件数: 8件 上限超過: 田村 会議費 34,500円(上限 30,000円) 上限超過: 山本 備品 62,000円(上限 50,000円) --- 費目別 --- 交通費: 3件 27,880円 会議費: 2件 46,700円 備品: 3件 98,400円 --- 承認待ち --- 中島 7/28 申請から21日 田村 8/1 申請から17日 -------------------------------------- 合計: 172,980円 承認待ち: 3件 / 上限超過: 2件 上限を超えた申請の確認が必要です。
よくある質問
実行結果は本当にExcelで動かしたものですか?
いいえ。このページの出力だけは、実際に走らせて得たものではありません。
VBAはExcelの中でしか動かないため、他のカテゴリのようにサーバー側で実行して結果を取ることができません。載せている表示はコードを読んで書き起こしたもので、金額の合計は計算し直して確かめてあります。
本当に動くところを見たいなら、ご自分のExcelで実行してください。Alt+F11でVBEを開き、「挿入」→「標準モジュール」に貼り付けてF5。結果はCtrl+Gで開くイミディエイトウィンドウに出ます。
このマクロで、自分のデータが消えることはありますか
ありません。このマクロは読み取りと表示しかしていません。DeleteもClearもセルへの書き込みも1つも使っていません。
ただし自分で書き換えるときは気をつけてください。VBAでシートを変更すると、元に戻す(Ctrl+Z)が効きません。書き込む処理を足すときは、必ずブックの複製で試してから本番に使ってください。
Integer ではなく Long を使うのはなぜですか
Integerは32767までしか入らないからです。
金額なら簡単に超えます。超えると「オーバーフローしました」で止まります——しかも普段は動いていて、大きな金額の申請が来た日にだけ落ちます。
行番号も同じです。Excelは100万行あるので、Dim r As Integerでは足りません。
VBAで整数を扱うならLong。今の環境ではIntegerを使う利点はありません。
On Error GoTo 0 は書かないといけませんか
書いてください。これが無いと、その先で起きたエラーも全部エラー処理へ飛びます。
この演習でいえば、シートの取得だけを囲みたいのに、On Error GoTo 0を省くと集計の途中で起きたエラーまで「シートが見つかりません」と表示されます。原因を追う手がかりが消えてしまいます。
囲みたい範囲だけを囲み、すぐ戻す——これがVBAのエラー処理の基本です。On Error Resume Nextも同じで、使うなら1〜2行を囲んで直後にOn Error GoTo 0です。
🚩 次はこのカテゴリへ
VBAの書き方に慣れたら、VB.NETは驚くほど近く感じられます。SubもFunctionもSelect Caseもほとんど同じ形で書けます。違うのはExcelの外でも動くことと、List(Of T)のような今どきの入れ物が使えることです。VB.NETカテゴリの最終演習では、売上伝票の集計を作ります。