VBAで最終列を取得する方法|結合セルの落とし穴も解説

スポンサーリンク

「最終行は取れるようになったけど、最終列ってどう書くんだろう?」

VBAでマクロを組み始めると、次にぶつかるのがこの疑問ですよね。月ごとに列が増えていく集計表。他部署から届くフォーマットがバラバラのCSV。こうした列数が毎回変わる表を扱うようになると、最終列を自動で取得する方法が必要になります。

とはいえ、列数を手で数えてコードを書き直すのは面倒です。数え間違いでバグが出ることもありますよね。そこで役立つのが、Excel VBAで最終列を自動取得するテクニックなんです。

この記事では、End(xlToLeft)Cells.FindUsedRange という3つの方法をコード付きで比較します。さらに、多くの解説記事が触れていない「結合セルがあると最終列がズレる」落とし穴も、実際の表で検証しながら解説しますね。最終行の取得と同じ感覚で使いこなせるようになりますよ。

VBAで最終列を取得する3つの基本方法(結論と比較表)

最初に結論からお伝えします。VBAで最終列を取得する定番は次の3つです。

  • End(xlToLeft) — シートの右端から左に向かってデータを探す方法
  • Cells.Find — 空白列を無視して最後のデータセルを探す方法
  • UsedRange — シートで使われている範囲全体から最終列を割り出す方法

どれも一長一短があります。まずは3つの特徴を比較表で整理してみましょう。

項目End(xlToLeft)Cells.FindUsedRange
探し方右端から左へ探索全セルから最後のデータを検索使用範囲全体を判定
精度高い(ただし途中の空白列に注意)最も高いやや不安定
速度速いやや遅い速い
空白列の影響受けにくい受けない受けない
書式だけのセル無視する無視する含んでしまう
結合セルズレやすい比較的正確比較的正確
向いている場面1行目に見出しが整った表空白列が混ざる表範囲をざっくり掴みたいとき

迷ったら、まずは End(xlToLeft) から覚えてください。最終行取得で使う End(xlUp) の「向きを変えただけ」なので、いちばん理解しやすい方法なんです。ただし表の状態によっては正しく取れないケースもあります。その使い分けを、ここから1つずつ見ていきましょう。

なお、最終行の取得がまだあやふやな方は、姉妹記事の「VBA最終行の取得はEnd xlUpが基本!3つの方法を比較」を先に読んでおくと、この記事がぐっと理解しやすくなりますよ。

VBAで最終列を取得する3つの方法(コード付き)

ここから、比較表で紹介した3つの方法をそれぞれ具体的なコードで見ていきましょう。

End(xlToLeft)で最終列を取得する書き方

いちばんよく使うのが End(xlToLeft) です。考え方は最終行取得とまったく同じ。「下から上」を「右から左」に変えるだけなんです。

基本コード(1行目基準)

まずは1行目(見出し行)を基準に最終列を取得する定番コードです。

Dim lastCol As Long
lastCol = Cells(1, Columns.Count).End(xlToLeft).Column

このコードは「1行目の最終列」を取得しています。処理の流れをかんたんに説明しますね。

  1. Columns.Count でシートの最大列数(16,384列)を取得する
  2. Cells(1, 16384) で1行目の一番右のセル(XFD1)を指定する
  3. .End(xlToLeft) で、そこから左方向にデータの入ったセルを探す
  4. .Column で、見つかったセルの列番号を数値で返す

これはExcelで Ctrl + 左矢印キー を押したときと同じ動きです。シートの右端から左に向かってデータを探すので、途中に空白列があっても影響を受けにくいのが強みですよ。

Columns.Count(列の最大数を返すプロパティ)を使えば、ファイル形式に関係なく正しい列数が取れます。数値を直接書く必要はありません。

NOTE

Columns.Count はワークシートの最大列数を自動で返してくれます。Excel 2007以降のファイル形式(.xlsx / .xlsm)では16,384列(XFD列まで)が上限です。Excel 2003以前の形式(.xls)なら256列(IV列まで)になります。動作環境はMicrosoft 365・Excel 2021・2019で共通。マクロを保存するときは、必ずマクロ有効ブック(.xlsm)形式を選んでくださいね。

特定の行・シートから検索する場合

見出しが1行目ではなく2行目にあるケースもありますよね。その場合は Cells の行番号を変えるだけでOKです。

'--- 2行目を基準に最終列を取得 ---
Dim lastCol As Long
lastCol = Cells(2, Columns.Count).End(xlToLeft).Column

複数シートを扱うマクロでは、対象シートを明示しておくと安全です。With ステートメントを使いましょう。

Sub シート指定で最終列を取得()
    Dim lastCol As Long

    With ThisWorkbook.Worksheets("売上データ")
        lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
    End With

    MsgBox "最終列: " & lastCol
End Sub

ポイントは .Cells.Columns.Count の先頭にドット(.)をつけること。このドットが、With で指定したシートを参照している印です。ドットを忘れると、意図せず ActiveSheet(現在アクティブなシート)を見にいってしまうので気をつけてくださいね。

セルの指定方法をもっと詳しく知りたい方は、「VBA RangeとCellsの違いと使い分け|実務シナリオ別に選び方を解説」も参考になりますよ。

Cells.Findで最終列を取得する書き方(空白列があっても正確)

End(xlToLeft) は基本的に優秀です。ただし1行目に空白列が混ざる表だと、途中で止まってしまうことがあります。そんなときに頼れるのが Cells.Find なんです。

基本コード

Find メソッドを使って、シート全体から「最後にデータが入っている列」を探すコードがこちらです。

Dim lastCol As Long
lastCol = Cells.Find(What:="*", _
                     After:=Cells(1, 1), _
                     LookIn:=xlFormulas, _
                     LookAt:=xlPart, _
                     SearchOrder:=xlByColumns, _
                     SearchDirection:=xlPrevious).Column

ちょっと引数が多くて難しく見えますよね。でも、やっていることはシンプルです。1つずつ意味を確認しましょう。

  • What:="" — ワイルドカード「」で、何かしら文字が入ったセルを対象にする
  • After:=Cells(1, 1) — A1の「次」から探す。これで実質シートの末尾から検索が始まる
  • LookIn:=xlFormulas — 数式で空文字を返すセルも取りこぼさない
  • SearchOrder:=xlByColumns — 列方向で探す(最終列を取りたいので列順)
  • SearchDirection:=xlPrevious — 末尾から前へ向かって探す

Find はシート全体のセルを見て、いちばん右にデータがある列を返してくれます。だから1行目に空白列があっても、データ本体さえ埋まっていれば正確な最終列が取れるんです。

引数を毎回きちんと書くのには理由があります。FindLookInLookAt などの設定は、一度指定すると次回以降も保存される仕様なんです。前回の検索設定が残っていると、思わぬ結果を返すことがあります。公式ドキュメントでも「毎回明示的に指定すること」が推奨されていますよ。

なお、データが1つもないシートで Find を実行すると、戻り値が Nothing になってエラーが出ます。空シートも通り得るマクロでは、あとで紹介するエラー対策を入れておきましょう。

Endとの使い分けの判断基準

EndFind は、どちらを使うべきか迷いますよね。判断はシンプルです。

  • 見出し行がキレイに埋まっている表End(xlToLeft) で十分。速くてコードも短い
  • 1行目に空白列が混ざる・見出しが不揃いな表Cells.Find が安全

他部署から届くフォーマット違いのファイルを処理するなら、Find を選んでおくと事故が減ります。処理速度より確実性を優先したい場面、と覚えておくと迷いませんよ。

UsedRangeで最終列を取得する書き方

3つ目は UsedRange プロパティを使う方法です。シートで「使われている範囲」全体から最終列を割り出します。

基本コード

Dim lastCol As Long
lastCol = ActiveSheet.UsedRange.Columns.Count + ActiveSheet.UsedRange.Column - 1

UsedRange(ワークシートで使用されている範囲を返すプロパティ)は、データが入力されている範囲を自動で判定してくれます。

気になるのは + ActiveSheet.UsedRange.Column - 1 の部分ですよね。これは A列から表が始まっていないケースに対応するため の補正です。

たとえばC列からデータが始まっている表を考えてみましょう。

  • UsedRange.Columns.Count — 使用範囲の列数を返す(例: 5列)
  • UsedRange.Column — 使用範囲の開始列番号を返す(例: 3列目=C列)

Columns.Count だけだと結果は「5」になります。でも実際の最終列はG列(3 + 5 – 1 = 7列目)ですよね。だから開始列を足して1を引く補正が必要なんです。A列から始まる表なら、この補正は結果に影響しません。安全のため、いつも付けておくのがおすすめです。

書式だけ残ったセルがある場合の注意点

UsedRange には落とし穴が1つあります。書式が設定されただけのセル も「使用済み」とみなす点です。

過去にデータを入れて削除しても、背景色や罫線が残っていると、そのセルは使用範囲に含まれ続けます。その結果、実際のデータより右の列番号が返ってしまうんです。

対処法はシンプルです。余分な列をまるごと選択して、「クリア」→「すべてクリア」を実行してください。Deleteキーは値しか消さないので、書式が残ったままになります。ここは要注意ポイントですよ。

【落とし穴】結合セルがあると最終列がズレる理由

ここからが、この記事でいちばんお伝えしたい内容です。結合セルがある表 では、最終列の取得が思わぬ形でズレます。多くの解説記事が触れていない、実務でハマりやすいポイントなんです。

実機検証:結合セルの左上セルだけがカウントされる

具体例で見てみましょう。次のような売上集計表があるとします。

  • 1行目が見出し行。A列からG列までデータが入っている(本来の最終列はG=7列目)
  • ただし見出しの E1:G1 が「第1四半期」というグループ見出しとして横方向に結合されている
  • 2行目以降のデータ本体は、A列からG列まですべて埋まっている

この表で End(xlToLeft) を実行すると、どうなるでしょうか。

Sub 結合セルで最終列を検証()
    Dim lastCol As Long
    lastCol = Cells(1, Columns.Count).End(xlToLeft).Column
    MsgBox "取得した最終列: " & lastCol   '→ 5 が返る(本来は 7)
End Sub

結果は「5」。本来のG列(7)ではなく、E列(5)が返ってきます。

理由は、結合セルの値が左上のセルにしか入っていないからです。E1:G1 を結合すると、値はE1に格納され、F1とG1は空っぽの状態になります。右端から左へ探す End(xlToLeft) は、空のG1・F1を飛ばして、値のあるE1で止まってしまうわけです。だから列番号が2つ手前でズレるんですね。

一方で Cells.FindUsedRange は、データ本体(2行目以降)のG列を見てくれます。そのため、この表では正しく「7」を返します。ここが3つの方法の実力差が出るポイントですよ。

結合セルに対応したコード(結合列数を加算する)

「どうしても見出し行を基準にしたい」「結合セルがあってもEndで取りたい」。そんなときは、結合された列数を後から足してあげればOKです。

判定に使うのが MergeCells プロパティ(範囲に結合セルが含まれるとTrueを返す)です。

Sub 結合セルに対応して最終列を取得()
    Dim lastCol As Long
    Dim rngEnd As Range

    '--- まず通常どおり End で末端セルを取得 ---
    Set rngEnd = Cells(1, Columns.Count).End(xlToLeft)
    lastCol = rngEnd.Column

    '--- 末端が結合セルなら、結合列数の分だけ加算する ---
    If rngEnd.MergeCells Then
        lastCol = lastCol + rngEnd.MergeArea.Columns.Count - 1
    End If

    MsgBox "補正後の最終列: " & lastCol   '→ 7 が返る
End Sub

MergeArea.Columns.Count は、結合セルが何列分あるかを返します。今回のE1:G1なら「3」です。計算は 5 + 3 - 1 = 7。これで本来の最終列にピタリと合いますよね。

結合セルの有無を条件分岐で処理する書き方は、「VBA If文の使い方|条件分岐を基本から実務コードまで解説」も参考にしてみてください。

ただ、いちばんカンタンな回避策は「見出し行ではなくデータ本体の行を基準にする」ことです。結合セルは見出しに使われがち。データ本体が全列埋まっているなら、Cells.Find で全体を探すのがいちばん確実ですよ。

最終行・最終列を組み合わせて表範囲を動的に取得する

最終列が取れるようになったら、次は実務での活用です。最終行と最終列をセットで取れば、行数も列数も変わる表を丸ごと自動で扱えます。

Range(Cells, Cells)で表全体を選択する

最終行と最終列を両方取得して、データ範囲全体を動的に指定するコードがこちらです。

Sub データ範囲を動的に取得()
    Dim lastRow As Long
    Dim lastCol As Long

    '--- 最終行と最終列をそれぞれ取得 ---
    lastRow = Cells(Rows.Count, 1).End(xlUp).Row
    lastCol = Cells(1, Columns.Count).End(xlToLeft).Column

    '--- A1から右下までを範囲として選択 ---
    Range(Cells(1, 1), Cells(lastRow, lastCol)).Select

    MsgBox "データ範囲: A1:" & Cells(lastRow, lastCol).Address
End Sub

Range(Cells(1, 1), Cells(lastRow, lastCol)) と書くと、A1から右下のセルまでを1つの範囲として扱えます。行が増えても列が増えても、コードを直さず対応できるのが便利なところですよ。

取得した最終列を使って列方向にループ処理したい場面もありますよね。その書き方は「VBA For~Next文の使い方|繰り返し処理の基本から実務パターンまで解説」で解説しています。

列番号をアルファベット表示にする(実務パターン)

End(xlToLeft).Column が返すのは「7」のような数値です。でも実務では「G列」のようにアルファベットで表示したい場面がありますよね。1〜2行で変換できます。

Dim lastCol As Long
Dim colLetter As String

lastCol = Cells(1, Columns.Count).End(xlToLeft).Column

'--- 列番号をアルファベットに変換 ---
colLetter = Split(Cells(1, lastCol).Address, "$")(1)

MsgBox "最終列は " & colLetter & " 列です"   '→ 7 なら「G 列」

Cells(1, 7).Address$G$1 を返します。これを「$」で区切ると ["", "G", "1"] の配列になり、その2番目(インデックス1)が列名の「G」というわけです。

列番号とアルファベットの変換をもっと深掘りしたい方は、「Excelで列番号をアルファベットに変換する方法|COLUMN・ADDRESS・SUBSTITUTEの組み合わせ技」で詳しく紹介しています。関数での変換テクニックもまとめていますよ。

よくあるトラブルと対処法

最終列の取得はシンプルに見えて、意外とハマりポイントがあります。事前に知っておけば慌てずに済むので、チェックしておきましょう。

「最終列が正しく取得できない」時のチェックリスト

「コードは合っているはずなのに、取得した最終列が変……」というときは、次の3点を順番に確認してみてください。

  1. 基準にした行は正しいかCells(1, Columns.Count) の行番号(1つ目の引数)が、見出しの入った行になっているか確認しましょう。見出しが2行目にあるのに1行目を指定していた、というミスは意外と多いんです。
  2. その行に結合セルがないか — 前の章で見たとおり、結合セルがあると End は手前で止まります。返り値が想定より小さいなら、まず結合を疑ってみてください。
  3. 書式だけのセルが右に残っていないかUsedRange を使っている場合は要注意。過去のデータ削除で書式が残っていると、実際より大きな列番号が返ります。

チェックの順番に迷ったら、いったん Cells.Find に切り替えてみるのも手です。結果が変われば、原因は空白列か結合セルだと当たりがつきますよ。

空白セルが混ざる表での回避策

途中に空白列が混ざる表では、End(xlToLeft) が空白の手前で止まることがあります。1行目がスカスカな表だと、特に起きやすいトラブルです。

回避策は2つあります。1つは、確実にデータが埋まっている行を基準にすること。もう1つは、Cells.Find でシート全体から探すことです。

そして Find を使うときは、空シート対策も忘れずに入れておきましょう。

Sub 安全に最終列を取得()
    Dim rngFound As Range
    Dim lastCol As Long

    Set rngFound = Cells.Find(What:="*", _
                             After:=Cells(1, 1), _
                             LookIn:=xlFormulas, _
                             LookAt:=xlPart, _
                             SearchOrder:=xlByColumns, _
                             SearchDirection:=xlPrevious)

    '--- 見つからなければ Nothing になるので判定する ---
    If rngFound Is Nothing Then
        lastCol = 1   'データがない場合は1列目を返す
    Else
        lastCol = rngFound.Column
    End If

    MsgBox "最終列: " & lastCol
End Sub

Find は該当セルがないと Nothing を返します。それをそのまま .Column につなぐとエラーになるので、If rngFound Is Nothing Then で受け止めておくのがコツ。これで空シートを渡されても止まらない、丈夫なマクロになりますよ。

VBAをこれから体系的に学びたい方は、コードを書く前段階として「VBEの画面の見方を図解で解説|6つのウィンドウの名前と役割を初心者向けに整理」もあわせてどうぞ。

まとめ|VBA最終列取得は表の状態で方法を使い分ける

VBAで最終列を取得する3つの方法を紹介しました。最後にポイントを整理しておきますね。

  • End(xlToLeft) — 定番でいちばん手軽。見出しがキレイに埋まった表向き
  • Cells.Find — 空白列があっても正確。フォーマット違いのファイル処理に強い
  • UsedRange — 範囲をざっくり掴みたいとき向き。書式だけのセルに注意

そして最大の注意点が 結合セル です。End(xlToLeft) は結合セルの手前で止まり、左上セルの列番号を返してしまいます。見出しに結合セルがある表では、データ本体の行を基準にするか、Cells.Find を使うのが安全でしたよね。

どの方法にも得意・不得意があります。だからこそ「表の状態を見て使い分ける」のがいちばん確実です。まずは今回のコードをコピーして、ご自身の表で試してみてください。想定どおりの列番号が返るか、MsgBox で確かめながら覚えていくのがおすすめですよ。

最終行の取得とあわせて使えば、行数も列数も変わる表を丸ごと自動化できます。まだ最終行があやふやな方は、姉妹記事の「VBA最終行の取得はEnd xlUpが基本!3つの方法を比較」もぜひチェックしてみてくださいね。

タイトルとURLをコピーしました