TL;DR —
Range.Findはコードから使う Excel の Ctrl+F です。行番号や位置は 返しません — 一致したセルを指すRangeを、一致がなければNothingを返します。 だから結果に対して最初にやることはIs Nothingのチェックです。これを飛ばすと、最初の 「一致なし」の実行で 「オブジェクト変数が設定されていません」(error 91)で落ちます。 2 つの引数は妥協できません。Find は前回の値を覚えているからです —LookIn:=xlValuesとLookAt:=xlWholeを毎回の呼び出しで指定してください。さもないとマクロは、ユーザーが Find ダイアログに最後に打ち込んだ設定を引き継いでしまいます。
' A 列で "Widget" を探し、その行番号を安全に報告する。
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Data")
Dim found As Range
Set found = ws.Columns("A").Find( _
What:="Widget", _
LookIn:=xlValues, _
LookAt:=xlWhole) ' xlWhole = セルが "Widget" と完全一致。部分一致ではない
If found Is Nothing Then
MsgBox "Not found"
Else
MsgBox "Found in row " & found.Row ' found は Range。.Row / .Address / .Value を使う
End If
シートを検索することはマクロが最もよくやる作業であり、Find はそれにふさわしい道具です —
Excel 自身の最適化された検索エンジンであり、実データではセルを 1 つずつ回る手書きループを
大差で上回ります。ただし Find はオブジェクトモデルのどのメソッドよりも隠れた状態を多く
抱えていて、その状態こそが、同じコードがマシンごとに違う答えを返す理由です。本記事は 2 つの
事実 — Find はオブジェクトを返す、そして Find は前回の設定を引きずる — を軸に据え、
そこから導かれる残りすべてを順に見ていきます。
この記事で学べること
- 考え方の軸 —
Findは位置ではなくRange(またはNothing)を返す - error 91 のクラッシュを防ぐチェック — 結果に触れる前に
Is Nothingをテストする - 引数を記憶する罠 — なぜ
LookInとLookAtを毎回必ず指定するのか xlWholeとxlPart— 「完全一致」と「部分一致」の違いFindNextでループしてすべての一致を取得する(無限ループにせずに)Findがループに勝る場面と、InStrとの違い
考え方の軸:Find が返すのはセルであって数値ではない
InStr から来ると Find は似て見えますが、振る舞いはまるで違います。InStr は文字列の中を
検索し、数値 — 一致した文字位置、または 0 — を返します。Range.Find はセルをまたいで
検索し、Range — 着地したセルへの生きたポインタ、何も一致しなければ特別な値 Nothing —
を返します。
このたった 1 つの違いがすべてを決めます。結果がオブジェクトなので Set で代入し、そこから
必要なものを読み取ります — found.Row、found.Column、found.Address、found.Value。
そして「一致なし」は 0 でも "" でもなく Nothing で表されるので、= でテストできません。
If found = Nothing は思ったようにはコンパイルすらされません — Nothing は Is キーワードで
判定します。If found Is Nothing です。
このイメージ — Find はセルを返すか、Nothing を返すか — を頭に置いておけば、下記の 最頻出クラッシュは起きる前から見えています。
最も大事なルール:結果に触れる前に Is Nothing をチェックする
これがそのバグで、ほぼ全員が一度は書きます。
' 間違い。Find は必ず成功すると決めつけている。
Dim found As Range
Set found = ws.Columns("A").Find(What:="Widget", LookIn:=xlValues, LookAt:=xlWhole)
MsgBox found.Row ' "Widget" が存在しない瞬間に error 91
このコードはテストでは完璧に動きます — テストデータに "Widget" が入っているからです。
出荷して、その値がないシートを食わせると、Find は Nothing を返します。次の行が Nothing に
.Row を尋ね、VBA は 実行時エラー 91「オブジェクト変数または With ブロック変数が設定されて
いません」 を発生させます。Find で最もよくあるエラーであり、しかも完全に防げます。
' 正しい。まず Is Nothing で分岐する。
Set found = ws.Columns("A").Find(What:="Widget", LookIn:=xlValues, LookAt:=xlWhole)
If found Is Nothing Then
' 「見つからない」を処理する - メッセージ、既定値、終了
Else
' found.Row、found.Value などを安全に使える
End If
すべての Find には Is Nothing テストが続きます。 On Error とそのハンドラが一緒に
移動するのと同じように、この 2 つは一緒に打ち込む 1 つの単位として扱いましょう。「必ず一致
すると確信できる Find」など存在しません — 検索するとはそもそも、答えを知らないという
ことなのですから。
何時間も無駄にする罠:Find は前回の引数を覚えている
これこそが Find を「呪われている」ように感じさせる挙動です。引数のほとんどは省略可能で、
省略すると Find は固定の既定値を使わず、Find が前回この Excel セッションのどこかで実行された
ときの値を再利用します。それには、ユーザーが Ctrl+F ダイアログに手で打ち込んだ
最後の検索も含まれます。
だから、この無害そうな呼び出しは、
Set found = ws.Cells.Find(What:="2026") ' LookIn? LookAt? MatchCase? ...すべて継承
ある実行では数式に一致し、次の実行では値に一致する、ある実行では完全一致で、次の実行では 部分一致になる — あなたが見ることも設定することもない状態に左右されて。症状は人を狂わせます。 「昨日は動いた」「私のマシンでは動く」「たまに見つかる」。直し方は規律です — 結果を決める 引数を毎回、明示的に渡すこと。
Set found = ws.Cells.Find( _
What:="2026", _
LookIn:=xlValues, _ ' xlValues(見える値)と xlFormulas(背後の数式)
LookAt:=xlWhole, _ ' xlWhole(完全一致)と xlPart(部分一致)
MatchCase:=False, _
SearchOrder:=xlByRows)
私のルールはこうです。着地するセルを変えうる引数 — LookIn、LookAt、MatchCase — は、
毎回の呼び出しで名前を付けて指定し、既定値には決して頼らない。3 打鍵は、バグレポートより
ずっと安上がりです。
xlWhole と xlPart:完全一致と部分一致
LookAt は人が最もよく間違える引数です。どちらの値も「動いてしまう」のに、正しいのは片方
だけだからです。LookAt:=xlWhole は、セルの内容全体が検索語と等しいセルに一致します。
LookAt:=xlPart は、その語をどこかに含むセルに一致します。
' セルにはこのテキストが入っている: Order 100 shipped
ws.Cells.Find(What:="100", LookAt:=xlWhole) ' 一致しない - セルは正確に "100" ではない
ws.Cells.Find(What:="100", LookAt:=xlPart) ' 一致する - "100" がテキスト内に現れる
ID、コード、正確なキーを引くなら xlWhole が欲しいはずです。さもないと "1002"、"31007"、
"Order 100" の中の "100" にも一致してしまいます。長いテキストの中のキーワードを探すなら
xlPart です。間違ったほうを選ぶのは、あの古典的な「なぜあのセルに一致したの?」バグで、
エラーは決して出ません — ただ間違った Range を返すだけです。
すべての一致を見つける:FindNext ループ
Find が返すのは1 つのセル — 開始位置として指定したセルの後に来る最初の一致です。
すべての出現に対して処理するには、FindNext でループします。これは前回のヒットから続きを
探します。落とし穴は、FindNext が範囲の末尾に達すると先頭へ回り込むことで、素朴な
ループは永遠に回り続けます。直し方は、最初の一致のアドレスを覚えておき、そこへ戻って
きたら止めることです。
Dim found As Range, firstAddress As String
Set found = ws.Columns("A").Find(What:="Widget", LookIn:=xlValues, LookAt:=xlWhole)
If Not found Is Nothing Then
firstAddress = found.Address ' <-- 開始位置を覚えておく
Do
found.Offset(0, 1).Value = "seen" ' 各一致に対して何かする
Set found = ws.Columns("A").FindNext(found)
Loop While Not found Is Nothing And found.Address <> firstAddress
End If
firstAddress を取り忘れることが FindNext の最頻出バグです — ループは範囲を延々と周回し、
同じ一致を処理し続けます。もう 1 つ、静かに効いてくるルール。FindNext ループの中で行を
削除したり挿入したりしないこと。 検索の最中にグリッドを変えると、Find の位置感覚が壊れます。
一致を削除する必要があるなら、まずアドレスを集めてから後でまとめて削除しましょう
(逆順ループと Union のパターンは VBA Delete Rows を参照)。
コードでの検索と置換
Range.Replace は Find の兄弟で、同じ「記憶する引数」を共有しますが、仕事全体を 1 回の
呼び出しで片付けます — ループも Is Nothing も不要です。
ws.Columns("A").Replace _
What:="N/A", Replacement:="0", _
LookAt:=xlWhole, MatchCase:=False
Replace は範囲全体に一度に作用するので、Find をループして各セルに書き込むよりはるかに
高速で、範囲ではなく「何か変わったかどうか」の True/False を返します。一致を変更したい
ときは Replace を、一致を調べたり移動したりしたいときは Find/FindNext を選びましょう。
Find と InStr:グリッド検索と文字列検索
名前が似ていて置き換え可能に聞こえますが、そうではありません。検索対象がワークシートの
ときは Find を使います — 欲しいのはセルであり、Excel の高速なネイティブ検索です。
検索対象がすでに変数に入った 1 つの文字列のときは InStr を使います — 欲しいのは
そのテキスト内の文字位置です。両方を使う定番パターンもあります — Find で行を特定し、
InStr でその行の 1 つのフィールドから部分文字列を取り出す。文字列の中で「テキスト手術」を
するなら、それは Find ではなく InStr とその仲間 の仕事です。
そしてはっきり言っておくべき判断があります。値を探すのに For Each cell In range ループを
書いてはいけません。 Find は Excel 自身のインデックス付き検索です。10 万行の列でも
ほぼ即座に返るのに対し、ループは全セルを 1 つずつすり潰していきます。検索を自作するのは、
遅く、長く、間違えやすいのです。
ExcelMaster の活用
Find は異例なほど多くの隠れた挙動を 1 つのメソッドに凝縮しています — 数値ではなく
オブジェクトを返すこと、Nothing の結果、記憶する引数、xlWhole/xlPart の選択、回り込む
ループ。どれもエラーを出さずに間違った結果を生む失敗モードを持っています — まさに、テストを
すり抜けて本番で表面化する類のバグです。
ExcelMaster なら、検索を
説明するだけで済みます。「ステータスが Cancelled の行をすべて見つけてフラグを立てて」
「この請求書番号を引いてそこへ飛んで」と言えば、引数を明示的に固定し、Is Nothing ガードを
置き、すべての一致が必要なときは適切な FindNext ループを書いて Find を組み立てます —
そして何かを変える前にシートをバックアップします。ブックもコードもあなたの手元に残り、
継承された LookAt が静かに間違ったセルに一致する、あの一幕だけを飛ばせます。
よくある質問
VBA の Find で error 91 が出るのはなぜ?
Range.Find は一致がないと Nothing を返し、その Nothing から .Row のようなプロパティを
読んだ瞬間に error 91(「オブジェクト変数または With ブロック変数が設定されていません」)が
発生するからです。結果を使う前に必ず If found Is Nothing Then でテストしましょう。この
クラッシュはたいてい、データに検索語がないときだけ現れます — だからテストをすり抜けるのです。
VBA の Find は一致がないと何を返す?
0 でも空文字列でもなく、特別な値 Nothing を返します。Nothing は = で比較できないので、
Is キーワードで — If found Is Nothing と — チェックしなければなりません。一致に成功した
ときは、見つかったセルを指す Range を返します。
VBA の Find で LookAt の xlWhole と xlPart の違いは?
xlWhole は内容全体が検索語と等しいセルにのみ一致し、xlPart はその語をどこかに含むセルに
一致します。ID やコードのような正確なキーには xlWhole を、長いテキストの中のキーワードを
探すときは xlPart を使いましょう。間違ったほうを選ぶと、エラーなしに間違ったセルを返します。
VBA で最初の 1 つだけでなく、一致するセルすべてを見つけるには?
Find で最初の一致を取り、その .Address を覚えておき、アドレスが最初のものに戻ってくるまで
FindNext(found) でループします — Loop While Not found Is Nothing And found.Address <> firstAddress。最初のアドレスを取ることが肝心です — それがないと、ループは範囲を永遠に
回り込みます。
シートを検索するのに VBA の Find とループのどちらを使うべき?
Find を使いましょう。Excel 組み込みの最適化された検索であり、全セルを回る For Each ループ
より、特に大きな範囲で、はるかに高速です。手動ループはより長く、より間違えやすくもあります。
Find の引数では表現できない独自の一致ロジックが必要なときだけループしましょう。
検証環境
動作確認: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-08。
関連ガイド: VBA AutoFilter · VBA Sort · VBA Delete Rows · VBA InStr · VBA Range
