VBA to write data in cells very slow - excel

I have written a very straightforward script which writes data into excel cells.
Basically, this is a loop over an array and it writes data into specific cells or formulas.
The problem is that this part of the script is extremly slow.
Any ideas on how to improve thath?
Thanks.
For j = 0 To i - 1
'Insère nouvelle ligne
Rows(startRow & ":" & startRow).Select
Selection.Copy
Rows(startRow + 1 & ":" & startRow + 1).Select
Selection.Insert Shift:=xlDown
'Insère données
If roomType(j) <> "" Then
Feuil3.Cells(startRow, 1).Value = roomName(j)
Feuil3.Cells(startRow, 2).Value = roomSurface(j)
Feuil3.Cells(startRow, 7).Value = roomPeople(j)
Feuil3.Cells(startRow, 12).Value = roomPeople(j)
Feuil3.Cells(startRow, 5).Value = dict.Item(roomType(j))
Feuil3.Cells(startRow, 3).Value = roomHeight(j)
Feuil3.Range("F" & startRow).Formula = "=IFERROR(IF($E" & startRow & "=Data!$A$55,,ROUNDUP($B" & startRow & "/VLOOKUP($E" & startRow & ",Data!$A$3:$E$55,4,FALSE),0)),)"
Feuil3.Range("H" & startRow).Formula = "=$C$25"
Feuil3.Range("I" & startRow).Formula = "=IF($E" & startRow & "=Data!$A$55,$B" & startRow & "*$E$55,(MAX(F" & startRow & ",G" & startRow & ")*H" & startRow & "))"
Feuil3.Range("N" & startRow).Formula = "=IFERROR(VLOOKUP($K" & startRow & ",$M$22:$O$26,3,FALSE),)"
Feuil3.Range("O" & startRow).Formula = "=IFERROR(IF(ISBLANK(M" & startRow & ")=TRUE,L" & startRow & "*N" & startRow & ",L" & startRow & "*M" & startRow & "*N" & startRow & "),)"
Feuil3.Range("Q" & startRow).Formula = "=MAX(I" & startRow & ",O" & startRow & ")"
Feuil3.Range("T" & startRow).Formula = "=IFERROR(MAX(R" & startRow & ",S" & startRow & ")/(B" & startRow & "*C" & startRow & "),)"
End If
startRow = startRow + 1
Next j
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True

It is quicker to assign an array to a range of cells than to assign to individual cells in a loop. You could try sometihing like: Worksheets("MySheet").Range("A1:D100").Value = myArray. It is because communication between VBA and Excel takes some time. A little bit more on that here: VBA Excel large data manipulation taking forever
Also using Cells instead of Range is aboout 2.6 times faster. Here is a question on that: Range() VS Cells() - run times

I cut the script in pieces.
This part takes most of the time
For j = 0 To i - 2
Feuil3.Cells(startRow, 5).Value = dict.Item(roomType(j))
Feuil3.Cells(startRow, 6).Formula = "=IFERROR(IF($E" & startRow & "=Data!$A$55,,ROUNDUP($B" & startRow & "/VLOOKUP($E" & startRow & ",Data!$A$3:$E$55,4,FALSE),0)),)"
Feuil3.Cells(startRow, 8).Formula = "=$C$25"
Feuil3.Cells(startRow, 9).Formula = "=IF($E" & startRow & "=Data!$A$55,$B" & startRow & "*$E$55,(MAX(F" & startRow & ",G" & startRow & ")*H" & startRow & "))"
Feuil3.Cells(startRow, 14).Formula = "=IFERROR(VLOOKUP($K" & startRow & ",$M$22:$O$26,3,FALSE),)"
Feuil3.Cells(startRow, 15).Formula = "=IFERROR(IF(ISBLANK(M" & startRow & ")=TRUE,L" & startRow & "*N" & startRow & ",L" & startRow & "*M" & startRow & "*N" & startRow & "),)"
Feuil3.Cells(startRow, 17).Formula = "=MAX(I" & startRow & ",O" & startRow & ")"
Feuil3.Cells(startRow, 20).Formula = "=IFERROR(MAX(R" & startRow & ",S" & startRow & ")/(B" & startRow & "*C" & startRow & "),)"
startRow = startRow + 1
Next j

Related

Animated multicell formula array for Excel VBA only returns 0's?

It looks like a simple enough code to me, but I can't get it to work properly. I'm trying to iterate through rows of values in order to add them one at a time to a display row, storing the previous value in an extra row in order to avoid circular calculations.
The WriteRow loads properly on the first iteration, but after that all results in the WriteRow and StoreRow are 0.
Here's the code:
Const StartRow As Long = 2
Const LastRow As Long = 367
Const StoreRow As Long = 369
Const WriteRow As Long = 370
Dim RowNumber As Long
Range("A" & StoreRow, "T" & StoreRow).ClearContents
Range("A" & WriteRow, "T" & WriteRow).ClearContents
DoEvents
Application.Wait (Now + TimeValue("00:00:1"))
For RowNumber = StartRow To LastRow
DoEvents
Range("A" & WriteRow, "T" & WriteRow).FormulaArray = "=A" & StoreRow & ":T" & StoreRow & "+A" & RowNumber & ":T" & RowNumber
Range("A" & StoreRow, "T" & StoreRow).FormulaArray = "=A" & WriteRow & ":T" & WriteRow
Application.Wait (Now + TimeValue("00:00:1"))
DoEvents
Next RowNumber
End Sub
I did also try adding Range("A" & WriteRow, "T" & WriteRow).AutoFill Destination:=Range("A" & WriteRow & ":T" & WriteRow), but the AutoFill kept failing. The formula bar shows "{=A370:T370}" for StoreRow and "{=A369:T369+A2:T2}" for WriteRow.
I think the problem is that there's still a circular reference. Is there a good way to get around it?
Found an answer that produces the results I was looking for:
Sub ExpensesChart()
Const StartRow As Long = 2
Const LastRow As Long = 367
Const StoreRow As Long = 369
Const WriteRow As Long = 370
Dim RowNumber As Long
Dim val As Double
Range("A" & StoreRow, "T" & StoreRow).ClearContents
Range("A" & WriteRow, "T" & WriteRow).ClearContents
For RowNumber = StartRow To LastRow
DoEvents
Range("A" & WriteRow, "T" & WriteRow).FormulaArray = "=A" & StoreRow & ":T" & StoreRow & "+A" & RowNumber & ":T" & RowNumber
Range("A" & WriteRow, "T" & WriteRow).Formula = Range("A" & WriteRow, "T" & WriteRow).Value
Range("A" & StoreRow & ":T" & StoreRow).FormulaArray = Array(Range("A" & WriteRow & ":T" & WriteRow))
Application.Wait (Now + TimeValue("00:00:1"))
DoEvents
Next RowNumber
End Sub

How to convert several non-adjacent columns to lowercase

This is looping through a worksheet that is about 10k rows and it is taking a considerable amount of time. Is there a way to do this faster aside from an array? thank you
For i = 2 To spberowcnt
With spbe30
.Range("b" & i).Value = LCase(.Range("b" & i).Value)
.Range("d" & i).Value = LCase(.Range("d" & i).Value)
.Range("i" & i).Value = LCase(.Range("i" & i).Value)
.Range("j" & i).Value = LCase(.Range("j" & i).Value)
.Range("l" & i).Value = LCase(.Range("l" & i).Value)
.Range("m" & i).Value = LCase(.Range("m" & i).Value)
.Range("n" & i).Value = LCase(.Range("n" & i).Value)
.Range("p" & i).Value = LCase(.Range("p" & i).Value)
.Range("q" & i).Value = LCase(.Range("q" & i).Value)
.Range("r" & i).Value = LCase(.Range("r" & i).Value)
.Range("z" & i).Value = LCase(.Range("z" & i).Value)
.Range("aa" & i).Value = LCase(.Range("aa" & i).Value)
End With
Next i
For i = 2 To spberowcnt
With spbe60
.Range("b" & i).Value = LCase(.Range("b" & i).Value)
.Range("d" & i).Value = LCase(.Range("d" & i).Value)
.Range("i" & i).Value = LCase(.Range("i" & i).Value)
.Range("j" & i).Value = LCase(.Range("j" & i).Value)
.Range("l" & i).Value = LCase(.Range("l" & i).Value)
.Range("m" & i).Value = LCase(.Range("m" & i).Value)
.Range("n" & i).Value = LCase(.Range("n" & i).Value)
.Range("p" & i).Value = LCase(.Range("p" & i).Value)
.Range("q" & i).Value = LCase(.Range("q" & i).Value)
.Range("r" & i).Value = LCase(.Range("r" & i).Value)
.Range("z" & i).Value = LCase(.Range("z" & i).Value)
.Range("aa" & i).Value = LCase(.Range("aa" & i).Value)
End With
Next i
This is the array solution
Sub test()
Application.ScreenUpdating = False
Dim arrWorksheets(1) As Variant, ws As Worksheet
Set arrWorksheets(0) = spbe30
Set arrWorksheets(1) = spbe60
Dim arrColumns As Variant
arrColumns = Array("B", "D", "AA") 'adjust to your needs
Dim arrValues As Variant
Dim iWs As Long, iC As Long, i As Long
For iWs = 0 To UBound(arrWorksheets)
Set ws = arrWorksheets(iWs)
For iC = 0 To UBound(arrColumns)
arrValues = ws.Range(arrColumns(iC) & "2").Resize(spberowcnt).Value
For i = 1 To UBound(arrValues, 1)
arrValues(i, 1) = LCase(arrValues(i, 1))
Next
ws.Range(arrColumns(iC) & "2").Resize(spberowcnt).Value = arrValues
Next
Next
Application.ScreenUpdating = True
End Sub
Alternative: helper columns ...
You could try something like the following, looping over the columns instead of the individual cells and using Evaluate and Lower on the entire column. You could also process adjacent columns together.
cols = Array("B", "D", "I:J", "L:N", "P:R", "Z:AA")
For i = LBound(cols) to Ubound(cols)
Dim col As String
col = cols(i)
With spbe30
Dim rng As Range
Set rng = .Rows("2:" & spberowcnt).Columns(col)
rng.Value = .Evaluate("LOWER(" & rng.Address & ")")
End With
Next
But as mentioned in comments, an array is probably the way to go.

excel VBA: populate results in cells instead of formula - speed up processing

I have a macro executing formulas that are entered into cells, then applied to roughly 70,000 cells. The process takes more than 24 hours (it's still running). I need to find a way to speed up the process. My first thought is to populate the cells with the results of the formula instead of the formula itself, but I'm at a lost.
Currently, the macro scans three different worksheets to determine how many unique values there are. Then the formulas are applied for each unique value. Below is my code for one of the worksheets where the formulas are applied. I have some test code commented out that limited the rows to 40, but when I run all unique rows for this sample I have 56,136. For 40 rows, this still takes about 5 minutes.
'return to Summary and throw in formulas for each unique alarm per type
Range("A1").Select
Sheets("AlarmHistory-Summary").Select
Dim RowHeader As Long
Dim RowFirst As Long
Dim RowSecond As Long
Dim aUnqRowFirst As Long
Dim aUnqRowLast As Long
Dim dUnqRowFirst As Long
Dim dUnqRowLast As Long
Dim oUnqRowFirst As Long
Dim oUnqRowLast As Long
RowHeader = 1
RowFirst = 2
RowSecond = 3
dUnqRowFirst = RowFirst
dUnqRowLast = dUnqRowFirst + dCountUnique
aUnqRowFirst = dUnqRowLast + 1
aUnqRowLast = aUnqRowFirst + aCountUnique
oUnqRowFirst = aUnqRowLast + 1
oUnqRowLast = oUnqRowFirst + oCountUnique
Const ReturnType1 As String = "RETURN"
'Digital Point formulas
Range(dUnqRowFirst).Select
Set dSVA = Range("A" & dUnqRowFirst & ":A" & dUnqRowLast)
Set dSVB = Range("B" & dUnqRowFirst & ":B" & dUnqRowLast)
Set dSVC = Range("C" & dUnqRowFirst & ":C" & dUnqRowLast)
Set dSVD = Range("D" & dUnqRowFirst & ":D" & dUnqRowLast)
Set dSVE = Range("E" & dUnqRowFirst & ":E" & dUnqRowLast)
Set dSVF = Range("F" & dUnqRowFirst & ":F" & dUnqRowLast)
'Set dSVG = Range("G" & dUnqRowFirst & ":G" & dUnqRowLast)
Set dSVH = Range("H" & dUnqRowFirst & ":H" & dUnqRowLast)
'Set dSVA = Range("A" & dUnqRowFirst & ":A40")
'Set dSVB = Range("B" & dUnqRowFirst & ":B40")
'Set dSVC = Range("C" & dUnqRowFirst & ":C40")
'Set dSVD = Range("D" & dUnqRowFirst & ":D40")
'Set dSVE = Range("E" & dUnqRowFirst & ":E40")
'Set dSVF = Range("F" & dUnqRowFirst & ":F40")
'Set dSVG = Range("G" & dUnqRowFirst & ":G40")
'Set dSVH = Range("H" & dUnqRowFirst & ":H40")
dSVA.Formula = "=IFERROR(LOOKUP(2,1/(COUNTIF($A$" & RowHeader & ":A" & RowHeader & ",'AlarmHistory-Digital'!$D$" & dRowFirst & ":$D$" & dRowLast & ")=0),'AlarmHistory-Digital'!$D$" & dRowFirst & ":$D$" & dRowLast & "),"""")"
dSVB.Formula = "=IFERROR(LOOKUP(2,1/(COUNTIF($A$" & RowHeader & ":A" & RowHeader & ",'AlarmHistory-Digital'!$D$" & dRowFirst & ":$D$" & dRowLast & ")=0),'AlarmHistory-Digital'!$E$" & dRowFirst & ":$E$" & dRowLast & "),"""")"
dSVC.Formula = "=IF($A" & RowFirst & "="""","""",IFERROR(AVERAGEIFS('AlarmHistory-Digital'!$O:$O,'AlarmHistory-Digital'!$D:$D,$A" & RowFirst & ",'AlarmHistory-Digital'!$B:$B,""" & ReturnType1 & """),0))"
dSVD.Formula = "=IF($A" & RowFirst & "="""","""",IFERROR(MINIFS('AlarmHistory-Digital'!$O:$O,'AlarmHistory-Digital'!$D:$D,$A" & RowFirst & ",'AlarmHistory-Digital'!$B:$B,""" & ReturnType1 & """),0))"
dSVE.Formula = "=IF($A" & RowFirst & "="""","""",IFERROR(MAXIFS('AlarmHistory-Digital'!$O:$O,'AlarmHistory-Digital'!$D:$D,$A" & RowFirst & ",'AlarmHistory-Digital'!$B:$B,""" & ReturnType1 & """),0))"
dSVF.Formula = "=IF($A" & RowFirst & "="""","""",IFERROR(COUNTIFS('AlarmHistory-Digital'!$D:$D,$A" & RowFirst & ",'AlarmHistory-Digital'!$B:$B,""" & ReturnType1 & """),0))"
Range("G" & dUnqRowFirst).FormulaArray = "=IFERROR(LARGE(IF('AlarmHistory-Digital'!$D$" & dRowFirst & ":$D$" & dRowLast & "=$A" & RowFirst & ",'AlarmHistory-Digital'!$O$" & dRowFirst & ":$O$" & dRowLast & "),F" & RowFirst & "-ROUNDUP($F" & RowFirst & "*0.8,0)+1),"""")"
Range("G" & dUnqRowFirst).AutoFill Range("G" & dUnqRowFirst & ":G" & dUnqRowLast)
dSVH.Formula = "=COUNTIFS('AlarmHistory-Digital'!D:D,A" & RowFirst & ",'AlarmHistory-Digital'!O:O,""<""&G" & RowFirst & ")"
Range(aUnqRowFirst).Select
MsgBox "Digital Calculations Applied"
You can use advance filter, where A1:H1 are heading:
Range("Sheet1!A1:H1000").AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Range( _
"Sheet2!A1:H1"), Unique:=True

VB Code to check if text fits cell

I am building a tool on excel, I need to check if text fits the cell, if not I have to merge cells to fit text. cells are fixed I cannot use autofit or option.
Please suggest VB Code, thanks in advance
Answered elsewhere, but this is a basic code that works. Autosize.
Sub merger()
Set c = ActiveSheet.Range("A:A").Find(what:="Executive Weekly Summary")
If Not c Is Nothing Then
startrow = c.Row + 1
endrow = ActiveSheet.Range("A" & startrow).End(xlDown).Row - 1
End If
'"A" & startrow & ":I" & endrow
OldWidth = ActiveSheet.Range("A" & startrow & ":A" & endrow).ColumnWidth ' Save original width
ActiveSheet.Range("A" & startrow & ":A" & endrow).EntireColumn.AutoFit
fitwidth = ActiveSheet.Range("A:A").ColumnWidth ' Get width required to fit entire text
ActiveSheet.Range("A" & startrow & ":A" & endrow).ColumnWidth = OldWidth ' Restore original width
If OldWidth < fitwidth Then
Do Until OldWidth = fitwidth
ActiveSheet.Range("").Merge
endrow = endrow + 1
OldWidth = ActiveSheet.Range("A" & startrow & ":A" & endrow).ColumnWidth
ActiveSheet.Range("A" & startrow & ":A" & endrow).EntireColumn.AutoFit
fitwidth = ActiveSheet.Range("A:A").ColumnWidth
ActiveSheet.Range("A" & startrow & ":A" & endrow).ColumnWidth = OldWidth
Loop
End If
End Sub

Need to run a Do While loop across multiple worksheets

I have been trying to run the same Do While loop function across multiple worksheets in a workbook and compile the data in another worksheet. The code works for the one worksheet that is specified but how do I get it to work across the others that are in the workbook at the same time?
Also worth mentioning that I only want it to run on some of the worksheets not all that are in the workbook (sheets are named as years - 2014, 2015 etc).
This is the code
Sub Total_Button1_Click()
Dim i As Integer
Dim strSheetFrom As String
Dim j As Integer
Dim strSheetTo As String
i = 3
j = 2
strSheetFrom = "2014"
strSheetTo = "Total"
Do While Trim(Sheets(strSheetTo).Range("B" & CStr(j)).Text) <> ""
j = j + 2
Loop
Do While Trim(Sheets(strSheetFrom).Range("B" & CStr(i)).Text) <> ""
If UCase(Trim(Sheets(strSheetFrom).Range("A" & CStr(i)).Text)) = "Y" Then
Sheets(strSheetTo).Range("B" & j & ":G" & j).Value = Sheets(strSheetFrom).Range("B" & i & ":G" & i).Value
Sheets(strSheetTo).Range("H" & j & ":I" & j).Value = Sheets(strSheetFrom).Range("I" & i & ":J" & i).Value
Sheets(strSheetTo).Range("J" & j & ":J" & j).Value = Sheets(strSheetFrom).Range("L" & i & ":L" & i).Value
Sheets(strSheetTo).Range("K" & j & ":K" & j).Value = Sheets(strSheetFrom).Range("Q" & i & ":Q" & i).Value
Sheets(strSheetTo).Range("L" & j & ":AH" & j).Value = Sheets(strSheetFrom).Range("s" & i & ":AO" & i).Value
j = j + 1
End If
i = i + 1
Loop
MsgBox "Total book created"
End Sub
Try making your strSheetFrom variable an array something like this:
strSheetFrom = new strSheetFrom[3]
strSheetFrom[2] = "2012"
strSheetFrom[1] = "2013"
strSheetFrom[0] = "2014"
Then put your code into another loop like so:
dim w as integer
for w = 0 To 3
Do While Trim(Sheets(strSheetTo).Range("B" & CStr(j)).Text) <> ""
j = j + 2
Loop
Do While Trim(Sheets(strSheetFrom[w]).Range("B" & CStr(i)).Text) <> ""
If UCase(Trim(Sheets(strSheetFrom[w]).Range("A" & CStr(i)).Text)) = "Y" Then
Sheets(strSheetTo).Range("B" & j & ":G" & j).Value = Sheets(strSheetFrom[w]).Range("B" & i & ":G" & i).Value
Sheets(strSheetTo).Range("H" & j & ":I" & j).Value = Sheets(strSheetFrom[w]).Range("I" & i & ":J" & i).Value
Sheets(strSheetTo).Range("J" & j & ":J" & j).Value = Sheets(strSheetFrom[w]).Range("L" & i & ":L" & i).Value
Sheets(strSheetTo).Range("K" & j & ":K" & j).Value = Sheets(strSheetFrom[w]).Range("Q" & i & ":Q" & i).Value
Sheets(strSheetTo).Range("L" & j & ":AH" & j).Value = Sheets(strSheetFrom[w]).Range("s" & i & ":AO" & i).Value
j = j + 1
End If
i = i + 1
Loop
w -= 1
next
I haven't tested it, but something like that. You get the idea.
Use a For Each and iterate over the Worksheet collections like this
'Variables
Dim useWorkSheet As Worksheet
Dim totalWorkSheet As Worksheet
Dim yearAsNumeric As Integer
Dim startingYear As Integer
'Settings
startingYear = 2014
'To reference the total worksheet so we can work with it
Set totalWorkSheet = ActiveWorkbook.Worksheets("Total")
'Iterate over each item in the collection
For Each useWorkSheet In ActiveWorkbook.Worksheets
'Force the name into a numeric value. If it starts with anything non numeric (A-Z|a-z|$,#,etc) then it will return 0
yearAsNumeric = Val(useWorkSheet.Name)
'Greater than or equal to the year we want to start with?
If yearAsNumeric >= startingYear Then
'Yes. Do your stuff here
useWorkSheet.Name
End If
Next

Resources