web-dev-qa-db-ja.com

VBAを使用したExcelで最後に使用したセルの検索エラー

最後に使用したセル値を検索する場合、次を使用します。

Dim LastRow As Long

LastRow = Range("E4:E48").End(xlDown).Row

Debug.Print LastRow

単一の要素をセルに入れると、間違った出力が得られます。しかし、セルに複数の値を入力すると、出力は正しくなります。この背後にある理由は何ですか?

159
james

NOTE:これを「ワンストップポスト」にして、Correctの方法を使用して最後の行を見つけることができます。これには、最後の行を見つけるときに従うべきベストプラクティスも含まれます。したがって、新しいシナリオ/情報に出会ったときはいつでも更新し続けます。


最後の行を見つける信頼できない方法

最後の行を見つける最も一般的な方法のいくつかは、非常に信頼性が低いため、使用しないでください。

  1. 使用範囲
  2. xlDown
  3. CountA

UsedRangeを使用して、データを含む最後のセルを見つけるためにを使用しないでください。非常に信頼性が低いです。この実験を試してください。

セルA5に何かを入力します。これで、以下のメソッドのいずれかを使用して最後の行を計算すると、5が得られます。次に、セルA10を赤に色付けします。以下のコードのいずれかを使用すると、5が得られます。Usedrange.Rows.Countを使用すると、何が得られますか? 5にはなりません。

UsedRangeがどのように機能するかを示すシナリオを次に示します。

enter image description here

xlDownも同様に信頼できません。

このコードを検討してください

lastrow = Range("A1").End(xlDown).Row

データがあるセル(A1)が1つしかない場合はどうなりますか?最終的にワークシートの最後の行に到達します!セルA1を選択してから押す End キーを押しながら Down Arrow キー。また、範囲内に空白のセルがある場合、信頼性の低い結果が得られます。

CountAも、間に空白のセルがある場合に誤った結果を与えるため、信頼できません。

したがって、UsedRangexlDown、およびCountAを使用して最後のセルを見つけることは避けてください。


列の最後の行を検索

列Eの最後の行を見つけるには、これを使用します

With Sheets("Sheet1")
    LastRow = .Range("E" & .Rows.Count).End(xlUp).Row
End With

.の前にRows.Countがあることに気付いた場合。私たちはしばしばそれを無視することを選びました。 THIS 表示される可能性のあるエラーについての質問を参照してください。 .およびRows.Countの前にColumns.Countを使用することを常にお勧めします。この質問は、Rows.CountがExcel 2003以前では65536を返し、Excel 2007以降では1048576を返すため、コードが失敗するという古典的なシナリオです。同様に、Columns.Countはそれぞれ256および16384を返します。

Excel 2007+に1048576行があるという上記の事実は、行の値を保持する変数を常にLongではなくIntegerとして宣言する必要があるという事実を強調しています。そうしないと、Overflowエラーが発生します。


シートの最後の行を検索

シートの最後のEffective行を見つけるには、これを使用します。 Application.WorksheetFunction.CountA(.Cells)の使用に注意してください。ワークシートにデータのあるセルがない場合、.FindRun Time Error 91: Object Variable or With block variable not setを提供するため、これが必要です。

With Sheets("Sheet1")
    If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
        lastrow = .Cells.Find(What:="*", _
                      After:=.Range("A1"), _
                      Lookat:=xlPart, _
                      LookIn:=xlFormulas, _
                      SearchOrder:=xlByRows, _
                      SearchDirection:=xlPrevious, _
                      MatchCase:=False).Row
    Else
        lastrow = 1
    End If
End With

テーブルの最後の行を検索(ListObject)

同じ原則が適用されます。たとえば、テーブルの3列目の最後の行を取得するには、次のようになります。

Sub FindLastRowInExcelTableColAandB()
Dim lastRow As Long
Dim ws As Worksheet, tbl as ListObject
Set ws = Sheets("Sheet1")  'Modify as needed
'Assuming the name of the table is "Table1", modify as needed
Set tbl = ws.ListObjects("Table1")

With tbl.ListColumns(3).Range
    lastrow = .Find(What:="*", _
                After:=.Cells(1), _
                Lookat:=xlPart, _
                LookIn:=xlFormulas, _
                SearchOrder:=xlByRows, _
                SearchDirection:=xlPrevious, _
                MatchCase:=False).Row
End With

End Sub
269
Siddharth Rout

注:この答えは this comment によって動機付けられました。 UsedRangeの目的は、上記の回答に記載されているものとは異なります。

最後に使用されたセルを見つける正しい方法については、最初にusedと見なされるものを決定してから、適切な方法を選択する必要があります。少なくとも3つの意味があります。

  1. 使用済み=空白以外、つまりdataを持ちます。

  2. Used = "... in use、つまり、dataまたはformatを含むセクションを意味します。" 公式ドキュメントによる 、これは保存時にExcelによって使用される基準です。 this もご覧ください。これに気付いていない場合、基準は予期しない結果を生成する可能性がありますが、たとえば特定の領域を強調表示または印刷するために意図的に悪用される可能性もあります(最終的にデータがない場合があります)。そしてもちろん、ワークブックを保存するときに使用する範囲の基準としては、自分の作業の一部が失われないようにすることが望ましいです。

  3. Used = "... in use、つまり、dataまたはformat)を含むセクションを意味します。または条件付き書式。2.と同じですが、条件付き書式ルールの対象となるセルも含まれます。

最後に使用したセルを見つける方法は、youが何を望むか(あなたの基準)によって異なります。

基準1については、 この答えを読むことをお勧めします。 UsedRangeは信頼できないとして引用されていることに注意してください。 UsedRangeはデータを含む最後のセルを報告することを意図したものではないため、これは誤解を招く(つまり、UsedRangeに「不公平」)と思います。したがって、その答えに示されているように、この場合は使用しないでください。 this comment も参照してください。

基準2の場合、UsedRangeは、この用途のために設計された他のオプションと比較して、最も信頼できるオプションです。最後のセルが更新されたことを確認するためにワークブックを保存する必要さえありません。 Ctrl+End 保存する前に間違ったセルに移動します(「ワークシートを保存するまで最後のセルはリセットされません」、 http://msdn.Microsoft.com/en-us/library/aa139976%28v=office .10%29.aspx 。これは古い参照ですが、この点では有効です)。

基準3については、組み込みの方法がわかりません。基準2は、条件付き書式を考慮していません。数式に基づいて書式設定されたセルがあり、UsedRangeまたは Ctrl+End。図では、書式設定が明示的に適用されているため、最後のセルはB3です。セルB6:D7には条件付き書式ルールから派生した形式があり、これはUsedRangeによっても検出されません。これを考慮するには、VBAプログラミングが必要になります。

enter image description here


特定の質問についてこの背後にある理由は何ですか?

コードでは、End(xlDown)jumpingdownの場合、範囲E4:E48の最初のセルをトランポリンとして使用します。

「誤った」出力は、おそらく最初のセル以外に非空白セルがない場合に取得されます。次に、暗闇の中でジャンプしています、つまりワークシートを下に移動します(blank空の文字列!)。

ご了承ください:

  1. 範囲に非連続の非空白セルが含まれている場合、誤った結果が得られます。

  2. 空白でないセルが1つだけあるが、最初のセルではない場合でも、コードは正しい結果を提供します。

29
sancho.s

最後の行、列、セルを決定し、データ用、フォーマット済み(グループ化/コメント化/非表示)セルまたは条件付きフォーマットのためにこのワンストップ関数を作成しました。

Sub LastCellMsg()
    Dim strResult As String
    Dim lngDataRow As Long
    Dim lngDataCol As Long
    Dim strDataCell As String
    Dim strDataFormatRow As String
    Dim lngDataFormatCol As Long
    Dim strDataFormatCell As String
    Dim oFormatCond As FormatCondition
    Dim lngTempRow As Long
    Dim lngTempCol As Long
    Dim lngCFRow As Long
    Dim lngCFCol As Long
    Dim strCFCell As String
    Dim lngOverallRow As Long
    Dim lngOverallCol As Long
    Dim strOverallCell As String

    With ActiveSheet

        If .ListObjects.Count > 0 Then
            MsgBox "Cannot return reliable results, as there is at least one table in the worksheet."
            Exit Sub
        End If

        strResult = "Workbook name: " & .Parent.Name & vbCrLf
        strResult = strResult & "Sheet name: " & .Name & vbCrLf

        'DATA:
        'last data row
        If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
            lngDataRow = .Cells.Find(What:="*", _
             After:=.Range("A1"), _
             Lookat:=xlPart, _
             LookIn:=xlFormulas, _
             SearchOrder:=xlByRows, _
             SearchDirection:=xlPrevious, _
             MatchCase:=False).Row
        Else
            lngDataRow = 1
        End If
        'strResult = strResult & "Last data row: " & lngDataRow & vbCrLf

        'last data column
        If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
            lngDataCol = .Cells.Find(What:="*", _
             After:=.Range("A1"), _
             Lookat:=xlPart, _
             LookIn:=xlFormulas, _
             SearchOrder:=xlByColumns, _
             SearchDirection:=xlPrevious, _
             MatchCase:=False).Column
        Else
            lngDataCol = 1
        End If
        'strResult = strResult & "Last data column: " & lngDataCol & vbCrLf

        'last data cell
        strDataCell = Replace(Cells(lngDataRow, lngDataCol).Address, "$", vbNullString)
        strResult = strResult & "Last data cell: " & strDataCell & vbCrLf

        'FORMATS:
        'last data/formatted/grouped/commented/hidden row
        strDataFormatRow = StrReverse(Split(StrReverse(.UsedRange.Address), "$")(0))
        'strResult = strResult & "Last data/formatted row: " & strDataFormatRow & vbCrLf

        'last data/formatted/grouped/commented/hidden column
        lngDataFormatCol = Range(StrReverse(Split(StrReverse(.UsedRange.Address), "$")(1)) & "1").Column
        'strResult = strResult & "Last data/formatted column: " & lngDataFormatCol & vbCrLf

        'last data/formatted/grouped/commented/hidden cell
        strDataFormatCell = Replace(Cells(strDataFormatRow, lngDataFormatCol).Address, "$", vbNullString)
        strResult = strResult & "Last data/formatted cell: " & strDataFormatCell & vbCrLf

        'CONDITIONAL FORMATS:
        For Each oFormatCond In .Cells.FormatConditions

            'last conditionally-formatted row
            lngTempRow = CLng(StrReverse(Split(StrReverse(oFormatCond.AppliesTo.Address), "$")(0)))
            If lngTempRow > lngCFRow Then lngCFRow = lngTempRow

            'last conditionally-formatted column
            lngTempCol = Range(StrReverse(Split(StrReverse(oFormatCond.AppliesTo.Address), "$")(1)) & "1").Column
            If lngTempCol > lngCFCol Then lngCFCol = lngTempCol
        Next
        'no results are returned for Conditional Format if there is no such
        If lngCFRow <> 0 Then
            'strResult = strResult & "Last cond-formatted row: " & lngCFRow & vbCrLf
            'strResult = strResult & "Last cond-formatted column: " & lngCFCol & vbCrLf

            'last conditionally-formatted cell
            strCFCell = Replace(Cells(lngCFRow, lngCFCol).Address, "$", vbNullString)
            strResult = strResult & "Last cond-formatted cell: " & strCFCell & vbCrLf
        End If

        'OVERALL:
        lngOverallRow = Application.WorksheetFunction.Max(lngDataRow, strDataFormatRow, lngCFRow)
        'strResult = strResult & "Last overall row: " & lngOverallRow & vbCrLf
        lngOverallCol = Application.WorksheetFunction.Max(lngDataCol, lngDataFormatCol, lngCFCol)
        'strResult = strResult & "Last overall column: " & lngOverallCol & vbCrLf
        strOverallCell = Replace(.Cells(lngOverallRow, lngOverallCol).Address, "$", vbNullString)
        strResult = strResult & "Last overall cell: " & strOverallCell & vbCrLf

        MsgBox strResult
        Debug.Print strResult

    End With

End Sub

結果は次のようになります。
determine last cell

より詳細な結果を得るには、コード内のいくつかの行のコメントを外すことができます。
last column, row

1つの制限があります-シートにテーブルがある場合、結果が信頼できなくなる可能性があるため、この場合はコードの実行を避けることにしました。

If .ListObjects.Count > 0 Then
    MsgBox "Cannot return reliable results, as there is at least one table in the worksheet."
    Exit Sub
End If
16
ZygD

ソリューションを使用する際に留意すべき重要な注意事項...

LastRow = ws.Cells.Find(What:="*", After:=ws.range("a1"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row

...は、LastRow変数がLong型であることを確認することです。

Dim LastRow as Long

そうしないと、.XLSXワークブックの特定の状況でOVERFLOWエラーが発生します

これはカプセル化された関数であり、さまざまなコードの使用にドロップします。

Private Function FindLastRow(ws As Worksheet) As Long
    ' --------------------------------------------------------------------------------
    ' Find the last used Row on a Worksheet
    ' --------------------------------------------------------------------------------
    If WorksheetFunction.CountA(ws.Cells) > 0 Then
        ' Search for any entry, by searching backwards by Rows.
        FindLastRow = ws.Cells.Find(What:="*", After:=ws.range("a1"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
    End If
End Function
9
Bishop

Siddarth Routの回答に追加して、Findに行番号ではなくRangeオブジェクトを返させることでCountA呼び出しをスキップし、返されたRangeオブジェクトをテストしてNothing(空白のワークシート)かどうかを確認します。

また、LastRowプロシージャの私のバージョンに空白のワークシートのゼロを返させると、空白であることがわかります。

8
no comprende

誰もこれについて言及していないのだろうか、しかし最後に使用したセルを取得する最も簡単な方法は:

Function GetLastCell(sh as Worksheet) As Range
    GetLastCell = sh.Cells(1,1).SpecialCells(xlLastCell)
End Function

これは基本的に、あなたが得るのと同じセルを返します Ctrl + End セルA1を選択した後。

注意事項:Excelは、ワークシートで使用された右下のセルを追跡します。たとえば、Bに何かを入力し、H8に何か他のものを入力し、後でH8の内容を削除して、 Ctrl + End H8セル​​に移動します。上記の関数は同じ動作をします。

8
dotNET

元の質問は問題が最後のセルを見つけることに関するものなので、この回答では、予期しない結果を得るさまざまな方法をリストします;これを解決するための私の見解については、 "マクロを使用してExcelシートのデータを含む最後の行を見つける方法は?" を参照してください。

まず、 sancho.sによる回答 および GlennFromIowaによるコメント を展開し、さらに詳細を追加します。

[...]最初に使用するものを決定する必要があります。少なくとも6つの意味があります。セルには:

  • 1)データ、つまり、おそらく空白の値になる式。
  • 2)値、つまり非空白の式または定数。
  • 3)フォーマット
  • 4)条件付き書式設定。
  • 5)セルに重なる形状(コメントを含む);
  • 6)テーブル(リストオブジェクト)への関与。

どの組み合わせをテストしますか?いくつか(テーブルなど)はテストが難しく、一部はまれ(データ範囲外の形状など)ですが、その他は状況(たとえば、空の値を持つ数式)によって異なる場合があります。

他に考慮したいこと:

  • A)非表示行(例:オートフィルター)、空白セルまたは空白行はありますか?
  • B)どのようなパフォーマンスが許容されますか?
  • C)VBAマクロは、ブックまたはアプリケーションの設定に何らかの影響を与えることができますか?

それを念頭に置いて、「最後のセル」を取得する一般的な方法で予期しない結果がどのように生成されるかを見てみましょう:

  • 質問の.End(xlDown)コードは、説明されている理由により、最も簡単に壊れます(たとえば、単一の空でないセルがある場合、または間に空白セルがある場合があります) in Siddharth Routによる回答 ここ(「xlDownは等しく信頼性が低い」を検索します。)????
  • Counting(CountAまたはCells*.Count)または.CurrentRegionに基づくソリューションも、空白のセルまたは行があると破損します????
  • 列の末尾から逆方向に検索する.End(xlUp)を含むソリューションは、CTRL + UPと同様に、data(空の値を生成する式は「データ」と見なされます)を探します- 可視行(オートフィルターを有効にして使用すると、誤った結果が生成される場合があります⚠️)。

    標準的な落とし穴を避けるように注意する必要があります(詳細については、再度 Siddharth Routによる回答 を参照します。ここで、"列の最後の行を見つける"セクションを探します。 )、sht.Rows.Countに依存する代わりに、最後の行(Range("A65536").End(xlUp))をハードコーディングするなど。

  • .SpecialCells(xlLastCell)はCTRL + ENDと同等で、「使用範囲」の一番下と一番右のセルを返すため、「使用範囲」に依存する場合に適用されるすべての警告は、この方法にも適用されます。さらに、「使用範囲」はワークブックの保存時およびworksheet.UsedRangeへのアクセス時にのみリセットされるため、xlLastCellは未保存の変更(たとえば、いくつかの行が削除された後)で古い結果を生成する場合があります。 dotNETによる近くの回答 を参照してください。
  • sht.UsedRangesancho.sによる回答 で詳細に説明されています)は、データとフォーマットの両方(条件付きフォーマットではありませんが)およびワークシートの使用範囲。これは必要な場合とそうでない場合があります。

    よくある間違いは、.UsedRange.Rows.Count⚠️を使用することであることに注意してください。これは、最後の行番号ではなく、使用範囲の行数を返します。最初の数行は空白です)、詳細については newguyのへの回答をご覧ください。マクロを使用してExcelシートのデータを含む最後の行を見つけるにはどうすればよいですか?

  • .Findを使用すると、データ(式を含む)または非空白値任意の列を含む最後の行を見つけることができます。数式と値のどちらを使用するかを選択できますが、問題はExcelの[検索]ダイアログの既定値に戻す️️⚠️であり、これはユーザーを非常に混乱させる可能性があります。また、慎重に使用する必要があります。 Siddharth Routの回答 を参照してください(セクション「シートの最後の行を検索」
  • ループ内の個々のCells 'をチェックするより明示的なソリューションは、Excel関数を再利用するよりも一般に低速ですが(パフォーマンスは向上しますが)、検索する内容を正確に指定できます。 UsedRangeおよびVBA配列に基づく my solution を参照して、指定された列のデータを含む最後のセルを検索します-非表示の行、フィルター、空白を処理し、検索のデフォルトを変更せず、非常に高性能です。

どのソリューションを選択する場合でも、注意してください

  • Longの代わりにIntegerを使用して行番号を格納する(65,000行を超えるOverflowを取得しないようにする)
  • 作業中のワークシートを常に指定する(つまり、Dim ws As Worksheet ... ws.Range(...)の代わりにRange(...)
  • .ValueVariant)を使用する場合は、セルにエラー値が含まれていると失敗するため、.Value <> ""のような暗黙のキャストを避けてください。
6
Nickolay
sub last_filled_cell()
msgbox range("a65536").end(xlup).row
end sub

「ここでa65536は列の最後のセルです。このコードはExcel sti72003でテストされました」200

uが「a1,048,576」を使用している場合

私のコードは、初心者がend(xlup)および他の関連コマンドでできることの概念を理解するためのものです。

5
Ashwith Ullal

しかし、この質問はVBAを使用して最後の行を見つけようとしていますが、ワークシート関数の配列式を含めると、頻繁にアクセスされるので良いと思います:

{=ADDRESS(MATCH(INDEX(D:D,MAX(IF(D:D<>"",ROW(D:D)-ROW(D1)+1)),1),D:D,0),COLUMN(D:D))}

角括弧なしで数式を入力してからヒットする必要があります Shift + Ctrl + Enter 配列式にします。

これにより、列Dの最後に使用されたセルのアドレスが表示されます。

3
M-M

を模倣する方法を探していました CTRL+Shift+End、したがって、dotNETソリューションは素晴らしいです。ただし、Excel 2010でエラーを回避したい場合はsetを追加する必要があります。

Function GetLastCell(sh As Worksheet) As Range
  Set GetLastCell = sh.Cells(1, 1).SpecialCells(xlLastCell)
End Function

そして、これを自分で確認する方法:

Sub test()
  Dim ws As Worksheet, r As Range
  Set ws = ActiveWorkbook.Sheets("Sheet1")
  Set r = GetLastCell(ws)
  MsgBox r.Column & "-" & r.Row
End Sub
2
J. Chomel

過去3年以上、これらは定義された列(行)および行(列)ごとに最後の行と最後の列を見つけるために使用している関数です。

最後の列:

Function lastCol(Optional wsName As String, Optional rowToCheck As Long = 1) As Long

    Dim ws  As Worksheet

    If wsName = vbNullString Then
        Set ws = ActiveSheet
    Else
        Set ws = Worksheets(wsName)
    End If

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

End Function

最終行:

Function lastRow(Optional wsName As String, Optional columnToCheck As Long = 1) As Long

    Dim ws As Worksheet

    If wsName = vbNullString Then
        Set ws = ActiveSheet
    Else
        Set ws = Worksheets(wsName)
    End If

    lastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row

End Function

OPの場合、これは列Eの最後の行を取得する方法です。

Debug.Print lastRow(columnToCheck:=Range("E4:E48").Column)

1
Vityata
Sub lastRow()

    Dim i As Long
        i = Cells(Rows.Count, 1).End(xlUp).Row
            MsgBox i

End Sub

sub LastRow()

'Paste & for better understanding of the working use F8 Key to run the code .

dim WS as worksheet
dim i as long

set ws = thisworkbook("SheetName")

ws.activate

ws.range("a1").select

ws.range("a1048576").select

activecell.end(xlup).select

i= activecell.row

msgbox "My Last Row Is " & i

End sub
1
user85489