🎯 最終演習

VBAで経費精算表を集計してみよう(総合演習)

ここまでの VBA レッスンで学んだことを全部つないで、動くものを1つ完成させます。新しい構文は出てきません。Sub・Function・ユーザー定義型・配列・For Each・Select Case・With・Range と Cells——すでに練習したものだけで組み立てます。

作るのは経費精算表の集計です。シートから読み取り、費目ごとに合計し、上限を超えた申請を見つけ、承認待ちが長引いているものを知らせます。

このページだけは、実行結果を実際に走らせて得ていません。VBAはExcelの中でしか動かないため、他のカテゴリのようにサーバー側で走らせることができないからです。

載せている「Excelで実行したときの表示」は、コードを読んで書き起こしたものです。金額の合計は計算し直して確かめてありますが、実際にExcelで動かした画面そのものではありません。この点だけ、他のカテゴリと違います。

だからこの演習は「お手本と同じコードを書けたか」を確かめる形にしています。書き写して完成させる体験はできますが、本当に動くところを見るには、ご自分のExcelに貼り付けて実行してください。手順はステップ1で説明します。

シートを壊す操作は1つも書いていません。読み取りと表示だけで、DeleteClearも使いません。手元のブックで試しても、元のデータは変わりません。

このページの結果は、次の「経費」シートを読んだ場合のものです。同じ表を作れば、同じ結果になります。

   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は使わないでください。Integer32767までしか入りません——金額なら簡単に超えます。超えるとオーバーフローで止まります

VBAで整数を扱うならLong——これは覚えてしまってよい決まりです。

#8/18/2026#というシャープで囲む書き方が日付リテラルです。月/日/年の順——日本の順序ではないので注意してください。

試すには: ExcelでAlt+F11を押してVBEを開き、「挿入」→「標準モジュール」で貼り付けます。実行はF5、結果はCtrl+Gで開くイミディエイトウィンドウに出ます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
Excelで実行したときの表示
申請日: 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を書かずに真偽値が作れます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
Excelで実行したときの表示
読み取った件数: 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 Then2つの条件を見ています

この一手間を省くと壊れます。If 金額 > limit Thenだけだと、上限を決めていない費目が全部「超過」になります(0円を超えているので)。

VBAのAndには注意点があります。——短絡評価をしませんIf limit > 0 And 何か Thenと書くと、limitが0でも右側が必ず評価されます。他の言語なら左が偽なら右を見ませんが、VBAは両方見ます。

だから右側でエラーが起きうるとき(0で割る、存在しないものを触る)は、Ifを入れ子にしてください。ここでは単純な比較なので問題ありません。

上限を変えたくなったらLimitOfの中だけを直します。判定する側は触りません。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
Excelで実行したときの表示
交通費: 上限 20,000円
会議費: 上限 30,000円
備品: 上限 50,000円
その他: 上限なし
--- 判定の違い ---
1000円 > 上限0 だけで見ると: 超過
上限ありを確かめると: 範囲内

STEP 4For Eachで費目別に集計する

🎯 このステップのゴール: 配列を回して合計し、DateDiffで日数を数える

費目ごとの合計を出します。外側で費目を回し、内側で全件を見るという二重ループです。

For Each cat In Array("交通費", "会議費", "備品")——Array()でその場で配列が作れます

catAs Variantで宣言しているのに注目してください。For Eachで回す変数はVariantでなければなりませんAs Stringにするとコンパイルエラーになります——VBAでよく引っかかるところです。

だから文字列として使うときはCStr(cat)で変換します。そのままでも動くことが多いのですが、型をはっきりさせておくほうが安全です。

表示する順番をArray()で自分で決めているのも要点です。データに現れた順でも五十音順でもなく、見せたい順に並べています。

日数の計算はDateDiff("d", 開始, 終了)です。"d"が「日単位で数える」という指定で、"m"なら月、"yyyy"なら年になります。

自分で引き算しないでください。VBAの日付は内部では数値なので終了 - 開始でも日数が出ますが、DateDiffのほうが意図がはっきりします。月や年で数えたくなったときも、第1引数を変えるだけです。

承認済みなら0を返すようにしているのも大事なところです。「待っていない」を0日として扱えば、使う側でIfを書かずに済みます

LBoundUBound配列の範囲を取ります。0 Toと決めつけないでください——ステップ2で1 Toにしているので、0から回すと1件目で落ちます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
Excelで実行したときの表示
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——空の配列では上限が下限より小さくなるので、これで判定できます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
Excelで実行したときの表示
そのシートは見つかりません。シート名を確認してください。

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→標準モジュールに貼り付けてF5Ctrl+Gで結果が見られます。

ALERT_DAYS7に変えれば、承認待ちの知らせが増えます。1か所直すだけで判定が変わる——定数に切り出しておいた効果が確かめられます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
Excelで実行したときの表示
===== 経費精算の集計 =====
件数: 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で開くイミディエイトウィンドウに出ます。

このマクロで、自分のデータが消えることはありますか

ありません。このマクロは読み取りと表示しかしていませんDeleteClearもセルへの書き込みも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は驚くほど近く感じられますSubFunctionSelect Caseもほとんど同じ形で書けます。違うのはExcelの外でも動くことと、List(Of T)のような今どきの入れ物が使えることです。VB.NETカテゴリの最終演習では、売上伝票の集計を作ります。

VB.NETのレッスンへ進む
広告スペース(バナー)
広告スペース(記事内)