ねらい:Excelでも「数百万行」を“落ちない・待てる・再利用できる”型で扱う
「CSVを開いたら固まった」「10万行超えたあたりからマクロが全然終わらない」
数万行までは“なんとなく書いたVBA”でも動きますが、数十万〜数百万行になると一気に限界が見えてきます。
数百万行対応テンプレのゴールは、こうです。
シートを直接ガチャガチャ触る書き方をやめて、「配列・バッファ・辞書」を前提にした“数百万行用の型”を持つこと。
そして、その型を一度作っておけば、JOIN・集計・差分・クリーニングなど、どの処理にも流用できるようにすることです。
ここから、「なぜ遅くなるのか」「どう書けば速く・安定して動くのか」を、初心者向けにかみ砕いてテンプレ化していきます。
数百万行で“絶対にやってはいけない”書き方
セルを1つずつ触るループ
典型的な遅いコードがこれです。
Dim i As Long
For i = 2 To lastRow
If Cells(i, 1).Value = "A" Then
Cells(i, 2).Value = "OK"
End If
Next i
VB数千行ならまだしも、数十万行〜数百万行になると、
「セルへの読み書き」がボトルネックになって、ほぼ動かなくなります。
理由はシンプルで、
セルを1つ読むたびに「Excel本体とのやり取り」が発生するからです。
これを数十万回・数百万回繰り返すと、VBAの処理ではなく「Excelとの通信」が詰まります。
数百万行対応テンプレでは、「セルを1つずつ触る」ことをやめて、
「一気に配列に読み込んで、配列の中だけで処理して、最後に一気に書き戻す」型に切り替えます。
Select / Activate / Offset 連発
もう一つの典型例が、こういう書き方です。
Range("A2").Select
Do While ActiveCell.Value <> ""
If ActiveCell.Value = "A" Then
ActiveCell.Offset(0, 1).Value = "OK"
End If
ActiveCell.Offset(1, 0).Select
Loop
VBSelect や Activate は、画面上の「選択状態」を変える命令です。
数百万行でこれをやると、画面更新と内部状態の切り替えで、あっという間に死にます。
数百万行対応テンプレでは、「Select/Activate禁止」を前提にします。
必要なのは「どのセルかを指す Range 変数」であって、「画面上で選択されているかどうか」ではありません。
基本方針:配列・辞書・バッファで“Excelの外側”で処理する
一括読み込み → メモリ内で処理 → 一括書き戻し
数百万行対応の基本パターンは、次の三段階です。
シートのデータを、Variant配列に一括で読み込む
配列の中で、ループ・条件分岐・集計・JOINなどを行う
結果を、必要な範囲に一括で書き戻す
このパターンにすると、「Excelとのやり取り」は
読み込み時の1回と、書き戻し時の1回だけになります。
あとはVBAのメモリの中だけで完結するので、桁違いに速くなります。
コアテンプレ:シート→配列→シートの骨格
まずは、どの処理でも使える「基本の骨格」を押さえましょう。
Sub Template_ArrayProcess()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Data")
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Dim src As Variant
src = ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol)).Value ' 一括読み込み
Dim i As Long
Dim rCount As Long
rCount = UBound(src, 1)
' ここから先は「src配列の中だけ」で処理する
For i = 2 To rCount
' src(i, 1) や src(i, 2) を使って処理
Next i
' 必要なら、結果を別配列 dst に作ってから書き戻す
' ws.Range("G1").Resize(UBound(dst, 1), UBound(dst, 2)).Value = dst
End Sub
VBポイントは、Range.Value を配列に受けるとき、
「二次元の Variant 配列」として一気に入る、というところです。
src(行, 列) という形でアクセスできるので、
Cells(i, j).Value の代わりに src(i, j) を使えば、
Excelとの通信なしで高速に処理できます。
コア部品1:数百万行対応の「条件付き書き換え」テンプレ
例題:フラグ列を付ける処理
Data シートに、次のようなデータがあるとします。
A列:顧客コード
B列:売上金額
売上が 100,000 以上なら「優良」、それ以外は「通常」というフラグを C列に付けたい。
これを数十万行〜数百万行でやるときのテンプレです。
配列での実装
Sub FlagCustomerBySales()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Data")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then Exit Sub
Dim src As Variant
src = ws.Range("A1:B" & lastRow).Value ' A,B列を一括読み込み
Dim rCount As Long
rCount = UBound(src, 1)
Dim dst() As Variant
ReDim dst(1 To rCount, 1 To 1) ' C列用の1列配列
Dim i As Long
dst(1, 1) = "フラグ" ' 見出し
For i = 2 To rCount
If IsNumeric(src(i, 2)) Then
If src(i, 2) >= 100000 Then
dst(i, 1) = "優良"
Else
dst(i, 1) = "通常"
End If
Else
dst(i, 1) = ""
End If
Next i
ws.Range("C1").Resize(rCount, 1).Value = dst ' 一括書き戻し
End Sub
VBここでの重要ポイントを整理します。
読み込みは A:B の2列だけに絞る(不要な列は読まない)
結果用の dst 配列を別に用意し、そこにだけ書き込む
最後に C列に一括で書き戻す
このパターンは、「元データは触らず、結果だけ別列に出す」処理にとても向いています。
コア部品2:数百万行対応の「集計」テンプレ(辞書+配列)
例題:顧客別売上集計
Data シートに、次のような明細があるとします。
A列:顧客コード
B列:売上金額
これを「顧客コードごとの合計売上」に集計したい。
ピボットテーブルでもできますが、数百万行でVBAから柔軟に扱いたいときのテンプレです。
辞書を使った集計
Sub AggregateSalesByCustomer()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Data")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then Exit Sub
Dim src As Variant
src = ws.Range("A2:B" & lastRow).Value ' 見出しを除いて読み込み
Dim rCount As Long
rCount = UBound(src, 1)
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long
For i = 1 To rCount
Dim key As String
key = CStr(src(i, 1))
Dim val As Double
If IsNumeric(src(i, 2)) Then
val = CDbl(src(i, 2))
Else
val = 0
End If
If dict.Exists(key) Then
dict(key) = dict(key) + val
Else
dict.Add key, val
End If
Next i
Dim outCount As Long
outCount = dict.Count
Dim dst() As Variant
ReDim dst(0 To outCount, 1 To 2)
dst(0, 1) = "顧客コード"
dst(0, 2) = "合計売上"
Dim idx As Long
idx = 1
Dim k As Variant
For Each k In dict.Keys
dst(idx, 1) = k
dst(idx, 2) = dict(k)
idx = idx + 1
Next k
Dim wsOut As Worksheet
On Error Resume Next
Set wsOut = ThisWorkbook.Worksheets("AggCustomer")
On Error GoTo 0
If wsOut Is Nothing Then
Set wsOut = ThisWorkbook.Worksheets.Add
wsOut.Name = "AggCustomer"
End If
wsOut.Cells.Clear
wsOut.Range("A1").Resize(outCount + 1, 2).Value = dst
End Sub
VBここでの“数百万行対応”的なポイントは、次の通りです。
明細は配列に一括読み込み
集計は Dictionary の中だけで完結
結果は配列に詰めて一括書き戻し
顧客数が数万件程度なら、数百万行の明細でも十分現実的な速度で動きます。
コア部品3:数百万行対応の「フィルタ+抽出」テンプレ(配列フィルタ)
例題:売上が 100,000 以上の行だけを別シートに抽出
Data シートの A:B 列から、「売上が 100,000 以上」の行だけを抜き出して、
HighSales シートに出したい、という例です。
可変長配列での抽出
Sub ExtractHighSales()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Data")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then Exit Sub
Dim src As Variant
src = ws.Range("A1:B" & lastRow).Value ' 見出し込みで読み込み
Dim rCount As Long
rCount = UBound(src, 1)
Dim dst() As Variant
ReDim dst(1 To rCount, 1 To 2) ' 最大行数で確保しておく
dst(1, 1) = src(1, 1)
dst(1, 2) = src(1, 2)
Dim outRow As Long
outRow = 2
Dim i As Long
For i = 2 To rCount
If IsNumeric(src(i, 2)) Then
If src(i, 2) >= 100000 Then
dst(outRow, 1) = src(i, 1)
dst(outRow, 2) = src(i, 2)
outRow = outRow + 1
End If
End If
Next i
Dim wsOut As Worksheet
On Error Resume Next
Set wsOut = ThisWorkbook.Worksheets("HighSales")
On Error GoTo 0
If wsOut Is Nothing Then
Set wsOut = ThisWorkbook.Worksheets.Add
wsOut.Name = "HighSales"
End If
wsOut.Cells.Clear
wsOut.Range("A1").Resize(outRow - 1, 2).Value = dst
End Sub
VBここでは、「最初に最大行数で配列を確保しておき、実際に使った分だけ書き戻す」という型を使っています。
ReDim Preserve をループの中で何度も使うと遅くなるので、
数百万行対応では「最初に大きめに取って、最後に“使った分だけ”出す」方が安定します。
数百万行対応の“お約束”テンプレ(高速化ラッパ+エラー・ログ)
高速化ラッパは必ずセットで
数百万行を扱うときは、画面更新や再計算も大きな負担になります。
なので、処理の前後で必ず「高速化ラッパ」を挟むテンプレを使います。
Sub SpeedOn()
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
End Sub
Sub SpeedOff()
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
VB各メイン処理では、こう使います。
Sub RunHeavyProcess()
Const MODULE_NAME As String = "RunHeavyProcess"
On Error GoTo ErrHandler
SpeedOn
LogStart MODULE_NAME, "重い処理開始"
' 数百万行処理本体
LogEnd MODULE_NAME, "重い処理終了"
SpeedOff
Exit Sub
ErrHandler:
SpeedOff
LogError MODULE_NAME, "MAIN", Err
End Sub
VB「SpeedOn/SpeedOff+ログ+配列処理」のセットが、数百万行対応テンプレの“基本形”になります。
もう一歩踏み込んだ話:メモリと分割処理の考え方
配列のサイズとメモリ
数百万行×数十列を一気に配列に読み込むと、
PCのメモリによっては厳しくなることがあります。
例えば、100万行×10列の配列は、
ざっくり「100万×10×16バイト(Variantの中身+オーバーヘッド)」くらいのオーダーになります。
実際にはもう少し複雑ですが、「数百万行×数十列」を一気に読むのは避けた方が安全です。
現実的な対策としては、次のようなものがあります。
列数を絞る(本当に必要な列だけ読む)
行をチャンク(塊)に分けて処理する
チャンク処理の簡単なテンプレ
例えば、「100万行を20万行ずつ5回に分けて処理する」イメージです。
Sub ProcessInChunks()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Data")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then Exit Sub
Dim chunkSize As Long
chunkSize = 200000
Dim startRow As Long
startRow = 2
Do While startRow <= lastRow
Dim endRow As Long
endRow = startRow + chunkSize - 1
If endRow > lastRow Then endRow = lastRow
Dim src As Variant
src = ws.Range("A" & startRow & ":B" & endRow).Value
' src配列に対して処理
' 必要なら結果を書き戻し
startRow = endRow + 1
Loop
End Sub
VBこの型を使うと、「一度に抱える行数」を制御できるので、
メモリの心配を減らしつつ、数百万行を順番に処理できます。
まとめ:数百万行対応テンプレは「配列・辞書・バッファ前提の書き方」に切り替えること
今日のポイントをぎゅっとまとめると、こうなります。
セルを1つずつ触るループや Select/Activate をやめる。
Range.Value を配列に一括読み込みし、配列の中だけで処理する。
集計やJOINには Dictionary を使い、結果は配列に詰めて一括書き戻しする。
高速化ラッパ(ScreenUpdating/Calculation)とログをセットで使う。
必要に応じて、列数を絞る・チャンク処理で行数を分割する。
この“数百万行用の型”さえ一度身につけてしまえば、
今まで「10万行で限界だったマクロ」が、
「数十万〜数百万行でも現実的な時間で終わるマクロ」に変わっていきます。

