Find last used cell in Excel VBA
Asked Answered
C

14

202

When I want to find the last used cell value, I use:

Dim LastRow As Long

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

Debug.Print LastRow

I'm getting the wrong output when I put a single element into a cell. But when I put more than one value into the cell, the output is correct. What's the reason behind this?

Csc answered 23/6, 2012 at 12:20 Comment(3)
#71680Gunshy
@FreeSoftwareServers I disagree - as there are many answers showing how to find the last row, this specific question is unique and specific in that it's asking why a particular method does not work, a method suggested in a variety of other "how to" answers.Kirt
I believe my below answer offers the most ideal one-stop solution. I'm open to criticism, but with new filter/query tools, I'm at least confident enough to post this comment and face the wrath of criticism for a rather brazen claim...Saharan
D
359

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).Row
End 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 If
End 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 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
Duff answered 23/6, 2012 at 13:33 Comment(20)
Siddharth Rout can you please elaborate and explain your last comment? Why might you get two different values for the 2 methods you describe?Rubino
@phan: Type something in cell A5. Now when you calculate the last row with any of the methods given above, it will give you 5. Now color the cell A10 red. If you now use the any of the above code, you will still get 5. If you use Usedrange.Rows.Count what do you get? It won't be 5. Usedrange is highly unreliable to find the last row.Duff
Do note that .Find unfortunately messes up the user's settings in the Find dialog - i.e. Excel only has 1 set of settings for the dialog, and you using .Find replaces them. Another trick is to still use UsedRange, but use it as an absolute (but unreliable) maximum from which you determine the correct maximum.Biography
@CarlColijn: I wouldn't call it messing. :) Excel simply remembers the last setting. Even when you manually do a Find, it remembers the last setting which in fact is a boon if one knows this "fact"Duff
Thank you for your great answer!. It helped me a lot. I'd like to translate this article to share with my Korean friends. It will be posted here ctrlaltdel Please let me know if you mind it. then I'll delete it.Interlunation
@KeithPark: Please go ahead :) Knowledge only has a meaning if it is spread :)Duff
@SiddharthRout Nice approach! If the last used cell happened to contain only a Comment and nothing else would we need two Find() statements to locate it? Can the Find() method find either data or Comments??Akee
@Gary'sStudent: No. The above .Find will not find comments. For comments you can use SpecialCells with xlCellTypeComments :)Duff
I think that your description of UsedRange (it is highly unreliable to find the last cell which has data) is misleading. UsedRange is simply not intended for that purpose, even though in some cases it may give the correct result. I think that the experiment proposed adds to the confusion. The result obtained with UsedRange ($A$1:$A$8) does not depend on first entering data an deleting it. The figure on the right will still be the same even without having entered data an deleted it. Please see my answer.Catalpa
What about the PowerShell spin on this VBA code? I've tried this $lastrow = $Worksheet.UsedRange.Rows.Count and $lastcol = $Worksheet.UsedRange.Columns.Count along with $range = $WorkSheet.Range($lastrow,$lastcol) which results in every row being selected.Mop
I found an interesting bug in Excel2013. When the ActiveCell is in a table, the result is the last row of the table. To solve this, I assign the ActiveCell to a temporary range variable, then Range("A1").Select, then find, then temporary range.select. Ugly, but effective.Taffrail
It does seem to work for A clumn, but if I adjust range to C1 it always returns 1 as last row (not because of Else statement). So not sure what's wrong hereQuintinquintina
Nevermind. Used formula for the sheet and not columnQuintinquintina
to apply for last column then use this? ".Cells(1, .Columns.Count).End(xlToLeft).Column"Zrike
@DucAnhNguyen : YesDuff
@SiddharthRout I made an edit to your post, which I rarely do to good answers. However, I believe it's too misleading to not make note that your xlup option will skip hidden rows. Feel free to reword as you see fit.Saharan
I missed a tl;dr for last row in column;)Kozlowski
Found my way here when my Find code started returning incorrect results. Is there a method to return the last cell when the sheet contains a spilled range? I didn't add as a new question as maybe an update to this answer would suffice if anyone knows off hand. As an example - =SEQUENCE(20) in cell A1 will return A1 as the last cell, throw in some static data and you'd need to check each value to see if it does spill and if it spills below the range returned by FIND.Veriee
@DarrenBartrup-Cook Not sure I understand. Can you share a screenshot of what you mean?Duff
@DarrenBartrup-Cook You're likely not looking for it anymore but to avoid the spill of your formula to be missed (and for others still looking for an answer), check out pgSystemtester's answer for Excel 2021 and above.Manteltree
C
40

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 http://msdn.microsoft.com/en-us/library/aa139976%28v=office.10%29.aspx. It 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.

Catalpa answered 24/12, 2014 at 13:34 Comment(2)
I agree that one has first to decide what is considered used. I see at least 6 meanings. Cell has: 1) data, i.e., a formula, possibly resulting in a blank value; 2) a value, i.e., a non-blank formula or constant; 3) formatting; 4) conditional formatting; 5) a shape (including Comment) overlapping the cell; 6) involvement in a Table (List Object). Which combination do you want to test for? Some (such as Tables) may be more difficult to test for, and some may be rare (such as a shape outside of data range), but others may vary based on the situation (e.g., formulas with blank values).Scalariform
I agree @Siddharth Rout's answer is misleading (at best). UsedRange and it's counterpart [in this context] SpecialCells(xlCellTypeLastCell) are both very reliable and extremely useful. And, as pointed out here, if you're actual looking for the last used row, in 9 out of 10 cases, it's the most efficient and reliable method. The key is knowing what it means and how and when to leverage it's power.Procto
M
22

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 With

End 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 Sub
End If
Maximilian answered 23/12, 2015 at 21:55 Comment(1)
@franklin - I've just noticed an inbox message with your correction which was rejected by reviewers. I corrected that mistake. I already used this function once when I needed and I will use it again, so really, huge thanks, my friend!Maximilian
L
13

Since the original question is about problems with finding the last cell, in this answer I will list the various ways you can get unexpected results; see my answer to "How can I find last row that contains data in the Excel sheet with a macro?" for my take on solving this.

I'll start by expanding on the answer by sancho.s and the comment by GlennFromIowa, adding even more detail:

[...] one has first to decide what is considered used. I see at least 6 meanings. Cell has:

  • 1) data, i.e., a formula, possibly resulting in a blank value;
  • 2) a value, i.e., a non-blank formula or constant;
  • 3) formatting;
  • 4) conditional formatting;
  • 5) a shape (including Comment) overlapping the cell;
  • 6) involvement in a Table (List Object).

Which combination do you want to test for? Some (such as Tables) may be more difficult to test for, and some may be rare (such as a shape outside of data range), but others may vary based on the situation (e.g., formulas with blank values).

Other things you might want to consider:

  • A) Can there be hidden rows (e.g. autofilter), blank cells or blank rows?
  • B) What kind of performance is acceptable?
  • C) Can the VBA macro affect the workbook or the application settings in any way?

With that in mind, let's see how the common ways of getting the "last cell" can produce unexpected results:

  • The .End(xlDown) code from the question will break most easily (e.g. with a single non-empty cell or when there are blank cells in between) for the reasons explained in the answer by Siddharth Rout here (search for "xlDown is equally unreliable.") 👎
  • Any solution based on Counting (CountA or Cells*.Count) or .CurrentRegion will also break in presence of blank cells or rows 👎
  • A solution involving .End(xlUp) to search backwards from the end of a column will, just as CTRL+UP, look for data (formulas producing a blank value are considered "data") in visible rows (so using it with autofilter enabled might produce incorrect results ⚠️).

    You have to take care to avoid the standard pitfalls (for details I'll again refer to the answer by Siddharth Rout here, look for the "Find Last Row in a Column" section), such as hard-coding the last row (Range("A65536").End(xlUp)) instead of relying on sht.Rows.Count.

  • .SpecialCells(xlLastCell) is equivalent to CTRL+END, returning the bottom-most and right-most cell of the "used range", so all caveats that apply to relying on the "used range", apply to this method as well. In addition, the "used range" is only reset when saving the workbook and when accessing worksheet.UsedRange, so xlLastCell might produce stale results⚠️ with unsaved modifications (e.g. after some rows were deleted). See the nearby answer by dotNET.
  • sht.UsedRange (described in detail in the answer by sancho.s here) considers both data and formatting (though not conditional formatting) and resets the "used range" of the worksheet, which may or may not be what you want.

    Note that a common mistake ️is to use .UsedRange.Rows.Count⚠️, which returns the number of rows in the used range, not the last row number (they will be different if the first few rows are blank), for details see newguy's answer to How can I find last row that contains data in the Excel sheet with a macro?

  • .Find allows you to find the last row with any data (including formulas) or a non-blank value in any column. You can choose whether you're interested in formulas or values, but the catch is that it resets the defaults in the Excel's Find dialog ️️⚠️, which can be highly confusing to your users. It also needs to be used carefully, see the answer by Siddharth Rout here (section "Find Last Row in a Sheet")
  • More explicit solutions that check individual Cells' in a loop are generally slower than re-using an Excel function (although can still be performant), but let you specify exactly what you want to find. See my solution based on UsedRange and VBA arrays to find the last cell with data in the given column -- it handles hidden rows, filters, blanks, does not modify the Find defaults and is quite performant.

Whatever solution you pick, be careful

  • to use Long instead of Integer to store the row numbers (to avoid getting Overflow with more than 65k rows) and
  • to always specify the worksheet you're working with (i.e. Dim ws As Worksheet ... ws.Range(...) instead of Range(...))
  • when using .Value (which is a Variant) avoid implicit casts like .Value <> "" as they will fail if the cell contains an error value.
Lemuelah answered 22/4, 2018 at 23:1 Comment(2)
What is lastrow = .Range("b" & .Rows.Count).End(xlUp).Row doing? Finding the last row, then going up from the end?Kozlowski
@Kozlowski basically, yes. In my answer I settled on this description: "search backwards from the end of a column [looking] for data ([details omitted]) in visible rows", and I stand by it.Lemuelah
L
12

One important note to keep in mind when using the solution ...

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

... is to ensure that your LastRow variable is of Long type:

Dim LastRow as Long

Otherwise you will end up getting OVERFLOW errors in certain situations in .XLSX workbooks

This is my encapsulated function that I drop in to various code uses.

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
Liquesce answered 2/1, 2015 at 18:58 Comment(0)
F
9

I would add to the answer given by Siddarth Rout to say that the CountA call can be skipped by having Find return a Range object, instead of a row number, and then test the returned Range object to see if it is Nothing (blank worksheet).

Also, I would have my version of any LastRow procedure return a zero for a blank worksheet, then I can know it is blank.

Fetial answered 5/11, 2014 at 15:24 Comment(0)
O
9

I wonder that nobody has mentioned this, But the easiest way of getting the last used cell is:

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

This essentially returns the same cell that you get by Ctrl + End after selecting Cell A1.

A word of caution: Excel keeps track of the most bottom-right cell that was ever used in a worksheet. So if for example you enter something in B3 and something else in H8 and then later on delete the contents of H8, pressing Ctrl + End will still take you to H8 cell. The above function will have the same behavior.

Oneida answered 27/4, 2015 at 15:21 Comment(4)
Last Cell in Excel sometimes refers to an empty cell (from Used Range) that is different from Last Used Cell ;).Huarache
The OP needed just the last row but you are right, last cell should be H5; But you can test your function after deleting value in A5 You will see that the last cell is that empty cell, and I think your code needs some edits like that Cells(1,1).Select() is invalid it maybe is ActiveSheet.Cells(1,1).Select; Also in VBA it's not recommended to use Select ;).Huarache
This breaks two cardinal rules for Excel VBA: Don't use Select! And don't assume sheet you want is the active one.Breathing
This is an old answer, but it is missing a Set.Medium
S
7

Updated at End of 2021

With Excel's new calculation engine and array functionality, and Filter Function, I believe this topic should now be far less contested and that the below options offer the best mix of speed, reliability, and simplicity (which has proven difficult to balance in the past as the numerous posts here illustrate).

Also, I'm defining last used as NOT blank as defined by the isBlank function.

Excel Formula

First, note that the Filter Function makes it much simpler to get a last cell using the below formulas for a specific row or column (in these case Column A or Row 1):

=MAX(FILTER(ROW(A:A),NOT(ISBLANK(A:A))))
=MAX(FILTER(COLUMN(1:1),NOT(ISBLANK(1:1))))

VBA Function For Last Row Specific Range

Using the above function we can convert it into a VBA function, yet make it even faster by limiting the range, while expanding its capabilities by doing multiple columns (thanks to Chris Neilsen for immediate feedback tweeking/suggestions). I also found massive speed improvement by scoping each column to only be a range with a row HIGHER than the previous last row.

Function FindLastRowInRange(someColumns As Range) As Long
Const zFx = "=MAX(FILTER(ROW(????),NOT(ISBLANK(????)),0))"
   
   Dim tRng As Range, i As Long, tRow As Long, pRng As Range
   With someColumns.Worksheet
      Set tRng = Intersect(someColumns.EntireColumn, .UsedRange)
      
      For i = 1 To tRng.Columns.Count
         
         Set pRng = Intersect(tRng.Columns(i), _
         Range(.Rows(FindLastRowInRange + 1), .Rows(.Rows.Count)))
         
         If Not pRng Is Nothing Then
            tRow = .Evaluate(Replace(zFx, "????", _
               pRng.Address, 1, -1))
         
            If tRow > FindLastRowInRange Then _
               FindLastRowInRange = tRow
            
         End If
      Next i
   End With
End Function

VBA Function For Last Row In Worksheet

To consider the entire worksheet (all columns), I would recommend using a different VBA formula that references the prior one, but is a Volatile Function. This ensures that the formula updates with any changes to a worksheet. Obviously, one could combine these two formulas, but I prefer to limit the usage of volatile functions.

Function FindLastRowInSheet(anywhereInSheet As Range) As Long
      Application.Volatile
      FindLastRowInSheet = FindLastRowInRange(anywhereInSheet.Worksheet.UsedRange)
End Function

Advantages Compared To Other Options

  • Allows for some or ALL rows/columns in worksheet without changing approach.
  • No possibility of missing hidden rows as is a risk with xlup
  • Ignores formatted/usedrange issues.
  • Does not interfere with user's Find settings.
  • Uses worksheet functionality which is faster than VBA calcs.
  • No counting cells (performance hog).

Hopefully this ends the debate, but if anyone finds weaknesses in this please share.

Saharan answered 28/11, 2019 at 4:11 Comment(6)
@Saharan looks like a good use of the new functionality. Couple of suggestions though (I can't test ATM, so I may be wrong on some of these) 1) would be better to use the Worksheet.Evaluate method - zWS.Evaluate 2) with that, you probably don't need to qualify the range with WS name, thus avoiding some String work (better speed) 3) I don't see the IfError handling empty columns 4) probably don't need an Intersect on each column of tangoRange. Just use tangoRange.Columns(i) 5) might want to handle non-contiguous someColumns 6) have you done any performance tests?Norway
@chrisneilsen thanks for feedback! I'll review all of your suggestions later today.Saharan
Scratch item 3, I see it now. Alternatively you might be able to use the "no result" parameter of Filter for thatNorway
@chrisneilsen tested your suggestions and was able to optimize code. Thanks a lot! New answer now up. I'll do some performance tests later.Saharan
@Saharan interesting optimization on resizing the range. One thought though: if I'm reading that correctly, it's sized to the last found row +1 down to the end of the sheet? Would it be better to limit it to the end of the used range?Norway
@chrisneilsen it is scoped to the used range based on the initial tRng. I then narrowed the search to be within pRng (preferred Range) to be anything lower than the current max value, but still within tRng. At first I didn't like the idea of redefining the search range each iteration, but it turn out to be a massive performance improvement for some absurd tests I was running (and still am). I could probably optimize it a little by storing the last row of used range instead of constantly counting... so that's a good idea for a little improvement. On next iteration.Saharan
D
3
sub last_filled_cell()
msgbox range("A65536").end(xlup).row
end sub

Here, A65536 is the last cell in the Column A this code was tested on excel 2003.

Directoire answered 1/10, 2015 at 5:9 Comment(2)
Can you explain how your code answers this old question?Everlasting
While this answer is probably correct and useful, it is preferred if you include some explanation along with it to explain how it helps to solve the problem. This becomes especially useful in the future, if there is a change (possibly unrelated) that causes it to stop working and users need to understand how it once worked.Avellaneda
G
3

However this question is seeking to find the last row using VBA, I think it would be good to include an array formula for worksheet function as this gets visited frequently:

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

You need to enter the formula without brackets and then hit Shift + Ctrl + Enter to make it an array formula.

This will give you address of last used cell in the column D.


Thanks to pgsystemtester, this will give you the row number of last used cell:

{=MATCH(INDEX(D:D,MAX(IF(D:D<>"",ROW(D:D)-ROW(D1)+1)),1),D:D,0)}
Gunshy answered 8/5, 2017 at 21:48 Comment(1)
I like this. I might alter slightly to only get row number... '{=MATCH(INDEX(D:D,MAX(IF(D:D<>"",ROW(D:D)-ROW(D1)+1)),1),D:D,0)}'Saharan
R
3

I was looking for a way to mimic the CTRL+Shift+End, so dotNET solution is great, except with my Excel 2010 I need to add a set if I want to avoid an error:

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

and how to check this for yourself:

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
Randolphrandom answered 17/5, 2017 at 15:23 Comment(0)
P
1
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
Portsalut answered 29/6, 2016 at 8:7 Comment(0)
I
1

For the last 3+ years these are the functions that I am using for finding last row and last column per defined column(for row) and row(for column):

Last Column:

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

Last Row:

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

For the case of the OP, this is the way to get the last row in column E:

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

Last Row, counting empty rows with data:

Here we may use the well-known Excel formulas, which give us the last row of a worksheet in Excel, without involving VBA - =IFERROR(LOOKUP(2,1/(NOT(ISBLANK(A:A))),ROW(A:A)),0)

In order to put this in VBA and not to write anything in Excel, using the parameters for the latter functions, something like this could be in mind:

Public Function LastRowWithHidden(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

    Dim letters As String
    letters = ColLettersGenerator(columnToCheck)
    LastRowWithHidden = ws.Evaluate("=IFERROR(LOOKUP(2,1/(NOT(ISBLANK(" & letters & "))),ROW(" & letters & " )),0)")

End Function

Function ColLettersGenerator(col As Long) As String

    Dim result As Variant
    result = Split(Cells(1, col).Address(True, False), "$")
    ColLettersGenerator = result(0) & ":" & result(0)

End Function
Introspect answered 1/6, 2018 at 19:30 Comment(7)
This will return an incorrect result if the last row/column is hidden.Saharan
@PGSystemTester - yes, but in my understanding, when I program it, if it is hidden it is not the last column/row that is needed.Introspect
Glad that works for you. I suspect your situation is not a typical use-case. More frequently when i work with clients that need the last row, they are seaching for the lowest cell with data, not the lowest visible cell with data. Anyway... glad it works. 👍Saharan
@PGSystemTester - I got your point, but taking care of the structure and not allowing invisible cells works like a charm.Introspect
@PGSystemTester - yeah, if the task possibly allows empty rows, I would probably use the EVAL() and the famous Excel formula. Although people may think that Eval() is evil and this is another interesting story on which to write...Introspect
Three years later.... what's the Eval story?!?Saharan
@Saharan - long story, different opinions, choose whichever suits you - google.com/search?q=is+eval+evilIntrospect
B
1

Last Row in a regular range or a Table (ListObject)

  1. Finding the last row requires using different methods if the range is a regular range or table (List Object).
  2. Finding the last row in tables requires specifying additional parameters (table name , the column relative position to the first tables column).

I created this universal function for last row, regardless of range type. Just give it any cell reference whatsoever and it will return the last row. No hassle having to knew range characteristics, especially if your ranges are some times a regular range and sometimes a ListObject. Using a regular range method on a table might return wrong results. Sure you can plan ahead of time and use the right method each time, but why bother if you can utilizes a universal function ?

Sub RunMyLastRow()
Dim Result As Long
Result = MyLastRow(Worksheets(1).Range("A1"))
End Sub
    Function MyLastRow(RefrenceRange As Range) As Long
    Dim WS As Worksheet
    Dim TableName As String
    Dim ColNumber As Long
    Dim LastRow As Long
    Dim FirstColumnTable As Long
    Dim ColNumberTable As Long
    Set WS = RefrenceRange.Worksheet
    TableName = GetTableName(RefrenceRange)
    ColNumber = RefrenceRange.Column
    
    ''If the table (ListObject) does not start in column "A" we need to calculate the 
    ''first Column table and how many Columns from its beginning the Column is located.
    If TableName <> vbNullString Then
     FirstColumnTable = WS.ListObjects(TableName).ListColumns(1).Range.Column
     ColNumberTable = ColNumber - FirstColumnTable + 1
    End If 

    If TableName = vbNullString Then
    LastRow = WS.Cells(WS.Rows.Count, ColNumber).End(xlUp).Row
    Else
    LastRow = WS.ListObjects(TableName).ListColumns(ColNumberTable).Range.Find( _
               What:="*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
    End If
    MyLastRow = LastRow
    End Function
    
    ''Get Table Name by Cell Range
    Function GetTableName(RefrenceRange As Range) As String
        If RefrenceRange.ListObject Is Nothing Then
            GetTableName = vbNullString
        Else
            GetTableName = RefrenceRange.ListObject.Name
        End If
    End Function
Bookish answered 16/7, 2021 at 17:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.