目次

はじめに:Findメソッドの有用性と「実行時エラー 91」の壁

Excel VBAにおいて、膨大なデータの中から特定の顧客名や社員番号、商品コードなどが入力されているセルを探し出す際、最も強力で処理速度も速い機能が Range.Find メソッドです。これはExcelのシート上でショートカットキー「Ctrl + F」を押して呼び出す「検索と置換」ダイアログの機能を、VBAから直接制御するものです。VLOOKUP関数とは異なり、検索対象がどの列にあっても探し出すことができ、見つかったセルの背景色を変えたり、その隣のセルの値を取得したりといった柔軟な操作が可能です。

しかし、VBA初心者がFindメソッドを使い始めた際に、ほぼ100%の確率で直面する大きな壁があります。それが、「実行時エラー ’91’: オブジェクト変数または With ブロック変数が設定されていません。」という難解なエラーメッセージによるマクロの強制終了です。

このエラーは、俗に「Nothingエラー」とも呼ばれます。検索した結果、目的のデータが見つからなかった場合に、VBAが「Nothing(空っぽ)」という状態を返し、その「空っぽ」のデータに対して何らかの操作を行おうとした瞬間にシステムが破綻するために発生します。他のプログラミング言語(JavaやC#など)に精通している方であれば「Null参照例外(NullReferenceException)」と言えば分かりやすいでしょう。タイトルにある「Nullチェック」とは、VBAにおいては「Nothing判定」を行うことを指します。

本記事では、FindメソッドでなぜこのNothingエラーが発生するのかという内部的なメカニズムを解き明かし、エラーでマクロを止めないための正しいNothing判定(Nullチェック)の書き方を解説します。さらに、「シート上には確実にデータが存在するのに、VBAのFindメソッドでは見つからずNothingになってしまう」という実務で頻発するVBA特有の悪魔的な仕様(トラップ)とその解決策を、網羅的かつ詳細に解説します。

「Nothing」エラー(エラー91)が発生するメカニズム

まずは、なぜ「実行時エラー 91」が発生するのか、その構造を正確に理解しておく必要があります。

VBAにおける「Null」と「Nothing」の正確な違い

一般的なIT用語として「データが存在しないこと」を「Null(ヌル)」と呼ぶことが多いですが、VBAの世界においてはデータ型によって「空っぽ」を意味するキーワードが異なります。

  • Empty: 初期化されていないVariant型変数の状態。
  • Null: データベース(AccessやSQL)との連携時などで「有効なデータが入っていない」ことを示す状態。
  • “”(空文字列): 文字列型(String)変数が空である状態。
  • Nothing: オブジェクト型(Range、Worksheet、Workbookなど)の変数が、「どの実体も参照していない(セットされていない)」状態。

Findメソッドは、検索結果として「セルそのもの(Rangeオブジェクト)」を返します。したがって、検索値が見つからなかった場合、Findメソッドは「該当するセルはありませんでした」という意味で Nothing を返します。

Nothing状態のオブジェクトのプロパティを参照することの危険性

実行時エラー91が発生する典型的な「悪いコード」を見てみましょう。

' 【エラーになる危険なコード】
Dim foundCell As Range
' "田中"という文字をA列から探す
Set foundCell = Range("A:A").Find(What:="田中")

' 見つかったセルの行番号をメッセージで表示する
MsgBox "田中の行番号は: " & foundCell.Row

もしA列に「田中」が存在すれば、このコードは正常に動き、行番号を表示します。しかし、「田中」が存在しなかった場合、foundCell には Nothing が代入されます。
その直後の行で foundCell.Row(空っぽの物体の行番号)を取得しようとします。物理的に存在しないセルの行番号など取得できるはずがなく、VBAは「オブジェクト変数が設定されていません(中身がNothingである)」という致命的なエラーを出してマクロを強制停止させます。

正しいNothing判定(Nullチェック)の書き方と基本テンプレート

このエラーを防ぐための絶対の鉄則は、「Findメソッドを実行した直後に、必ず結果がNothingかどうかの判定(If文)を入れる」ことです。

オブジェクト変数がNothingであるかどうかを判定するには、=(イコール)記号ではなく、Is 演算子を使用します。これがVBAにおける正しい「Nullチェック」の構文です。

' -----------------------------------------------------------
' Findメソッドの正しいNothing判定テンプレート
' -----------------------------------------------------------
Sub SafeFindMethod()
    Dim ws As Worksheet
    Dim searchRange As Range
    Dim foundCell As Range
    Dim searchKey As String

    ' シートと検索範囲、検索値を指定
    Set ws = ThisWorkbook.Worksheets("名簿")
    Set searchRange = ws.Range("A:A")
    searchKey = "田中"

    ' 1. Findメソッドの実行(結果をオブジェクト変数にSetする)
    Set foundCell = searchRange.Find(What:=searchKey, LookAt:=xlWhole)

    ' 2. Nothing判定(Nullチェック)
    If foundCell Is Nothing Then
        ' 見つからなかった場合の処理
        MsgBox "指定されたデータ「" & searchKey & "」は見つかりませんでした。", vbExclamation
        Exit Sub ' エラーを出さずにマクロを安全に終了する
    Else
        ' 見つかった場合の処理(ここではじめてオブジェクトのプロパティに触れる)
        MsgBox "データが見つかりました。セル番地: " & foundCell.Address & vbCrLf & _
               "行番号: " & foundCell.Row

        ' 例:見つかったセルの右隣(B列)の値を書き換える
        foundCell.Offset(0, 1).Value = "確認済み"
    End If
End Sub

If foundCell Is Nothing Then を書くことで、データが存在しないという事実を「エラー」としてではなく「正常な分岐」として処理することができるようになります。Findメソッドを使う際は、この構文を必ずセットで記述する癖をつけてください。

確実にあるはずのデータが「Nothing」になる5つの落とし穴と原因

Nothing判定の書き方をマスターすれば、マクロが強制終了することはなくなります。しかし、実務において次に直面するのが、「シートを目視すると確実にデータが存在しているのに、マクロを実行すると常に見つからず(Nothing判定に入ってしまい)処理が行われない」という現象です。
これは、Findメソッドの引数指定に関するVBA特有の厄介な仕様や、Excelの検索機能の性質に起因しています。主な5つの原因とその対処法を解説します。

原因1:最大のトラップ「引数の省略」による前回検索条件の引き継ぎ

Findメソッドには、検索値(What)以外にも様々な引数が存在しますが、VBAの仕様上、What以外の引数は省略可能です。しかし、Findメソッドの引数を省略すると、前回Excel上で「検索と置換」を行った際の設定(または前回のマクロで指定した設定)がそのまま引き継がれるという極めて危険な仕様になっています。

例えば、直前にユーザーがExcelの画面上で「大文字と小文字を区別する」「セル内容が完全に同一であるものを検索する」にチェックを入れて検索していた場合、マクロ内で Range.Find("apple") とだけ書いて引数を省略すると、シート上にある「Apple」や「apple pie」という文字列は、ユーザーの直前の設定に引きずられて「完全一致かつ大文字小文字を区別する」条件が適用されるため、見つからずにNothingとなってしまいます。

【対処法】
Findメソッドを使用する際は、省略可能な引数であっても、実務上影響の大きい以下の3つの引数は必ず明示的に指定(ハードコーディング)してください。

  • LookAt:= (完全一致か部分一致か)
  • LookIn:= (値か数式か)
  • MatchCase:= (大文字・小文字を区別するかどうか。通常は False)

原因2:完全一致(xlWhole)と部分一致(xlPart)の指定ミス

引数 LookAt の指定ミスです。

  • LookAt:=xlWhole (完全一致):セルの内容が検索値と「完全に」同じでなければ見つかりません。
  • LookAt:=xlPart (部分一致):セルの内容の一部に検索値が含まれていれば見つかります。

例えば、セルに「東京都新宿区」と入力されている場合、「新宿」というキーワードで検索した際、xlWhole を指定しているとNothingになります。用途に合わせて正しく指定してください。

原因3:検索対象(値か、数式か)の指定ミス(xlValues と xlFormulas)

引数 LookIn は、セルの「何」を見て検索するかを指定します。

  • LookIn:=xlValues:セルに表示されている「見た目の値」を検索します。
  • LookIn:=xlFormulas:数式バーに表示されている「数式そのもの」を検索します。

例えば、A1セルに =B1+C1 という数式が入っており、計算結果として画面に「1000」と表示されているとします。このとき「1000」を検索値として LookIn:=xlFormulas で検索すると、数式内に1000という文字がないためNothingになります。表示されている計算結果を検索したい場合は、必ず LookIn:=xlValues を指定してください。

原因4:非表示の行・列、または結合セルの影響

Findメソッドは「画面上で見えているもの」を検索する機能であるため、対象の行や列が「非表示(隠し行・隠し列)」になっている場合、LookIn:=xlValues を指定しているとデータを見つけることができずNothingになります。(※xlFormulas を指定した場合は非表示セルも検索可能です)。
また、結合されたセルを検索する場合、検索ヒットするのは「結合範囲の左上端のセル」のみとなるため、検索範囲の指定方法によっては見逃されてNothingになることがあります。

【対処法】
検索を実行する前に、シートの非表示行・列を解除するか、検索ロジックを見直す必要があります。

原因5:日付データ検索における表示形式とシリアル値の不一致

Findメソッドで「日付」を検索する場合が最もトラブルが多発します。VBA内部の日付(Date型)と、シート上の表示形式(例:2023/10/01 と 令和5年10月1日 など)の違いをFindメソッドが正しく解釈できないためです。この問題については、記事の後半で詳細な解決策を提示します。

複数件のデータを検索する「FindNext」での正しいNothingチェック

単一のデータを検索するだけでなく、「検索条件に一致するすべてのセルを見つけて、背景色を赤にする」といった処理を行いたい場合、Findメソッドの後に FindNext メソッドを組み合わせてループ処理を行います。

しかし、この FindNext においても正しい制御を行わないと、無限ループに陥ってExcelがフリーズするか、再びNothingエラーでクラッシュしてしまいます。

無限ループを防ぐ最初のアドレス(firstAddress)の記憶

FindNext メソッドは、検索範囲の末尾まで到達すると、自動的に一番上(最初)に戻って再び検索を続けるという仕様になっています。そのため、「最初に見つかったセルのアドレス」を変数に記憶しておき、ループの中で見つかったセルのアドレスが最初のアドレスと一致したら「一周した」と判断してループを抜け出す(Exit Do)ロジックが不可欠です。

FindNextを利用した安全な全件抽出のテンプレートコード

以下が、エラーを出さず、無限ループにもならない FindNext の完全なテンプレートです。

Sub SearchAllData()
    Dim ws As Worksheet
    Dim searchRange As Range
    Dim foundCell As Range
    Dim firstAddress As String
    Dim searchKey As String
    Dim count As Long

    Set ws = ThisWorkbook.Worksheets("データ")
    Set searchRange = ws.Range("A:A")
    searchKey = "東京都"
    count = 0

    ' 1回目の検索(必ず引数を明示的に指定する)
    Set foundCell = searchRange.Find(What:=searchKey, LookIn:=xlValues, LookAt:=xlPart, MatchCase:=False)

    ' 最初のNothingチェック
    If Not foundCell Is Nothing Then
        ' 見つかった場合、最初のアドレスを記憶する
        firstAddress = foundCell.Address

        ' Do...Loopで次々と検索していく
        Do
            ' 目的の処理(例:背景色を黄色にする、カウントを増やす等)
            foundCell.Interior.Color = vbYellow
            count = count + 1

            ' 次のセルを検索する
            Set foundCell = searchRange.FindNext(foundCell)

            ' 【重要】次が見つからなかった場合、または最初のアドレスに戻ってきた場合はループを抜ける
            If foundCell Is Nothing Then Exit Do
            If foundCell.Address = firstAddress Then Exit Do

        Loop

        MsgBox count & "件のデータが見つかり、色を変更しました。", vbInformation
    Else
        MsgBox "該当するデータはありませんでした。", vbExclamation
    End If
End Sub

【高度なテクニック】日付検索でNothingエラーを完全に防ぐ方法

前述の通り、Findメソッドで「日付」を検索しようとすると、シート上の表示形式のわずかな違いによって高い確率でNothingになります。例えば、検索値に DateValue("2023/10/01") を渡し、シート上に 2023/10/1 と表示されていた場合、Findメソッドはこれらを一致しないと判定することがあります。

日付検索でNothingエラーを完全に防ぐための最善のアプローチは以下のいずれかです。

アプローチ1:表示形式を完全に一致させた「文字列」として検索する
シート上の日付がどのように表示されているか(例:yyyy/mm/dd なのか yyyy/m/d なのか)を確認し、VBAの Format 関数を使って検索値を完全に同じ「文字列」に変換してから検索します。

Dim targetDate As Date
Dim searchString As String
Dim foundDateCell As Range

targetDate = DateSerial(2023, 10, 1)
' シートの表示形式(yyyy/m/d)に合わせて文字列に変換
searchString = Format(targetDate, "yyyy/m/d")

' LookIn:=xlValues を指定して文字列として検索
Set foundDateCell = Range("A:A").Find(What:=searchString, LookIn:=xlValues, LookAt:=xlWhole)

アプローチ2:Findメソッドを諦め、Forループとシリアル値で比較する
実務において、日付検索でFindメソッドの不確実性に悩まされるくらいであれば、最初からFindメソッドを使わず、対象範囲のデータを配列に格納してVBA内で「シリアル値」として直接比較する(ループ処理)方が、圧倒的に確実でバグが発生しません。

Sub SearchDateByLoop()
    Dim ws As Worksheet
    Dim targetDate As Date
    Dim i As Long
    Dim lastRow As Long
    Dim isFound As Boolean

    Set ws = ThisWorkbook.Worksheets("データ")
    targetDate = DateSerial(2023, 10, 1)
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
    isFound = False

    ' A列のデータを上から順にシリアル値で比較
    For i = 2 To lastRow
        If IsDate(ws.Cells(i, 1).Value) Then
            If ws.Cells(i, 1).Value = targetDate Then
                MsgBox "見つかりました。行番号: " & i
                isFound = True
                Exit For
            End If
        End If
    Next i

    If Not isFound Then
        MsgBox "指定された日付は見つかりませんでした。"
    End If
End Sub

データが数万件以上でない限り、このループ処理による検索でも一瞬で完了するため、日付検索においては特にお勧めの手法です。

まとめ:Findメソッドは「見つからないこと」を前提に設計する

VBAのFindメソッドを使用した際に発生する「実行時エラー 91」は、プログラムの欠陥ではなく、「目的のデータが存在しない場合の指示(エラーハンドリング)」がプログラマーによって記述されていないことに起因します。

エラーを防ぐための絶対的な鉄則は以下の3点に集約されます。

  1. Findメソッドの戻り値を変数に格納した直後、必ず If [変数名] Is Nothing Then を用いてNothing判定(Nullチェック)を行う。
  2. 前回検索時の設定の引き継ぎによる予期せぬ検索失敗(Nothing化)を防ぐため、LookAtLookIn の引数は省略せずに必ず明示的に指定する。
  3. FindNext を使用する場合は、無限ループを防ぐために最初に見つかったセルのアドレスを必ず記録し、アドレスが一致した時点でループを脱出する。

Findメソッドは、「探せば必ず見つかるはずだ」という楽観的な前提でコードを書くのではなく、「見つからなかった場合はどう振る舞うべきか」を常に想定した防衛的なプログラミングが求められる機能です。本記事で紹介したテンプレートを基本形としてご自身のマクロに組み込み、エラーで強制終了することのない堅牢な自動化ツールを構築してください。