Error in finding last used cell in Excel with VBA Error in finding last used cell in Excel with VBA vba vba

Error in finding last used cell in Excel with VBA

NOTE: I intend to make this a "one stop post" where you can use the Correct way to find the last row. This will also cover the best practices to follow when finding the last row. And hence I will keep on updating it whenever I come across a new scenario/information.

Unreliable ways of finding the last row

Some of the most common ways of finding last row which are highly unreliable and hence should never be used.

  1. UsedRange
  2. xlDown
  3. CountA

UsedRange should NEVER be used to find the last cell which has data. It is highly unreliable. Try this experiment.

Type something in cell A5. Now when you calculate the last row with any of the methods given below, it will give you 5. Now color the cell A10 red. If you now use the any of the below code, you will still get 5. If you use Usedrange.Rows.Count what do you get? It won't be 5.

Here is a scenario to show how UsedRange works.

enter image description here

xlDown is equally unreliable.

Consider this code

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

What would happen if there was only one cell (A1) which had data? You will end up reaching the last row in the worksheet! It's like selecting cell A1 and then pressing End key and then pressing Down Arrow key. This will also give you unreliable results if there are blank cells in a range.

CountA is also unreliable because it will give you incorrect result if there are blank cells in between.

And hence one should avoid the use of UsedRange, xlDown and CountA to find the last cell.

Find Last Row in a Column

To find the last Row in Col E use this

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

If you notice that we have a . before Rows.Count. We often chose to ignore that. See THIS question on the possible error that you may get. I always advise using . before Rows.Count and Columns.Count. That question is a classic scenario where the code will fail because the Rows.Count returns 65536 for Excel 2003 and earlier and 1048576 for Excel 2007 and later. Similarly Columns.Count returns 256 and 16384, respectively.

The above fact that Excel 2007+ has 1048576 rows also emphasizes on the fact that we should always declare the variable which will hold the row value as Long instead of Integer else you will get an Overflow error.

Note that this approach will skip any hidden rows. Looking back at my screenshot above for column A, if row 8 were hidden, this approach would return 5 instead of 8.

Find Last Row in a Sheet

To find the Effective last row in the sheet, use this. Notice the use of Application.WorksheetFunction.CountA(.Cells). This is required because if there are no cells with data in the worksheet then .Find will give you Run 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 IfEnd With

Find Last Row in a Table (ListObject)

The same principles apply, for example to get the last row in the third column of a table:

Sub FindLastRowInExcelTableColAandB()Dim lastRow As LongDim ws As Worksheet, tbl as ListObjectSet ws = Sheets("Sheet1")  'Modify as needed'Assuming the name of the table is "Table1", modify as neededSet 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).RowEnd WithEnd Sub

Note: this answer was motivated by this comment. The purpose of UsedRange is different from what is mentioned in the answer above.

As to the correct way of finding the last used cell, one has first to decide what is considered used, and then select a suitable method. I conceive at least three meanings:

  1. Used = non-blank, i.e., having data.

  2. Used = "... in use, meaning the section that contains data or formatting."As per official documentation, this is the criterion used by Excel at the time of saving. See also this official documentation.If one is not aware of this, the criterion may produce unexpected results, but it may also be intentionally exploited (less often, surely), e.g., to highlight or print specific regions, which may eventually have no data.And, of course, it is desirable as a criterion for the range to use when saving a workbook, lest losing part of one's work.

  3. Used = "... in use, meaning the section that contains data or formatting" or conditional formatting.Same as 2., but also including cells that are the target for any Conditional Formatting rule.

How to find the last used cell depends on what you want (your criterion).

For criterion 1, I suggest reading this answer.Note that UsedRange is cited as unreliable. I think that is misleading (i.e., "unfair" to UsedRange), as UsedRange is simply not meant to report the last cell containing data. So it should not be used in this case, as indicated in that answer. See also this comment.

For criterion 2, UsedRange is the most reliable option, as compared to other options also designed for this use. It even makes it unnecessary to save a workbook to make sure that the last cell is updated.Ctrl+End will go to a wrong cell prior to saving(“The last cell is not reset until you save the worksheet”, from is an old reference, but in this respect valid).

For criterion 3, I do not know any built-in method.Criterion 2 does not account for Conditional Formatting. One may have formatted cells, based on formulas, which are not detected by UsedRange or Ctrl+End.In the figure, the last cell is B3, since formatting was applied explicitly to it. Cells B6:D7 have a format derived from a Conditional Formatting rule, and this is not detected even by UsedRange.Accounting for this would require some VBA programming.

enter image description here

As to your specific question:What's the reason behind this?

Your code uses the first cell in your range E4:E48 as a trampoline, for jumping down with End(xlDown).

The "erroneous" output will obtain if there are no non-blank cells in your range other than perhaps the first. Then, you are leaping in the dark, i.e., down the worksheet(you should note the difference between blank and empty string!).

Note that:

  1. If your range contains non-contiguous non-blank cells, then it will also give a wrong result.

  2. If there is only one non-blank cell, but it is not the first one, your code will still give you the correct result.

I created this one-stop function for determining the last row, column and cell, be it for data, formatted (grouped/commented/hidden) cells or conditional formatting.

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 WithEnd Sub

Results look like this:
determine last cell

For more detailed results, some lines in the code can be uncommented:
last column, row

One limitation exists - if there are tables in the sheet, results can become unreliable, so I decided to avoid running the code in this case:

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