Out of Stack Space - How to optimize VBA code - excel

I am writing a code to retrieve a specific date in a (somewhat) large excel spreadsheet(2,000 entries).
I just realize that my code will not work and it will only get worse. Could you please advise me.
I give to my function:
array() that contain my data from an excel spreadsheet
FirstDate which is the date I am looking for, as dd mm yyyy
DateSave() to save all the position where this date appears (multiple transaction on the same day)
The code will not be able to work with a database of 5,000 row as it will have to stack it if the date is at the end of the table. What could I do to fix this issue?
Thank you very much
Function looping(array() As Variant, FirstDate As Date, DateSave() As Long)
Dim i As Long
Dim PositionInArray As Long
PositionInArray = 0
For i = LBound(array, 1) To UBound(array, 1)
If array(i, 1) = FirstDate Then
ReDim Preserve DateSave(PositionInArray)
DateSave(PositionInArray) = i
PositionInArray = PositionInArray + 1
End If
'If end of list and array not initialize ie. Not value in it
If i = UBound(array, 1) And (Not DateSave) = -1 Then
Call looping(array(), FirstDate + 1, DateSave())
ElseIf i = UBound(array, 1) Then
'Array has been initialized
Exit For
End If
Next i
End Function
Edit: Change data base to excel spreadsheet

I've renamed the function and parameters. The function returns the result rather than having a ByRef parameter. I've used a collection to store the row indexes.
Function GetDatePositions(ByRef database() As Variant, ByVal searchDate As Date) As Long()
Const colDates As Long = 1 'the index of the column holding dates
Dim i As Long
Dim collRowIndexes As New Collection
For i = LBound(database, 1) To UBound(database, 1)
If database(i, colDates) = searchDate Then
collRowIndexes.Add i
End If
Next i
If collRowIndexes.Count = 0 Then
GetDatePositions = GetDatePositions(database, searchDate + 1)
Exit Function
End If
Dim res() As Long
ReDim res(0 To collRowIndexes.Count - 1)
Dim v As Variant
i = 0
For Each v In collRowIndexes
res(i) = v
i = i + 1
Next v
GetDatePositions = res
End Function
EDIT
There is no need to search each consecutive date. We just need to keep track of the next date that is bigger than the search date.
Function GetDatePositions(ByRef database() As Variant, ByVal searchDate As Date) As Long()
Const colDates As Long = 1 'the index of the column holding dates
Dim i As Long
Dim collRowIndexes As New Collection
Dim dateFound As Boolean
Dim nextDate As Date
Dim tempDate As Date
dateFound = False
For i = LBound(database, 1) To UBound(database, 1)
tempDate = database(i, colDates)
If tempDate = searchDate Then
dateFound = True
collRowIndexes.Add i
Else
If Not dateFound Then
If searchDate < tempDate Then
If nextDate = 0 Then
nextDate = tempDate
ElseIf tempDate < nextDate Then
nextDate = tempDate
End If
End If
End If
End If
Next i
'
If collRowIndexes.Count = 0 Then
If nextDate = 0 Then
Err.Raise 5, "GetDatePositions", "No date found"
Else
GetDatePositions = GetDatePositions(database, nextDate)
Exit Function
End If
End If
Dim res() As Long
ReDim res(0 To collRowIndexes.Count - 1)
Dim v As Variant
i = 0
For Each v In collRowIndexes
res(i) = v
i = i + 1
Next v
GetDatePositions = res
End Function
Obviously, an assumption that all dates are rounded is made. But if dates also contain time (hours, minutes, seconds) then tempDate = database(i, colDates) needs to be replaced with tempDate = VBA.Int(database(i, colDates))

Related

Excel Search by Date Range and Name

I have this formula
=INDEX(C1:C8,LARGE(IF((A1:A8>=H2)*(A1:A8<=H3)*(B1:B8=H4),ROW(C1:C8),""),1))
it will get the last section from range and name
but it gets laggy when i created a summary of section where i use this code
can anyone suggest a formula where it wont get lag
There are many ways to achieve the result you want, but a lot depends on your version of Excel.
The LARGE and ROW might be causing the delay. You could replace LARGE with MAX.
=INDEX(C1:C8,MAX((A1:A8>=H2)*(A1:A8<=H3)*(B1:B8=H4)*ROW(C1:C8)))
If you have Excel 365, XMATCH might do better. It has an option to search bottom to top:
=INDEX(C1:C8,XMATCH(H4,IF((A1:A8>=H2)*(A1:A8<=H3),B1:B8),0,-1))
Filtering the lookup array with the IF will reduce the compare operations for XMATCH.
Try either of the following code (using excel vba as a "sub" or within the worksheet as a "function"):
Function Code:
Function Test02(name01 As String, MaxDate As Range, MinDate As Range, Rng01 As Range)
'Rng01 Column 1 = "Date"
'Rng01 Column 2 = "jonjon"
'Rng01 Column 3 = "mtce"
Dim Arr01 As Variant ' Array of data
Dim i01 As Long 'Counter
Dim Temp01 As String 'saves the current "mtce" until a larger date is found
Dim TempDate
TempDate = 0
Arr01 = Rng01
For i01 = 1 To UBound(Arr01, 1)
If Arr01(i01, 1) < MaxDate And Arr01(i01, 1) > MinDate And Arr01(i01, 2) = name01 And TempDate < Arr01(i01, 1) Then
Test02 = Arr01(i01, 3)
TempDate = Arr01(i01, 1)
End If
Next i01
End Sub
Instead of having a function, you could use a "Sub" instead and only run it when there is an update, and keep the outputs as static strings.) You might have to edit the following code to repeat over the whole data set, but this should be a good start. If you could give me a better snapshot of how the data is laid out, I might be able to write this code this for you (if this is what you want).
This would be the basis of the sub:
Sub Test01()
Dim Arr01 As Variant ' Array of data
Dim i01 As Long 'Counter
Dim Temp01 As String 'saves the current "mtce" until a larger date is found
Dim TempDate
'Rng01 Column 1 = "Date"
'Rng01 Column 2 = "jonjon"
'Rng01 Column 3 = "mtce"
MinDate = Range("H2")
MaxDate = Range("H3")
name01 = Range("H4")
Rng01 = Range("A1:C8")
TempDate = 0
Arr01 = Rng01
For i01 = 1 To UBound(Arr01, 1)
If Arr01(i01, 1) < MaxDate And Arr01(i01, 1) > MinDate And Arr01(i01, 2) = name01 And TempDate < Arr01(i01, 1) Then
Temp01 = Arr01(i01, 3)
TempDate = Arr01(i01, 1)
End If
Next i01
Range("H5") = Temp01
End Sub

Ways to Speed Up a Lookup Function in VBA?

I created a lookup function that finds the result from a separate tab within the same worksheet with 4 different fields to match.
When running, this takes entirely too long to complete (to the point where I have to kill the macro run). I need to build the same lookup function for 8 different fields, based on the exact same match criteria. Any advice on how to speed up this query or build it in a more dynamic way, so I can lookup all 8 columns at once rather than building functions and subs for each lookup field?
Function fcst_bal_find(ByVal Anode As String, ByVal LoB As String, ByVal Month As String, ByVal Year As String) As Variant
Dim Fcst_Essbase As Worksheet
Dim fcst_rowcnt
Dim act_rowcnt
fcst_rowcnt = Sheets("Date Dims").Range("B7")
act_rowcnt = Sheets("Date Dims").Range("B8")
Set Fcst_Essbase = Sheets("Fcst Essbase Pull")
For i = 2 To fcst_rowcnt + 4
If WorksheetFunction.Trim(Fcst_Essbase.Cells(i, 1).Value) = Anode Then
If WorksheetFunction.Trim(Fcst_Essbase.Cells(i, 2).Value) = LoB Then
If WorksheetFunction.Trim(Fcst_Essbase.Cells(i, 3).Value) = Month Then
If "Y" & Right(WorksheetFunction.Trim(Fcst_Essbase.Cells(i, 4).Value), 2) = Year Then
fcst_bal_find = Fcst_Essbase.Cells(i, 5).Value
Exit Function
End If
End If
End If
End If
Next i
fcst_bal_find = "N/A"
End Function
Sub balfcst_find()
Dim fcst_tab As Worksheet
Dim match As Variant
Dim Anode As String
Dim LoB As String
Dim Month As String
Dim Year As String
Dim fcst_rowcnt
Dim act_rowcnt
fcst_rowcnt = Sheets("Date Dims").Range("B7")
act_rowcnt = Sheets("Date Dims").Range("B8")
Set fcst_tab = Sheets("Cartesian Product - Fcst")
For i = 2 To fcst_rowcnt
Anode = fcst_tab.Range("A" & i).Value
LoB = fcst_tab.Range("B" & i).Value
Month = fcst_tab.Range("C" & i).Value
Year = fcst_tab.Range("D" & i).Value
match = fcst_bal_find(Anode, LoB, Month, Year)
fcst_tab.Cells(i, 5) = match ' test the output
Next i
End Sub
Here is an example of using variant array to match something from a current project of mine. You can modify to suit your needs.
Private Function verifyMod(modValue As Double, state As String) As Boolean
If Len(modValue) Then
Dim modTable As ListObject
Set modTable = lookupsAUState.ListObjects("stateWritingCoModMinMax")
Dim v As Variant
v = modTable.DataBodyRange.value
Dim company As String
company = StrConv(xmlCo.Range("insuranceCompanyName"), vbProperCase)
Dim x As Long
For x = LBound(v) To UBound(v)
If v(x, modTable.ListColumns("Company").index) = company Then
If v(x, modTable.ListColumns("State").index) = state Then
If modValue >= v(x, modTable.ListColumns("Min").index) And modValue <= v(x, modTable.ListColumns("Max").index) Then
verifyMod = True
Else
MsgBox state & " allows for modifications between " & v(x, modTable.ListColumns("Min").index) & " and " & v(x, modTable.ListColumns("Max").index) & ". Please enter a modification within that range."
End If
Exit For
End If
End If
Next
End If
End Function

Select columns ranges in a VBA

I want to select a range (of values, the whole column) for the values: FirstDate, EndDate and Number. My VBA:
The Output of the below VBA is highlighted (Column D) :
Sub DateTest()
Dim FirstDate As Date ' Declare variables.
Dim IntervalType As String
Dim Number As Integer
Dim EndDate As Date
Dim TempDate As Date
Dim i As Integer
IntervalType = "m" ' "m" specifies MONTHS as interval.
FirstDate = Cells(1, 1).Value
EndDate = Cells(1, 2).Value
Number = Cells(1, 3).Value ' "Number" For the syntax DateAdd.
' If the number is not greater than zero an infinite loop will happen.
If Number <= 0 Then
MsgBox "Number needs to be greater than 0", vbCritical
Exit Sub
End If
i = 1
Do Until TempDate = EndDate
If i <= 1 Then
TempDate = DateAdd(IntervalType, Number, FirstDate)
Else
TempDate = DateAdd(IntervalType, Number, TempDate)
End If
i = i + 1
Debug.Print i
Loop
Range("D1").Value = i - 1
End Sub
As I wrote before I want to run my Macro not only for the first 3 cells (currently the macro works fine for the value (1,1) (1,2) (1,3)), as you can see above for FirstDate, EndDate and Number
I want to use for all dates in Column1, Column2, Column3 for example:
I already tried this:
Sub DateTest()
Dim FirstDate As Date ' Declare variables.
Dim IntervalType As String
Dim Number As Integer
Dim EndDate As Date
Dim TempDate As Date
Dim i As Integer
IntervalType = "m" ' "m" specifies MONTHS as interval.
With ActiveWorkbook.Worksheets(1)
lLastRow = .UsedRange.Rows.Count
For lRow = 1 To lLastRow
FirstDate = Format(.Cells(lRow, 1).Value, "YYYY-MM-DD")
EndDate = Format(.Cells(lRow, 2).Value, "YYYY-MM-DD")
Number = .Cells(lRow, 3).Value
' If the number is not greater than zero an infinite loop will happen.
If Number <= 0 Then
MsgBox "Number needs to be greater than 0", vbCritical
Exit Sub
End If
i = 1
Do Until TempDate = EndDate
If i <= 1 Then
TempDate = DateAdd(IntervalType, Number, FirstDate)
Else
TempDate = DateAdd(IntervalType, Number, TempDate)
End If
i = i + 1
Debug.Print i
Loop
Range("D1").Value = i - 1
Next
End With
End Sub
But is still transforming the 1 row.
If I'm understanding what you need correctly it's because you're calling out Range("D1").Value so it will always update that cell. You can make it more dynamic by using the lRow variable you already set up to place it into the correct row.
Replacing this Range("D1").Value = i - 1 with this Cells(lRow, 4).Value = i - 1
Sub DateTest()
Dim FirstDate As Date ' Declare variables.
Dim IntervalType As String
Dim Number As Integer
Dim EndDate As Date
Dim TempDate As Date
Dim i As Integer
IntervalType = "m" ' "m" specifies MONTHS as interval.
With ActiveWorkbook.Worksheets(1)
lLastRow = .UsedRange.Rows.Count
For lRow = 1 To lLastRow
FirstDate = Format(.Cells(lRow, 1).Value, "YYYY-MM-DD")
EndDate = Format(.Cells(lRow, 2).Value, "YYYY-MM-DD")
Number = .Cells(lRow, 3).Value
' If the number is not greater than zero an infinite loop will happen.
If Number <= 0 Then
MsgBox "Number needs to be greater than 0", vbCritical
Exit Sub
End If
i = 1
Do Until TempDate = EndDate
If i <= 1 Then
TempDate = DateAdd(IntervalType, Number, FirstDate)
Else
TempDate = DateAdd(IntervalType, Number, TempDate)
End If
i = i + 1
Debug.Print i
Loop
Cells(lRow, 4).Value = i - 1
'Range("D1").Value = i - 1
Next
End With
End Sub

Count the number of time that we increase a given Date range

I want to count the number of times (given a range Date) that I move a date (forward) given also an interval, let's say, 6 months.
Example:
The range:
Start date: 2019/08/05
End date: 2020/08/05
Interval: 6 Months
1st time: 2019/08/05 + 6 Months = 2020/02/05
2nd time: 2020/02/05 + 6 Months = 2020/08/05
For this case, the output = 2
I want to print my desirable output in a specific cell.
I wanted to build a VBA using the syntax:
DateAdd ( interval, number, date )
Private Sub CommandButton1_Click()
Dim FirstDate As Date ' Declare variables.
Dim IntervalType As Integer
Dim Number As Integer
Dim EndDate As Date
Dim TempDate As Date
Dim i As Integer
IntervalType = "m" ' "m" specifies MONTHS as interval.
FirstDate = Cells(1, 1).Value
EndDate = Cells(1, 2).Value
Number = Cells(1, 3).Value ' "Number" For the syntax DateAdd.
i = 1
Do Until TempDate = EndDate
TempDate= DateAdd(IntervalType, Number, FirstDate)
i = i + 1
Loop
Range("D1").Value = i
End Sub
But I'm getting this error:
The error you are getting is because you are trying to assign a String to and Integer data type. Change IntervalType to a String.
Sub DateTest()
Dim FirstDate As Date ' Declare variables.
Dim IntervalType As String
Dim Number As Integer
Dim EndDate As Date
Dim TempDate As Date
Dim i As Integer
IntervalType = "m" ' "m" specifies MONTHS as interval.
FirstDate = Cells(1, 1).Value
EndDate = Cells(1, 2).Value
Number = Cells(1, 3).Value ' "Number" For the syntax DateAdd.
' If number is not greater than zero an infinite loop will happen.
If Number <= 0 Then
MsgBox "Number needs to be greater than 0", vbCritical
Exit Sub
End If
i = 1
Do Until TempDate = EndDate
If i <= 1 Then
TempDate = DateAdd(IntervalType, Number, FirstDate)
Else
TempDate = DateAdd(IntervalType, Number, TempDate)
End If
i = i + 1
Debug.Print i
Loop
Range("D1").Value = i - 1
End Sub
Also, You were running an infinite Loop with TempDate always looping from the FirstDate variable.
Also, if your Number is less than or equal to zero then you will get an infinite loop.
Also, the Else gets rid of the overflow error. Without it, the TempDate would reset after every loop.

compilation error, Sub or Function not defined

Currently realising a project for my company to highlight anomalies in orders, I wrote this VBA code in order to compare status of orders and a day date difference between the current date and the order date . The dates are as follows on the csv : "2019-05-06 3:11pm"
When I affecte the macro to a button and click on it I get this error : "Compilation error, Sub or function not defined" and the debugger cursor points on "Sub Problem()"...
I hope you can help me!! thanks by advance :)
here is my code :
Sub Problem()
Dim orderDate As Date
Dim difDate
Dim statusToUse As Range
Set statusToUse = Range("C2:C100")
Dim statusCell As Range
Dim a As String
a = "accepted"
Dim s As String
s = "shipped"
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim l As Integer
Dim m As Integer
Dim dateToUse As Range
Set dateToUse = Range("D2:D100")
Dim dateCell As Range
Dim currentDate As String
currentDate = Date
y = Split(Split(Range("A1").Value, ":")(1), "-")
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For Each statusCell In statusToUse.Areas
For Each dateCell In dateToUse.Areas
orderDate = DateValue(Join(Array(y(2), y(1), y(0)), "-"))
difDate = DateDiff("d", currentDate, orderDate)
If status = a And difDate <= 7 Then
status.Interior.ColorIndex = 46
i = i + 1
ElseIf status = a And difDate <= 2 Then
status.Interior.ColorIndex = 27
j = j + 1
ElseIf status = a And difDate > 7 Then
status.Interior.ColorIndex = 3
k = k + 1
ElseIf status = s Then
status.Interior.ColorIndex = 10
l = l + 1
Else
m = m + 1
End If
i = i - j
Next dateCell
Next statusCell
MsgBox "There are" + i + "risky orders"
End Sub
This error usually occurs when the sub is in the worksheet and not in a new module.
If it's in a module already, please check if the name of the module is different of "module1".

Resources