ループ処理 (For / Do)
VBA でプログラムを書く際、「同じ処理を何度も繰り返す」場面は非常に多く発生します。セルの値を1行ずつ処理したり、シートを順番に操作したり、条件を満たすまでデータを探したり——こうした繰り返し処理を実現するのが**ループ(反復処理)**です。
VBA には主に For…Next、For Each…Next、Do While…Loop、Do Until…Loop の4種類のループ構文があります。この記事では、それぞれの構文と使い方、使い分けの基準、そして実務で役立つテクニックまで詳しく解説します。
ループ処理が必要となるシチュエーション
実務では、以下のような場面でループ処理が必要になります:
- データの一括処理: 表の全行に対して同じ計算や書式設定を行う
- シートの順次操作: すべてのシートに同じ処理を適用する
- 条件付き検索: 特定の条件を満たすデータが見つかるまで探す
- データの集計: 売上データを1件ずつ加算して合計を算出する
- ファイルの一括処理: フォルダ内のすべてのファイルに対して処理を行う
For…Next(回数指定ループ)
基本構文
For...Next は、繰り返す回数が事前にわかっている場合に使用するループです。カウンター変数を使って、開始値から終了値まで1ずつ増やしながら処理を繰り返します。
' 構文
For カウンター変数 = 開始値 To 終了値
' 繰り返す処理
Next カウンター変数
基本的な使用例
Sub ForNextBasic()
Dim i As Long
' 1 から 5 まで繰り返す
For i = 1 To 5
Debug.Print "現在の値: " & i
Next i
' => 現在の値: 1
' => 現在の値: 2
' => 現在の値: 3
' => 現在の値: 4
' => 現在の値: 5
End Sub
Next の後のカウンター変数名は省略可能です(Next だけでも動作します)。ただし、ネストしたループでは可読性のために記述することをお勧めします。
セルへの連番入力
Sub FillSerialNumbers()
Dim i As Long
' A1 から A10 まで連番を入力
For i = 1 To 10
Cells(i, 1).Value = i
Next i
End Sub
Step キーワード(増分の指定)
Step を使うと、カウンター変数の増分を変更できます。
Sub ForNextStep()
Dim i As Long
' 2 ずつ増やす(偶数のみ)
Debug.Print "--- 偶数 ---"
For i = 2 To 10 Step 2
Debug.Print i
Next i
' => 2, 4, 6, 8, 10
' 3 ずつ増やす
Debug.Print "--- 3の倍数 ---"
For i = 3 To 15 Step 3
Debug.Print i
Next i
' => 3, 6, 9, 12, 15
End Sub
逆順ループ(Step に負の値)
Step に負の値を指定すると、大きい値から小さい値に向かってループできます。
Sub ForNextReverse()
Dim i As Long
' 10 から 1 まで逆順
For i = 10 To 1 Step -1
Debug.Print i
Next i
' => 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
End Sub
For i = 10 To 1 と書くだけではループは1回も実行されません。既定の Step は 1 なので、10 から 1 に向かって増やすことはできず、即座にループが終了します。逆順にするには必ず Step -1(または負の値)を指定してください。
行を削除する処理では、**下から上に向かって処理する(逆順ループ)**のが鉄則です。上から順に削除すると、行がずれてしまい正しく処理できません。
' ○ 逆順ループで行を削除
For i = lastRow To 1 Step -1
If Cells(i, 1).Value = "" Then
Rows(i).Delete
End If
Next i For Each…Next(コレクションの反復)
基本構文
For Each...Next は、コレクション(集合)内のすべての要素を1つずつ処理するためのループです。シートの一覧、セル範囲、配列などの要素を順番に取り出します。
' 構文
For Each 要素変数 In コレクション
' 各要素に対する処理
Next 要素変数
ワークシートの一括処理
Sub ForEachSheets()
Dim ws As Worksheet
' すべてのシートの名前を出力
For Each ws In ThisWorkbook.Worksheets
Debug.Print ws.Name
Next ws
End Sub
セル範囲の処理
Sub ForEachRange()
Dim cell As Range
' A1:A10 の各セルを処理
For Each cell In Range("A1:A10")
If cell.Value <> "" Then
Debug.Print cell.Address & ": " & cell.Value
End If
Next cell
End Sub
配列の処理
Sub ForEachArray()
Dim fruits As Variant
fruits = Array("りんご", "みかん", "ばなな", "ぶどう")
Dim item As Variant
For Each item In fruits
Debug.Print item
Next item
' => りんご
' => みかん
' => ばなな
' => ぶどう
End Sub
配列を For Each で処理する場合、要素変数は Variant 型で宣言する必要があります。String や Long 型で宣言するとエラーになります。
' ○ 正しい
Dim item As Variant
For Each item In Array("A", "B", "C")
' × エラーになる
Dim item As String
For Each item In Array("A", "B", "C")ただし、Worksheet や Range などのオブジェクトコレクションの場合は、対応するオブジェクト型で宣言できます。
For Next と For Each の比較
| 特徴 | For…Next | For Each…Next |
|---|---|---|
| 繰り返しの基準 | カウンター変数(数値) | コレクションの要素 |
| インデックス操作 | ○ i で自由にアクセス可能 | △ インデックスは直接使えない |
| 逆順処理 | ○ Step -1 で可能 | × 不可 |
| コレクション処理 | △ やや冗長 | ○ 簡潔に書ける |
| 可読性 | 数値処理向き | コレクション処理向き |
Do While…Loop(条件が True の間ループ)
基本構文
Do While...Loop は、条件が True である間、処理を繰り返します。繰り返す回数が事前にわからない場合に使用します。
' 構文(前判定): 条件が False なら1回も実行されない
Do While 条件式
' 繰り返す処理
Loop
' 構文(後判定): 最低1回は実行される
Do
' 繰り返す処理
Loop While 条件式
基本的な使用例
Sub DoWhileBasic()
Dim count As Long
count = 1
Do While count <= 5
Debug.Print "カウント: " & count
count = count + 1
Loop
' => カウント: 1
' => カウント: 2
' => カウント: 3
' => カウント: 4
' => カウント: 5
End Sub
セルの値を順次処理(空白セルまで)
Sub DoWhileCells()
Dim i As Long
i = 1
' A列のセルを空白になるまで処理
Do While Cells(i, 1).Value <> ""
Debug.Print "行 " & i & ": " & Cells(i, 1).Value
i = i + 1
Loop
End Sub
後判定ループ(Do…Loop While)
条件を後ろで判定する形式では、ループ内の処理が最低1回は実行されます。
Sub DoLoopWhile()
Dim answer As String
Do
answer = InputBox("「はい」と入力すると終了します")
Loop While answer <> "はい"
MsgBox "終了しました"
End Sub
前判定(Do While...Loop)と後判定(Do...Loop While)の違いは、条件チェックのタイミングです。ユーザー入力を受け付ける場面など、「まず1回は処理を実行したい」場合には後判定を使います。
Do Until…Loop(条件が True になるまでループ)
基本構文
Do Until...Loop は、条件が True になるまで(つまり条件が False の間)処理を繰り返します。Do While の条件を反転させたものです。
' 構文(前判定)
Do Until 条件式
' 繰り返す処理
Loop
' 構文(後判定)
Do
' 繰り返す処理
Loop Until 条件式
基本的な使用例
Sub DoUntilBasic()
Dim count As Long
count = 1
' count が 6 になるまで繰り返す
Do Until count > 5
Debug.Print "カウント: " & count
count = count + 1
Loop
' => カウント: 1
' => カウント: 2
' => カウント: 3
' => カウント: 4
' => カウント: 5
End Sub
Do While と Do Until の比較
同じ処理を Do While と Do Until で書き比べると、条件式が反転する関係にあることがわかります。
' Do While: 条件が True の間ループ
Do While Cells(i, 1).Value <> ""
i = i + 1
Loop
' Do Until: 条件が True になるまでループ(同じ動作)
Do Until Cells(i, 1).Value = ""
i = i + 1
Loop
Do While と Do Until は機能的に同じことができます。条件式がより自然に読める方を選びましょう。一般的には Do While(〜の間は繰り返す)の方が直感的に理解しやすいとされています。
Do While / Do Until では、ループ内で条件が変化する処理を必ず入れる必要があります。条件が永遠に満たされない(または永遠に満たされたまま)だと、処理が止まらなくなります(無限ループ)。
' × 危険:無限ループ(count が変化しない)
Dim count As Long
count = 1
Do While count <= 5
Debug.Print count
' count = count + 1 を忘れている!
Loop万が一無限ループに入ってしまった場合は、Ctrl + Break(または Esc)キーで強制停止できます。
Exit 文によるループの途中脱出
Exit For
For...Next や For Each...Next のループを途中で抜けるには Exit For を使用します。
Sub ExitForExample()
Dim i As Long
' 特定の値を見つけたらループを抜ける
For i = 1 To 100
If Cells(i, 1).Value = "完了" Then
Debug.Print "「完了」が見つかりました: 行 " & i
Exit For
End If
Next i
' ループ終了後の処理
If i > 100 Then
Debug.Print "「完了」は見つかりませんでした"
End If
End Sub
Exit Do
Do While...Loop や Do Until...Loop のループを途中で抜けるには Exit Do を使用します。
Sub ExitDoExample()
Dim attempt As Long
Dim password As String
attempt = 0
Do While True ' 無限ループ
attempt = attempt + 1
password = InputBox("パスワードを入力してください(試行 " & attempt & "/3)")
If password = "secret" Then
MsgBox "ログイン成功!"
Exit Do
End If
If attempt >= 3 Then
MsgBox "試行回数の上限に達しました"
Exit Do
End If
Loop
End Sub
ループのネスト(入れ子)
ループの中にさらにループを書くことをネスト(入れ子)と呼びます。二次元的なデータ(行と列)を処理する場合に頻繁に使用します。
二重ループの基本
Sub NestedLoopBasic()
Dim row As Long
Dim col As Long
' 5行 × 3列 のセルに値を入力
For row = 1 To 5
For col = 1 To 3
Cells(row, col).Value = "R" & row & "C" & col
Next col
Next row
' => A1="R1C1", B1="R1C2", C1="R1C3"
' => A2="R2C1", B2="R2C2", C2="R2C3"
' => ...
End Sub
九九の表を作成
Sub MultiplicationTable()
Dim i As Long
Dim j As Long
For i = 1 To 9
For j = 1 To 9
Cells(i, j).Value = i * j
Next j
Next i
End Sub
ネストしたループの Exit For
Exit For はネストしたループの場合、最も内側のループのみを抜けます。外側のループも抜けたい場合は、フラグ変数を使うか、GoTo 文を使用します。
Sub NestedExitFor()
Dim row As Long
Dim col As Long
Dim found As Boolean
found = False
For row = 1 To 100
For col = 1 To 10
If Cells(row, col).Value = "エラー" Then
Debug.Print "エラーが見つかりました: " & Cells(row, col).Address
found = True
Exit For ' 内側のループのみ抜ける
End If
Next col
If found Then Exit For ' 外側のループも抜ける
Next row
End Sub
ループのネストが3段階以上になると、コードの可読性が大幅に低下します。深いネストが必要な場合は、内側のループを別のプロシージャに分割することを検討しましょう。
4種類のループの使い分け
比較表
| 特徴 | For…Next | For Each…Next | Do While…Loop | Do Until…Loop |
|---|---|---|---|---|
| 繰り返し条件 | 回数指定 | コレクションの要素数 | 条件が True の間 | 条件が True になるまで |
| 回数がわかる場合 | ◎ 最適 | ○ | △ | △ |
| コレクション処理 | △ | ◎ 最適 | △ | △ |
| 回数が不明な場合 | △ | △ | ◎ 最適 | ◎ 最適 |
| 逆順処理 | ○ | × | ○ | ○ |
| インデックスアクセス | ○ | × | ○ | ○ |
| 無限ループのリスク | なし | なし | あり | あり |
使い分けの基準
For…Next が適しているケース:
- 繰り返す回数が事前にわかっている
- カウンター変数(行番号など)を使う必要がある
- 逆順で処理する必要がある(行の削除など)
For Each…Next が適しているケース:
- ワークシートやセル範囲などのコレクションを処理する
- 配列の全要素を順番に処理する
- インデックスが不要な場合
Do While / Do Until が適しているケース:
- 繰り返す回数が事前にわからない
- 特定の条件を満たすまで(または満たす間)処理を続ける
- ユーザー入力を繰り返し受け付ける
' ○ For...Next: 回数が決まっている
Sub Example_ForNext()
Dim i As Long
For i = 1 To 10
Cells(i, 1).Value = i * 100
Next i
End Sub
' ○ For Each: コレクションの全要素を処理
Sub Example_ForEach()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
ws.Tab.Color = RGB(200, 200, 255)
Next ws
End Sub
' ○ Do While: 終了条件が動的
Sub Example_DoWhile()
Dim i As Long
i = 1
Do While Cells(i, 1).Value <> ""
Cells(i, 2).Value = UCase(Cells(i, 1).Value)
i = i + 1
Loop
End Sub
実践的な活用例
例1:条件に一致するデータの集計
Option Explicit
Sub SumByDepartment()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("売上データ")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
Dim totalSales As Currency
Dim count As Long
totalSales = 0
count = 0
' A列が「営業部」のデータの B列(売上)を合計
Dim i As Long
For i = 2 To lastRow ' 2行目から(ヘッダー除く)
If ws.Cells(i, 1).Value = "営業部" Then
totalSales = totalSales + ws.Cells(i, 2).Value
count = count + 1
End If
Next i
Debug.Print "営業部の売上合計: " & Format(totalSales, "#,##0") & " 円"
Debug.Print "対象件数: " & count & " 件"
Set ws = Nothing
End Sub
例2:すべてのシートに対して処理を実行
Option Explicit
Sub FormatAllSheets()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
' 各シートの1行目をヘッダーとして書式設定
With ws.Rows(1)
.Font.Bold = True
.Font.Size = 12
.Interior.Color = RGB(68, 114, 196)
.Font.Color = RGB(255, 255, 255)
End With
Debug.Print ws.Name & " のヘッダーを書式設定しました"
Next ws
End Sub
例3:重複データの検出
Option Explicit
Sub FindDuplicates()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("データ")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
Dim i As Long
Dim j As Long
Dim duplicateCount As Long
duplicateCount = 0
' 二重ループで重複を検出
For i = 2 To lastRow - 1
For j = i + 1 To lastRow
If ws.Cells(i, 1).Value = ws.Cells(j, 1).Value Then
ws.Cells(j, 1).Interior.Color = RGB(255, 200, 200)
duplicateCount = duplicateCount + 1
End If
Next j
Next i
If duplicateCount > 0 Then
MsgBox duplicateCount & " 件の重複が見つかりました", vbExclamation
Else
MsgBox "重複はありません", vbInformation
End If
Set ws = Nothing
End Sub
例4:入力値の検証ループ
Option Explicit
Sub InputWithValidation()
Dim inputValue As Variant
Dim isValid As Boolean
isValid = False
Do Until isValid
inputValue = InputBox("1〜100 の数値を入力してください")
' キャンセルされた場合
If StrPtr(inputValue) = 0 Then
MsgBox "キャンセルされました"
Exit Sub
End If
' 数値チェック
If Not IsNumeric(inputValue) Then
MsgBox "数値を入力してください", vbExclamation
ElseIf CLng(inputValue) < 1 Or CLng(inputValue) > 100 Then
MsgBox "1〜100 の範囲で入力してください", vbExclamation
Else
isValid = True
End If
Loop
MsgBox "入力された値: " & inputValue, vbInformation
End Sub
パフォーマンスの最適化
ループ処理は大量のデータを扱う場合にパフォーマンスに大きく影響します。以下のテクニックを活用して、処理速度を改善しましょう。
画面更新と自動計算の一時停止
Sub OptimizedLoop()
' パフォーマンス最適化
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Dim i As Long
For i = 1 To 10000
Cells(i, 1).Value = i * 2
Next i
' 設定を戻す
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
配列を使った高速処理
セルを1つずつ読み書きするよりも、配列にまとめて読み込み、処理後に一括で書き戻す方が圧倒的に高速です。
Sub FastLoopWithArray()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("データ")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
' セルの値を配列に一括読み込み
Dim data As Variant
data = ws.Range("A1:B" & lastRow).Value
' 配列上で処理(セルアクセスなし = 高速)
Dim i As Long
For i = 1 To UBound(data, 1)
If data(i, 1) <> "" Then
data(i, 2) = data(i, 1) * 1.1 ' 10%増し
End If
Next i
' 配列の値をセルに一括書き戻し
ws.Range("A1:B" & lastRow).Value = data
Set ws = Nothing
End Sub
1万行以上のデータを処理する場合、セルを1つずつ操作するループと配列を使うループでは、処理速度に数十倍〜数百倍の差が出ることがあります。大量データを扱うループでは積極的に配列を活用しましょう。
注意点とよくある間違い
1. カウンター変数の型は Long を使う
' × Integer は 32,767 までしか扱えない
Dim i As Integer ' 大量データでオーバーフローの危険
' ○ Long を使えば約21億まで扱える
Dim i As Long ' 安全
VBA では行番号を扱うカウンター変数には常に Long 型を使いましょう。Excel のシートは最大 1,048,576 行あるため、Integer 型(最大 32,767)ではオーバーフローする可能性があります。
2. For Each でコレクションを変更しない
For Each のループ中にコレクションの要素を追加・削除すると、予期しない動作やエラーが発生します。
' × 危険:ループ中にシートを削除
Sub DangerousLoop()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If ws.Name Like "temp*" Then
ws.Delete ' ループ中の削除は危険!
End If
Next ws
End Sub
' ○ 安全:逆順の For...Next ループで削除
Sub SafeDeleteLoop()
Dim i As Long
Application.DisplayAlerts = False
For i = ThisWorkbook.Worksheets.Count To 1 Step -1
If ThisWorkbook.Worksheets(i).Name Like "temp*" Then
ThisWorkbook.Worksheets(i).Delete
End If
Next i
Application.DisplayAlerts = True
End Sub
3. Do ループのカウンター更新忘れ
' × 無限ループになる
Sub InfiniteLoop()
Dim i As Long
i = 1
Do While i <= 10
Debug.Print i
' i = i + 1 を忘れている!
Loop
End Sub
' ○ 正しい
Sub CorrectLoop()
Dim i As Long
i = 1
Do While i <= 10
Debug.Print i
i = i + 1 ' カウンターを必ず更新
Loop
End Sub
練習問題
まとめ
- For…Next は繰り返す回数が事前にわかっている場合に使用する。
Stepで増分を変更でき、Step -1で逆順ループが可能 - For Each…Next はコレクション(シート一覧、セル範囲、配列)の全要素を処理する場合に最適。要素変数は
Variantまたはオブジェクト型で宣言する - Do While…Loop は条件が
Trueの間ループを継続する。繰り返し回数が不明な場合に使用する - Do Until…Loop は条件が
Trueになるまでループを継続する。Do Whileと条件が反転の関係にある - Exit For / Exit Do でループを途中で脱出できる
- ネストしたループでは、内側の
Exit Forは最も内側のループのみを抜ける - 行の削除は逆順ループ(
Step -1)で行うのが鉄則 - 大量データの処理では、配列への一括読み込みと画面更新の一時停止で大幅に高速化できる
- カウンター変数には
IntegerではなくLong型を使用する