検証環境: Excel 365 v2509 · Excel 2021 · Excel 2019 · 最終確認 2026-06-12
要点 — VBA から VLOOKUP を呼ぶ方法は 2 つ あり、その違いが「値がないときにマクロが落ちるか」を決めます。Application.WorksheetFunction.VLookup は値が見つからないと実行時エラーを投げます。Application.VLookup(WorksheetFunction なし)は エラー値 を返し、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 を検査 |
検索が積み上がってきたら — 代わりに「突き合わせ」を伝える
あなたは WorksheetFunction 対 Application の議論をしたかったわけではありません。「今月のエクスポートを価格表に突き合わせて、存在しないコードに印を付けたい」だっただけです。正しい VLOOKUP を選び、失敗をガードし、今世紀中に終わるようループを Dictionary に書き直す頃には、その配管がマクロそのものになっています。ExcelMaster Agent なら、突き合わせを自然な日本語で説明できます — 「A 列の各コードを価格シートで引いて、価格を C 列に入れ、一致しないものを一覧にして」 — するとすべてを 1 パスで行う Python を、ブックを先にバックアップしたうえで書き出します。無料で試す →
関連ガイド
- VBA Dictionary — メモリ上の O(1) 検索
- VBA Array — 範囲を一括で読み書きする
- VBA Copy Paste — クリップボードを飛ばして速いマクロに
- VBA On Error — Resume Next と GoTo、マクロがバグを隠す理由
- VBA For ループ — 実務の 8 例
よくある質問
VBA で VLOOKUP を使うには?
application オブジェクト経由で呼びます:Application.WorksheetFunction.VLookup(検索値, テーブル範囲, 列番号, False)。完全一致には第 4 引数に常に False を渡します。値がないかもしれないなら、代わりに Application.VLookup(WorksheetFunction なし)を使い、結果を 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(...) を使えば、検索がうっかりアクティブシートを読むことを防げます。
