次の DEMO を見にいく
Excel

Excel VBAでSumIfが遅い…を解決!大量データをサクサク計算する方法

k.w
\お買い物マラソン開催中/
スポンサーリンク

問題の整理:SumIfが遅い理由をやさしく理解

大量データで SumIf(SUMIF/SUMIFS)を使うと、計算やマクロの実行が遅くなります。理由は主に3つです。範囲が広いほど比較回数が増えること、同じ条件で何度も同じ検索をやり直していること、そしてシート上のセルに何度もアクセスして往復が発生することです。これらは少しずつ積み重なり、行数が増えるほど時間が急に伸びます。

また、数値と文字列の「見た目は同じでも型が違う」ケースも、内部で変換が走って遅くなることがあります。さらに、揮発性関数や再計算の設定が影響する場合もあります。まずは「どこで時間がかかっているか」を測り、比較できる土台を作ることが大切です。

この記事では、まず再現できるサンプルを用意し、素直な実装のベースラインを確認します。そのうえで、範囲を絞る、配列で一括読み込み、アルゴリズムの見直し、Dictionary によるグループ集計、そして数式側(LAMBDA)での代替まで、段階的に改善していきます。

なぜ件数が増えると急に遅くなるの?

比較や参照の回数は行数に比例して増えます。さらに同じ条件で繰り返し検索すると、回数が二重三重にふくらみます。これが「少し増えると急に遅い」の正体です。

サンプルデータと再現手順(計測の土台)

改善を比べるには、同じ条件で測ることが大切です。ここでは、10万行規模でも縮小できるように、列の構成と測定の手順をそろえます。列は「日付」「カテゴリ」「金額」「メモ」の4列を想定します。カテゴリごとに金額を合計するケースを例にします。

計測は Timer 関数で開始と終了の時刻を取り、差を秒で出します。初回実行はキャッシュが効かず遅いことがあるため、2回目の実行時間も記録し、双方をメモします。

“`vba

‘ 計測用の小さなヘルパー

Private Function Sec() As Double

    Sec = Timer

End Function

Private Sub LogTime(tag As String, ByRef t As Double)

    Debug.Print tag & “: ” & Format(Timer – t, “0.000”) & “s”

    t = Timer

End Sub

“`

乱数でカテゴリ(例: A〜E)と金額を入れたシートを用意し、各手法の時間を測ります。すべて同じブック、同じデータで比較します。

初回だけ遅い影響はどう切り分ける?

同じコードを続けて2回以上実行し、1回目と2回目の時間を分けて記録します。比較には2回目以降の値を使うと傾向が見えやすくなります。

素直なVBA(WorksheetFunction/Evaluate)の実装と課題

まずは分かりやすいベースラインです。WorksheetFunction.SumIf をループの中で何度も呼ぶと、式の評価とシート参照が繰り返されます。

“`vba

Sub Baseline_SumIf()

    Dim t As Double: t = Timer

    Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets(“Data”)

    Dim lastRow As Long: lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    Dim i As Long, cat As String, val As Double

    For i = 2 To lastRow

        cat = ws.Cells(i, 2).Value ‘カテゴリ

        val = Application.WorksheetFunction.SumIf(ws.Range(“B2:B” & lastRow), cat, ws.Range(“C2:C” & lastRow))

        ws.Cells(i, 5).Value = val ‘結果をE列に出力

    Next

    Debug.Print “Baseline_SumIf:”, Format(Timer – t, “0.000s”)

End Sub

“`

Evaluate で同等の式を評価する方法もありますが、結局は毎回同じ範囲に対して評価が走ります。どちらも「何度も同じことをしている」点がボトルネックです。

WorksheetFunction と Evaluate はどちらが速い?

環境や式の内容で差が変わります。重要なのは「呼ぶ回数を減らす」ことで、どちらを選ぶかよりも再評価の回数のほうが影響が大きいです。

範囲を絞る:最小必要範囲の特定

まず効くのは、対象範囲を必要最小限にすることです。A:A のような全列指定は簡単ですが、無駄が増えます。最終行を取得して、実データのある範囲だけを参照します。

“`vba

Function LastRow(ws As Worksheet, col As Long) As Long

    LastRow = ws.Cells(ws.Rows.Count, col).End(xlUp).Row

End Function

Sub NarrowRange()

    Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets(“Data”)

    Dim lr As Long: lr = LastRow(ws, 2)

    Dim rngCat As Range, rngVal As Range

    Set rngCat = ws.Range(“B2:B” & lr)

    Set rngVal = ws.Range(“C2:C” & lr)

    ‘ ここから先の手法は rngCat / rngVal を使う

End Sub

“`

名前付き範囲を使って、更新のたびに端点を再設定する方法もあります。まずは「広すぎる範囲」を避けるだけで、目に見える改善になることが多いです。

全列指定と必要範囲指定の差は?

行数が多いほど差が広がります。全列指定は空セルも含むため、比較や走査に余計な時間がかかります。

配列一括読み込み:Variant配列+一次ループ

次は、Range をそのまま何度も読むのをやめて、配列に一度だけ読み込みます。シートとVBAの間の行き来(COMの呼び出し)を減らすことで、大きく短縮できます。

“`vba

Sub ArrayScan()

    Dim t As Double: t = Timer

    Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets(“Data”)

    Dim lr As Long: lr = ws.Cells(ws.Rows.Count, 2).End(xlUp).Row

    Dim cats As Variant, vals As Variant

    cats = ws.Range(“B2:B” & lr).Value2

    vals = ws.Range(“C2:C” & lr).Value2

    Dim i As Long

    Dim result() As Double

    ReDim result(1 To UBound(cats, 1))

    ‘ 例:各行のカテゴリと一致する値の合計を逐次更新

    ‘ (実際には後述のDictionaryのほうが効率的)

    For i = 1 To UBound(cats, 1)

        If IsNumeric(vals(i, 1)) Then

            result(i) = result(i) + CDbl(vals(i, 1))

        End If

    Next

    Debug.Print “ArrayScan:”, Format(Timer – t, “0.000s”)

End Sub

“`

配列化すると、文字列の数値や空白の扱いなど、型の差が表に出ます。比較前に正規化(Trim、CStr、CDbl など)を行うと、結果が安定します。

配列でメモリ不足にならない?

行×列の要素数に比例してメモリを使います。数十万行でも通常は問題ありませんが、列が多い場合は必要な列だけに絞る、分割して読み込むなどで調整できます。

アルゴリズム再設計:同条件の再計算をゼロに

SumIf を何度も呼ぶ代わりに、「条件→合計」を一度だけ作ればよい場面が多いです。たとえば、カテゴリごとに合計を作っておき、必要なときにその表から取り出します。これで同じ計算を繰り返す無駄がなくなります。

ソートしてからまとめて走査する、あるいは条件ごとにバケット(箱)を用意して数字を入れていく、といった設計も効果的です。二重ループでも、ループの内側で重い操作(シート参照、式評価)をしないなら、十分実用的な速度になります。

“`vba

‘ キー(カテゴリ)ごとに合計をキャッシュする方針

Private Type SumCache

    Key As String

    Total As Double

End Type

“`

二重ループは常に悪い?

内側で重い処理をしなければ、二重ループでも速いことがあります。重要なのは「重い処理の回数」と「データのまとまり方」です。

Dictionaryでグループ集計:SumIf多発の置換

Scripting.Dictionary を使うと、「カテゴリ→合計」というマップを一度で作れます。キーが存在しなければ0で初期化し、値を足していきます。最後に結果をまとめて書き戻すことで、シート往復と重複計算を減らせます。

“`vba

Sub DictGroupSum()

    Dim t As Double: t = Timer

    Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets(“Data”)

    Dim lr As Long: lr = ws.Cells(ws.Rows.Count, 2).End(xlUp).Row

    Dim cats As Variant, vals As Variant

    cats = ws.Range(“B2:B” & lr).Value2

    vals = ws.Range(“C2:C” & lr).Value2

    Dim d As Object: Set d = CreateObject(“Scripting.Dictionary”)

    Dim i As Long, k As String, v As Double

    For i = 1 To UBound(cats, 1)

        k = CStr(cats(i, 1))

        If IsNumeric(vals(i, 1)) Then v = CDbl(vals(i, 1)) Else v = 0

        If d.Exists(k) Then

            d(k) = d(k) + v

        Else

            d.Add k, v

        End If

    Next

    ‘ 結果の書き戻し例(カテゴリ一覧を作ってからマッチさせるなど)

    ‘ ここでは簡単にイミディエイトに出力

    Dim key As Variant

    For Each key In d.Keys

        Debug.Print key, d(key)

    Next

    Debug.Print “DictGroupSum:”, Format(Timer – t, “0.000s”)

End Sub

“`

早期バインドを使う場合は、参照設定で「Microsoft Scripting Runtime」を有効にし、Dim d As New Scripting.Dictionary と書きます。遅延バインド(CreateObject)なら参照設定は不要です。

Dictionary と Collection の違いは?

Dictionary はキーの有無を調べやすく、キー一覧や存在確認が簡単です。Collection はキー検索が弱く、集計用途では Dictionary のほうが扱いやすいです。

LAMBDA+ヘルパー関数:数式だけで再利用

最近の Excel では、LAMBDA や動的配列関数でグループ集計を表計算側だけで行えます。VBA を書けないチームでも共有しやすい利点があります。

例として、カテゴリ別の合計を作るヘルパーを組み合わせます。ユニークなカテゴリ一覧を出し、その横に合計列を並べます。

“`

=UNIQUE(B2:B100000)

=MAP(G2:G?,LAMBDA(k,SUMIF(B2:B100000,k,C2:C100000)))

“`

さらに LAMBDA 名を付けておけば、同じブック内で再利用できます。ただし、対象バージョンや関数の互換性に注意します。Power Query で同じことを作る選択肢もあり、更新や整形が中心ならそちらのほうが向く場合もあります。

Power Query とどちらを選ぶ?

頻繁に構造変換や結合、クレンジングが必要なら Power Query が便利です。数式でシート上に結果を直接出したい、学習コストを下げたい場合は LAMBDA+動的配列が手軽です。

まとめ&チェックリスト:実装順と落とし穴

まずはシンプルな順番で試します。

  • 範囲を絞る(全列指定をやめる)
  • 計測の土台を作る(ベースラインの時間を記録)
  • 配列で一括読み込みしてシート往復を減らす
  • アルゴリズムを見直し、同条件の再計算をゼロにする
  • Dictionary で一括集計してから書き戻す
  • 数式(LAMBDA/動的配列)や Power Query の活用も検討する

よくある落とし穴は、文字列と数値の型が混在して比較に失敗すること、隠れた再計算やイベントで時間がかかること、そして「測らずに感覚で判断する」ことです。必ず時間を測り、改善の前後を比べましょう。

それでも遅い時の最後の選択肢は?

処理を分割してバッチ化する、前処理でデータを小さくする、外部ツール(データベース、Power Query)の利用を検討します。必要なら計算量の多い部分を別のアプリで行い、Excel は表示と最終整理に集中させます。

スポンサーリンク
記事URLをコピーしました