TL;DR — Excel のテーブルは
ListObjectです。自分の見出し・データ本体・端を追跡し、データを 追加すると 自動拡張 する範囲です。ws.ListObjects.Add(xlSrcRange, range, , xlYes)で作って名前を 付けます。各部はアドレスを計算する代わりに名前で指定します —.HeaderRowRange、.DataBodyRange、.ListColumns("Amount").DataBodyRange。行はlo.ListRows.Addで追加し、これは自動で書式を整え数式を 延長します。唯一の罠:空(見出しだけ)のテーブルでは.DataBodyRangeがNothingなので、ループはIf Not lo.DataBodyRange Is Nothingで守りましょう。
Dim lo As ListObject, r As ListRow
Set lo = Worksheets("Data").ListObjects.Add( _
SourceType:=xlSrcRange, _
Source:=Worksheets("Data").Range("A1").CurrentRegion, _
XlListObjectHasHeaders:=xlYes)
lo.Name = "tblSales"
Set r = lo.ListRows.Add ' 一番下に新しい行を追加する
r.Range.Cells(1, 1).Value = "North"
r.Range.Cells(1, 2).Value = 1200
' 数式の列は構造化参照を使って自ら埋まる
lo.ListColumns("Total").DataBodyRange.Formula = "=[@Qty]*[@Price]"
「本物の」VBA の半分は、同じ 3 行です。最後に使われた行を探し、その下に書き、数式を延長する。この 3 行はどれも、シートが自分でできる帳簿付けを、あなたが代わりにやっているのです。それこそが Excel のテーブルの核心です。テーブルは、自分の端を知っている範囲です。 見出しがどこで終わりデータがどこで始まるかを追跡し、行を追加すれば伸び、自前の名前付き列を保ちます。範囲を ListObject に一度変換すれば、最終行探しも、壊れやすい "A2:A" & lastRow の文字列組み立ても、「行を挿入したのに数式が付いてこなかった」というバグも、すべて同時に消えます。以下はすべて、この 1 つの考えを応用したものです。
この記事で学べること
- 考え方の軸 — テーブル(
ListObject)は自分の端を知り、自動拡張する ListObjects.Addによる範囲のテーブル化と、名前付け- アドレスを計算する代わりに、各部を名前で指定する
ListRowsによる確実な行の追加と削除- 空のテーブルで
DataBodyRangeがNothingになる罠 - 挿入に耐える構造化参照の数式と、行のループ
考え方の軸:自分の端を知っている範囲
素の範囲は、自分の広がりについて鈍感です — Range("A1:D100") が 100 行なのはあなたがそう言ったからで、何を追加しても 100 行のままです。ListObject は違います。自己認識を持つ範囲です。1 行目が見出しであること、データ本体がその下すべてであること、右端が最後の列であることを覚えています。一番下に行を書き込むと、その行を自分の中に取り込み、書式と数式を合わせて延長します。
Dim lo As ListObject
Set lo = Worksheets("Data").ListObjects("tblSales")
Debug.Print lo.ListRows.Count ' データの行数 — End(xlUp) は不要
Debug.Print lo.Range.Address ' 見出しから最終行まで、テーブル全体
Debug.Print lo.DataBodyRange.Address ' データだけ、見出しなし
オブジェクトが自分の端を追跡するので、「これは何行だろう?」ともう二度と尋ねません — テーブルに尋ねます。このたった 1 つの転換が、コードから End(xlUp) の儀式を取り除きます。そしてこれが、テーブルが ピボット や チャート の正しい土台である理由です。これらのオブジェクトが読む元データが、いまや自ら伸びるからです。
範囲をテーブルに変換する
既存の範囲を ListObjects.Add でテーブルに変え、コードからも数式からも明確に参照できるように名前を付けます。
Dim lo As ListObject
Set lo = Worksheets("Data").ListObjects.Add( _
SourceType:=xlSrcRange, _
Source:=Worksheets("Data").Range("A1").CurrentRegion, _
XlListObjectHasHeaders:=xlYes)
lo.Name = "tblSales" ' これでどこからでも tblSales として参照できる
CurrentRegion は Source に効く便利な技です — A1 を囲む連続ブロック全体をつかむので、広がりをハードコードせずに済みます。xlYes は 1 行目が見出しだと Excel に伝えます(ここを間違えると列名が "Column1"、"Column2" になります)。名前は飾りではありません。tblSales は、別のシートからテーブルに到達する方法(Range("tblSales"))であり、構造化参照が読む方法(tblSales[Amount])であり、ピボットキャッシュがそれを指す方法です。コードで作るテーブルにはすべて名前を付けましょう。名前のない Table1 は、いつか壊れるマジックナンバーです。
各部を名前で指定する
ここがテーブルの真価が出るところです。すべての部分に名前付きのプロパティがあるので、「金額」を "D2:D" & lastRow に翻訳するのをやめられます。
lo.HeaderRowRange ' 見出しのセル
lo.DataBodyRange ' すべてのデータ行、見出しなし
lo.ListColumns("Amount").DataBodyRange ' 1 つの列のデータ
lo.ListRows(1).Range ' 1 行まるごと
lo.Range ' すべて、見出し + 本体(+ 集計)
lo.ListColumns("Amount").DataBodyRange を、素の範囲での等価物 — 見出しを走査して Amount 列を見つけ、最終行を見つけ、アドレス文字列を継ぎ合わせる — と比べてみてください。テーブル版は、列を挿入しても、何の名前も変えず、行を千追加しても、動き続けます。集計行は lo.ShowTotals = True でオンにでき、すると lo.TotalsRowRange も現れます。列の集計は lo.ListColumns("Amount").TotalsCalculation = xlTotalsCalculationSum で設定します。
確実なやり方での行の追加と削除
行を追加するには ListRows.Add を使いましょう — テーブルの下のセルに書き込んで取り込まれるのを期待する、ということは決してしないでください。
Dim r As ListRow
Set r = lo.ListRows.Add ' 一番下に追加し、新しい行を返す
r.Range.Cells(1, 1).Value = "North"
r.Range.Cells(1, 2).Value = 1200
' 途中に挿入する場合: lo.ListRows.Add Position:=3
ListRows.Add は新しい ListRow を返し、テーブルの書式と数式をその行に延長し、テーブルに結び付いたピボットやチャートを伸ばします。Cells でテーブルの下のセルに書き込むのは、古典的な罠です。テーブルが自動拡張してそれを飲み込むこともあれば、しないこともあり(「テーブルに含める」設定と、その行が本当に隣接しているかによります)、挙動はマシンによって非決定的になります。削除も同じきれいなやり方で、インデックスを使います — lo.ListRows(2).Delete は、手作業でセルをずらすのと違い、データ行を削除して隙間を閉じます。
空のテーブルの罠と行のループ
実行時エラー 91 を投げる ListObject の落とし穴がこれです。テーブルに見出し行しかなくデータがないとき、.DataBodyRange は空の範囲ではなく Nothing です。それを何も考えずにループするとクラッシュします。
' 安全 — DataBodyRange に触れる前に守る
If Not lo.DataBodyRange Is Nothing Then
Dim cell As Range
For Each cell In lo.ListColumns("Amount").DataBodyRange
cell.Value = cell.Value * 1.1
Next cell
End If
空になりうるテーブルで DataBodyRange を読むコードは、すべて守りましょう。行をオブジェクトとしてたどるには ListRows をループします — これは空のテーブルでは自然に空(反復ゼロ回)なので、守りは要りません。
Dim lr As ListRow
For Each lr In lo.ListRows
Debug.Print lr.Range.Cells(1, 1).Value
Next lr
列の数式には、構造化参照で列全体に一度だけ書き込み、あとは埋めさせます。lo.ListColumns("Total").DataBodyRange.Formula = "=[@Qty]*[@Price]" です。[@Qty] という記法は「この行の Qty」を意味し、挿入された行や並べ替えられた列に耐え、=B2*C2 よりはるかに読みやすくなります。構造化参照が .Formula の中でどう振る舞うかは VBA Formula を、テーブルの下にある範囲の基礎は VBA Range を参照してください。
ExcelMaster の活用
時間を無駄にするテーブルのミスは、微妙なものです。空のテーブルでクラッシュしたループ、テーブルの下に書かれて取り込まれなかった行、テーブルがとっくに知っているのに End(xlUp) でまだ最終行を探しているレポート。どれもタイプミスではありません — 助けるはずの構造と戦っている、動くコードです。
ExcelMaster は、テーブルを、それがそうであるオブジェクトとして扱います。「今月の行を追加して、金額の列を合計して」と頼めば、ListRows.Add で追加し、ループする前に DataBodyRange を守り、列の数式を挿入に耐える構造化参照として書き、列を名前で指定するので、並べ替えられたシートがマクロを壊しません。あなたはデータへの変更を説明するだけ。端を計算し直す代わりにテーブル自身の端を使うのは、それがやります — そしてそのテーブルを指すすべてのピボットとチャートが、ただで更新されます。
よくある質問
VBA でテーブル(ListObject)を作成するには?
範囲を ListObjects.Add で変換します。Set lo = ws.ListObjects.Add(SourceType:=xlSrcRange, Source:=ws.Range("A1").CurrentRegion, XlListObjectHasHeaders:=xlYes) として、lo.Name = "tblSales" で名前を付けます。CurrentRegion は連続ブロック全体をつかむので広がりをハードコードせずに済み、xlYes は 1 行目が見出しを持つと Excel に伝えます。
VBA で Excel のテーブルに行を追加するには?
lo.ListRows.Add を使います。これは一番下に行を追加し、ListRow として返し、テーブルの書式と数式を延長します。Set r = lo.ListRows.Add: r.Range.Cells(1, 2).Value = 1200 です。途中に挿入するには lo.ListRows.Add Position:=3 です。Cells でテーブルの下のセルに書き込まないでください — 取り込まれるかどうかは非決定的です。
VBA で DataBodyRange がエラーになるのはなぜ?
見出し行しかなくデータのないテーブルでは、DataBodyRange が空の範囲ではなく Nothing に等しいので、それに触れるコードはすべて実行時エラー 91 を出します。守りましょう。If Not lo.DataBodyRange Is Nothing Then ... End If です。あるいは lo.ListRows をループします — これは空のテーブルでは単に 0 回反復するだけなので、守りは要りません。
VBA でテーブルの列を参照するには?
ListColumns を使います。lo.ListColumns("Amount").DataBodyRange は見出しを除いたその列のデータで、lo.ListColumns("Amount").Range は見出しを含みます。数式では構造化参照を使います — lo.ListColumns("Total").DataBodyRange.Formula = "=[@Qty]*[@Price]" — ここで [@Qty] は「この行の Qty」を意味し、挿入された行や並べ替えられた列に耐えます。
End(xlUp) で最終行を探すのをやめるには?
範囲をテーブルに変換します。ListObject は自分の端を追跡するので、lo.ListRows.Count が行数を、lo.DataBodyRange がデータを与え、lo.ListRows.Add はアドレス計算なしで追加します。テーブルは行を追加すると自動拡張し、これはまさに End(xlUp) が手作業でやっていた帳簿付けです。
検証環境
検証環境: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-09-02.
関連ガイド: VBA Pivot Table · VBA Chart · VBA Range · VBA Formula · VBA AutoFilter
