I've done a lot of searching to try and optimize this code. I've reduced the run time down significantly, but I can't seem to find anything else (note: I've done all the xlcalculationmanual and screenupdating = false jazz)
Here is the basic structure of my current loop. The matrix is currently 5 rows down with data to loop through and 9 across.
Application.Calculation = xlCalculationManual
i = 0
Do While wsc1.Cells(10, i + 65) <> "things" And wsc1.Cells(10, i + 65) <> "thing2" And wsc1.Cells(10, i + 65) <> ""
j = 0
Do While wsc1.Cells(j + 11, 64) <> ""
wsc.Cells(109, 3) = wsc1.Cells(j + 11, 64) 'rows
wsc.Cells(109, 6) = wsc1.Cells(10, i + 65) 'columns
Application.Calculation = xlCalculationAutomatic
Application.Calculation = xlCalculationManual
wsc1.Cells(j + 11, i + 65) = wsc.Range("O6") 'Print
j = j + 1
Loop
i = i + 1
Loop
I assume my next best option is storing the column/row vector as a variant and looping through that?
Thanks
Can you also add these lines?
Application.EnableEvents = False
Application.ScreenUpdating = False ' it seems that you already have this one?
Give this a try. However, having to wait for the sheet calculation is a pretty hard slowdown, there's really not much that can be done beyond this to improve performance if we can't put the calculations in the code.
Sub tgr()
Dim wsc1 As Worksheet
Dim CValues As Variant
Dim FValues As Variant
Dim Results() As Variant
Dim i As Long, j As Long
Dim xlCalc As XlCalculation
With Application
xlCalc = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
End With
On Error GoTo CleanExit
Set wsc1 = ActiveWorkbook.ActiveSheet
With wsc1.Range("BL11", wsc1.Cells(wsc1.Rows.Count, "BL").End(xlUp))
If .Row < 11 Then Exit Sub 'No data
If .Cells.Count = 1 Then
ReDim CValues(1 To 1, 1 To 1)
CValues(1, 1) = .Value
Else
CValues = .Value
End If
End With
With wsc1.Range("BM10", wsc1.Cells(10, wsc1.Columns.Count).End(xlToLeft))
If .Column < Columns("BM").Column Then Exit Sub 'No data
If .Cells.Count = 1 Then
ReDim FValues(1 To 1, 1 To 1)
FValues(1, 1) = .Value
Else
FValues = .Value
End If
End With
ReDim Results(1 To UBound(CValues, 1), 1 To UBound(FValues, 2))
For i = LBound(CValues, 1) To UBound(CValues, 1)
For j = LBound(FValues, 2) To UBound(FValues, 2)
wsc1.Range("C109").Value = CValues(i, 1)
wsc1.Range("F109").Value = FValues(1, j)
wsc1.Calculate
Results(i, j) = wsc1.Range("O6").Value
Next j
Next i
wsc1.Range("BM11").Resize(UBound(Results, 1), UBound(Results, 2)).Value = Results
CleanExit:
With Application
.Calculation = xlCalc
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
Related
I wrote a code to match data (MaterialPN vs. MaterialPS and WeekPN vs. WeekPS) and copy appropriate values between two sheets (Packaging Needs - PN and Packaging Staging - PS).
I already turned off ScreenUpdating, Calculations and Events. This made the run time go from 5 minutes to 1 minute, which is still quite slow (my data is ~3000 rows only). I also tried forcing an exit of the If Statement when the WeekPN is not equal to WeekPS with the use of GoTo Flag1, but this did not make my code run any faster.
Any tips on how to make this code more efficient?
Thanks in advance for any help!
Sub PackagingNeeds2PackagingStaging()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
With Sheets("Packaging Needs")
i = .Cells(.Rows.Count, 5).End(xlUp).Row
End With
With Sheets("Packaging Staging")
l = .Cells(.Rows.Count, 1).End(xlUp).Row
End With
j = 25
For k = 9 To i
For x = 5 To l
For Z = 14 To j
MaterialPN = Sheets("Packaging Needs").Cells(k, 5).Value
MaterialPS = Sheets("Packaging Staging").Cells(x, 1).Value
WeekPN = Sheets("Packaging Needs").Cells(4, Z).Value
WeekPS = Sheets("Packaging Staging").Cells(x, 12).Value
If WeekPN <> WeekPS Then GoTo Flag1
If WeekPN = WeekPS Then
If MaterialPN = MaterialPS Then
Sheets("Packaging Staging").Cells(x, 19).Value = Sheets("Packaging Needs").Cells(k, Z).Value
End If
End If
Flag1:
Next
Next
k = k + 5
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End Sub
A few suggestions - could be faster to use Variant arrays, and also if Match were appropriate here (difficult to say without knowing how many matches you expect to make - if only one then you can also Exit For to break out of a loop once you get a match)
Sub PackagingNeeds2PackagingStaging()
Const J As Long = 25 'use Const for fixed values
Dim i As Long, x As Long, l As Long, k As Long, z As Long
Dim shtPN As Worksheet, shtPS As Worksheet
Dim MaterialPN, MaterialPS, WeekPS
'use worksheet variables
Set shtPN = ThisWorkbook.Sheets("Packaging Needs")
Set shtPS = ThisWorkbook.Sheets("Packaging Staging")
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
i = shtPN.Cells(shtPN.Rows.Count, 5).End(xlUp).Row
l = shtPS.Cells(shtPS.Rows.Count, 1).End(xlUp).Row
For k = 9 To i Step 5 '<< use Step instead of your line below
MaterialPN = shtPN.Cells(k, 5).Value '<< moved this up
For x = 5 To l
MaterialPS = shtPS.Cells(x, 1).Value '<< moved this up
WeekPS = shtPS.Cells(x, 12).Value '<< ...and this
For z = 14 To J
If shtPN.Cells(4, z).Value = WeekPS Then
If MaterialPN = MaterialPS Then
shtPS.Cells(x, 19).Value = shtPN.Cells(k, z).Value
End If
End If
Next z
Next x
'k = k + 5 '<<< don't change the counter inside a For loop!
Next k
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End Sub
I have a very large Dataset ~ 100000 rows with 2 columns, I want to calculate a rolling count based on 2 criterias, basically how many times value in col 1 wrt col 2.
Dataset looks like this
I have written the following code
This is partial dataset, actual has 100000 rows, I want the answer in col c
Sub test()
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim id, data_week, ans, a As Variant
Dim p As Double
a = 100000
Debug.Print Now()
id = Sheet1.Range("A2:A" & a).Value
data_week = Sheet1.Range("B2:B" & a).Value
ans = Sheet1.Range("c2:c" & a).Value
For p = 1 To a
ans(p, 1) = Application.WorksheetFunction.CountIfs(Sheet1.Range("A2:A" & p + 1), id(p, 1),
Sheet1.Range("b2:b" & p + 1), data_week(p, 1))
Next p
Sheet1.Range("c2:c" & a).Value = ans
Debug.Print Now()
Application.Calculation = xlCalculationAutomatic
End Sub
This is taking awfully long in VBA, wondering if there's a faster way to do it interms optimising the code, appreciate your help.
Try. This takes 3 seconds to run.
Sub test3()
Dim vDB, ans()
Dim Ws As Worksheet
Dim a As Long
Dim i As Long, id, myDay
Dim n As Integer
Set Ws = Sheets(1)
a = 100000
Debug.Print Now()
With Ws
vDB = .Range("a2", .Range("b" & a))
ReDim ans(1 To UBound(vDB, 1), 1 To 1)
id = vDB(1, 1)
myDay = vDB(1, 2)
For i = 1 To UBound(vDB, 1)
If vDB(i, 1) <> "" Then
If id = vDB(i, 1) And myDay = vDB(i, 2) Then
n = n + 1
ans(i, 1) = n
Else
id = vDB(i, 1)
myDay = vDB(i, 2)
n = 1
ans(i, 1) = n
End If
End If
DoEvents
Next
.Range("c2").Resize(UBound(ans)) = ans
End With
Debug.Print Now()
End Sub
I have tried to write a code that will look at cells in B2 from Row 4 to R2000 and if the content is zero then hide the row. My problem is that the code is running very slow and often stops responding.
If you can help me what it is that is causing it to run slow, I can probably fix it myself, but I am not sure what would be a more efficient approach. As you can see I have tried with turning screen updates off, but it didn’t help much.
The code is below
Sub HideRows()
BeginRow = 4
EndRow = 2059
ChkCol = 2
Application.ScreenUpdating = False
Rows("1:2059").EntireRow.Hidden = False
For RowCnt = BeginRow To EndRow
If Cells(RowCnt, ChkCol).Value = 0 Then
Cells(RowCnt, ChkCol).EntireRow.Hidden = True
End If
Next RowCnt
Application.ScreenUpdating = True
End Sub
Try hiding everything in one go instead of every time a 0 is found
Sub HideRows()
Dim BeginRow As Long, EndRow As Long, ChkCol As Long
Dim HideRng As Range
BeginRow = 4
EndRow = 2059
ChkCol = 2
Application.ScreenUpdating = False
Rows("1:2059").EntireRow.Hidden = False
For rowcnt = BeginRow To EndRow
If Cells(rowcnt, ChkCol).Value2 = 0 Then
If HideRng Is Nothing Then
Set HideRng = Cells(rowcnt, ChkCol)
Else
HideRng = Union(HideRng, Cells(rowcnt, ChkCol))
End If
End If
Next rowcnt
If Not HideRng Is Nothing Then HideRng.EntireRow.Hidden = True
Application.ScreenUpdating = True
End Sub
Without seeing your workbook, it's hard to know for sure, but generally Excel is pretty slow at hiding rows. In your code, each row is hidden one at a time, so that's potentially 1000+ individual "hide this row" commands to Excel.
It's much faster to hide the rows in "chunks". This macro (I wrote it ages ago because I was dealing with the same problem) does that, so it should be much faster. In your case, you'd call it like this:
Call hideRows(ActiveSheet, 4, 2059, 0, 2, 2)
There's also an inverted setting that would hide rows unless the value in column 2 was equal to zero. You'd just add "True" to the end of your function call.
Sub hideRows(ws As Worksheet, startRow As Long, endRow As Long, valCrit As Variant, Optional startCol As Long = 1, Optional endCol As Long = 1, Optional invert As Boolean = False)
Dim loopCounter As Long
Dim rowCounter As Long
Dim colCounter As Long
Dim endConsRow As Long
Dim tempArr As Variant
Dim toAdd As Long
Dim toHide As String
Dim sameVal As Boolean
Dim consBool As Boolean
Dim tempBool As Boolean
Dim rowStr As String
Dim goAhead As Boolean
Dim i As Long
If startRow > endRow Then
toAdd = endRow - 1
Else
toAdd = startRow - 1
End If
tempArr = ws.Range(ws.Cells(startRow, startCol), ws.Cells(endRow, endCol)).Value
ws.Rows(startRow & ":" & endRow).Hidden = False
loopCounter = 1
For rowCounter = LBound(tempArr, 1) To UBound(tempArr, 1)
For colCounter = LBound(tempArr, 2) To UBound(tempArr, 2)
sameVal = False
goAhead = False
If IsNumeric(valCrit) Then
If tempArr(rowCounter, colCounter) = valCrit Then
sameVal = True
End If
Else
If tempArr(rowCounter, colCounter) Like valCrit Then
sameVal = True
End If
End If
If sameVal Then
If invert = True Then
loopCounter = loopCounter + 1
Exit For
End If
goAhead = True
ElseIf colCounter = UBound(tempArr, 2) Then
If invert = False Then
loopCounter = loopCounter + 1
Exit For
End If
goAhead = True
End If
If goAhead = True Then
endConsRow = rowCounter
consBool = True
Do Until consBool = False
tempBool = False
For i = LBound(tempArr, 2) To UBound(tempArr, 2)
sameVal = False
If endConsRow = UBound(tempArr, 1) Then
Exit For
ElseIf IsNumeric(valCrit) Then
If tempArr(endConsRow + 1, i) = valCrit Then
sameVal = True
End If
Else
If tempArr(endConsRow + 1, i) Like valCrit Then
sameVal = True
End If
End If
If sameVal Then
If invert = False Then
endConsRow = endConsRow + 1
tempBool = True
End If
Exit For
ElseIf i = UBound(tempArr, 2) Then
If invert = True Then
endConsRow = endConsRow + 1
tempBool = True
End If
End If
Next
If tempBool = False Then
consBool = False
End If
Loop
rowStr = loopCounter + toAdd & ":" & endConsRow + toAdd
If toHide = "" Then
toHide = rowStr
ElseIf Len(toHide & "," & rowStr) > 255 Then
ws.Range(toHide).EntireRow.Hidden = True
toHide = rowStr
Else
toHide = toHide & "," & rowStr
End If
loopCounter = loopCounter + 1 + (endConsRow - rowCounter)
rowCounter = endConsRow
Exit For
End If
Next
Next
If Not toHide = "" Then
ws.Range(toHide).EntireRow.Hidden = True
End If
End Sub
Can you use Autofilter?
Option Explicit
Public Sub HideRowsWhereColBis0()
ActiveSheet.Range("B4:B2059").AutoFilter Field:=1, Criteria1:="<>0"
End Sub
I have the following basic script that merges cells with the same value in Column R
Sub MergeCells()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim rngMerge As Range, cell As Range
Set rngMerge = Range("R1:R1000")
MergeAgain:
For Each cell In rngMerge
If cell.Value = cell.Offset(1, 0).Value And IsEmpty(cell) = False Then
Range(cell, cell.Offset(1, 0)).Merge
GoTo MergeAgain
End If
Next
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
What I would like to do is repeat this in columns A:Q and S:T but, I would like these columns to be merged in the same merged cell ranges as column R, i.e. if R2:R23 is merged then A2:A23, B2:B23, C2:C23 etc. will also be merge.
Columns A:Q do not contain values, column S:T have values but, these will be the same values throughout the range.
Any ideas
Apols for the earlier edit - this now deals with more than one duplicate in col R.
Note that this approach will work on the current (active) sheet.
Sub MergeCells()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim cval As Variant
Dim currcell As Range
Dim mergeRowStart As Long, mergeRowEnd As Long, mergeCol As Long
mergeRowStart = 1
mergeRowEnd = 1000
mergeCol = 18 'Col R
For c = mergeRowStart To mergeRowEnd
Set currcell = Cells(c, mergeCol)
If currcell.Value = currcell.Offset(1, 0).Value And IsEmpty(currcell) = False Then
cval = currcell.Value
strow = currcell.Row
endrow = strow + 1
Do While cval = currcell.Offset(endrow - strow, 0).Value And Not IsEmpty(currcell)
endrow = endrow + 1
c = c + 1
Loop
If endrow > strow+1 Then
Call mergeOtherCells(strow, endrow)
End If
End If
Next c
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
Sub mergeOtherCells(strw, enrw)
'Cols A to T
For col = 1 To 20
Range(Cells(strw, col), Cells(enrw, col)).Merge
Next col
End Sub
You can try the below code as well. It would require you to put a 'No' after the last line in column R (R1001) so as to end the while loop.
Sub Macro1()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
flag = False
k = 1
While ActiveSheet.Cells(k, 18).Value <> "No"
i = 1
j = 0
While i < 1000
rowid = k
If Cells(rowid, 18).Value = Cells(rowid + i, 18).Value Then
j = j + 1
flag = True
Else
i = 1000
End If
i = i + 1
Wend
If flag = True Then
x = 1
While x < 21
Range(Cells(rowid, x), Cells(rowid + j, x)).Merge
x = x + 1
Wend
flag = False
k = k + j
End If
k = k + 1
Wend
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
I have used the below mentioned excel formula.
=INDEX(TABL,SMALL(IF(COUNTIF(H2,$A$1:$A$325779)*COUNTIF(I2,"<="&$B$1:$B$325779),ROW(TABL)-MIN(ROW(TABL))+1),1),3)
Where "TABL",a table, is A1:E325779 and is the source of my lookup array.
The formula mentioned is the exact requirement but is taking a lot of time to update the excel for 400,000+ cells containing this formula.
Can this be optimized?
Or can this be equated to a faster macro?
Its taking 1 second to update 1 cell!!! That's a very long time to update all 400K+ cells once!!!
Screenshot of a sample worksheet is as below.
I have based my program on Martin Carlsson's.
it is processing 100 records in 30 seconds. can it be improved?
Sub subFindValue()
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Cells(2, 12) = Format(DateTime.Now, "yyyy-MM-dd hh:mm:ss")
Dim varRow As Variant
Dim varRowMain As Variant
Dim lookupTable As Variant
Dim lookupValueTable As Variant
lookupValueTable = Range("G2:J309011").Value
lookupTable = Range("A2:D325779").Value
varRowMain = 1
varRow = 1
Do Until varRowMain = 309011
Do Until varRow = 325779
If lookupTable(varRow, 1) = lookupValueTable(varRowMain, 1) And lookupTable(varRow, 2) >= lookupValueTable(varRowMain, 2) Then
lookupValueTable(varRowMain, 3) = lookupTable(varRow, 3)
lookupValueTable(varRowMain, 4) = lookupTable(varRow, 4)
Exit Do
End If
varRow = varRow + 1
Loop
If IsEmpty(lookupValueTable(varRowMain, 3)) Then
lookupValueTable(varRowMain, 3) = "NA_OX"
lookupValueTable(varRowMain, 4) = "NA_OY"
End If
varRowMain = varRowMain + 1
varRow = 1
Loop
Range("G2:J309011").Value = lookupValueTable
Cells(3, 12) = Format(DateTime.Now, "yyyy-MM-dd hh:mm:ss")
Application.ScreenUpdating = True
Application.DisplayStatusBar = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End Sub
Is this what you need?
Sub subFindValue()
'Speed up
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Dim strNamedValue As String: strNamedValue = Range("E3")
Dim curHigherThanValue As Currency: curHigherThanValue = Range("F3")
Dim varRow As Variant
varRow = 1
Do Until IsEmpty(Cells(varRow, 1))
If Cells(varRow, 1) = strNamedValue And Cells(varRow, 2) > curHigherThanValue Then
Range("G3") = Cells(varRow, 3)
Exit Do
End If
varRow = varRow + 1
Loop
'Slow down
Application.ScreenUpdating = True
Application.DisplayStatusBar = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End Sub
This should work and be much faster then any VBA solution that would require looping every row as long as you can sort the date in Column B Descending:
Enter the following Formula As an Array (Instead of Enter use Ctrl+Shift+Enter
=INDEX($C$1:$C$15,MATCH(G2,IF($A$1:$A$15=F2,$B$1:$B$15),-1))
You should end up with something like:
Explanation:
IF($A$1:$A$15=F2,$B$1:$B$15)
Is building an array of values equal to the rows in column B where The Test word is in the same Row column A.
MATCH(G2,IF($A$1:$A$15=F2,$B$1:$B$15),-1)
This is using the Array built from the Id statement to find the smallest value greater than or equal to the Look up value from test data.
=INDEX($C$1:$C$15,MATCH(G2,IF($A$1:$A$15=F2,$B$1:$B$15),-1))
Once it is all together the 'INDEX' will return the value in Column C that is at the same position as the matched value.
UPDATE: If you are looking for what tigeravatar's Answer returns then here is another VBA function that will return all values:
Sub GetValues()
With Application
.ScreenUpdating = False
.EnableEvents = False
.Calculation = xlCalculationManual
End With
Dim strMetalName As String: strMetalName = [E3]
Dim dbMinimumValue As Double: dbMinimumValue = [F3]
Range("G3:G" & Rows.Count).ClearContents
With Range("TABL")
.AutoFilter Field:=1, Criteria1:=strMetalName
.AutoFilter Field:=2, Criteria1:=">=" & dbMinimumValue, Operator:=xlAnd
Range("C2", [C2].End(xlDown)).Copy [G3]
.AutoFilter
End With
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = xlCalculationAutomatic
End With
End Sub
For me his took 5-7 minutes to run while this took 1.5 seconds, where my first answer returns the single row containing the closest matching result this sub will return ALL values greater then or equal too.
If your data is sorted on column 2 within column 1 then the SpeedTools Filter.Ifs function would be considerable faster than your formula (at least 50 times faster)
=FILTER.IFS(2,$A$1:$C$325779,3,1,E3,2,">" & F3)
Disclaimer: I am the author of SpeedTools which is a commercial Excel addin product. You can download a full trial version from: http://www.decisionmodels.com/FastExcelV3SpeedTools.htm
You may need to adjust where the output goes (it assumes that the results should be output in cell G3 and down), but this should run pretty quickly:
Sub subFindValue()
Dim rngFound As Range
Dim arrResults() As Variant
Dim varFind As Variant
Dim dCompare As Double
Dim ResultIndex As Long
Dim strFirst As String
varFind = Range("E3").Text
dCompare = Range("F3").Value2
Range("G3:G" & Rows.Count).ClearContents
With Range("TABL").Resize(, 1)
Set rngFound = .Find(varFind, .Cells(.Cells.Count), xlValues, xlWhole)
If Not rngFound Is Nothing Then
ReDim arrResults(1 To WorksheetFunction.CountIf(.Cells, varFind), 1 To 1)
strFirst = rngFound.Address
Do
If rngFound.Offset(, 1).Value > dCompare Then
ResultIndex = ResultIndex + 1
arrResults(ResultIndex, 1) = rngFound.Offset(, 2).Text
End If
Set rngFound = .Find(varFind, rngFound, xlValues, xlWhole)
Loop While rngFound.Address <> strFirst
End If
End With
If ResultIndex > 0 Then Range("G3").Resize(ResultIndex).Value = arrResults
End Sub