🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Excel VBA の VLookup — 見つからないと落ちる罠と、Dictionary が勝つとき

|

Excel VBA の VLookup — 見つからないと落ちる罠と、Dictionary が勝つとき

検証環境: Excel 365 v2509 · Excel 2021 · Excel 2019 · 最終確認 2026-06-12

要点 — VBA から VLOOKUP を呼ぶ方法は 2 つ あり、その違いが「値がないときにマクロが落ちるか」を決めます。Application.WorksheetFunction.VLookup は値が見つからないと実行時エラーを投げます。Application.VLookupWorksheetFunction なし)は エラー値 を返し、IsError で検査できます。そして数千行をループする段になると、正解はたいていどちらでもなく — Dictionary です。

Sub VLookupTwoWays()
    Dim tbl As Range
    Set tbl = Sheet1.Range("A:B")

    ' (1) WorksheetFunction — "Acme" がないと落ちる(実行時エラー 1004)
    Debug.Print Application.WorksheetFunction.VLookup("Acme", tbl, 2, False)

    ' (2) Application — 見つからなければエラー値を返す。落ちない
    Dim r As Variant
    r = Application.VLookup("Ghost", tbl, 2, False)
    If IsError(r) Then Debug.Print "見つからない" Else Debug.Print r
End Sub

考え方の軸:VLOOKUP は「セル用の関数」が VBA に出張している

VLOOKUP はセルの中で生きるために作られました。VBA から呼ぶとき、あなたはワークシート関数を借りています — そしてその貸し出しには、どのセル数式も警告しない 2 つの条件が付きます。値がないときの振る舞いと、ループで呼んだときのスケールの悪さです。この絵を持っておけば、本記事の 2 つの罠は驚きでなくなります。

だから最初の問いは「VBA で VLOOKUP をどう書くか?」ではありません — それは簡単な部分、1 行です。本当の問いは どちらの VLOOKUP か。VBA は正反対のエラー挙動を持つ 2 つを公開しているからです。そして次は、ループが絡んだ瞬間に そもそも呼ぶべきか です。

唯一のルール:WorksheetFunction.VLookup は落ち、Application.VLookup はエラーを返す

これが、最も報告される VBA VLOOKUP の問題 — 恐ろしい実行時エラー 1004「VLookup プロパティを取得できません」の正体です。

WorksheetFunction 経由で呼ぶと、値がないときは 実行時エラー が投げられ、マクロが止まります。Application 経由で直接呼ぶと、値がないときは エラー値Error 2042 など)として返り、IsError で検査できます — 落ちません。

どちらが「正しい」わけでもなく、2 つの状況のための道具です。値がないことが本当にバグで、マクロを止めたい(あるいは On Error で包んだ)なら WorksheetFunction。「見つからない」が想定内の正常な結果で、丁寧に扱いたいなら Application.VLookup です。

' (A) WorksheetFunction + On Error — 本物のエラーで止める/分岐する
Dim price As Double
On Error Resume Next
price = Application.WorksheetFunction.VLookup(code, tbl, 2, False)
If Err.Number <> 0 Then
    price = 0                       ' 既定値にして続行
    Err.Clear
End If
On Error GoTo 0

' (B) Application.VLookup + IsError — 「ないかもしれない」向けの、より素直な型
Dim res As Variant
res = Application.VLookup(code, tbl, 2, False)
If IsError(res) Then price = 0 Else price = res

型 (B) のほうが読みやすく、通常の制御フローのためにエラー処理を乗っ取りません — 失敗が想定内なら Application.VLookup を選びましょう。(散らばった On Error Resume Next が負債になる理由は VBA On Error を参照。)

本当にコストになる罠:ループの中の VLOOKUP は O(n²)

ここがチュートリアルの飛ばすところです。1 回の VLOOKUP は、一致が見つかるまで検索列を走査します。その呼び出しを行のループに入れると、毎回 列全体を走査し直します。

' ⚠ 遅い — 1 万行、各行が 1 万行のテーブルを VLOOKUP する。
' 最大 1 億回の比較。しかも毎周シートを読み直す。
Dim i As Long
For i = 2 To 10000
    Cells(i, "C").Value = Application.WorksheetFunction.VLookup( _
        Cells(i, "A").Value, Sheet2.Range("A:B"), 2, False)
Next i

これは二乗のオーダーの仕事で、実データでは「なぜマクロが固まっている?」が数分のコーヒー休憩に変わります。比較回数は行数の 二乗 で増えます — 行が倍なら待ち時間は 4 倍です。

対処は、繰り返し検索するのをやめること。検索テーブルを 一度 Dictionary に読み込みます — これはハッシュテーブルなので、各検索は O(n) ではなく O(1)。仕事全体が線形になります。

Sub LookupWithDictionary()
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")

    ' 両列を一括でメモリへ読み込む — なぜ効くかは VBA Array を参照
    Dim lookup As Variant
    lookup = Sheet2.Range("A2:B10000").Value
    Dim i As Long
    For i = 1 To UBound(lookup, 1)
        dict(lookup(i, 1)) = lookup(i, 2)        ' キー = A 列、値 = B 列
    Next i

    ' キーを読み、メモリで解決し、一括で書き戻す
    Dim keys As Variant, out() As Variant, n As Long
    keys = Sheet1.Range("A2:A10000").Value
    n = UBound(keys, 1)
    ReDim out(1 To n, 1 To 1)
    For i = 1 To n
        If dict.Exists(keys(i, 1)) Then out(i, 1) = dict(keys(i, 1)) Else out(i, 1) = "該当なし"
    Next i
    Sheet1.Range("C2").Resize(n, 1).Value = out      ' 書き込みは 1 回、1 万回ではない
End Sub

結果は同じですが、シートを 2 万回ではなく 2 回しか読まず、照合はハッシュベースです。1 万行では「一瞬」と「コーヒーを取りに行く」の差になります。(速さのもう半分 — 配列をセル単位でなく一括で読み書きする — は、それ自体が身につける価値のある習慣です。)

どれをいつ使うか

あなたの状況 使うもの 理由
一度きりの検索、値は常に存在するはず WorksheetFunction.VLookup 失敗 バグ — 投げさせる
一度きりの検索、値はないかもしれない Application.VLookup + IsError On Error なしで「見つからない」を扱う
多数の行をテーブルに照合 一度作る Dictionary 検索ごとに O(n) でなく O(1)
列の値でなく 行位置 が欲しい Application.Match VLOOKUP はアドレスを返さない
検索列が結果の にある Index/Match か Dictionary VLOOKUP は右方向しか見ない

意見:ループで VLOOKUP を呼んでいるなら、本当は Dictionary が欲しい

私が立つ一線はこれです:VLOOKUP はセルのもの、ループのものではない。 ワークシート関数を VBA から一度呼んで 1 つの値を取るのは、まったく問題ありません。それがデータ上の For ループに入った瞬間、あなたは 1 回の検索 用に設計された処理に、列を何千回も走査し直すよう頼んだのです — そして体感します。

「VLOOKUP でいい、知ってるし」という反射こそ、マクロを遅いままにします。プロの一手は、問題の形を見抜くこと:1 つのテーブルへの多数の検索 は、Dictionary の出番です。テーブルを一度メモリに作り、各行をそれに照合し、答えを一括で書き戻す。マクロは二乗から線形になり、そのためのコードは、ループする VLOOKUP に巻きつけるはずだったエラー処理より短いのです。

よくある VBA VLOOKUP のミス(と直し方)

症状 原因 直し方
実行時エラー 1004「VLookup プロパティを取得できません」 WorksheetFunction.VLookup が値を見つけられなかった Application.VLookup + IsError、または On Error で包む
大きなデータでマクロが固まる VLOOKUP を行ループで呼んだ(O(n²)) Dictionary を一度作り、メモリで解決
間違った値を返す 第 4 引数を省略 → 既定が 近似 一致 完全一致には常に False を渡す
値があるのに #N/A 検索列の末尾に空白 / Chr(160) 先にキーをきれいに — VBA Trim 参照
左方向に検索できない VLOOKUP は最初の列を右方向にしか探さない Index/Match か Dictionary
結果の格納で「型が一致しません」 結果がエラー値かもしれない Variant に格納し、使う前に IsError を検査

検索が積み上がってきたら — 代わりに「突き合わせ」を伝える

あなたは WorksheetFunctionApplication の議論をしたかったわけではありません。「今月のエクスポートを価格表に突き合わせて、存在しないコードに印を付けたい」だっただけです。正しい VLOOKUP を選び、失敗をガードし、今世紀中に終わるようループを Dictionary に書き直す頃には、その配管がマクロそのものになっています。ExcelMaster Agent なら、突き合わせを自然な日本語で説明できます — 「A 列の各コードを価格シートで引いて、価格を C 列に入れ、一致しないものを一覧にして」 — するとすべてを 1 パスで行う Python を、ブックを先にバックアップしたうえで書き出します。無料で試す →

関連ガイド

よくある質問

VBA で VLOOKUP を使うには? application オブジェクト経由で呼びます:Application.WorksheetFunction.VLookup(検索値, テーブル範囲, 列番号, False)。完全一致には第 4 引数に常に False を渡します。値がないかもしれないなら、代わりに Application.VLookupWorksheetFunction なし)を使い、結果を IsError で検査します。そうすれば失敗はマクロを落とさず、エラー値を返します。

VBA の VLOOKUP で実行時エラー 1004 が出るのはなぜ? WorksheetFunction 経由で呼び、値が見つからなかったからです — その経路は #N/A を返さずエラーを投げます。呼び出しを On Error Resume Next で包んで Err.Number を確認するか、検査できるエラー値を返す Application.VLookup を使い、If IsError(結果) で扱いましょう。

Application.VLookup と WorksheetFunction.VLookup の違いは? 同じ検索をしますが、失敗の仕方が違います。WorksheetFunction.VLookup は一致がないと実行時エラーを投げ、Application.VLookup はエラー値(Error 2042 など)を返し、IsError で検査します。失敗が本物のバグなら WorksheetFunction、「見つからない」が想定内なら Application を使います。

VBA の VLOOKUP は遅い? 1 回の呼び出しは問題ありません。多数の行のループで呼ぶと、各呼び出しが検索列を走査し直し、仕事が行数の二乗で増えるため遅くなります。多数の検索なら、テーブルを一度 Scripting.Dictionary に読み込み、各行を O(1) で解決します — 大きなデータでははるかに高速です。

VBA の VLOOKUP で別シートの値を引ける? はい — テーブル範囲をシートで修飾します:Application.WorksheetFunction.VLookup(キー, Worksheets("価格").Range("A:B"), 2, False)。完全修飾の Worksheets("名前").Range(...) を使えば、検索がうっかりアクティブシートを読むことを防げます。