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

Excel VBA で Find を使う — セルを正しく検索する(返すのは位置ではなく Range)

|

Excel VBA で Find を使う — セルを正しく検索する(返すのは位置ではなく Range)

TL;DRRange.Find はコードから使う Excel の Ctrl+F です。行番号や位置は 返しません — 一致したセルを指す Range を、一致がなければ Nothing を返します。 だから結果に対して最初にやることは Is Nothing のチェックです。これを飛ばすと、最初の 「一致なし」の実行で 「オブジェクト変数が設定されていません」(error 91)で落ちます。 2 つの引数は妥協できません。Find は前回の値を覚えているからです — LookIn:=xlValuesLookAt:=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 をテストする
  • 引数を記憶する罠 — なぜ LookInLookAt を毎回必ず指定するのか
  • xlWholexlPart — 「完全一致」と「部分一致」の違い
  • FindNext でループしてすべての一致を取得する(無限ループにせずに)
  • Find がループに勝る場面と、InStr との違い

考え方の軸:Find が返すのはセルであって数値ではない

InStr から来ると Find は似て見えますが、振る舞いはまるで違います。InStr文字列の中を 検索し、数値 — 一致した文字位置、または 0 — を返します。Range.Findセルをまたいで 検索し、Range — 着地したセルへの生きたポインタ、何も一致しなければ特別な値 Nothing — を返します。

このたった 1 つの違いがすべてを決めます。結果がオブジェクトなので Set で代入し、そこから 必要なものを読み取ります — found.Rowfound.Columnfound.Addressfound.Value。 そして「一致なし」は 0 でも "" でもなく Nothing で表されるので、= でテストできません。 If found = Nothing は思ったようにはコンパイルすらされません — NothingIs キーワードで 判定します。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" が入っているからです。 出荷して、その値がないシートを食わせると、FindNothing を返します。次の行が 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)

私のルールはこうです。着地するセルを変えうる引数 — LookInLookAtMatchCase — は、 毎回の呼び出しで名前を付けて指定し、既定値には決して頼らない。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.ReplaceFind の兄弟で、同じ「記憶する引数」を共有しますが、仕事全体を 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