在Excel VBA中查找上次使用的单元格

Find last used cell in Excel VBA

提问人:Mushahid Hussain 提问时间:6/23/2012 最后编辑:ZygDMushahid Hussain 更新时间:7/27/2022 访问量:185397

问:

当我想找到最后使用的单元格值时,我使用:

Dim LastRow As Long

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

Debug.Print LastRow

当我将单个元素放入单元格时,我得到了错误的输出。但是当我在单元格中输入多个值时,输出是正确的。 这背后的原因是什么?

VBA Excel 公式 单元格

评论

1赞 M-- 7/7/2017
stackoverflow.com/questions/71180/......
2赞 David Mays 9/24/2021
@FreeSoftwareServers我不同意 - 因为有很多答案显示了如何找到最后一行,这个特定问题是独特而具体的,因为它询问为什么特定方法不起作用,这是在各种其他“如何”答案中建议的方法。
0赞 pgSystemTester 12/28/2021
我相信我的以下答案提供了最理想的一站式解决方案。我对批评持开放态度,但有了新的过滤器/查询工具,我至少有足够的信心发布这条评论,并面对批评的愤怒,因为一个相当厚颜无耻的主张......

答:

358赞 Siddharth Rout 6/23/2012 #1

注意:我打算把它做成一个“一站式帖子”,你可以用它来找到最后一行。这还将涵盖查找最后一行时要遵循的最佳实践。因此,每当我遇到新的场景/信息时,我都会继续更新它。Correct


查找最后一行的不可靠方法

查找最后一行的一些最常见方法非常不可靠,因此永远不应该使用。

  1. UsedRange
  2. xl下
  3. 计数A

UsedRange永远不应该用于查找最后一个有数据的单元格。这是非常不可靠的。试试这个实验。

在单元格中键入内容。现在,当您使用下面给出的任何方法计算最后一行时,它都会给您 5。现在将单元格涂成红色。如果您现在使用以下任何代码,您仍将获得 5。如果你使用,你会得到什么?它不会是 5。A5A10Usedrange.Rows.Count

下面是一个演示工作原理的场景。UsedRange

enter image description here

xlDown同样不可靠。

请考虑以下代码

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

如果只有一个单元格 () 有数据,会发生什么?您最终将到达工作表中的最后一行!这就像选择单元格,然后按键,然后按键。如果某个区域中有空白单元格,这也将为您提供不可靠的结果。A1A1EndDown Arrow

CountA也是不可靠的,因为如果中间有空白单元格,它会给你不正确的结果。

因此,应避免使用 ,并找到最后一个单元格。UsedRangexlDownCountA


查找列中的最后一行

要查找 Col E 中的最后一行,请使用此命令

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

如果您注意到我们有一个之前的.我们经常选择忽略这一点。请参阅此问题,了解您可能遇到的错误。我总是建议使用 before 和 .该问题是代码将失败的典型方案,因为 Excel 2003 及更早版本以及 Excel 2007 及更高版本的返回。同样,分别返回 和 。.Rows.Count.Rows.CountColumns.CountRows.Count655361048576Columns.Count25616384

Excel 2007+具有行的上述事实也强调了这样一个事实,即我们应该始终声明将保存行值的变量,否则您将收到错误。1048576LongIntegerOverflow

请注意,此方法将跳过任何隐藏的行。回顾上面 A 列的屏幕截图,如果隐藏第 8 行,则此方法将返回而不是 .58


查找工作表中的最后一行

要查找工作表中的最后一行,请使用此选项。请注意 的用法。这是必需的,因为如果工作表中没有包含数据的单元格,那么将为您提供EffectiveApplication.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)

同样的原则也适用,例如,获取表第三列的最后一行:

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

评论

10赞 Siddharth Rout 8/14/2012
@phan:在单元格 A5 中键入内容。现在,当您使用上面给出的任何方法计算最后一行时,它都会给您 5。现在将单元格 A10 着色为红色。如果您现在使用上述任何代码,您仍将获得 5。如果你使用,你会得到什么?它不会是 5。Usedrange 查找最后一行非常不可靠。Usedrange.Rows.Count
6赞 Carl Colijn 1/31/2014
请注意 .不幸的是,“查找”会弄乱用户在“查找”对话框中的设置 - 即 Excel 只有 1 组对话框设置,而您使用 .查找替换它们。另一个技巧是仍然使用 UsedRange,但将其用作绝对(但不可靠)最大值,从中确定正确的最大值。
4赞 Siddharth Rout 1/31/2014
@CarlColijn:我不会称之为乱七八糟。:)Excel只是最后一个设置。即使你手动执行 ,它也会记住最后一个设置,如果知道这个“事实”,这实际上是一个福音remembersFind
3赞 Siddharth Rout 8/29/2014
@KeithPark:请继续:)知识只有在传播:)才有意义
12赞 sancho.s ReinstateMonicaCellio 12/24/2014
我认为您对(找到最后一个有数据的单元格非常不可靠)的描述具有误导性。 根本不是出于这个目的,即使在某些情况下它可能会给出正确的结果。我认为提出的实验增加了混乱。用 ($A$1:$A$8) 获得的结果不依赖于首先输入数据并删除数据。即使没有输入数据并删除数据,右侧的数字仍然相同。请看我的回答。UsedRangeUsedRangeUsedRange
9赞 no comprende 11/5/2014 #2

我会在 Siddarth Rout 给出的答案中补充一点,即可以通过让 Find 返回一个 Range 对象而不是行号来跳过 CountA 调用,然后测试返回的 Range 对象以查看它是否为 Nothing(空白工作表)。

此外,我会让我的任何 LastRow 过程版本为空白工作表返回零,然后我就可以知道它是空白的。

40赞 sancho.s ReinstateMonicaCellio 12/24/2014 #3

注意:这个答案是由这个评论引起的。UsedRange 的用途与上面答案中提到的不同。

至于找到最后使用的单元格的正确方法,首先要确定什么被认为是使用的,然后选择合适的方法。我至少设想了三个含义:

  1. Used = 非空白,即有数据

  2. Used = “...使用中,表示包含数据或格式的部分根据官方文档,这是Excel在保存时使用的标准。另请参阅此官方文档。 如果人们没有意识到这一点,该标准可能会产生意想不到的结果,但它也可能被故意利用(当然不太常见),例如,突出显示或打印特定区域,这些区域最终可能没有数据。 而且,当然,在保存工作簿时,最好将其作为范围的标准,以免丢失部分工作。

  3. Used = “...正在使用中,表示包含数据或格式的部分“或条件格式。与 2 相同,但也包括作为任何条件格式规则目标的单元格。

如何找到最后使用的单元格取决于想要什么(您的标准)。

对于标准 1,我建议阅读此答案。 请注意,这被认为是不可靠的。我认为这是误导性的(即“不公平”),因为这并不意味着报告包含数据的最后一个单元格。因此,在这种情况下不应使用它,如该答案所示。另请参阅此评论UsedRangeUsedRangeUsedRange

对于标准 2,UsedRange 是最可靠的选项,与同样为此用途设计的其他选项相比。它甚至不需要保存工作簿以确保更新最后一个单元格。+ 在保存之前会转到错误的单元格 (“在保存工作表之前,最后一个单元格不会重置”,来自 http://msdn.microsoft.com/en-us/library/aa139976%28v=office.10%29.aspx。 这是一个旧的参考,但在这方面是有效的)。CtrlEnd

对于标准 3,我不知道任何内置方法。 标准 2 不考虑条件格式。人们可能具有基于公式的格式化单元格,这些单元格未被 或 + 检测到。 在图中,最后一个单元格是 B3,因为格式已显式应用于它。单元格 B6:D7 具有从条件格式规则派生的格式,即使 也无法检测到。 考虑到这一点,需要一些 VBA 编程。UsedRangeCtrlEndUsedRange

enter image description here


至于你的具体问题这背后的原因是什么?

您的代码使用范围 E4:E48 中的第一个单元格作为蹦床,用于用 .End(xlDown)

如果范围中没有除第一个单元格之外的非空白单元格,则将获得“错误”输出。然后,你在黑暗中跳跃,即从工作表上跳下来 (您应该注意空白字符串和空字符串之间的区别!

请注意:

  1. 如果您的范围包含非连续的非空白单元格,那么它也会给出错误的结果。

  2. 如果只有一个非空白单元格,但它不是第一个单元格,则代码仍会给出正确的结果。

评论

5赞 GlennFromIowa 4/13/2017
我同意,人们必须首先决定什么被认为是使用的。我看到至少 6 种含义。单元格具有:1)数据,即公式,可能导致空白值;2)一个值,即一个非空白的公式或常量;3)格式化;4)条件格式;5)与单元格重叠的形状(包括注释);6)参与表(列表对象)。您要测试哪种组合?有些(如表)可能更难测试,有些可能很少见(如数据范围之外的形状),但其他的可能会因情况而异(例如,具有空白值的公式)。
2赞 Spinner 11/11/2021
我同意 @Siddharth Rout 的回答具有误导性(充其量)。UsedRange 和它的对应物 [在此上下文中] SpecialCells(xlCellTypeLastCell) 都非常可靠且非常有用。而且,正如这里所指出的,如果您真的在寻找最后使用的行,那么在 10 种情况下,有 9 种,这是最有效和最可靠的方法。关键是要知道它意味着什么,以及如何以及何时利用它的力量。
12赞 Bishop 1/3/2015 #4

使用解决方案时要记住的重要注意事项......

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

...是为了确保变量的类型为:LastRowLong

Dim LastRow as Long

否则,在某些情况下,您最终会遇到 OVERFLOW 错误。XLSX 工作簿

这是我的封装函数,我将其放入各种代码使用中。

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赞 dotNET 4/27/2015 #5

我想知道没有人提到过这一点,但是获取最后使用的单元格的最简单方法是:

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

这实质上返回了您在选择单元格 A1 后通过 + 获得的相同单元格。CtrlEnd

注意:Excel 会跟踪工作表中曾经使用过的最右下角的单元格。因此,例如,如果您在 B3 中输入某些内容,在 H8 中输入其他内容,然后删除 H8 的内容,按 + 仍会将您带到 H8 单元格。上述函数将具有相同的行为。CtrlEnd

评论

2赞 shA.t 4/28/2015
Last Cell在 Excel 中,有时是指与 ;) 不同的空单元格 (from )。Used RangeLast Used Cell
1赞 shA.t 4/28/2015
OP 只需要最后一行,但你是对的,最后一个单元格应该是 H5;但是您可以在删除 A5 中的值后测试您的函数,您会看到最后一个单元格是那个空单元格,我认为您的代码需要一些编辑,例如无效,它可能是;此外,在 VBA 中,不建议使用 ;)。Cells(1,1).Select()ActiveSheet.Cells(1,1).SelectSelect
5赞 Rachel Hettinger 6/4/2015
这违反了 Excel VBA 的两个基本规则:不要使用 Select!不要假设你想要的工作表是活跃的。
1赞 BigBen 10/11/2019
这是一个古老的答案,但它缺少一个.Set
3赞 Ashwith Ullal 10/1/2015 #6
sub last_filled_cell()
msgbox range("A65536").end(xlup).row
end sub

这里,是 A 列中的最后一个单元格,此代码已在 Excel 2003 上进行了测试。A65536

评论

0赞 shoover 10/2/2015
你能解释一下你的代码是如何回答这个老问题的吗?
2赞 Kevin Brown-Silva 10/3/2015
虽然这个答案可能是正确和有用的,但如果你在它旁边附上一些解释来解释它如何帮助解决问题,它是首选。这在将来变得特别有用,如果有更改(可能不相关)导致它停止工作,并且用户需要了解它曾经是如何工作的。
22赞 ZygD 12/24/2015 #7

我创建了这个一站式函数,用于确定最后一行、列和单元格,无论是数据、格式化(分组/注释/隐藏)单元格还是条件格式

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

存在一个限制 - 如果工作表中有表格,结果可能会变得不可靠,因此我决定避免在这种情况下运行代码:

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

评论

2赞 ZygD 2/9/2016
@franklin - 我刚刚注意到收件箱中您的更正被审稿人拒绝了。我纠正了这个错误。我已经在需要时使用过一次此功能,我会再次使用它,所以真的,非常感谢,我的朋友!
1赞 user85489 6/29/2016 #8
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
3赞 M-- 5/9/2017 #9

但是,这个问题是试图使用 VBA 找到最后一行,我认为最好包含工作表函数的数组公式,因为它经常被访问:

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

您需要输入不带括号的公式,然后点击 + + 使其成为数组公式。ShiftCtrlEnter

这将为您提供 D 列中上次使用的单元格的地址。


多亏了 pgsystemtester,这将为您提供上次使用的单元格的行号:

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

评论

1赞 pgSystemTester 11/28/2019
我喜欢这个。我可能会稍微改动一下,只得到行号......'{=MATCH(INDEX(D:D,MAX(IF(D:D<>“”,ROW(D:D)-ROW(D1)+1)),1),D:D,0)}'
3赞 J. Chomel 5/17/2017 #10

我一直在寻找一种模仿++的方法,所以dotNET解决方案很棒,除了我的Excel 2010,如果我想避免错误,我需要添加一个:CTRLShiftEndset

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
13赞 Nickolay 4/23/2018 #11

由于最初的问题是关于查找最后一个单元格的问题,因此在此答案中,我将列出获得意外结果的各种方法;请参阅我对“如何使用宏在Excel工作表中找到包含数据的最后一行?”的回答,了解我对此问题的看法。

我将首先扩展 sancho.s 的答案GlennFromIowa 的评论,添加更多细节:

[...]首先要决定什么被认为是使用的。我看到至少 6 种含义。细胞具有:

  • 1)数据,即公式,可能导致空白值;
  • 2)一个值,即一个非空白的公式或常量;
  • 3)格式化;
  • 4)条件格式;
  • 5)与单元格重叠的形状(包括注释);
  • 6)参与表(列表对象)。

您要测试哪种组合?有些(如表)可能更难测试,有些可能很少见(如数据范围之外的形状),但其他的可能会因情况而异(例如,具有空白值的公式)。

您可能需要考虑的其他事项:

  • A) 是否可以有隐藏行(例如自动过滤器)、空白单元格或空白
  • B) 什么样的性能是可以接受的?
  • C) VBA 宏是否会以任何方式影响工作簿或应用程序设置?

考虑到这一点,让我们看看获取“最后一个单元格”的常见方法如何产生意想不到的结果:

  • 问题的代码最容易中断(例如,使用单个非空单元格或当中间有空白单元格时),原因如下 Siddharth Rout 在这里的回答中解释(搜索“xlDown 同样不可靠”。 👎.End(xlDown)
  • 任何基于 ing( 或 )的解,或者在存在空白单元格或行👎时也会中断CountCountACells*.Count.CurrentRegion
  • 涉及从列末尾向后搜索的解决方案将与 CTRL+UP 一样,在可见行中查找数据(生成空白值的公式被视为“数据”)(因此在启用自动筛选的情况下使用它可能会产生不正确的结果⚠️)。.End(xlUp)

    您必须注意避免标准陷阱(有关详细信息,我将再次参考 Siddharth Rout 的答案,请查找“查找列中的最后一行”部分),例如硬编码最后一行 () 而不是依赖 .Range("A65536").End(xlUp)sht.Rows.Count

  • .SpecialCells(xlLastCell)相当于 CTRL+END,返回“已用范围”的最底部和最右边的单元格,因此适用于依赖“已用范围”的所有注意事项也适用于此方法。此外,“使用范围”仅在保存工作簿和访问 时重置,因此可能会因未保存的修改而产生过时的结果⚠️(例如,在删除某些行之后)。请参阅附近 dotNET 的答案worksheet.UsedRangexlLastCell
  • sht.UsedRange(在 sancho.s 的回答中详细描述这里)考虑数据和格式(尽管不是条件格式)并重置工作表的“使用范围”,这可能是也可能不是您想要的。

    请注意,一个常见的错误 ️是使用 ,它返回已使用⚠️范围内的行数,而不是最后一行号(如果前几行为空,它们会有所不同),有关详细信息,请参阅 newguy 对如何使用宏在 Excel 工作表中找到包含数据的最后一行的回答? .UsedRange.Rows.Count

  • .Find允许您查找包含任何数据(包括公式)或任何列中的非空白值的最后一行。您可以选择是否对公式或值感兴趣,但问题是它会重置 Excel 的“查找”对话框 ️️⚠️ 中的默认值,这可能会让您的用户感到非常困惑。它也需要谨慎使用,请在此处查看 Siddharth Rout 的答案“查找工作表中的最后一行”部分))
  • 在循环中检查单个 ' 的更明确的解决方案通常比重用 Excel 函数慢(尽管仍然可以执行),但可以让您准确指定要查找的内容。请参阅基于 VBA 数组的解决方案,以查找给定列中包含数据的最后一个单元格 - 它处理隐藏行、过滤器、空白,不修改“查找”默认值并且性能非常高。CellsUsedRange

无论您选择哪种解决方案,都要小心

  • 使用而不是存储行号(以避免获得超过 65k 行)和LongIntegerOverflow
  • 始终指定您正在使用的工作表(即 而不是Dim ws As Worksheet ... ws.Range(...)Range(...))
  • 使用 (这是一个 ) 时,请避免隐式强制转换,因为如果单元格包含错误值,它们将失败。.ValueVariant.Value <> ""

评论

0赞 Timo 5/12/2021
在做什么?找到最后一行,然后从最后开始?lastrow = .Range("b" & .Rows.Count).End(xlUp).Rowup
1赞 Nickolay 5/12/2021
@Timo基本上,是的。在我的回答中,我确定了这样的描述:“从列的末尾向后搜索 [查找] 可见行中的数据([省略详细信息]”,我支持它。
1赞 Vityata 6/2/2018 #12

在过去的 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)

最后一行,计算包含数据的空行:

在这里,我们可以使用众所周知的Excel公式,这些公式为我们提供了Excel中工作表的最后一行,而不涉及VBA -=IFERROR(LOOKUP(2,1/(NOT(ISBLANK(A:A))),ROW(A:A)),0)

为了将其放在 VBA 中而不是在 Excel 中编写任何内容,使用后一个函数的参数,可以考虑如下:

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

评论

0赞 pgSystemTester 11/28/2019
如果隐藏最后一行/列,这将返回不正确的结果。
0赞 Vityata 11/28/2019
@PGSystemTester - 是的,但在我的理解中,当我对它进行编程时,如果它被隐藏,它就不是需要的最后一列/行。
0赞 pgSystemTester 11/29/2019
很高兴这对你有用。我怀疑您的情况不是典型的用例。更常见的是,当我与需要最后一行的客户合作时,他们会搜索包含数据的最低单元格,而不是包含数据的最低可见单元格。无论如何。。。很高兴它起作用了。👍
0赞 Vityata 11/29/2019
@PGSystemTester - 我明白你的意思,但照顾好结构,不允许看不见的细胞就像一个魅力。
1赞 Vityata 6/9/2022
@pgSystemTester - 长话短说,不同的意见,选择适合你的 - google.com/search?q=is+eval+evil
7赞 pgSystemTester 11/28/2019 #13

于2021年底更新

借助 Excel 的新计算引擎和数组功能以及过滤器功能,我相信这个话题现在应该不那么有争议了,并且以下选项提供了速度、可靠性和简单性的最佳组合(事实证明,这在过去很难平衡,正如这里的众多帖子所示)。

此外,我按照 isBlank 函数的定义定义为 NOT blanklast used

Excel公式

首先,请注意,Filter Function 使用以下公式获取特定行或列的最后一个单元格变得更加简单(在这些情况下为 or ):Column ARow 1

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

最后一行特定范围的 VBA 函数

使用上述函数,我们可以将其转换为VBA函数,但通过限制范围使其更快,同时通过执行多个列来扩展其功能(感谢Chris Neilsen的即时反馈/建议)。我还发现,通过将每列的范围限定为仅比前一行高一行的范围,速度得到了巨大的提高。

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函数

要考虑整个工作表(所有列),我建议使用引用前一个工作表的不同 VBA 公式,但它是一个易失性函数。这可确保公式随工作表的任何更改而更新。显然,可以将这两个公式结合起来,但我更愿意限制可变函数的使用。

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

与其他选项相比的优势

  • 允许在不更改方法的情况下使用工作表中的部分或全部行/列。
  • 没有遗漏隐藏行的可能性,因为存在风险xlup
  • 忽略格式化/使用范围问题。
  • 不会干扰用户的设置。Find
  • 使用比 VBA 计算更快的工作表功能。
  • 没有计数单元格(性能猪)。

希望这结束了辩论,但如果有人发现其中的弱点,请分享。

评论

1赞 chris neilsen 12/28/2021
@pgSystemTester看起来很好地使用了新功能。不过有几个建议(我无法测试 ATM,所以我可能在某些方面是错误的)1) 最好使用该方法 - 2) 有了它,您可能不需要使用 WS 名称限定范围,从而避免了一些字符串工作(更好的速度)3) 我没有看到处理空列 4) 可能不需要在每列上相交。只需使用 5) 可能想要处理非连续 6) 您是否做过任何性能测试?Worksheet.EvaluatezWS.EvaluateIfErrortangoRangetangoRange.Columns(i)someColumns
0赞 pgSystemTester 12/28/2021
@chrisneilsen感谢您的反馈!我将在今天晚些时候审查您的所有建议。
1赞 chris neilsen 12/28/2021
划痕项目 3,我现在看到了。或者,您可以使用 Filter 的“no result”参数
0赞 pgSystemTester 12/28/2021
@chrisneilsen测试了您的建议,并能够优化代码。多谢!现在有了新的答案。我稍后会做一些性能测试。
0赞 chris neilsen 12/29/2021
@pgSystemTester调整范围大小的有趣优化。不过有一个想法:如果我没看错的话,它的大小会一直到最后找到的行 +1 到工作表的末尾?将其限制在已用范围的末尾会更好吗?
1赞 Noam Brand 7/17/2021 #14

常规区域或表中的最后一行 (ListObject)

  1. 如果范围是常规区域或表(列表对象),则查找最后一行需要使用不同的方法。
  2. 查找表中的最后一行需要指定其他参数(表名、列与第一个表列的相对位置)。

我为最后一行创建了这个通用函数,而不考虑范围类型。只需给它任何单元格引用,它就会返回最后一行。 无需了解范围特征,尤其是当您的范围有时是常规范围,有时是 ListObject 时。 对表使用常规范围方法可能会返回错误的结果。 当然,您可以提前计划并每次都使用正确的方法,但是如果您可以使用通用功能,为什么还要麻烦呢?

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