ねらい:VBAで「バッチ実行」を安全に回し、長時間でも落ちない仕組みを作る
バッチ実行は「人の操作なしに、複数の処理を決まった順でまとめて走らせる」ことです。Excelでも、タスクの並べ方、開始・終了の枠、ログ・進捗、失敗時の復帰、ファイル一括処理、スケジュール実行(OnTime)を組み合わせれば、実務レベルのバッチが構築できます。初心者が貼って動かせるテンプレと、なぜその形が「落ちない・壊れない」かを丁寧に説明します。
重要ポイントの深掘り
バッチは「止め方・戻し方」を先に用意するのが鉄則です。開始で描画・イベント・再計算を止め、終了で必ず元に戻す。各タスクは「独立して失敗・成功を記録」できるように枠を持たせます。進捗は1〜5%刻みで間引き、ログはStart/Finish/Errorを残すと、長時間でも安心して回せます。
バッチの基盤:開始・終了、ログ、進捗、失敗時復帰の枠
開始・終了の共通枠を必ず通す
' ModApp.bas
Option Explicit
Public Sub AppEnter(Optional ByVal status As String = "")
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
If Len(status) > 0 Then Application.StatusBar = status
End Sub
Public Sub AppLeave()
Application.StatusBar = False
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
VBログの最小実装で「何がいつ起きたか」を記録する
' ModLog.bas
Option Explicit
Public Sub LogInfo(ByVal action As String, ByVal detail As String)
WriteLog "INFO", action, detail
End Sub
Public Sub LogError(ByVal action As String, ByVal detail As String)
WriteLog "ERROR", action, detail
End Sub
Private Sub WriteLog(ByVal level As String, ByVal action As String, ByVal detail As String)
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets("Log")
If ws Is Nothing Then Set ws = ThisWorkbook.Worksheets.Add: ws.Name = "Log"
On Error GoTo 0
Dim r As Long: r = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1
ws.Cells(r, 1).Value = Format(Now, "yyyy-mm-dd HH:NN:SS")
ws.Cells(r, 2).Value = level
ws.Cells(r, 3).Value = action
ws.Cells(r, 4).Value = detail
End Sub
VB進捗は1〜5%刻みでUI更新を間引く
' ModProgress.bas
Option Explicit
Public Sub TickProgress(ByVal cur As Long, ByVal total As Long, Optional ByVal label As String = "進捗")
If total <= 0 Then Exit Sub
Dim stepN As Long: stepN = Application.WorksheetFunction.Max(1, total \ 100)
If cur Mod stepN = 0 Then
Application.StatusBar = label & " " & Format(cur / total, "0%") & " (" & cur & "/" & total & ")"
DoEvents
End If
End Sub
VBタスクを並べて一括実行するバッチランナー
タスクをSubとして登録し、順に走らせる
' ModBatchRunner.bas
Option Explicit
Public Sub Run_Batch()
On Error GoTo EH
AppEnter "バッチ開始"
LogInfo "Batch", "Start"
RunTask "Task_ImportCsv"
RunTask "Task_NormalizeData"
RunTask "Task_Aggregate"
RunTask "Task_ExportReport"
LogInfo "Batch", "Finish"
AppLeave
MsgBox "バッチ完了", vbInformation
Exit Sub
EH:
LogError "Batch", Err.Number & " - " & Err.Description
AppLeave
MsgBox "バッチ失敗: " & Err.Description, vbExclamation
End Sub
Private Sub RunTask(ByVal taskName As String)
On Error GoTo EH
LogInfo taskName, "Start"
Application.Run taskName
LogInfo taskName, "Finish"
Exit Sub
EH:
LogError taskName, Err.Number & " - " & Err.Description
' 続行/停止の方針に応じて選ぶ(ここでは続行)
End Sub
VB例題タスクの雛形(貼って差し替え)
' ModTasks.bas
Option Explicit
Public Sub Task_ImportCsv()
' CSVを読み取り、Inputシートへ書き出す
Dim path As String: path = ThisWorkbook.Path & "\input.csv"
Dim arr As Variant: arr = ReadCsvToArray(path)
Worksheets("Input").Range("A1").Resize(UBound(arr, 1), UBound(arr, 2)).Value = arr
End Sub
Public Sub Task_NormalizeData()
' トリム・型整形など
Dim arr As Variant: arr = Worksheets("Input").Range("A1").CurrentRegion.Value
Dim r As Long, c As Long
For r = 2 To UBound(arr, 1)
For c = 1 To UBound(arr, 2)
arr(r, c) = Trim$(CStr(arr(r, c)))
Next
Next
Worksheets("Input").Range("A1").Resize(UBound(arr, 1), UBound(arr, 2)).Value = arr
End Sub
Public Sub Task_Aggregate()
' 合計をSummaryへ
Dim arr As Variant: arr = Worksheets("Input").Range("A1").CurrentRegion.Value
Dim idx As Long: idx = IndexByHeader(arr, "Amount")
Dim s As Double, r As Long
For r = 2 To UBound(arr, 1)
s = s + CDbl(arr(r, idx))
TickProgress r, UBound(arr, 1), "集計"
Next
Worksheets("Summary").Range("B2").Value = s
Application.StatusBar = False
End Sub
Public Sub Task_ExportReport()
' OutputをCSVへ保存
Dim arr As Variant: arr = Worksheets("Output").Range("A1").CurrentRegion.Value
WriteArrayToCsv ThisWorkbook.Path & "\report.csv", arr
End Sub
VB' ModCsvUtil.bas(簡易CSV I/O)
Option Explicit
Public Sub WriteArrayToCsv(ByVal path As String, ByVal data As Variant)
Dim st As Object: Set st = CreateObject("ADODB.Stream")
st.Type = 2: st.Charset = "UTF-8": st.Open
Dim r As Long, c As Long, line As String
For r = 1 To UBound(data, 1)
line = ""
For c = 1 To UBound(data, 2)
Dim s As String: s = Replace(CStr(data(r, c)), """", """""")
line = line & IIf(c > 1, ",", "") & """" & s & """"
Next
st.WriteText line & vbCrLf
Next
st.SaveToFile path, 2
st.Close: Set st = Nothing
End Sub
Public Function ReadCsvToArray(ByVal path As String) As Variant
Dim st As Object: Set st = CreateObject("ADODB.Stream")
st.Type = 2: st.Charset = "UTF-8": st.Open: st.LoadFromFile path
Dim text As String: text = st.ReadText: st.Close: Set st = Nothing
Dim lines() As String: lines = Split(text, vbCrLf)
If UBound(lines) < 0 Then Exit Function
Dim head() As String: head = ParseCsvLine(lines(0))
Dim cols As Long: cols = UBound(head) + 1
Dim arr() As Variant: ReDim arr(1 To UBound(lines) + 1, 1 To cols)
Dim c As Long: For c = 1 To cols: arr(1, c) = head(c - 1): Next
Dim r As Long
For r = 2 To UBound(lines) + 1
If Len(lines(r - 1)) = 0 Then Exit For
Dim rec() As String: rec = ParseCsvLine(lines(r - 1))
For c = 1 To cols: arr(r, c) = IIf(c - 1 <= UBound(rec), rec(c - 1), ""): Next
Next
ReadCsvToArray = arr
End Function
Private Function ParseCsvLine(ByVal line As String) As String()
Dim res() As String, buf As String, i As Long, inQ As Boolean
ReDim res(0 To 0)
For i = 1 To Len(line)
Dim ch As String: ch = Mid$(line, i, 1)
If ch = """" Then
If inQ And i < Len(line) And Mid$(line, i + 1, 1) = """" Then buf = buf & """": i = i + 1 Else inQ = Not inQ
ElseIf ch = "," And Not inQ Then res(UBound(res)) = buf: buf = "": ReDim Preserve res(0 To UBound(res) + 1)
Else buf = buf & ch
End If
Next
res(UBound(res)) = buf
ParseCsvLine = res
End Function
Public Function IndexByHeader(ByVal data As Variant, ByVal name As String) As Long
Dim j As Long
For j = 1 To UBound(data, 2)
If StrComp(CStr(data(1, j)), name, vbTextCompare) = 0 Then IndexByHeader = j: Exit Function
Next
Err.Raise 9100, , "ヘッダーがありません: " & name
End Function
VBフォルダ内ファイルをバッチ処理するテンプレート
1ファイルずつ読み、失敗も記録して止めない設計
' ModFolderBatch.bas
Option Explicit
Public Sub Run_FolderBatch()
On Error GoTo EH
AppEnter "フォルダ一括"
LogInfo "FolderBatch", "Start"
Dim fso As Object: Set fso = CreateObject("Scripting.FileSystemObject")
Dim folder As Object: Set folder = fso.GetFolder(ThisWorkbook.Path & "\in")
Dim file As Object
Dim count As Long: count = 0
For Each file In folder.Files
If LCase$(fso.GetExtensionName(file.Name)) = "csv" Then
ProcessCsvFile file.Path, count
End If
Next
LogInfo "FolderBatch", "Finish count=" & count
AppLeave
MsgBox "処理完了: " & count & "件", vbInformation
Exit Sub
EH:
LogError "FolderBatch", Err.Number & " - " & Err.Description
AppLeave
MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
Private Sub ProcessCsvFile(ByVal path As String, ByRef count As Long)
On Error GoTo EH
LogInfo "File", "Start " & path
Dim arr As Variant: arr = ReadCsvToArray(path)
' ここで正規化や集計などを実施
Worksheets("Staging").Range("A1").Resize(UBound(arr, 1), UBound(arr, 2)).Value = arr
LogInfo "File", "Finish " & path
count = count + 1
Exit Sub
EH:
LogError "File", "Error " & path & " - " & Err.Description
End Sub
VB重要ポイントの深掘り
「1件失敗しても止めない」がバッチの基本です。各ファイルを独立してTry/Logできる構造にすると、全体の完了率が上がります。重い処理は配列I/Oでまとめて行い、進捗表示は件数基準で間引きます。
スケジュール実行:OnTimeで時刻予約し、自動バッチを回す
指定時刻に実行し、完了後に次回を予約する
' ModScheduler.bas
Option Explicit
Private gNext As Date
Public Sub ScheduleDaily(ByVal hhmm As String)
' 例: "02:00:00"(午前2時に毎日)
Dim today As Date: today = Date + TimeValue(hhmm)
If Now > today Then gNext = today + 1 Else gNext = today
Application.OnTime gNext, "Run_Batch", , True
MsgBox "次回実行予約: " & Format(gNext, "yyyy-mm-dd HH:NN:SS")
End Sub
Public Sub CancelSchedule()
On Error Resume Next
Application.OnTime gNext, "Run_Batch", , False
On Error GoTo 0
End Sub
VB重要ポイントの深掘り
OnTimeは「Excelが開いている間」だけ有効です。夜間バッチを狙うなら、PCとExcelを起動状態にしておく運用が必要です。完了後に次回を再予約する形や、起動時(Workbook_Open)に予約し直す形を併用すると安定します。
失敗対策:リトライ、バックアップ、排他ロック
保存や外部I/Oのリトライを入れて詰まりを回避する
' ModRetry.bas
Option Explicit
Public Function SaveCopyWithRetry(ByVal wb As Workbook, ByVal path As String, ByVal maxTry As Long) As Boolean
Dim i As Long
For i = 1 To maxTry
On Error Resume Next
wb.SaveCopyAs path
If Err.Number = 0 Then SaveCopyWithRetry = True: Exit Function
Err.Clear: Application.Wait Now + TimeValue("0:00:01")
On Error GoTo 0
Next
End Function
VB実行中フラグで二重起動を防ぐ
' ModLock.bas
Option Explicit
Public Function TryLock() As Boolean
On Error Resume Next
ThisWorkbook.Names("nm_Lock").RefersToRange.Value = Environ$("UserName") & "@" & Format(Now, "yyyy-mm-dd HH:NN:SS")
TryLock = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
Public Sub Unlock()
On Error Resume Next
ThisWorkbook.Names("nm_Lock").RefersToRange.Value = ""
On Error GoTo 0
End Sub
' 入口で使用
Public Sub Run_Batch_Safe()
If Not TryLock() Then MsgBox "他のバッチが実行中です。", vbExclamation: Exit Sub
On Error GoTo EH
Run_Batch
Unlock
Exit Sub
EH:
Unlock
End Sub
VB重要ポイントの深掘り
バッチは「長時間・大量I/O」を伴います。外部保存の失敗は一定確率で起きるため、リトライ戦略が効きます。二重起動は破壊的な事故に直結するので、TryLock/Unlockを入口へ必ず入れます。失敗時でもUnlockが走るよう、Finally相当の構造にします。
チャンク処理:巨大データを分割してピーク負荷を下げる
行チャンクで読み・処理・書き戻し・解放を繰り返す
' ModChunkBatch.bas
Option Explicit
Public Sub Run_ChunkAggregate()
On Error GoTo EH
AppEnter "チャンク集計"
Dim ws As Worksheet: Set ws = Worksheets("Input")
Dim last As Long: last = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim chunk As Long: chunk = 5000
Dim startRow As Long: startRow = 2
Dim total As Double
Do While startRow <= last
Dim endRow As Long: endRow = WorksheetFunction.Min(startRow + chunk - 1, last)
Dim arr As Variant: arr = ws.Range(ws.Cells(startRow, "A"), ws.Cells(endRow, "F")).Value
Dim i As Long, idx As Long: idx = 4 ' 例:AmountがD列
For i = 1 To UBound(arr, 1): total = total + CDbl(arr(i, idx)): Next
TickProgress endRow, last, "チャンク"
Erase arr
startRow = endRow + 1
Loop
Worksheets("Summary").Range("B2").Value = total
Application.StatusBar = False
AppLeave
MsgBox "合計: " & Format(total, "#,##0")
Exit Sub
EH:
AppLeave
MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
VB重要ポイントの深掘り
巨大配列を一度に載せず、数千行単位で分割するとピークメモリと処理時間が安定します。各チャンクで必ずEraseを挟み、UI応答のための進捗は間引いて呼びます。必要に応じてOnTimeで次チャンクを予約すると、さらにフリーズしにくくなります。
例題の通し方と確認ポイント
最小セットで「一括→ログ→進捗→CSV」まで回す
Run_Batch を実行して、LogシートにStart/Finish/Errorが記録されるか、StatusBarに進捗が出るか、report.csvが生成されるかを確認します。タスクを1つずつ止め、単体でも動くか確かめると原因切り分けが容易です。
フォルダ一括とチャンク集計の組み合わせで現場データに耐える
Run_FolderBatch と Run_ChunkAggregate を試し、ファイル数や行数が増えても「止まらず・落ちず・終わる」ことを確認します。時間がかかる場合は、進捗ラベルを適切にしてUI安心感を確保します。
まとめ:バッチは「枠・ログ・進捗・失敗許容」で安定運用になる
開始・終了の枠で戻せる安心、ログで辿れる安心、進捗で待てる安心、失敗時も続行できる安心——この4つが揃えば、Excelでも立派なバッチ運用が可能です。
