Optimize Performance of InStr check over range VBA - excel

Is it possible to speed up the below Sub() that checks for the - character in a range? It is looping over 25000 rows across 3 columns and doesn't perform as fast as I'd like it to.
Sub Character_Check()
For Each tCell In Range("A1:C25000")
If InStr(tCell.Text, "-") > 0 Then
MsgBox "True"
End If
Next
End Sub

Solution:
For these scenarios (massive same computation) I think it's faster to store the values in an array and then just set the results to the range at once. Refer to this answer for a further explanation, by using it, we can see a substantial optimization.
Code
Sub Character_Check_2()
Dim DateTimeReportsRequest As Date: DateTimeReportsRequest = Now()
Dim arrMark As Variant
Dim CounterRow As Long
Dim CounterCol As Long
ReDim arrMark(1 To Cells.SpecialCells(xlCellTypeLastCell).Row, 1 To 1)
For CounterCol = 1 To 3 'A to C
For CounterRow = 2 To Cells.SpecialCells(xlCellTypeLastCell).Row 'Rows 2 to 25k
If InStr(Cells(CounterRow, CounterCol).Text, "-") > 0 Then arrMark(CounterRow, 1) = "TRUE"
Next CounterRow
Next CounterCol
With Cells(1, 5).Resize(UBound(arrMark), 1)
.Value = arrMark
End With
MsgBox "Time to finish: " & DateDiff("s", DateTimeReportsRequest, Now()) & " seconds", vbOKOnly
End Sub
Demo
Testing for the 25k in 3 columns (assuming it needs to write it to the sheet which it's very demanding), we go from 11secs to ~1

Try the below simple alternative and see if it improves the performance:
Sub Find_All()
Dim rangeFirst As Range, rangeCurrent As Range
Dim searchRange As Range
Set searchRange = Range("A1:C25000")
Do
If rangeFirst Is Nothing Then
Set rangeFirst = searchRange.Find(What:="-")
Set rangeCurrent = rangeFirst
Else
Set rangeCurrent = searchRange.Find(What:="-", After:=rangeCurrent)
If rangeCurrent.Address = rangeFirst.Address Then Exit Do
End If
MsgBox (rangeCurrent)
Loop
End Sub

Related

Excel VBA - I Have Too Many Cells To Be Considered A Range [duplicate]

Is there some limit to what I can select in a range via VBA? Basically what I found is that if I were to hide an entire row while in a loop, it takes quite a while if there are lots of rows to hide.
ex) - Hide any row that doesn't have a value in column A
For i = 1 to 600
With Range("A" & i)
If .value = vbEmpty then .EntireRow.Hidden = True
End With
Next
The more speedy way of doing that is to make a single range that references each of those rows and then do a single ".entirerow.hidden = true" statement. And yes, I already have application.screenupdating = false set.
The problem I'm encountering is that if the string reference for the range is too long, it just fails.
The following code declares a function which accepts both a standard array of row numbers (in case the array is made before hand), as well as parameter arguments (in case you don't want to declare an array before hand, and the list of rows is small). It then creates a string which is used in the range reference.
Function GetRows(argsArray() As Long, ParamArray args() As Variant) As Range
Dim rngs As String
Dim r
For Each r In argsArray
rngs = rngs & "," & r & ":" & r
Next
For Each r In args
rngs = rngs & "," & r & ":" & r
Next
rngs = Right(rngs, Len(rngs) - 1)
Set GetRows = Range(rngs)
End Function
Function dfdfd()
Dim selList(50) As Long, j As Long
For i = 1 To 100
If i Mod 2 = 1 Then
selList(j) = i
j = j + 1
End If
Next
selList(50) = 101
GetRows(selList).Select
End Function
The 2nd function "dfdfd" is just used to give an example of when it fails. To see when it works, just make a new array with say - 5 items, and try that. It works.
Final (?) update:
Option Explicit
Public Sub test()
Dim i As Integer
Dim t As Long
Dim nRng As Range
t = Timer()
Application.ScreenUpdating = False
Set nRng = [A1]
For i = 1 To 6000
Set nRng = Union(nRng, Range("A" & i))
Next
nRng.RowHeight = 0
'nRng.EntireRow.Hidden = true
Application.ScreenUpdating = True
Debug.Print "Union (RowHeight): " & Timer() - t & " seconds"
'Debug.Print "Union (EntireRow.Hidden): " & Timer() - t & " seconds"
End Sub
Results:
Union (row height: 0.109375 seconds
Union (hidden row): 0.625 seconds
I think the magical function you're looking for here is Union(). It's built into Excel VBA, so look at the help for it. It does just what you'd expect.
Loop through your ranges, but instead of building a string, build up a multi-area Range. Then you can select or set properties on the whole thing at once.
I don't know what (if any) the limit on the number of areas you can build up in a single Range is, but it's bigger than 600. I don't know what (if any) limits there are on selecting or setting properties of a multi-area Range either, but it's probably worth a try.
A faster option might be to use the SpecialCells property to find the blanks then hide the rows:
Sub HideRows()
Dim rng As Range
Set rng = ActiveSheet.Range("A1:A600")
Set rng = rng.SpecialCells(xlCellTypeBlanks)
rng.EntireRow.Hidden = True
End Sub
This will only work on cells within the UsedRange, I think.
A minor speedup can be obtained if you set the RowHeight property to 0.
On my system it goes about twice as fast
(on 6000 iterations about 1.17 seconds versus 2.09 seconds)
You didn't mention what 'quite a while' is, and what version of XL you are using...
Your problem may be in part your row detect code that checks for a row you want to hide(?).
Here's my test code in XL 2003 (comment out one version then the other):
Option Explicit
Public Sub test()
Dim i As Integer
Dim t As Long
t = Timer()
Application.ScreenUpdating = False
For i = 1 To 6000
With Range("A" & i)
'If .Value = vbEmpty Then .EntireRow.Hidden = True
If .Value = vbEmpty Then .RowHeight = 0
End With
Next
Application.ScreenUpdating = True
Debug.Print Timer() - t & " seconds"
End Sub
There is a limit to the string length. I just encountered a similar problem and found that if the String Txt of
Range(Txt)
is larger then 255 characters my VBA throws an Error.eg. the code:
Debug.Print sheet1.Range("R2300,T2300,V2300,R2261,T2261,V2261,R1958,T1958,V1958,R1751,T1751,V1751,R1544,T1544,V1544,R1285,T1285,V1285,R1225,T1225,V1225,R1193,T1193,V1193,R1089,T1089,V1089,R802,T802,V802,R535,T535,V535,R264,T264,V264,R205,T205,V205,R168,T168,V168,R135,T135,V135,R101").Areas.count
throws an error (256 characters in string) whereas the code
Debug.Print sheet1.Range("R230,T2300,V2300,R2261,T2261,V2261,R1958,T1958,V1958,R1751,T1751,V1751,R1544,T1544,V1544,R1285,T1285,V1285,R1225,T1225,V1225,R1193,T1193,V1193,R1089,T1089,V1089,R802,T802,V802,R535,T535,V535,R264,T264,V264,R205,T205,V205,R168,T168,V168,R135,T135,V135,R101").Areas.count
has 255 characters and prints out "46" without Error. The number of Areas is in both cases the same.

Charting Non-Contiguous Data Using Declared Range Variables [duplicate]

Is there some limit to what I can select in a range via VBA? Basically what I found is that if I were to hide an entire row while in a loop, it takes quite a while if there are lots of rows to hide.
ex) - Hide any row that doesn't have a value in column A
For i = 1 to 600
With Range("A" & i)
If .value = vbEmpty then .EntireRow.Hidden = True
End With
Next
The more speedy way of doing that is to make a single range that references each of those rows and then do a single ".entirerow.hidden = true" statement. And yes, I already have application.screenupdating = false set.
The problem I'm encountering is that if the string reference for the range is too long, it just fails.
The following code declares a function which accepts both a standard array of row numbers (in case the array is made before hand), as well as parameter arguments (in case you don't want to declare an array before hand, and the list of rows is small). It then creates a string which is used in the range reference.
Function GetRows(argsArray() As Long, ParamArray args() As Variant) As Range
Dim rngs As String
Dim r
For Each r In argsArray
rngs = rngs & "," & r & ":" & r
Next
For Each r In args
rngs = rngs & "," & r & ":" & r
Next
rngs = Right(rngs, Len(rngs) - 1)
Set GetRows = Range(rngs)
End Function
Function dfdfd()
Dim selList(50) As Long, j As Long
For i = 1 To 100
If i Mod 2 = 1 Then
selList(j) = i
j = j + 1
End If
Next
selList(50) = 101
GetRows(selList).Select
End Function
The 2nd function "dfdfd" is just used to give an example of when it fails. To see when it works, just make a new array with say - 5 items, and try that. It works.
Final (?) update:
Option Explicit
Public Sub test()
Dim i As Integer
Dim t As Long
Dim nRng As Range
t = Timer()
Application.ScreenUpdating = False
Set nRng = [A1]
For i = 1 To 6000
Set nRng = Union(nRng, Range("A" & i))
Next
nRng.RowHeight = 0
'nRng.EntireRow.Hidden = true
Application.ScreenUpdating = True
Debug.Print "Union (RowHeight): " & Timer() - t & " seconds"
'Debug.Print "Union (EntireRow.Hidden): " & Timer() - t & " seconds"
End Sub
Results:
Union (row height: 0.109375 seconds
Union (hidden row): 0.625 seconds
I think the magical function you're looking for here is Union(). It's built into Excel VBA, so look at the help for it. It does just what you'd expect.
Loop through your ranges, but instead of building a string, build up a multi-area Range. Then you can select or set properties on the whole thing at once.
I don't know what (if any) the limit on the number of areas you can build up in a single Range is, but it's bigger than 600. I don't know what (if any) limits there are on selecting or setting properties of a multi-area Range either, but it's probably worth a try.
A faster option might be to use the SpecialCells property to find the blanks then hide the rows:
Sub HideRows()
Dim rng As Range
Set rng = ActiveSheet.Range("A1:A600")
Set rng = rng.SpecialCells(xlCellTypeBlanks)
rng.EntireRow.Hidden = True
End Sub
This will only work on cells within the UsedRange, I think.
A minor speedup can be obtained if you set the RowHeight property to 0.
On my system it goes about twice as fast
(on 6000 iterations about 1.17 seconds versus 2.09 seconds)
You didn't mention what 'quite a while' is, and what version of XL you are using...
Your problem may be in part your row detect code that checks for a row you want to hide(?).
Here's my test code in XL 2003 (comment out one version then the other):
Option Explicit
Public Sub test()
Dim i As Integer
Dim t As Long
t = Timer()
Application.ScreenUpdating = False
For i = 1 To 6000
With Range("A" & i)
'If .Value = vbEmpty Then .EntireRow.Hidden = True
If .Value = vbEmpty Then .RowHeight = 0
End With
Next
Application.ScreenUpdating = True
Debug.Print Timer() - t & " seconds"
End Sub
There is a limit to the string length. I just encountered a similar problem and found that if the String Txt of
Range(Txt)
is larger then 255 characters my VBA throws an Error.eg. the code:
Debug.Print sheet1.Range("R2300,T2300,V2300,R2261,T2261,V2261,R1958,T1958,V1958,R1751,T1751,V1751,R1544,T1544,V1544,R1285,T1285,V1285,R1225,T1225,V1225,R1193,T1193,V1193,R1089,T1089,V1089,R802,T802,V802,R535,T535,V535,R264,T264,V264,R205,T205,V205,R168,T168,V168,R135,T135,V135,R101").Areas.count
throws an error (256 characters in string) whereas the code
Debug.Print sheet1.Range("R230,T2300,V2300,R2261,T2261,V2261,R1958,T1958,V1958,R1751,T1751,V1751,R1544,T1544,V1544,R1285,T1285,V1285,R1225,T1225,V1225,R1193,T1193,V1193,R1089,T1089,V1089,R802,T802,V802,R535,T535,V535,R264,T264,V264,R205,T205,V205,R168,T168,V168,R135,T135,V135,R101").Areas.count
has 255 characters and prints out "46" without Error. The number of Areas is in both cases the same.

Application defined or object-defined error ('1004') [duplicate]

Is there some limit to what I can select in a range via VBA? Basically what I found is that if I were to hide an entire row while in a loop, it takes quite a while if there are lots of rows to hide.
ex) - Hide any row that doesn't have a value in column A
For i = 1 to 600
With Range("A" & i)
If .value = vbEmpty then .EntireRow.Hidden = True
End With
Next
The more speedy way of doing that is to make a single range that references each of those rows and then do a single ".entirerow.hidden = true" statement. And yes, I already have application.screenupdating = false set.
The problem I'm encountering is that if the string reference for the range is too long, it just fails.
The following code declares a function which accepts both a standard array of row numbers (in case the array is made before hand), as well as parameter arguments (in case you don't want to declare an array before hand, and the list of rows is small). It then creates a string which is used in the range reference.
Function GetRows(argsArray() As Long, ParamArray args() As Variant) As Range
Dim rngs As String
Dim r
For Each r In argsArray
rngs = rngs & "," & r & ":" & r
Next
For Each r In args
rngs = rngs & "," & r & ":" & r
Next
rngs = Right(rngs, Len(rngs) - 1)
Set GetRows = Range(rngs)
End Function
Function dfdfd()
Dim selList(50) As Long, j As Long
For i = 1 To 100
If i Mod 2 = 1 Then
selList(j) = i
j = j + 1
End If
Next
selList(50) = 101
GetRows(selList).Select
End Function
The 2nd function "dfdfd" is just used to give an example of when it fails. To see when it works, just make a new array with say - 5 items, and try that. It works.
Final (?) update:
Option Explicit
Public Sub test()
Dim i As Integer
Dim t As Long
Dim nRng As Range
t = Timer()
Application.ScreenUpdating = False
Set nRng = [A1]
For i = 1 To 6000
Set nRng = Union(nRng, Range("A" & i))
Next
nRng.RowHeight = 0
'nRng.EntireRow.Hidden = true
Application.ScreenUpdating = True
Debug.Print "Union (RowHeight): " & Timer() - t & " seconds"
'Debug.Print "Union (EntireRow.Hidden): " & Timer() - t & " seconds"
End Sub
Results:
Union (row height: 0.109375 seconds
Union (hidden row): 0.625 seconds
I think the magical function you're looking for here is Union(). It's built into Excel VBA, so look at the help for it. It does just what you'd expect.
Loop through your ranges, but instead of building a string, build up a multi-area Range. Then you can select or set properties on the whole thing at once.
I don't know what (if any) the limit on the number of areas you can build up in a single Range is, but it's bigger than 600. I don't know what (if any) limits there are on selecting or setting properties of a multi-area Range either, but it's probably worth a try.
A faster option might be to use the SpecialCells property to find the blanks then hide the rows:
Sub HideRows()
Dim rng As Range
Set rng = ActiveSheet.Range("A1:A600")
Set rng = rng.SpecialCells(xlCellTypeBlanks)
rng.EntireRow.Hidden = True
End Sub
This will only work on cells within the UsedRange, I think.
A minor speedup can be obtained if you set the RowHeight property to 0.
On my system it goes about twice as fast
(on 6000 iterations about 1.17 seconds versus 2.09 seconds)
You didn't mention what 'quite a while' is, and what version of XL you are using...
Your problem may be in part your row detect code that checks for a row you want to hide(?).
Here's my test code in XL 2003 (comment out one version then the other):
Option Explicit
Public Sub test()
Dim i As Integer
Dim t As Long
t = Timer()
Application.ScreenUpdating = False
For i = 1 To 6000
With Range("A" & i)
'If .Value = vbEmpty Then .EntireRow.Hidden = True
If .Value = vbEmpty Then .RowHeight = 0
End With
Next
Application.ScreenUpdating = True
Debug.Print Timer() - t & " seconds"
End Sub
There is a limit to the string length. I just encountered a similar problem and found that if the String Txt of
Range(Txt)
is larger then 255 characters my VBA throws an Error.eg. the code:
Debug.Print sheet1.Range("R2300,T2300,V2300,R2261,T2261,V2261,R1958,T1958,V1958,R1751,T1751,V1751,R1544,T1544,V1544,R1285,T1285,V1285,R1225,T1225,V1225,R1193,T1193,V1193,R1089,T1089,V1089,R802,T802,V802,R535,T535,V535,R264,T264,V264,R205,T205,V205,R168,T168,V168,R135,T135,V135,R101").Areas.count
throws an error (256 characters in string) whereas the code
Debug.Print sheet1.Range("R230,T2300,V2300,R2261,T2261,V2261,R1958,T1958,V1958,R1751,T1751,V1751,R1544,T1544,V1544,R1285,T1285,V1285,R1225,T1225,V1225,R1193,T1193,V1193,R1089,T1089,V1089,R802,T802,V802,R535,T535,V535,R264,T264,V264,R205,T205,V205,R168,T168,V168,R135,T135,V135,R101").Areas.count
has 255 characters and prints out "46" without Error. The number of Areas is in both cases the same.

how can I insert a condition "if it contains" to search for a specific letter in VBA?

I want to count how many times appear the parameters CA, CU and CH, in an excel that looks like this:
I have tried to use the following code, but as the cells don't contain only the parameter I am searching for, it doesn't work:
Sub ContarOV()
Dim cont As Variant
Dim sumaCA As Variant
Dim sumaCU As Variant
Dim sumaCH As Variant
sumaCA = 0
sumaCU = 0
sumaCH = 0
For cont = 3 To 12
If Cells(cont, 2) = ("CA") Then
sumaCA = sumaCA + 1
End If
If Cells(cont, 2) = ("CU") Then
sumaCU = sumaCU + 1
End If
If Cells(cont, 2) = ("CH") Then
sumaCH = sumaCH + 1
End If
Next cont
End Sub
As per #BigBen, I would try to avoid any iteration. What about one of the following options (assuming your data sits from A2:A?):
Sub Test()
Dim lr As Long, x As Long
Dim arr As Variant
Dim rng As Range
With Sheet1 'Change according to your sheets CodeName
'Get last used row
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
'Get data into memory for method 1
arr = Application.Transpose(.Range("A2:A" & lr).Value)
'Create range object for method 2
Set rng = .Range("A2:A" & lr)
'Method 1: Count values with FILTER
Debug.Print UBound(Filter(arr, "CA")) + 1
Debug.Print UBound(Filter(arr, "CU")) + 1
Debug.Print UBound(Filter(arr, "CH")) + 1
'Method 2: Count values with COUNTIF
Debug.Print WorksheetFunction.CountIf(rng, "CA*")
Debug.Print WorksheetFunction.CountIf(rng, "CU*")
Debug.Print WorksheetFunction.CountIf(rng, "CH*")
End With
End Sub
Btw, I would give sumaCA and your other variables a meaningfull data type, Long in this case.
You can use InStr() to return the position of the desired characters in the string. This would look something like If Not InStr(1, Cells(cont,2).Text, "CH") = 0 Then, but looping through strings is generally a slow process. Unless you have a specific need for looping, I like BigBen's answer a lot better than I like looping with InStr().

Range limit conundrum

Is there some limit to what I can select in a range via VBA? Basically what I found is that if I were to hide an entire row while in a loop, it takes quite a while if there are lots of rows to hide.
ex) - Hide any row that doesn't have a value in column A
For i = 1 to 600
With Range("A" & i)
If .value = vbEmpty then .EntireRow.Hidden = True
End With
Next
The more speedy way of doing that is to make a single range that references each of those rows and then do a single ".entirerow.hidden = true" statement. And yes, I already have application.screenupdating = false set.
The problem I'm encountering is that if the string reference for the range is too long, it just fails.
The following code declares a function which accepts both a standard array of row numbers (in case the array is made before hand), as well as parameter arguments (in case you don't want to declare an array before hand, and the list of rows is small). It then creates a string which is used in the range reference.
Function GetRows(argsArray() As Long, ParamArray args() As Variant) As Range
Dim rngs As String
Dim r
For Each r In argsArray
rngs = rngs & "," & r & ":" & r
Next
For Each r In args
rngs = rngs & "," & r & ":" & r
Next
rngs = Right(rngs, Len(rngs) - 1)
Set GetRows = Range(rngs)
End Function
Function dfdfd()
Dim selList(50) As Long, j As Long
For i = 1 To 100
If i Mod 2 = 1 Then
selList(j) = i
j = j + 1
End If
Next
selList(50) = 101
GetRows(selList).Select
End Function
The 2nd function "dfdfd" is just used to give an example of when it fails. To see when it works, just make a new array with say - 5 items, and try that. It works.
Final (?) update:
Option Explicit
Public Sub test()
Dim i As Integer
Dim t As Long
Dim nRng As Range
t = Timer()
Application.ScreenUpdating = False
Set nRng = [A1]
For i = 1 To 6000
Set nRng = Union(nRng, Range("A" & i))
Next
nRng.RowHeight = 0
'nRng.EntireRow.Hidden = true
Application.ScreenUpdating = True
Debug.Print "Union (RowHeight): " & Timer() - t & " seconds"
'Debug.Print "Union (EntireRow.Hidden): " & Timer() - t & " seconds"
End Sub
Results:
Union (row height: 0.109375 seconds
Union (hidden row): 0.625 seconds
I think the magical function you're looking for here is Union(). It's built into Excel VBA, so look at the help for it. It does just what you'd expect.
Loop through your ranges, but instead of building a string, build up a multi-area Range. Then you can select or set properties on the whole thing at once.
I don't know what (if any) the limit on the number of areas you can build up in a single Range is, but it's bigger than 600. I don't know what (if any) limits there are on selecting or setting properties of a multi-area Range either, but it's probably worth a try.
A faster option might be to use the SpecialCells property to find the blanks then hide the rows:
Sub HideRows()
Dim rng As Range
Set rng = ActiveSheet.Range("A1:A600")
Set rng = rng.SpecialCells(xlCellTypeBlanks)
rng.EntireRow.Hidden = True
End Sub
This will only work on cells within the UsedRange, I think.
A minor speedup can be obtained if you set the RowHeight property to 0.
On my system it goes about twice as fast
(on 6000 iterations about 1.17 seconds versus 2.09 seconds)
You didn't mention what 'quite a while' is, and what version of XL you are using...
Your problem may be in part your row detect code that checks for a row you want to hide(?).
Here's my test code in XL 2003 (comment out one version then the other):
Option Explicit
Public Sub test()
Dim i As Integer
Dim t As Long
t = Timer()
Application.ScreenUpdating = False
For i = 1 To 6000
With Range("A" & i)
'If .Value = vbEmpty Then .EntireRow.Hidden = True
If .Value = vbEmpty Then .RowHeight = 0
End With
Next
Application.ScreenUpdating = True
Debug.Print Timer() - t & " seconds"
End Sub
There is a limit to the string length. I just encountered a similar problem and found that if the String Txt of
Range(Txt)
is larger then 255 characters my VBA throws an Error.eg. the code:
Debug.Print sheet1.Range("R2300,T2300,V2300,R2261,T2261,V2261,R1958,T1958,V1958,R1751,T1751,V1751,R1544,T1544,V1544,R1285,T1285,V1285,R1225,T1225,V1225,R1193,T1193,V1193,R1089,T1089,V1089,R802,T802,V802,R535,T535,V535,R264,T264,V264,R205,T205,V205,R168,T168,V168,R135,T135,V135,R101").Areas.count
throws an error (256 characters in string) whereas the code
Debug.Print sheet1.Range("R230,T2300,V2300,R2261,T2261,V2261,R1958,T1958,V1958,R1751,T1751,V1751,R1544,T1544,V1544,R1285,T1285,V1285,R1225,T1225,V1225,R1193,T1193,V1193,R1089,T1089,V1089,R802,T802,V802,R535,T535,V535,R264,T264,V264,R205,T205,V205,R168,T168,V168,R135,T135,V135,R101").Areas.count
has 255 characters and prints out "46" without Error. The number of Areas is in both cases the same.

Resources