「For EachもDo Loopも書けるようになったのに、データが数千行になると急に遅くなる……」
VBAをある程度書けるようになると、こんな壁にぶつかりますよね。特に「重複をチェックしながらループする」処理は、データが増えるほど一気に重くなります。Excelがフリーズして、保存もできずにヒヤッとした経験がある方も多いはずです。
この遅さを根本から解決してくれるのが VBA Dictionary(連想配列) です。この記事では、基本メソッドの使い方から「なぜ配列より速いのか」、そして重複削除・グループ集計・別シート転記の実務コードまで解説していきますね。
VBA Dictionary(連想配列)とは?
Dictionaryは、キー(Key)と値(Item)をペアで保管するオブジェクトです。連想配列やハッシュマップとも呼ばれます。
イメージは「ラベル付きの引き出し」です。引き出しにラベル(キー)を貼っておけば、中身(値)を探すときにラベルを見るだけで一発で取り出せます。これがDictionaryの本質です。
ふつうの配列が「1番目・2番目」と番号で管理するのに対して、Dictionaryは「商品コード」「部門名」のような意味のある名前で管理できます。
| 比較項目 | 配列(Array) | Dictionary |
|---|---|---|
| 要素の指定方法 | 番号(インデックス) | キー(任意の文字列・数値) |
| キーの重複 | 概念がない | 不可(一意) |
| 「この値は既出か」の判定 | 全要素を順に照合 | Existsで一発判定 |
| 主な用途 | 連番管理、行データの一時保持 | 重複削除、グループ集計、対応表 |
つまりDictionaryは「名前で出し入れできて、しかも検索が速い」入れ物だと覚えてください。この2つの特徴が、大量データ処理の遅さを解決してくれます。
NOTE
VBE(Visual Basic Editor)は
Alt+F11で起動します。リボンから開く場合は「開発」タブ →「Visual Basic」を選びます。「開発」タブが表示されていないときは、「ファイル」→「オプション」→「リボンのユーザー設定」で「開発」にチェックを入れてください。VBEが開いたら「挿入」→「標準モジュール」でコードを書く場所を作ります。画面構成がわからない方は、VBE画面の見方の記事も参考にしてみてください。
Dictionaryを使う準備(参照設定とCreateObject)
Dictionaryを使うには、最初にオブジェクトを生成する必要があります。生成方法は2通りあります。
方法1: 参照設定する(事前バインディング)
VBEのメニューから「ツール」→「参照設定」を開きます。一覧から Microsoft Scripting Runtime にチェックを入れます。これで準備完了です。
Sub UseEarlyBinding()
Dim dic As Scripting.Dictionary '辞書オブジェクト
Set dic = New Scripting.Dictionary
dic.Add "りんご", 100
MsgBox dic("りんご") & "円"
End Sub
参照設定すると、入力中に候補が出る入力補完(IntelliSense)が効きます。コードが書きやすくなるので、自分のPCで使う場合はこちらがおすすめです。
方法2: CreateObjectで生成する(実行時バインディング)
参照設定をせずに、コード内でオブジェクトを直接生成する方法です。
Sub UseLateBinding()
Dim dic As Object '辞書オブジェクト
Set dic = CreateObject("Scripting.Dictionary")
dic.Add "りんご", 100
MsgBox dic("りんご") & "円"
End Sub
CreateObjectは参照設定が不要です。そのため、ファイルを他の人に配布する場合に向いています。環境による設定の違いを気にしなくて済むからです。
TIP
迷ったら CreateObject を選んでおくと安全です。参照設定はファイルごとに必要で、配布先で外れていると動かなくなります。本記事のコードはすべてCreateObjectで統一しています。
Dictionaryの基本メソッド
Dictionaryの基本操作を1つずつ見ていきましょう。どれもシンプルなので、すぐに使えるようになりますよ。
Add:キーと値を追加する
Add はキーと値のペアを登録するメソッドです。
Dim dic As Object '辞書オブジェクト
Set dic = CreateObject("Scripting.Dictionary")
dic.Add "A001", "りんご" 'キー"A001"に値"りんご"を登録
dic.Add "A002", "みかん"
同じキーをもう一度Addするとエラーになります。キーは重複できないルールだからです。
Exists:キーがあるか調べる
Exists は、指定したキーが登録済みかどうかを True / False で返します。
If dic.Exists("A001") Then
MsgBox "登録済みです"
Else
MsgBox "未登録です"
End If
重複チェックの主役になるメソッドです。後ほどの実務コードで大活躍します。
Item:値を取り出す・書き換える
Item はキーに対応する値を取り出すプロパティです。既定プロパティなので、dic("キー") と短く書けます。
MsgBox dic("A001") '"りんご"が表示される
dic("A001") = "青りんご" '値を上書き
NOTE
Itemには注意点があります。存在しないキーをdic("未登録キー")で参照すると、エラーにならず、そのキーが空の値で勝手に追加されてしまいます。存在確認には必ずExistsを使ってください。Itemでの判定は誤動作のもとになります。
Keys・Items:全キー・全値を取り出す
Keys は全キーを、Items は全値を、それぞれ配列で返します。
Dim k As Variant '取り出すキー
For Each k In dic.Keys
Debug.Print k & " → " & dic(k) 'キーと値をセットで出力
Next k
For Each でキーを1つずつ取り出し、dic(k) で対応する値を引いています。For Eachの使い方との組み合わせはDictionaryの定番パターンです。
Count・Remove:件数取得と削除
Count は登録ペア数を返すプロパティです。Remove は指定したキーのペアを削除します。
MsgBox "登録数: " & dic.Count & "件"
dic.Remove "A002" '"A002"のペアを削除
dic.RemoveAll '全ペアを削除
基本メソッド早見表
| 要素 | 種別 | 役割 |
|---|---|---|
| Add(キー, 値) | メソッド | ペアを追加(既存キーはエラー) |
| Exists(キー) | メソッド | キーの有無を True / False で返す |
| Item(キー) / dic(キー) | プロパティ | 値を取得・上書き |
| Keys | メソッド | 全キーを配列で返す |
| Items | メソッド | 全値を配列で返す |
| Count | プロパティ | 登録ペア数を返す |
| Remove(キー) | メソッド | 指定キーのペアを削除 |
| RemoveAll | メソッド | 全ペアを削除 |
なぜDictionaryは配列より速いのか
ここがこの記事のいちばん大事なところです。「なぜ速いのか」を理解すると、Dictionaryを使うべき場面が自分で判断できるようになります。
配列の重複チェックは「総当たり」になる
配列で「この値はもう登場したか」を調べるには、先頭から順に1つずつ照合するしかありません。これを線形探索と呼びます。
たとえば1,000件のデータから重複を除こうとすると、1件ごとに既存リストを全部チェックします。結果として、最悪で約100万回(1,000×1,000)の照合が必要です。データが10,000件なら約1億回に膨れ上がります。
これが二重ループ(O(n^2))の正体です。件数が10倍になると、処理時間は100倍になります。
Dictionaryは「ラベルから直接たどる」
一方、Dictionaryは内部でハッシュテーブルという仕組みを使っています。キーを計算式(ハッシュ関数)にかけて、値を置く場所を直接割り出します。
引き出しのラベルを見て、いきなり目的の引き出しを開けるイメージです。他の引き出しを順番に開けて確認する必要がありません。
そのため、Exists や dic(キー) での検索は、データ件数に関係なくほぼ一定時間(O(1))で終わります。重複チェックを伴うループ全体で見ても、DictionaryならO(n)で済みます。
件数が増えるほど差は爆発する
両者の照合回数を比べてみましょう。
| データ件数 | 配列の二重ループ(O(n^2)) | Dictionary(O(n)) |
|---|---|---|
| 100件 | 約1万回 | 100回 |
| 1,000件 | 約100万回 | 1,000回 |
| 10,000件 | 約1億回 | 1万回 |
| 100,000件 | 約100億回 | 10万回 |
100件程度なら配列でも一瞬です。しかし数千〜数万件になると、配列は数十秒〜数分かかる一方、Dictionaryは一瞬で終わります。この差が、冒頭の「急に遅くなる」問題の答えです。
TIP
配列が遅いのは「値で探す」ときだけです。番号(インデックス)で直接取り出す処理は配列も高速です。「名前や値で検索したい」ときがDictionaryの出番だと覚えておきましょう。
【実務】Dictionaryの活用コード3選
ここからは、実務でそのまま使えるパターンを3つ紹介します。どれもデータの準備から出力まで通しで動くコードです。
パターン1: 重複を削除して一意リストを作る
A列の商品コードから重複を除き、C列に一意のリストを書き出すコードです。
Sub RemoveDuplicates()
Dim ws As Worksheet '対象シート
Dim dic As Object '重複判定用の辞書
Dim lastRow As Long '最終行
Dim i As Long '行カウンター
Dim key As String '判定するキー
Dim outRow As Long '出力先の行
Set ws = ActiveSheet
Set dic = CreateObject("Scripting.Dictionary")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
outRow = 2
'--- A列を1行ずつ読み、未登録のものだけC列に書き出す ---
For i = 2 To lastRow
key = ws.Cells(i, 1).Value
If Not dic.Exists(key) Then
dic.Add key, True '登録済みとして記録
ws.Cells(outRow, 3).Value = key
outRow = outRow + 1
End If
Next i
MsgBox "重複を除いて " & dic.Count & " 件を書き出しました"
End Sub
ポイントは If Not dic.Exists(key) Then です。まだ登録されていないキーだけをC列に書き出しています。最終行は End(xlUp) で取得しています。詳しくは最終行を取得する方法をご覧ください。
パターン2: 部門別の売上を集計する
A列に部門名、B列に売上金額が入ったデータを、部門ごとに合計するコードです。
Sub GroupSum()
Dim ws As Worksheet '対象シート
Dim dic As Object '部門別集計用の辞書
Dim lastRow As Long '最終行
Dim i As Long '行カウンター
Dim dept As String '部門名(キー)
Dim amount As Double '売上金額
Dim k As Variant '出力時のキー
Dim outRow As Long '出力先の行
Set ws = ActiveSheet
Set dic = CreateObject("Scripting.Dictionary")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
'--- 部門名をキーにして売上を加算していく ---
For i = 2 To lastRow
dept = ws.Cells(i, 1).Value
amount = ws.Cells(i, 2).Value
If dic.Exists(dept) Then
dic(dept) = dic(dept) + amount '既存部門に加算
Else
dic.Add dept, amount '新しい部門を登録
End If
Next i
'--- 集計結果をD・E列に書き出す ---
outRow = 2
For Each k In dic.Keys
ws.Cells(outRow, 4).Value = k
ws.Cells(outRow, 5).Value = dic(k)
outRow = outRow + 1
Next k
MsgBox dic.Count & " 部門を集計しました"
End Sub
dic(dept) = dic(dept) + amount が集計の心臓部です。既に登録済みの部門なら現在の合計に加算し、初登場の部門なら新規登録しています。ピボットテーブルのグループ集計をコードで実現するイメージです。条件分岐はIf文の使い方で行っています。
パターン3: コード→名前の対応表で別シートに転記する
「商品マスタ」シートの対応表をDictionaryに読み込み、「売上」シートの商品コードを商品名に変換しながら転記するコードです。VLOOKUPをメモリ上で高速実行するイメージです。
Sub MappingTransfer()
Dim wsM As Worksheet '商品マスタシート
Dim wsS As Worksheet '売上シート
Dim dic As Object '対応表の辞書
Dim lastRowM As Long 'マスタの最終行
Dim lastRowS As Long '売上の最終行
Dim i As Long '行カウンター
Dim code As String '商品コード(キー)
Set wsM = Worksheets("商品マスタ")
Set wsS = Worksheets("売上")
Set dic = CreateObject("Scripting.Dictionary")
'--- マスタのコードと名前を対応表として読み込む ---
lastRowM = wsM.Cells(wsM.Rows.Count, 1).End(xlUp).Row
For i = 2 To lastRowM
dic(wsM.Cells(i, 1).Value) = wsM.Cells(i, 2).Value
Next i
'--- 売上シートのコードを名前に変換してC列に書き出す ---
lastRowS = wsS.Cells(wsS.Rows.Count, 1).End(xlUp).Row
For i = 2 To lastRowS
code = wsS.Cells(i, 1).Value
If dic.Exists(code) Then
wsS.Cells(i, 3).Value = dic(code) '対応する名前を転記
Else
wsS.Cells(i, 3).Value = "該当なし"
End If
Next i
MsgBox "転記が完了しました"
End Sub
対応表を一度Dictionaryに読み込んでしまえば、あとは dic(code) で一発参照できます。売上データが何万行あっても高速です。VLOOKUPの数式を大量に貼るより軽く、確実に動きます。
NOTE
マスタの読み込みで
dic(キー) = 値を使っているのは、マスタ側に重複コードがあっても後の行で上書きされるだけでエラーにならないからです。Addだと重複コードでエラーになるので、対応表の読み込みではこの書き方が安全です。
Dictionaryでよくあるエラーと対処法
| エラー | 原因 | 対処法 |
|---|---|---|
| 実行時エラー ‘457’:キーが重複 | 同じキーを Add した | Exists で事前チェックする。または dic(キー) = 値 で上書きする |
| 「ユーザー定義型は定義されていません」 | 参照設定なしで As Scripting.Dictionary と書いた | 参照設定を追加する。または CreateObject と As Object に変える |
| 実行時エラー ‘5’:引数が不正 | 存在しないキーを Remove した | Remove の前に Exists でキーの有無を確認する |
| 意図しない空キーが増える | 存在しないキーを Item で参照した | 存在確認は必ず Exists で行う。Item で判定しない |
| 実行時エラー ‘429’:オブジェクトを作成できません | まれに環境にコンポーネントがない | Windowsには標準搭載。エラー時はOSやOfficeの状態を確認する |
NOTE
Dictionaryを使うマクロは、必ず マクロ有効ブック(.xlsm) で保存してください。通常の .xlsx で保存すると、マクロが消えてしまいます。
配列・コレクションとの使い分け
VBAでデータをまとめて扱う方法には、配列・Collection・Dictionaryの3つがあります。場面に合わせて選びましょう。
| 比較項目 | 配列(Array) | Collection | Dictionary |
|---|---|---|---|
| 参照設定 | 不要 | 不要 | CreateObjectなら不要 |
| キーでの管理 | できない | 追加時のみ | できる |
| キーの存在確認 | 自前で実装 | メソッドなし | Exists |
| キー一覧の取得 | できない | できない | Keys |
| 値で探す速さ | 遅い(線形探索) | 遅い | 速い(ハッシュ) |
| 主な用途 | 連番・行データの保持 | 単純な要素の束 | 重複削除・集計・対応表 |
使い分けの目安
- 番号で順番に処理する → 配列
- 単純に要素を束ねたいだけ → Collection
- キーで検索・重複判定・集計したい → Dictionary
TIP
「重複を除きたい」「○○ごとに合計したい」「コードから名前を引きたい」という処理が出てきたら、Dictionaryを思い出してください。二重ループで書きかけていたコードが、半分以下の行数で、しかも何倍も速くなります。
まとめ
VBA Dictionaryは「キーと値をペアで保管し、高速に出し入れできる」オブジェクトです。
- Add でペアを追加し、Exists でキーの有無を判定する
- 値の取得・上書きは dic(キー) で書ける(存在しないキーの参照は厳禁)
- Keys / Items で全要素を取り出し、For Each でループする
- 速い理由はハッシュテーブル。値で探す処理が配列より圧倒的に速い
- 重複削除・グループ集計・別シート転記の3パターンが実務の定番
データが数千行を超えて「急に遅くなった」と感じたら、Dictionaryの出番です。まずは重複削除のコードをVBEに貼り付けて、二重ループとの速度差を体感してみてくださいね。
Dictionaryを使いこなせたら、次はVBA学習ロードマップで全体の学習ステップを確認してみましょう。
