最終行・最終列の取得

にメンテナンス済み

VBA でセルのデータを処理する際、「データが何行目まであるのか」を正確に把握することは欠かせません。行数をハードコーディング(直接数値で指定)してしまうと、データが増減するたびにコードを修正しなければなりません。

この記事では、VBA でデータの最終行・最終列を動的に取得する方法を3つ紹介し、それぞれの特徴と使い分けを詳しく解説します。

最終行・最終列の取得が必要となるシチュエーション

実務では、以下のような場面で最終行・最終列の取得が必要になります:

  • データの一括処理: 表のすべての行をループで処理する際の終了条件
  • データの追記: 既存データの最終行の次の行に新しいデータを追加する
  • 範囲の動的指定: データ量に応じてグラフの範囲やコピー範囲を自動調整する
  • 空白行の検出: データの途中に空白行がないか確認する
  • レポートの生成: データ件数に応じてレポートの書式を動的に設定する

方法1:End プロパティ(最も一般的)

基本的な考え方

End プロパティは、Excel で Ctrl + 方向キー を押したときと同じ動作をします。つまり、指定した方向にデータの端まで移動します。

最終行を取得する最も一般的な方法は、シートの最終行から上方向(xlUp)に向かって、最初にデータがあるセルを見つけるというアプローチです。

最終行の取得

end_xlup_basic.bas
Sub GetLastRow()
    Dim lastRow As Long

    ' A列の最終行を取得
    lastRow = Cells(Rows.Count, 1).End(xlUp).Row

    Debug.Print "A列の最終行: " & lastRow
End Sub

処理の流れ:

  1. Cells(Rows.Count, 1) — A列の最後のセル(A1048576)を起点にする
  2. .End(xlUp) — そこから上方向(xlUp)に Ctrl + ↑ と同じ動きで移動する
  3. .Row — 到達したセルの行番号を取得する
end_xlup_detail.bas
Sub GetLastRowDetail()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    ' A列の最終行
    Dim lastRowA As Long
    lastRowA = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    ' B列の最終行
    Dim lastRowB As Long
    lastRowB = ws.Cells(ws.Rows.Count, 2).End(xlUp).Row

    ' 複数列の中で最大の最終行
    Dim lastRow As Long
    lastRow = Application.WorksheetFunction.Max(lastRowA, lastRowB)

    Debug.Print "A列の最終行: " & lastRowA
    Debug.Print "B列の最終行: " & lastRowB
    Debug.Print "最大の最終行: " & lastRow

    Set ws = Nothing
End Sub

最終列の取得

最終列も同様の考え方で、**シートの最終列から左方向(xlToLeft)**に移動して取得します。

end_xltoleft.bas
Sub GetLastColumn()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    ' 1行目の最終列を取得
    Dim lastCol As Long
    lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column

    Debug.Print "1行目の最終列: " & lastCol
    Debug.Print "列名: " & Split(ws.Cells(1, lastCol).Address, "$")(1)

    Set ws = Nothing
End Sub

End プロパティの4つの方向

定数方向キーボード操作主な用途
xlUp上方向Ctrl + ↑最終行の取得
xlDown下方向Ctrl + ↓データの先頭から末尾へ
xlToLeft左方向Ctrl + ←最終列の取得
xlToRight右方向Ctrl + →データの先頭から右端へ
end_directions.bas
Sub EndDirections()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    ' 最終行(下から上へ)
    Debug.Print "最終行: " & ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    ' 最終列(右から左へ)
    Debug.Print "最終列: " & ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column

    ' A1 からデータの下端(上から下へ)
    Debug.Print "A1からの下端: " & ws.Range("A1").End(xlDown).Row

    ' A1 からデータの右端(左から右へ)
    Debug.Print "A1からの右端: " & ws.Range("A1").End(xlToRight).Column

    Set ws = Nothing
End Sub
xlDown で最終行を取得しない

Range("A1").End(xlDown) は「A1 から下方向にデータが連続している最後のセル」を返しますが、データの途中に空白行があると、空白行の手前で止まってしまいます。また、A1 が空の場合は次にデータがあるセルまでジャンプします。

最終行を取得する場合は、必ずシートの最終行から xlUp で上方向に検索する方法を使いましょう。

' × 空白行があると正しく取得できない
lastRow = Range("A1").End(xlDown).Row

' ○ 正しい方法
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
ワークシートを明示的に指定する

Cells(Rows.Count, 1) のようにシートを省略すると、ActiveSheet(現在アクティブなシート)が対象になります。意図しないシートのデータを取得してしまうバグを防ぐため、ws.Cells(ws.Rows.Count, 1) のようにワークシートを明示的に指定しましょう。

' △ ActiveSheet に依存(バグの原因になりやすい)
lastRow = Cells(Rows.Count, 1).End(xlUp).Row

' ○ シートを明示(安全)
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

方法2:UsedRange プロパティ

基本的な考え方

UsedRange は、ワークシート上で一度でも使用されたセル全体の範囲を返します。特定の列に依存せず、シート全体のデータ範囲を取得できます。

usedrange_basic.bas
Sub GetLastRowUsedRange()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    ' 使用範囲の最終行
    Dim lastRow As Long
    lastRow = ws.UsedRange.Rows(ws.UsedRange.Rows.Count).Row

    ' 使用範囲の最終列
    Dim lastCol As Long
    lastCol = ws.UsedRange.Columns(ws.UsedRange.Columns.Count).Column

    Debug.Print "最終行: " & lastRow
    Debug.Print "最終列: " & lastCol
    Debug.Print "使用範囲: " & ws.UsedRange.Address

    Set ws = Nothing
End Sub

UsedRange を使った行数・列数の取得

usedrange_count.bas
Sub UsedRangeCount()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    Debug.Print "使用範囲の行数: " & ws.UsedRange.Rows.Count
    Debug.Print "使用範囲の列数: " & ws.UsedRange.Columns.Count
    Debug.Print "使用範囲の開始行: " & ws.UsedRange.Row
    Debug.Print "使用範囲の開始列: " & ws.UsedRange.Column

    Set ws = Nothing
End Sub
UsedRange は意図しない範囲を返すことがある

UsedRange は「一度でも使用されたセル」の範囲を返すため、以下のようなケースで意図しない結果になることがあります:

  • セルの値を削除しても、書式設定が残っているセルは「使用済み」と判定される
  • 過去に値を入力して削除したセルが範囲に含まれることがある
  • データの範囲より大きな範囲が返される場合がある

正確な最終行を取得するには、End プロパティを使用する方が確実です。

方法3:CurrentRegion プロパティ

基本的な考え方

CurrentRegion は、指定したセルを含む連続したデータ領域(空白行・空白列で囲まれた矩形範囲)を返します。Excel で Ctrl + Shift + * を押したときと同じ範囲です。

currentregion_basic.bas
Sub GetLastRowCurrentRegion()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    ' A1 セルを含む連続データ領域
    Dim dataRange As Range
    Set dataRange = ws.Range("A1").CurrentRegion

    Debug.Print "データ範囲: " & dataRange.Address
    Debug.Print "行数: " & dataRange.Rows.Count
    Debug.Print "列数: " & dataRange.Columns.Count
    Debug.Print "最終行: " & dataRange.Rows(dataRange.Rows.Count).Row
    Debug.Print "最終列: " & dataRange.Columns(dataRange.Columns.Count).Column

    Set dataRange = Nothing
    Set ws = Nothing
End Sub

CurrentRegion でヘッダーを除いたデータ範囲を取得

currentregion_without_header.bas
Sub GetDataWithoutHeader()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    Dim dataRange As Range
    Set dataRange = ws.Range("A1").CurrentRegion

    ' ヘッダー(1行目)を除いたデータ範囲
    Dim dataRows As Long
    dataRows = dataRange.Rows.Count - 1  ' ヘッダー分を引く

    If dataRows > 0 Then
        Dim dataOnly As Range
        Set dataOnly = dataRange.Offset(1, 0).Resize(dataRows)
        Debug.Print "データ範囲(ヘッダー除く): " & dataOnly.Address
        Set dataOnly = Nothing
    Else
        Debug.Print "データがありません"
    End If

    Set dataRange = Nothing
    Set ws = Nothing
End Sub
チェック

CurrentRegion は空白行・空白列で区切られた「島」のようなデータ領域を返します。データの途中に空白行がある場合、空白行の手前までしか取得できない点に注意してください。

3つの方法の比較

特徴End プロパティUsedRangeCurrentRegion
取得対象指定列の最終行シート全体の使用範囲連続データ領域
空白行への耐性○ 影響なし○ 影響なし× 空白行で区切られる
書式のみのセルへの耐性○ 値があるセルのみ× 書式も含む○ 値があるセルのみ
列の指定必要不要起点セルの指定が必要
精度◎ 最も正確△ 過大になることがある
用途最終行・最終列の取得シート全体の範囲把握表全体の範囲取得

使い分けの基準

method_comparison.bas
Sub ComparisonExample()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    ' 方法1: End プロパティ(最も推奨)
    ' → 特定の列のデータの最終行を正確に取得したい場合
    Dim lastRowEnd As Long
    lastRowEnd = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    Debug.Print "End: " & lastRowEnd

    ' 方法2: UsedRange
    ' → シート全体の使用範囲を大まかに把握したい場合
    Dim lastRowUsed As Long
    lastRowUsed = ws.UsedRange.Rows(ws.UsedRange.Rows.Count).Row
    Debug.Print "UsedRange: " & lastRowUsed

    ' 方法3: CurrentRegion
    ' → 表形式のデータ全体を一括で取得したい場合
    Dim lastRowCurrent As Long
    lastRowCurrent = ws.Range("A1").CurrentRegion.Rows.Count
    Debug.Print "CurrentRegion: " & lastRowCurrent

    Set ws = Nothing
End Sub
迷ったら End プロパティを使う

最終行の取得方法で迷った場合は、**End プロパティ(Cells(Rows.Count, 列).End(xlUp).Row)**を使うのが最も確実です。書式のみのセルに影響されず、空白行があっても正しく動作し、特定の列のデータ末尾を正確に取得できます。

実践的な活用例

例1:データの最終行に新しいデータを追加

practical_append_data.bas
Option Explicit

Sub AppendData()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("顧客リスト")

    ' 最終行の次の行を取得
    Dim newRow As Long
    newRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row + 1

    ' 新しいデータを入力
    ws.Cells(newRow, 1).Value = "C" & Format(newRow - 1, "000")  ' 顧客ID
    ws.Cells(newRow, 2).Value = InputBox("顧客名を入力してください")
    ws.Cells(newRow, 3).Value = Date  ' 登録日

    Debug.Print "行 " & newRow & " にデータを追加しました"

    Set ws = Nothing
End Sub

例2:データの全行をループで処理

practical_loop_all_rows.bas
Option Explicit

Sub ProcessAllRows()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("売上データ")

    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

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

    Debug.Print "データ範囲: A1:" & ws.Cells(lastRow, lastCol).Address(False, False)
    Debug.Print "データ件数: " & (lastRow - 1) & " 件(ヘッダー除く)"

    ' 全行を処理
    Dim i As Long
    Dim totalSales As Currency
    totalSales = 0

    For i = 2 To lastRow  ' 2行目(ヘッダーの次)から最終行まで
        If IsNumeric(ws.Cells(i, 3).Value) Then
            totalSales = totalSales + ws.Cells(i, 3).Value
        End If
    Next i

    Debug.Print "売上合計: " & Format(totalSales, "#,##0") & " 円"

    Set ws = Nothing
End Sub
ループ処理 (For / Do)
VBAのループ処理(For Next、For Each、Do While、Do Until)の書き方を初心者向けに徹底解説。繰り返し処理の基本構文から、Exit文によるループ脱出、ネスト、パフォーマンス最適化まで、実践的なコード例とともに紹

例3:動的な範囲をコピー

practical_dynamic_copy.bas
Option Explicit

Sub CopyDataRange()
    Dim wsSource As Worksheet
    Dim wsDest As Worksheet
    Set wsSource = ThisWorkbook.Sheets("元データ")
    Set wsDest = ThisWorkbook.Sheets("レポート")

    ' 元データの範囲を動的に取得
    Dim lastRow As Long
    Dim lastCol As Long
    lastRow = wsSource.Cells(wsSource.Rows.Count, 1).End(xlUp).Row
    lastCol = wsSource.Cells(1, wsSource.Columns.Count).End(xlToLeft).Column

    ' データが存在するか確認
    If lastRow < 2 Then
        MsgBox "コピーするデータがありません", vbExclamation
        Exit Sub
    End If

    ' 動的な範囲をコピー
    wsSource.Range(wsSource.Cells(1, 1), wsSource.Cells(lastRow, lastCol)).Copy _
        Destination:=wsDest.Range("A1")

    Debug.Print lastRow & " 行 × " & lastCol & " 列のデータをコピーしました"

    Set wsSource = Nothing
    Set wsDest = Nothing
End Sub

例4:データが存在しない場合のエラー対策

practical_empty_check.bas
Option Explicit

Sub SafeGetLastRow()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    ' データが1行目(ヘッダーのみ)の場合の対策
    If lastRow <= 1 Then
        MsgBox "データがありません。処理を中止します。", vbInformation
        Set ws = Nothing
        Exit Sub
    End If

    ' ヘッダーすらない場合(完全に空のシート)
    If ws.Cells(1, 1).Value = "" And lastRow = 1 Then
        MsgBox "シートが空です", vbExclamation
        Set ws = Nothing
        Exit Sub
    End If

    Debug.Print "データ件数: " & (lastRow - 1) & " 件"

    ' このあとデータ処理を行う...

    Set ws = Nothing
End Sub

例5:最終行と最終列を汎用関数にまとめる

practical_utility_functions.bas
Option Explicit

' 指定した列の最終行を取得する関数
Function GetLastRow(ws As Worksheet, Optional col As Long = 1) As Long
    GetLastRow = ws.Cells(ws.Rows.Count, col).End(xlUp).Row
End Function

' 指定した行の最終列を取得する関数
Function GetLastCol(ws As Worksheet, Optional row As Long = 1) As Long
    GetLastCol = ws.Cells(row, ws.Columns.Count).End(xlToLeft).Column
End Function

' 複数列のうち最大の最終行を取得する関数
Function GetMaxLastRow(ws As Worksheet, ParamArray cols() As Variant) As Long
    Dim maxRow As Long
    Dim col As Variant
    maxRow = 0

    For Each col In cols
        Dim currentRow As Long
        currentRow = ws.Cells(ws.Rows.Count, CLng(col)).End(xlUp).Row
        If currentRow > maxRow Then maxRow = currentRow
    Next col

    GetMaxLastRow = maxRow
End Function

' 使用例
Sub TestUtilityFunctions()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    Debug.Print "A列の最終行: " & GetLastRow(ws)
    Debug.Print "B列の最終行: " & GetLastRow(ws, 2)
    Debug.Print "1行目の最終列: " & GetLastCol(ws)
    Debug.Print "A〜C列の最大最終行: " & GetMaxLastRow(ws, 1, 2, 3)

    Set ws = Nothing
End Sub

注意点とよくある間違い

1. データが空のシートでの End プロパティの挙動

A列にデータが1件もない場合、Cells(Rows.Count, 1).End(xlUp).Row1を返します(A1にジャンプするため)。これはヘッダーがある場合と同じ値なので、データの有無を正しく判定するにはセルの値もチェックする必要があります。

empty_sheet_check.bas
Sub CheckEmptySheet()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    ' lastRow = 1 の場合、ヘッダーだけか完全に空か判定
    If lastRow = 1 And ws.Cells(1, 1).Value = "" Then
        Debug.Print "シートは完全に空です"
    ElseIf lastRow = 1 Then
        Debug.Print "ヘッダーのみ(データなし)"
    Else
        Debug.Print "データあり: " & (lastRow - 1) & " 件"
    End If

    Set ws = Nothing
End Sub

2. 結合セルでの注意

結合されたセルが含まれる列で End(xlUp) を使用する場合、結合セルの最上部の行番号が返されることがあります。結合セルがある表では注意が必要です。

3. フィルター使用中の注意

オートフィルターが適用されている状態でも、End(xlUp) は非表示の行を含む実際の最終行を返します。フィルター使用中に表示行のみの最終行を取得したい場合は、SpecialCells を使用します。

filtered_lastrow.bas
Sub GetFilteredLastRow()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    ' フィルターの有無にかかわらず、実際の最終行を取得
    Dim actualLastRow As Long
    actualLastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    Debug.Print "実際の最終行: " & actualLastRow

    ' 表示されているセルのみの件数を取得
    On Error Resume Next
    Dim visibleCount As Long
    visibleCount = ws.Range("A2:A" & actualLastRow).SpecialCells(xlCellTypeVisible).Count
    On Error GoTo 0
    Debug.Print "表示行数: " & visibleCount

    Set ws = Nothing
End Sub

練習問題

A列に100行のデータがあるシートで、次のコードの lastRow の値はいくつですか?(A1はヘッダー、A2〜A101がデータ)
Dim lastRow As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
正解は「101」です。A1がヘッダー、A2〜A101がデータなので、データの最終行はA101(101行目)です。Cells(Rows.Count, 1).End(xlUp) はシートの最下部から上方向に検索し、最初にデータがあるA101にたどり着きます。

データの途中(A5)に空白行があるシートで最終行を正しく取得できるのはどの方法ですか?

正解は Cells(Rows.Count, 1).End(xlUp).Row です。この方法はシートの最下部から上方向に検索するため、途中の空白行に影響されません。Range("A1").End(xlDown) は空白行の手前で止まり、CurrentRegion も空白行で範囲が区切られてしまいます。

既存のデータの最終行の「次の行」にデータを追加するコードとして正しいものはどれですか?

正解は Cells(Cells(Rows.Count, 1).End(xlUp).Row + 1, 1).Value = "新規データ" です。End(xlUp).Row で最終行を取得し、+ 1 で次の空白行を指定しています。+ 1 がないと最終行のデータを上書きしてしまいます。

まとめ

  • VBA でデータの最終行を取得する最も確実な方法は Cells(Rows.Count, 列).End(xlUp).Row
  • 最終列の取得には Cells(行, Columns.Count).End(xlToLeft).Column を使う
  • End プロパティは空白行があっても正しく動作し、書式のみのセルに影響されない
  • UsedRange はシート全体の使用範囲を返すが、書式のみのセルも含むため過大な範囲になることがある
  • CurrentRegion は連続データ領域を返すが、空白行で区切られるため途中に空白がある表には不向き
  • Range("A1").End(xlDown) は途中の空白行で止まるため、最終行の取得には使わない
  • データが存在しない場合(空のシート)のチェックを忘れないようにする
  • ワークシートは必ず明示的に指定し、ActiveSheet に依存しないコードを書く
Range と Cells
Excel VBAでのセル参照にはRangeとCellsがあり、それぞれ異なる特性を持ちます。この記事では、RangeとCellsの使い分け方や具体例を通して、どちらをどのように使うべきかを詳しく解説します。初心者から中級者まで、VBAコー
ループ処理 (For / Do)
VBAのループ処理(For Next、For Each、Do While、Do Until)の書き方を初心者向けに徹底解説。繰り返し処理の基本構文から、Exit文によるループ脱出、ネスト、パフォーマンス最適化まで、実践的なコード例とともに紹
範囲の値を取得
バッチファイルを扱う上で効果的な、バッチファイルへ引数を渡す処理について、基本的な使い方からオプションの設定方法まで分かり易く説明しています。また、具体的なサンプルコードも記載しています。
実行速度の改善
出来上がったものを実行してみると、処理中に画面がガタガタ動いたり、マウスが頻繁に処理中モードと通常モードに切り替わってしまうことはありませんか?そういった方向けに、作成した処理の前に読み込ませるだけで、簡単に上記の問題が解決され、さらに高速
#VBA #最終行 #最終列 #End #UsedRange #CurrentRegion #セル操作 #初心者向け