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

Excel VBA の Offset — セルからの相対移動(Offset と Resize、そして 1 つずれる罠)

|

Excel VBA の Offset — セルからの相対移動(Offset と Resize、そして 1 つずれる罠)

TL;DRrng.Offset(rowDelta, colDelta) は、rng から指定した行数・列数だけ ずらした新しい範囲を返します。これは場所ではなく一歩です。同じサイズを 保ち、rng 自体は変えず、何も選択しません。2 つの数値は相対的です — Offset(1, 0) は 1 行Offset(0, 0) はそのセル自身、負の値は上・左へ 進みます。Offset は移動し、Resize はサイズを変えます。この 2 つを組み合わせる と、VBA で最も便利な範囲イディオムが手に入ります。 data.Offset(1).Resize(data.Rows.Count - 1) — 「見出しを除くすべて」です。

Offset は、マクロが「今見つけたセルの隣のセル」と言うための方法です。一致した 値・合計・見出しを見つけたところで、その 1 列隣、あるいは 1 行下のセルを読み書き したくなります。その相対的な移動こそが Offset の仕事のすべてで、引数を座標として ではなく歩数として読むようになれば、自然と身につきます。

この記事で学べること

  • 考え方の軸:Offset は絶対アドレスではなく、相対的な一歩
  • 1 つずれる罠 — なぜ Offset(1, 1) は A1 ではなく B2 に着くのか
  • Offset はサイズを保ち、Resize はサイズを変える — そしてなぜ普通は両方必要なのか
  • 見出しを飛ばす定番イディオム Offset(1).Resize(n - 1) の解説
  • Range.Offset(VBA)が OFFSET() ワークシート関数とどう違うのか

考え方の軸:Offset は場所ではなく一歩

あるセルに立ってみましょう。Offset(rowsDown, colsRight) はその歩数だけ進み、 着いたセル(またはブロック)を渡します — まったく新しい範囲です。元の範囲は そのまま。移動は相対的なので、同じ Offset(1, 0) は、A1 からでも Z500 からでも 「1 行下」を意味します。

Sub OffsetSteps()
    Debug.Print Range("B2").Offset(1, 0).Address   ' -> $B$3   (1 行下へ)
    Debug.Print Range("B2").Offset(0, 1).Address   ' -> $C$2   (1 列右へ)
    Debug.Print Range("B2").Offset(-1, 0).Address  ' -> $B$1   (1 行上へ)
    Debug.Print Range("B2").Offset(0, 0).Address   ' -> $B$2   (移動なし)
End Sub

Offset(0, 0) がそのセル自身になることに注目してください。これが、引数が位置では なく差分(デルタ)である証拠です。絶対的なセルが欲しいなら、A1 からのオフセットを 計算するのではなく Cells(row, col) を使います。Offset は「すでに持っている何かからの 相対」のためのものです。

初心者がはまるルール:1 つずれる罠

Offset の最も多いバグは、引数を座標として読んでしまうことです。Offset(1, 1) は、 セル A1 や「1 行目・1 列目」を意味しません。基準セルから1 行下・1 列右を 意味します。A1 からなら B2 に着きます。

Range("A1").Offset(1, 1).Address   ' -> $B$2($A$1 ではない)
Range("A1").Offset(0, 0).Address   ' -> $A$1

2 つの引数を毎回「下、右」と声に出して読めば、このバグは消えます。これは定番の ループパターンの説明にもなります。見つけた各行の隣に結果を書くには、ループ カウンターの分だけオフセットします。そして最初の行は Offset(1, …) ではなく Offset(0, …) を使います。

誰もがつまずく違い:Offset は移動、Resize はサイズ変更

Offset は範囲の大きさを決して変えません — 形全体をスライドさせます。10 セルの 列を移動すれば、別の場所に 10 セルの列が得られます。

Range("A1:A10").Offset(0, 1).Address   ' -> $B$1:$B$10   (10 セル、右へ移動)

範囲のサイズを変えるには、別のメソッド Resize(rows, cols) が必要です。これは 左上のセルを起点にブロックを描き直します。

Range("A1").Resize(5, 2).Address       ' -> $A$1:$B$5   (5 行、2 列)

この 2 つは範囲演算の両輪です。Offset はどこを、Resize はどれくらいの大きさを 決めます。両方をセットで使う場面は絶えずあります — 最も多いのは見出し行を落とす ときです。

覚える価値のあるイディオム:Offset + Resize で見出しを飛ばす

見出しを含む表があり、データ本体だけが欲しいとします。1 行下にオフセットして 見出しから外れます — ただし Offset だけでは高さが同じままなので、範囲はデータより 1 行はみ出てしまいます。Resize でその 1 行分だけ縮めて戻します。

Sub DataBodyOnly()
    Dim tbl As Range, body As Range
    Set tbl = Range("A1").CurrentRegion          ' 見出し + データ
    Set body = tbl.Offset(1).Resize(tbl.Rows.Count - 1)
    body.Select                                  ' データ行だけ、見出しなし
End Sub

Offset(1)(列の差分は既定で 0)はブロック全体を 1 行下にスライドさせ、 Resize(tbl.Rows.Count - 1) は、はみ出した最下行を切り落とします。結果は見出しを 取り除いたデータ本体 — 並べ替え・コピー・消去にちょうど欲しい範囲です。この 1 行が、 A2:A の文字列を手で組み立てる多くの作業を置き換えます。tbl をベタ書きせずに 求める方法は、最終行の求め方CurrentRegion を参照してください。

シートの端の罠

Offset は相対的なので、ワークシートの端を越えて進むと、無言で丸め込まれるのでは なくエラーになります。

Range("A1").Offset(-1, 0)   ' 実行時エラー 1004 — 1 行目より上には行けない

マクロが左上の角の近くで上や左へ進む場合(見出しを探すときによくあります)は、その 移動をガードするか、上向きにオフセットする前に rng.Row > 1 を確認しましょう。

メソッドの Offset とワークシート関数の OFFSET

名前も考え方も同じですが、同じ道具ではありません。ワークシート関数の OFFSET() はセルの中に置かれ、揮発性(volatile)で (変更のたびに再計算され、ブックが遅くなることがあります)、数式のために値または 参照を返します。Range.Offset は VBA のメソッドで、Range オブジェクトを返し、 揮発性ではなく、コードが動くときだけ実行されます。マクロを書いているならメソッド を使いましょう。ワークシート関数に手を伸ばすのは、数式の中で常に自動更新される 参照が必要なときだけです。

ExcelMaster の活用

Offset を多用するコードは、たいてい 1 つのものを組み立てています。見つけた何かに 対して相対的に置かれる結果 — 各一致の隣のフラグ、1 列隣の累計、ラベルの下の行から コピーした値です。ExcelMaster は、その意図を平易な日本語で受け取ります — 「延滞している請求書それぞれの隣に、延滞日数を書いて」といった具合に — そして 相対参照のロジックを生成するので、差分を手で数えたり、1 つずれるバグをデバッグ したりせずに済みます。

きっちり書いたマクロのループの中では、これからも Offset を書くでしょう。しかし 「各行の隣に結果を置く」という日々の作業なら、(1, 0)(0, 1) を頭の中で考える より、配置を言葉で説明する方が速く、間違えにくいのです。

よくある質問

VBA の Offset は何をするのですか?

rng.Offset(rowDelta, colDelta) は、rng から指定した行数・列数だけずらした新しい 範囲を、同じサイズのまま返します。相対的な移動です。Offset(1, 0) は 1 行下、 Offset(0, 1) は 1 列右、負の値は上または左へ動きます。元の範囲を変えることも 選択することもありません。

なぜ Offset(1, 1) はセル A1 を返さないのですか?

引数が座標ではなく差分だからです。Offset(1, 1) は基準セルから 1 行下・1 列右を 意味するので、Range("A1").Offset(1, 1) は B2 になります。相対的な一歩ではなく 絶対的なセルが欲しいときは Cells(row, col) を使いましょう。

Offset と Resize の違いは何ですか?

Offset は範囲をサイズを変えずに移動し、Resize(rows, cols) は移動せずに左上の セルを起点にサイズを変えます。両者はよく組み合わせます — rng.Offset(1).Resize(rng.Rows.Count - 1) は、1 行下げて 1 行縮めることで見出し行を 飛ばします。

Offset で見出し行を飛ばすには?

table.Offset(1).Resize(table.Rows.Count - 1) を使います。Offset(1) はブロックを 1 行下げて見出しの先へ動かし、Resize は、はみ出した最下行を切り落として、データ 本体だけを残します。

VBA の Range.Offset は OFFSET ワークシート関数と同じですか?

いいえ。Range.OffsetRange オブジェクトを返す VBA のメソッドで、揮発性では ありません。OFFSET() ワークシート関数はセルの中に置かれ、揮発性で(絶えず再計算 されます)、数式のために値または参照を返します。マクロではメソッドを、常時更新 される数式の参照が必要なときだけ関数を使いましょう。

検証環境

検証環境: Excel 365(Windows 11)、VBA 7.1 — 最終確認 2026-07-30。

関連ガイド: VBA Last Row · VBA UsedRange vs CurrentRegion · VBA Range · OFFSET Worksheet Function