I have a massive Excel template that's having performance issues. I'd like to optimize the VBA code I'm using to modify cells to be more of an "all at once" approach. I have several basic functions using loops to modify values. Here's an example:
Dim aCell as Range
For Each aCell In Range("A1:A9999").Cells
'appends prefix to value of each cell
aCell.Value = "CC_" & aCell.Value
Next aCell
While this works, the drawback of this is that it causes several recalculations and updates that slows down the template. I'm familiar with turning calculations/screen updating on and off, but for reasons I won't go into, that's not an option.
This code below does NOT work, but it's the approach I'm looking for. Is there a way to make such a change using an array or some other tool I'm not thinking of that would minimize the templates calculation updates?
Range("A1:A9999").Value = "CC_" & Range("A1:A9999").Value
Thank you!
Reading/writing to/from the worksheet takes a lot of time. Do the modifications within a VBA array, then write it back.
Dim myRange As Range, myArr As Variant
Set myRange = Range("A1:A9999")
myArr = myRange
For i = 1 To UBound(myArr, 1)
myArr(i, 1) = "CC_" & myArr(i, 1)
Next i
myRange = myArr
You could temporarily create a column full of functions, then paste those values over the column A values:
Range("XFD1:xfd9999").Formula = "=""CC_""&A1"
Calculate
Range("A1:a9999").Value = Range("XFD1:XFD8").Value
Range("XFD1:XFD9999").ClearContents
I'm operating on the assumption here that you are not using column XFD for anything else. If you are, you could use a different column for this purpose.
FWIW, you can do it without a loop using Evaluate like this:
Sub addText()
With Range("A1:A9999")
.Value2 = .Worksheet.Evaluate("INDEX(""CC_""&" & .Address & ",)")
End With
End Sub
I was revisiting this (trying to still make it faster) and now that I have a little better understanding, I would recommend an approach shown below. The accepted answer is hard-coded for one column, which was my example asked, but the below approach is more dynamic.
Sub sampleArraySheetEdit()
Dim aRange As Range: Set aRange = Range("A1:B9999") ' or whatever your range is...
Dim vRng() As Variant: vRng = aRange
Dim r As Long, c As Long
For r = LBound(vRng, 1) To UBound(vRng, 1) 'this ensures all cells always accounted for
For c = LBound(vRng, 2) To UBound(vRng, 2)
'perform you operation here....
vRng(r, c) = "CC_" & vRng(r, c)
Next c
Next r
aRange = vRng
End Sub
Related
I previously used the following code -
Dim HSArr(2 To 250) As Variant
Dim HSVal As Long
For HSVal = LBound(HSArr) To UBound(HSArr)
HSArr(HSVal) = Cells(HSVal, 1) & " " & Cells(HSVal, 2)
Next HSVal
It was an array that would concatenate column A and B, then the array would be output onto the worksheet in "P2:P250".
Sub SumData()
Const dFormula As String _
= "=IFERROR(SUMPRODUCT(--(P$2:P$250=I2),D$2:D$250,F$2:F$250),"""")"
With ThisWorkbook.Worksheets("Sheet1").Range("K2:K250")
.Formula = dFormula
.Value = .Value
End With
End Sub
This code does what I want to an extent but requires "P2:P250" to contain the array output, but I don't want to output anything onto the worksheet.
There's a piece of information that I do not understand here, how I can introduce those values from the array into the SUMPRODUCT (instead of "P2:P250"), even if its not by methods of array, as AFAIK I can't use that as a range without it being on the worksheet itself. Any idea?
Building an array with VBA then trying to use it with a formula in a worksheet without placing that information into a worksheet generally wouldn't work, however here you can use the source data in the formula instead:
=IFERROR(SUMPRODUCT(--(A$2:A$250&" "&B$2:B$250=I2),D$2:D$250,F$2:F$250),"""")
I am attempting to simplify my code when writing back data from an array into an Excel spreadsheet.
I have an array of 2 rows and 49 columns (the data is dates used for the horizontal part in a number of graphs). The date's adapt based on user-input and it is then written back into the Excel spreadsheet.
Currently I have written below code for loading the data into the array and writing it back into the spreadsheet (and it works as intended).
Dim LocalArray() As Variant
LocalArray = Sheets("Data").Range("L4003:BH4004").Value2
.Range("M280").Resize(UBound(LocalArray, 1), UBound(LocalArray, 2)) = LocalArray
.Range("M336").Resize(UBound(LocalArray, 1), UBound(LocalArray, 2)) = LocalArray
.Range("M394").Resize(UBound(LocalArray, 1), UBound(LocalArray, 2)) = LocalArray
Above is only part of the code though as I need to include the array in 15 different places (graphs). Hence I repeat the same line of code a lot of times which seems very ineffecient.
I have tried using below simple line of code to write the array data back into the spreadsheet:
.Range("M280:BI281,M336:BI337,M394:BI395").Value2 = LocalArray
However, writing back the array data using above code makes every second range appear wrong with cells including N/A (ref. below picture).
How do I write this code the simplest way possible (and requiring as little processing power from the user's PC as possible)?
Thank you very much!
It is much easier to examine and remember the dimensions of the source range.
Say we have data in a rectangular set of cells from B2 through D3:
That we wish to copy elsewhere several times. The first time will be a block starting at E6:
Sub dural()
Dim LocalArray() As Variant
Dim rng As Range, rw As Long, cl As Long
Set rng = Sheets("Sheet1").Range("B2:D4")
rw = rng.Rows.Count
cl = rng.Columns.Count
LocalArray = rng.Value2
Range("E6").Resize(rw, cl) = LocalArray
End Sub
Running this yields:
So all we need to remember is rw and cl.
EDIT#1:
For an easy way to loop the deposits:
Sub dural()
Dim LocalArray() As Variant
Dim rng As Range, rw As Long, cl As Long
Dim a
Set rng = Sheets("Sheet1").Range("B2:D4")
rw = rng.Rows.Count
cl = rng.Columns.Count
LocalArray = rng.Value2
Range("E6").Resize(rw, cl) = LocalArray
' Now try looping
For Each a In Array("a12", "b16", "c23")
Range(a).Resize(rw, cl) = LocalArray
Next a
End Sub
I am sorry if this sort of questions has already been asked, I couldn't find the answer I needed and I am quite new to VBA.
I am trying to match some values from one table to the other via an Index Match which is moving between 2 Workbooks. To do this properly I have used two loops For To. But my Code is really slow when it comes to a few thousands lines. Can I improve it with an Array or something else?
Dim mainWB As Workbook
Dim mainWS As Worksheet
Set mainWB = ActiveWorkbook
Set mainWS = mainWB.Sheets(1)
Dim RowsToProcess As Long
RowsToProcess = mainWS.Range("C" & Rows.Count).End(xlUp).Row
Dim lastCol As Long
lastCol = mainWS.Cells(10, Columns.Count).End(xlToLeft).Column
Range(Cells(11, 8), Cells(RowsToProcess, lastCol)).Select
Selection.NumberFormat = "General"
For i = 1 To lastCol
For a = 1 To RowsToProcess
If Workbooks("template.xls").Sheets("#").Cells(10 + a, 7 + i).Value <> vbNullString Then
Cells(10 + a, 7 + i).Select
ActiveCell.FormulaR1C1 = _
"= "Long Formula" "
End If
Next
Next
For my Long formula. It is basically doing 2 Index(Match. between this Workbook and 2 others, but I thought I would take it out here to keep it clearer
Thank you very much for the help!
If you are effectively defining a range you can use the first and last columns and rows addresses and create a range object.
See examples here: Dynamic ranges
You can then do:
Range(myRange).SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "LongFormula"
which effectively replaces all blank cells with your formula without having to do all the loops.
Combine those steps with the items mentioned in the comments such as avoiding .select.
Summary:
Avoid the .select
Use WITH where possible when working with objects
Get the first cell address and last cell address and create your range object
Use the special cells method to set blanks in this range to your formula.
I am trying to avoid the use of loops for populating arrays since they take a lot of time when managing a lot of data.
Apparently as well, that is possible and easy in VBA but often results in problems.
Here is the code:
sub populate()
'put the whole column in an array
Dim AppArray() As Variant
Dim AppRange As Range
'calculate the last row of the column 1 of sheets
Dim LstRow As Integer
LstRow = Sheets("whole").Cells(Sheets("whole").Rows.Count, "A").End(xlUp).row
'here I calculate the range that I want to pass to the array
Set AppRange = Sheets("whole").Range(Cells(1, 1), Cells(LstRow, 1))
MsgBox ("apprange " & AppRange.Address)
'i dont know if I need to redim or not
ReDim AppArray(1 To LstRow)
'here comes the point. populate the array with the values of the range
AppArray = AppRange.Value
End Sub
This does not work. I also tried application.tranpose(AppRange.Value).
I used:
For i = 1 To LstRow
Debug.Print AppArray(i)
Next
and an error appears, so somehow there is no AppArray(1).
I would be very happy if you can comment on that. More than just arranging the code suggest even other pages (links) to populate arrays with values of ranges when these ranges are not known in advance.
If the case is that looping is very time consuming and that arrays can be populated straight away, I don't understand why 99% of the pages referring to arrays use a loop (or nested loop) to populate an array.
I found the answer.
dim myRange as range
dim myArray() as variant
myRange = range(cells(2,3),cells(10,15))
redeem myArray(1 to 20,1 to 20)
myArray=myRange
It's always much faster to work with variables and arrays than with cells values.
As for the problem, I need to be able to compare all data in Variant array A to all data in Variant array B. I know I need some kind of double loop (so that every A value is checked against all B values), but I can't figure out how to do it. Here's what I have so far:
Sub Button_Click()
Dim trgtRange As Variant
Dim tempRange As Variant
Set myRange = ThisWorkbook.Sheets(1).Range("L:L")
For Each cell In myRange
If IsEmpty(cell) Then
ActiveCell.Offset(-1, 0).Select
currentRow = ActiveCell.Row
Set trgtRange = Range("L2:L" & currentRow)
Exit For
End If
Next cell
Set tempRange = Range("A1:A" & currentRow - 1)
' Insert a double loop here
End Sub
So, trgtRange is the Variant A and tempRange is Variant B. I know I could have set the Variant B up a little easier, but I already did it that way. After all, code should be polished as last operation anyway.
You might be wondering why Variants A and B are completely the same. Well, that's because I need to compare them so that I can find values that are close to each other, (i.e 10000 and 12000) and I need to incorporate some kind of tolerance for it.
Here is my answer. Why do you need two loops to do this. Some relative addressing handles this issue quite nicely. Set up a spreadsheet like this for an example:
and your code is simply this
Sub Button_Click()
Dim dblTolerance As Double
Dim tmp As Range
'Get source range
Set tmp = ActiveSheet.Range("A2")
'Get tolerance from sheet or change this to an assignment to hard code it
dblTolerance = ActiveSheet.Range("D13")
'use the temporary variable to cycle through the first array
Do Until tmp.Value = ""
'Use absolute function to determine if you are within tolerance and if so put match in the column
'NOTE: Adjust the column offset (set to 4 here) to match whichever column you want result in
If Abs(tmp.Value - tmp.Offset(0, 2).Value) < dblTolerance Then
tmp.Offset(0, 4).Value = "Match"
Else
tmp.Offset(0, 4).Value = "No Match"
End If
'Go to the next row
Set tmp = tmp.Offset(1, 0)
Loop
'Clean up
Set tmp = Nothing
End Sub
The comments in the code explain how it works. This is superior to a double loop because relative referencing is faster, the memory use is more efficient and you only have to make one pass at each row.
If you are required for some reason to use a double loop let me know, but that is inferior performance wise to this methodology. Hope this helps.