The Real Used Range (not UsedRange)
Using the Find Method
I've recently written this function:
'*******************************************************************************
' Purpose: Returns the used range of a worksheet.
' Returns: Range Object.
'*******************************************************************************
Function URng(Optional NotActiveSheet As Worksheet) As Range
Dim objWs As Worksheet
If Not NotActiveSheet Is Nothing Then
Set objWs = NotActiveSheet
Else: Set objWs = ActiveSheet: End If
If Not objWs Is Nothing Then
With objWs
If Not .Cells.Find("*", .Cells(.Rows.count, .Columns.count), -4123, , 1) _
Is Nothing Then Set URng = .Range(.Cells(.Cells.Find("*", _
.Cells(.Rows.count, .Columns.count)).Row, .Cells.Find("*", _
.Cells(.Rows.count, .Columns.count), , , 2).Column), .Cells(.Cells _
.Find("*", , , , 1, 2).Row, .Cells.Find("*", , , , 2, 2).Column))
End With
Set objWs = Nothing
End If
End Function
'*******************************************************************************
' Remarks: To remove any confusion about the search, consider a worksheet *
' containing only 3 rows and 3 columns. Then the search order *
' would be: *
' ------------------------------------------------------------------
' | Type | Start | Search Order |
' |----------------------------------------------------------------|
' | First Used Row | C3 | A1,B1,C1,A2,B2,C2,A3,B3,C3. |
' | First Used Column | C3 | A1,A2,A3,B1,B2,B3,C1,C2,C3. |
' | Last Used Row | A1 | C3,B3,A3,C2,B2,A2,C1,B1,A1. |
' | Last Used Column | A1 | C3,C2,C1,B3,B2,B1,A3,A2,A1. |
'*******************************************************************************
where you might find the following part to your interest:
With objWs
If Not .Cells.Find("*", .Cells(.Rows.count, .Columns.count), -4123, , 1) _
Is Nothing Then Set URng = .Range(.Cells(.Cells.Find("*", _
.Cells(.Rows.count, .Columns.count)).Row, .Cells.Find("*", _
.Cells(.Rows.count, .Columns.count), , , 2).Column), .Cells(.Cells _
.Find("*", , , , 1, 2).Row, .Cells.Find("*", , , , 2, 2).Column))
End With
or in a Sub
Sub RealUsedRange()
Const cSheet As Variant = "Sheet1" ' Worksheet Name/Index
Dim URng As Range ' Real Used Range
With ThisWorkbook.Worksheets(cSheet)
If Not .Cells.Find("*", .Cells(.Rows.Count, .Columns.Count), -4123, , _
1) Is Nothing Then Set URng = .Range(.Cells(.Cells.Find("*", _
.Cells(.Rows.Count, .Columns.Count)).Row, .Cells.Find("*", _
.Cells(.Rows.Count, .Columns.Count), , , 2).Column), _
.Cells(.Cells.Find("*", , , , 1, 2).Row, .Cells _
.Find("*", , , , 2, 2).Column))
End With
If Not URng Is Nothing Then
Debug.Print "The Real Used Range address is [" & URng.Address & "]"
Else
MsgBox "Worksheet '" & cSheet & "' is empty."
End If
End Sub
Other 'fractions' of the Real Used Range using the Find Method.
By the way, as the UsedRange property has failed you so will the SpecialCells method rather sooner than later since they are somehow connected. See example.
The Find Method's 6(9) Arguments
Microsoft (VBA Help)
Syntax: expression. Find(What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat)
Expression is a range object, in our case it will be .Cells
which refers to all cells in the worksheet.
The Find method has 9 arguments:
- What
- After
- LookIn
- LookAt
- SearchOrder
- SearchDirection
- MatchCase
- MatchByte
- SearchFormat
You can use them in Argument:Parameter
style or enter them by adding their parameters in the exact order (separated by commas) which will be used here.
The What argument is understandable by itself: search for anything "*". The MatchCase argument is by default False
(Caution: it is not by default False
for the Replace
method though). The arguments MatchByte and SearchFormat are beyond the scope of this case and will not be further investigated, which leaves us with arguments number 2 to 6.
2. After has to be a one-cell range contained in expression (Initial Search Range). Note that this cell will be searched last, so if you use .Cells(1, 1)
or .Cells(1)
the search will start with the next cell e.g. .Cells(1, 2)
, .Cells(2, 1)
... or .Cells(2)
, or the previous cell e.g. .Cells(1,.Columns.Count)
, .Cells(.Rows.Count,1)
or .Cells(.Cells.Count)
depending on the SearchOrder
and SearchDirection
parameters. The default value is .Cells(1, 1)
or .Cells(1)
, which can be omitted when used.
To calculate the Last Used Row, Last Used Column or Last Used Cell this parameter will be omitted (.Cells(1)
) because we want to start searching from the last cell going up or to the left.
To calculate the First Used Row, First Used Column or First Used Cell this parameter will be .Cells(.Rows.Count, .Columns.Count)
or .Cells(.Cells.Count)
because we want to start searching from the first cell going down or to the right.
3. LookIn can be one of the following XLLookIn
constants:
- xlValues
or -4163
will find any cell with a value except a cell containing a formula that evaluates to "".
- xlFormulas
or -4123
will find any cells with a value including cells containing a formula that evalutates to "". This parameter will be used because we cannot ignore cells containing a formula that evaluates to "".
- xlComments
or -4144
will find any cell containing a comment (not used in this case).
4. LookAt can be one of the following XLLookAt
constants:
xlWhole
or 1
searches for whole strings only i.e. to find a cell
containing e.g. Word, it will find a cell containing Word, but will not find a cell containing WordId.
xlPart
or 2
searches for parts of the string i.e. to find a cell
containing e.g. Word it will find cells containing both, Word or WordId.
From everything I've read it is unclear using which parameter would make our search faster so it will be omitted in our case. Should be further investigated.
5. SearchOrder can be one of the following XLSearchOrder
constants:
xlByRows
or 1
will perform the search by rows e.g. in a Next
search from the last cell it will search in A1, B1, C1...A2, B2, C2...(.Rows.Count, .Columns.Count)
.
xlByColumns
or 2
will perform the search by columns e.g. in a
Next
search from the last cell it will search in A1, A2, A3...B1, B2, B3...(.Rows.Count, .Columns.Count)
.
6. SearchDirection can be one of the following XLSearchDirection
constants:
xlNext
or 1
(Default) in a 'by columns' search (xlByColumns
) with After:="A5"
will search in A6, A7, A8...
xlPrevious
or 2
in a 'by columns' search (xlByColumns
) with After:="A5"
will search in A4, A3, A2...
VBA Remarks
The settings for LookIn, LookAt, SearchOrder, and
MatchByte are saved each time you use this method. If you don’t
specify values for these arguments the next time you call the method,
the saved values are used. Setting these arguments changes the
settings in the Find dialog box, and changing the settings in the Find
dialog box changes the saved values that are used if you omit the
arguments. To avoid problems, set these arguments explicitly each time
you use this method.
Note
There is the What argument, there are 2 arguments (After, SearchDirection) with possible default values and 3 arguments (LookIn, LookAt, SearchOrder) that are saved each time.
Dissecting the Real Used Range Expression
If Not .Cells.Find("*", .Cells(.Rows.Count, .Columns.Count), -4123, , _
1) Is Nothing Then Set URng = .Range(.Cells(.Cells.Find("*", _
.Cells(.Rows.Count, .Columns.Count)).Row, .Cells.Find("*", _
.Cells(.Rows.Count, .Columns.Count), , , 2).Column), _
.Cells(.Cells.Find("*", , , , 1, 2).Row, .Cells _
.Find("*", , , , 2, 2).Column))
to be continued...
A1:A2
(the first contiguous data range) orA1:D7
(the bounding box containing all available data)? Also, what dataset are you testing the code to get the shown execution times? \$\endgroup\$ValueBoundingBox
function fails for me on the lineSet x = Application.Union
when using the dataset in your image. \$\endgroup\$A1:D7
. Also you are correct, I didn't actually test the first function on that dataset in the image. That's just an example I found online :P I guess the alternative would be checking whether the range has values before hand, however it doesn't matter massively given the fact it's super slow :) \$\endgroup\$STD_Performance
is missing from your past bin. Can you add it or better yet provide download link fr the test workbook? I thought of a simpler solution but don't want to post it without a speed comparison. \$\endgroup\$