この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Microsoft LearnのListObjectおよびDataBodyRangeの定義を参照し、Excelテーブルのヘッダーとデータ行を区別した読み取り手順をまとめます。検証ステータス:📘 公式仕様確認済み・Excel実機未実行
Excel VBAのテーブルが空のときに止まる原因:ListObjectとDataBodyRangeを安全に読む
問題:先月は動いたのに今月は止まった
Excelのテーブルを対象にしたVBAで、月初の台帳にはまだデータがなく、件数確認のつもりでDataBodyRange.Rows.Countを評価したらエラーになることがあります。データ行が一件以上存在することを暗黙の前提にしてしまうのが原因の一つです。見出しだけの表と、データがある表とでは、行を取得できる条件が違います。
初心者にとって紛らわしいのは「テーブルは存在するのにデータ範囲が存在しない」状況です。ListObjectはテーブルそのものを表すオブジェクトですが、DataBodyRangeはヘッダーを除いたデータ部分を表します。だから空のテーブルを判定するときは、テーブルの存在確認とデータ行の有無の確認を別々に行います。
ダミーの練習テーブルを作る
Windows版Excelデスクトップで新しいブックを開き、シートを「確認用」にします。A1に「品目」、B1に「担当」、C1に「状態」と入力し、表を選択して[挿入]→[テーブル]で見出し付きテーブルを作成します。テーブル名をExcelのテーブルデザインから「案件一覧」に変更してください。
最初はデータを一行だけ追加し、A2「資料整理」、B2「担当A」、C2「未着手」のようなダミー値を入れます。後でデータ行を削除して空テーブルを試します。企業の正式な台帳を使う必要はありません。なお、テーブル名が変わったままだとVBAは対象を見つけられません。シート名とテーブル名を両方照合してください。
読み取り専用の安全なサンプル
Option Explicit
Public Sub InspectTableRows()
Dim ws As Worksheet
Dim tbl As ListObject
Dim dataRange As Range
Dim rowCount As Long
Set ws = ThisWorkbook.Worksheets("確認用")
Set tbl = ws.ListObjects("案件一覧")
Set dataRange = tbl.DataBodyRange
If dataRange Is Nothing Then
Debug.Print "データ行はありません"
Exit Sub
End If
rowCount = dataRange.Rows.Count
Debug.Print "データ行数=" & CStr(rowCount)
Debug.Print "先頭行の品目=" & CStr(dataRange.Cells(1, 1).Value2)
End Sub
このコードは表を更新しません。データ行が一件あるときは「データ行数=1」「先頭行の品目=資料整理」の二行が表示される想定です。データ行をすべて削除した状態では「データ行はありません」の一行になり、データ範囲のRows.Countへ進まない設計です。これらは公式APIの説明から組み立てた期待結果であり、Excel実機で得たログではありません。
なぜNothingを確認するのか
Nothingはオブジェクト参照が設定されていない状態を示します。VBAでは、オブジェクトであるRangeを確認する際に「Is Nothing」を使います。文字列の空欄を調べる方法とは違います。テーブルの中身がゼロ行なら、DataBodyRangeがNothingになり得ます。そこで先に存在確認を行い、その後にRows.CountやCellsを使う順序にします。
Countがゼロになると思い込んでDataBodyRange.Rows.Countを直接呼ぶと、DataBodyRange自体がNothingのとき、そのプロパティを参照できません。「データなし」を正常な業務状態として扱うことが設計のポイントです。月初の伝票一覧、未着手のタスク表、処理済みデータを消去した検査表など、ゼロ件があり得る帳票にはこの発想が必要です。
ListObjectのHeaderRowRangeは見出し、DataBodyRangeはデータ、TotalsRowRangeは集計行に関連します。これらを混ぜて行数を数えると「ヘッダー一行をデータとして数えてしまう」ような不具合につながります。DataBodyRangeを使うことで意図をコード上に明示できますが、テーブル自体が無い場合の対処は別です。
一か所変更して挙動を確認
一件のデータを入れた状態で実行し、次にテーブルのデータ行だけ削除して再実行してください。予想した表示と照らします。さらにA2へ値を入れ直し、再度一件を検出できるか確認します。
次に、VBAのテーブル名をわざと「案件一覧2」に変更して試します。この場合はDataBodyRangeの確認へ進む前に、ListObjectsの名前解決で実行時エラーになります。名前のタイプミスは「空テーブル」と同じ原因ではありません。現場では、必要なシートやテーブルが存在しない場合に分かりやすいメッセージで止める例外処理を足すと、運用担当者の切り分けが容易になります。
実務で役立つ切り分け
テーブル内に空白の行が一行残っている場合、DataBodyRangeが存在することがあります。「行の存在」と「業務項目が入力されていること」は別です。空白行も除きたいなら、件数だけでなく必須列の値やステータスの内容を判定してください。逆に「未入力行も管理対象」であれば、空白行を安易に除外すると取りこぼします。
AutoFilterで非表示になった行も、DataBodyRangeというテーブルのデータ範囲に含まれます。表示行だけを数えたい処理では、フィルターとSpecialCellsなど別の考慮が必要です。今回の例は「テーブルの全データ行数」であり「今画面に見えている行数」ではありません。この区別が曖昧だとレポートの件数が合わなくなります。
処理対象をThisWorkbookからActiveWorkbookへ変更する場合は、アクティブ状態で動作先が変化する点に注意してください。マクロを別ブックから起動する設計なら、対象ブックを明示的に受け取る方が安全です。また、削除や加工のVBAへ拡張する前に、ゼロ件、一件、複数件、フィルター中、集計行ありという五つのテストケースを用意すると事故を減らせます。
行数だけを知りたいときはListRowsも使える
今回の主題はDataBodyRangeが存在しない場合の安全な処理ですが、単にデータ行の数を数えるだけなら、ListRowsコレクションのCountを使う選択肢もあります。ListRowsはテーブルのデータ行のコレクションで、Microsoftの公式仕様ではヘッダー、集計行、挿入行を含まないと説明されています。
Debug.Print "データ行数=" & CStr(tbl.ListRows.Count)
上記は最初のコードでtblが取得できた後に実行する一行です。テーブル自体が存在しない場合の例外には対応しません。データ行が0件かどうかを知りたいだけなら、DataBodyRangeからRangeを受け取るより直接的です。一方、データ行の中にあるセルを参照したい場合は、DataBodyRangeを取得してNothingを調べてから処理する方が意図が明確になります。
「テーブルの行がゼロ」「一行はあるがセルが空」「フィルターにより画面で見えない」の三つを同じ状態だと考えないことが重要です。前者はテーブルのデータ行数、二番目はセルの内容、三番目は表示状態です。何を数えたいのかを決めてからプロパティを選んでください。
件数が合わないときの確認順
「テーブルの行数」と「何らかの値が入力された行数」は一致しない場合があります。データ行だけ削除した状態、値だけ消去して空白行が残った状態、フィルターで非表示になった状態を別々に作って観察してください。DataBodyRange.Rows.Countが表すのはデータ範囲の行数であって、入力済みレコード数や画面上の表示行数ではありません。件数の定義を帳票の仕様書に書くことが大切です。
表が見つからない場合は、シート名の変更とテーブル名の変更を最初に疑います。シート名をコードに直接書く設計では、利用者による名称変更に弱くなります。運用環境では、作業対象を確認できるメッセージと、対象ファイル名・テーブル名を残す診断ログが役に立ちます。ログに顧客名や個人情報を含む実データを出す必要はありません。
公式情報
今回の小さなコードが示すのは、空のテーブルを異常ではなく通常のケースとして扱うことです。ゼロ件のときに安全に終了できるコードは、データがあるときだけ動くコードよりも日常運用で信頼できます。
