Excel VBA for loop causes 100% CPU - excel

Application.ScreenUpdating = False
Dim r As Range
Dim a As Long
Set op = Worksheets("ZVCTOSTATUS")
Set CP = op.Columns("J")
Set CTO = op.Range("J1")
Set OD = op.Columns("G")
Set ZV = op.Columns("H")
op.Activate
fa = op.Range("J" & Rows.Count).End(xlUp).Row
Set r = op.Range("J2:J" & fa)
For Each C In r
CTO = CP.Cells(C.Row, 1).Value
If CTO = "FG BOOKED" Or CTO = "CLOSED" Then
ZV.Cells(C.Row, 1) = 0
ElseIf CTO = "NOT STARTED" Or CTO = "UNCONFIRMED" Then
ZV.Cells(C.Row, 1) = OD.Cells(C.Row, 1).Value
End If
Next C
I am using this code to go through my worksheet making a For loop to change value in Column H by referencing to Column J.
When this code is used on a standalone worksheet, it seems to work perfectly. But once I port it over to a much bigger file which has data connection, and I run this macro only individually, it causes my CPU to run at 100% and takes up to 10 minutes.
Does anyone know why this is happening?

To help your macro run smoother you can insert the below code before your main code (just below the sub) and right after your code (just before the end sub)
This will turn off screen updates, alerts, and set the calculation to manual so no formulas are updating until after the process has ran.
'Please Before Main Code'
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.Calculation = xlManual
'Insert main code here'
'Place After Main code'
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.Calculation = xlAutomatic

It seems you fell in a trap which has the following features:
You are using a large excel file which is several MB in size
The excel document is full of formula and data connection
Additionally it might have pivot tables and charts
Calculation option for Formula is Automatic
Try this:
1. Go to formula tab
2. Click "Calculation Option"
3. Select "Manual"
Now execute the macro you have created. It should be good to go. Once the macro is executed. You can change the calculation option.
Note: You can control the calculation option problematically as well using below snippet:
Dim CalcMode As Long
' This will set the calculation mode to manual
With Application
CalcMode = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
End With
<< Add your macro processing here >>
' Again switch back to the original calculation option
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = CalcMode
End With
Excel tries to calculate the values (based on formula) everytime any cell is changed. This is done for the entire document for every cell updated by your macro. So, for large excel document, it causes high CPU consumption.

You are setting values of cells one at a time triggering a recalculation. The way to do this correctly is to read the columns into memory first, set the values and write the results with one operation.
Public Sub AnswerPost()
Dim r_status As Range, r_value As Range, r_calc As Range
Dim i As Long, n As Long
Dim op As Worksheet
Set op = Worksheets("ZVCTOSTATUS")
' Find the number of items on cell "J2" and below
n = Range(op.Range("J2"), op.Range("J2").End(xlDown)).Rows.Count
' Set the n×1 range of cells under "J", "G" and "H" columns
Set r_status = op.Range("J2").Resize(n, 1)
Set r_value = op.Range("G2").Resize(n, 1)
Set r_calc = op.Range("H2").Resize(n, 1)
Dim x_status() As Variant, x_value() As Variant, x_calc() As Variant
' Read cells from the worksheet into memory arrays
x_status = r_status.Value2
x_value = r_value.Value2
x_calc = r_status.Value2
' Set values of x_calc based on x_status, row by row.
For i = 1 To n
Select Case x_status(i, 1)
Case "FG BOOKED", "CLOSED"
x_calc(i, 1) = 0#
Case "NOT STARTED", "UNCONFIRMED"
x_calc(i, 1) = x_value(i, 1)
End Select
Next i
' Write the resulting array back into the worksheet
r_calc.Value2 = x_calc
End Sub
Test case for above code

Related

Copy columns between sheets, if they do not yet exist

I'm looking for a way or method to copy (adding new) columns between sheets.
Let me illustrate:
Sheet: template
Sheet: student
Initially I duplicate "Template" and rename it.
But when additional tasks are added to "Template" I want to update "Student" minding that I have already changed the content in range B2:D4. So copy/pasting the whole range is not an option.
What's the best way to go about this?
First checking if row A in the destination sheet has a value, if not copy/paste that column?
A push in the right direction (or some code to get started on) would be very much appreciated.
You can achieve this by looping true columns headers, given they are in the first row and all tabs are named appropriately:
Sub AddTask()
With Application
.ScreenUpdating = False
.DisplayAlerts = False
.AskToUpdateLinks = False
.DisplayStatusBar = True
End With
Dim wb As Workbook: Set wb = ThisWorkbook
With wb
Dim LastTemplateCol As Long: LastTemplateCol = .Worksheets("Template").Cells(1, Columns.Count).End(xlToLeft).Column
For i = 2 To LastTemplateCol
Dim TempTask As String: TempTask = .Worksheets("Template").Cells(1, i).Value
Dim LastStudentCol As Long: LastStudentCol = .Worksheets("Student").Cells(1, Columns.Count).End(xlToLeft).Column
For t = 2 To LastStudentCol
Dim StudTask As String: StudTask = .Worksheets("Student").Cells(1, t).Value
Dim Exists As Boolean: Exists = False
If TempTask = StudTask Then
Exists = True
GoTo taskloop:
Else
GoTo studloop:
End If
studloop:
Next t
If Exists = False Then
.Worksheets("Template").Cells(1, i).Columns.EntireColumn.Copy
.Worksheets("Student").Cells(1, LastStudentCol + 1).PasteSpecial
End If
taskloop:
Next i
End With
Application.CutCopyMode = False
End Sub

merging sheets from left to right, not top down using range method

i just like to open several source files (all excel) and always copy the complete data rom sheet 1 into my target-sheet. First part works well.
The unusual thing is that i want the tables to be merged from the left to right (horizontical), not from top down.
Of course the range needs to adjust dynamically. The allocation part is also working. Whats not working is to copy it over my target sheet and always add from left to right.
Means
Worksheet 1 hast data from A1:C10
Worksheet 2 has data from A1:B20
should be merged like
Worksheet 1 hast data from A1:C10 -> A1:C10
Worksheet 2 has data from A1:B20 -> D1:E20
etc. I cannot do this. It either gives me a 1004, or says that the object doesnt support the method.
Here's the code:
Application.ScreenUpdating = False 'Das "Flackern" ausstellen
Set oTargetSheet = ActiveWorkbook.Sheets.Add
lErgebnisSpalte = 1
sPfad = "C:\Users\TEST\"
sDatei = Dir(CStr(sPfad & "*.xl*"))
Do While sDatei <> ""
Set oSourceBook = Workbooks.Open(sPfad & sDatei, False, True)
z1 = oSourceBook.Sheets(1).UsedRange.Rows.Count
s1 = oSourceBook.Sheets(1).UsedRange.Columns.Count
**oSourceBook.Sheets(1).Range(oSourceBook.Cells(1, 1), oSourceBook.Cells(z1, s1)).Copy oTargetSheet.Range(oTargetSheet.Cells(1, lErgebnisSpalte), oTargetSheet.Cells(z1, s1))**
lErgebnisSpalte = lErgebnisSpalte + 1
oSourceBook.Close False 'nicht speichern
'Next File
sDatei = Dir()
Loop
Application.ScreenUpdating = True 'Das Bildschirm-Aktualisieren wieder einschalten
'Variablen aufräumen
Set oTargetSheet = Nothing
Set oSourceBook = Nothing
End Sub
Debug keeps saying:
Object doesnt support the method; and marks this line:
**oSourceBook.Sheets(1).Range(oSourceBook.Cells(1, 1), oSourceBook.Cells(z1, s1)).Copy oTargetSheet.Range(oTargetSheet.Cells(1, lErgebnisSpalte), oTargetSheet.Cells(z1, s1))**
This works for me (in this case I just copy the existing range into the next free column)
Private Sub Worksheet_Activate()
Dim colNr As Integer
For i = 1 To 100
colNr = ThisWorkbook.ActiveSheet.Range("A1").End(xlToRight).Column
colNr = colNr + 1
ThisWorkbook.ActiveSheet.Range("A1:B5").Copy Destination:=ThisWorkbook.ActiveSheet.Cells(1, colNr)
Next i
End Sub
I hope this helped.

Excel Macro to break out tabs to account specific workbooks

Sub CostCenterMarco2014()
Dim xlCalc As XlCalculation
Dim CC As String
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim ccf As Range
Dim ccl As Range
Dim tt As Integer
On Error Resume Next
' Turn off events and screen updating
With Application
xlCalc = .Calculation
.Calculation = xlCalculationManual
.EnableEvents = False
.ScreenUpdating = False
.DisplayAlerts = False
End With
Set thisbook = ActiveWorkbook
' Iteration over SAP cost centers
For i = 2 To 30
CC = thisbook.Worksheets(1).Cells(i, 1).Value
thisbook.Worksheets("Summary").Range("B2").Value = CC
thisbook.Worksheets("Summary").Calculate
Workbooks.Add
thisbook.Worksheets("Summary").Range("A1:Z100").Copy
ActiveWorkbook.Worksheets("Sheet1").Range("A1").PasteSpecial xlPasteFormats
ActiveWorkbook.Worksheets("Sheet1").Range("A1").PasteSpecial xlPasteValues
ActiveWorkbook.Worksheets("Sheet1").Columns("A:Z").AutoFit
' Iteration over 5 sheets
For j = 4 To 7
ActiveWorkbook.Worksheets.Add after:=ActiveWorkbook.Worksheets
ActiveWorkbook.Worksheets(j).Name = thisbook.Worksheets(j).Name
'Copy header row
thisbook.Worksheets(j).Rows(1).Copy Destination:=ActiveWorkbook.Worksheets(j).Range("A1")
' Depending on the format of header row
'tt = ActiveWorkbook.Worksheets(j).Range("A1").CurrentRegion.Columns.Count
tt = ActiveWorkbook.Worksheets(j).Range("IV1").End(xlToLeft).Column
With thisbook.Worksheets(j)
Set ccf = .Range("A:A").Find(what:=CC, after:=.Cells(1, 1), LookIn:=xlValues, SearchDirection:=xlNext)
If Not ccf Is Nothing Then
Set ccl = .Range("A:A").FindPrevious(after:=ccf)
.Range(.Cells(ccf.Row, 1), .Cells(ccl.Row, tt)).Copy Destination:=ActiveWorkbook.Worksheets(j).Range("A2")
End If
End With
Application.CutCopyMode = False
ActiveWorkbook.Worksheets(j).Range("A1").CurrentRegion.Columns.AutoFit
thisbook.Worksheets(j).Range("A1").Select
Next j
ActiveWorkbook.Worksheets("Sheet1").Name = "Summary"
ActiveWorkbook.Worksheets("Sheet2").Delete
ActiveWorkbook.Worksheets("Sheet3").Delete
ActiveWorkbook.Worksheets("Summary").Select
ActiveWorkbook.Worksheets("Summary").Range("A1").Select
ActiveWorkbook.SaveAs Filename:="\\REDACTED\2.February 2019\Monthly Expense Report February 2019-" & CC '& ".xlsx"
ActiveWorkbook.Close
Next i
' Turn on events and screen updating
With Application
.Calculation = xlCalc
.EnableEvents = True
.ScreenUpdating = True
.DisplayAlerts = False
End With
On Error GoTo 0
End Sub
So I won't profess to knowing a whole lot about coding in general. I took a couple classes in college so I feel like I can at least feel my way through this one. This macro was given to me by someone who is no longer at my company. Most of it is working as intended and it worked completely last month.
This month however the Iteration over 5 sheets section just doesn't seem to be working. I tried to step through the macro and it creates a new workbook and pastes the summary info inside, but then when it gets to copying the tabs it doesn't copy any of the 4 details tabs I need or their name even.
What I end up with is all of the individual cost centers in their own file with summary as intended, but the detail tabs are not being copied. Any help is appreciated.
In this line
ActiveWorkbook.Worksheets.Add after:=ActiveWorkbook.Worksheets
the after parameter is expecting a single sheet reference, not a reference to the entire Worksheets collection.
If, for example, you want to add a sheet to the end then you can use Count to locate the last sheet, using it as the sheet index:
ActiveWorkbook.Worksheets.Add _
after:=ActiveWorkbook.Worksheets(ActiveWorkbook.Worksheets.Count)
Remove On Error Resume Next unless, and until, the code is fully tested and working. Even then, this should be a last resort and used to circumvent a specific issue that can safely be ignored.
After removing the nasty error blocks I had to add (ActiveWorkbook.Worksheets.Count)as referenced above. After that I was getting an error at thisbook.Worksheets(j).Range("A1").Select which I solved by just deleting it since it didn't seem like it was needed. Everything seems to be working appropriately now. Thanks for all the help.

How to efficiently run through rows of data to match a criterion using excel VBA?

Normally what I do on my program is using logic like:
Total_rows_worksheet = Worksheets("Sample").Range("A" & Rows.Count).End(xlUp).Row
For i = 2 to Total_rows_worksheet
If Worksheets("Sample").Cells(i,1)=Criteria Then
[Perform this action]
End If
Next i
However, when the number of rows get large, code that runs through the entire sheet gets a bit slow. Also, I remember my programmer friend telling me that it is a common mistake for beginner programmers to run through all the data. The correct way according to him was to point to the rows of interest, but I do not know exactly how to do that in Excel.
Disable screen updating and calculation before the loop and it will increase the speed immensely.
Sub testing()
'Disable screen updating and calculation
Dim uRange As Range
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Total_rows_worksheet = Worksheets("Sample").Range("A" & Rows.Count).End(xlUp).Row
For i = 1 To Total_rows_worksheet
If Worksheets("Sample").Cells(i, 1) = "OK" Then
If uRange Is Nothing Then
Set uRange = Worksheets("Sample").Cells(i, 1)
Else
Set uRange = Union(uRange, Worksheets("Sample").Cells(i, 1))
End If
End If
Next i
uRange.Value = "THIS USED TO SAY OK"
'Enable screen updating and calculation when done
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Edit 1: Depending on the task, another way to speed things up is to change all at once by adding ranges to a Union.
Note: Screen updating should be the first to be disabled and the last to be enabled when going for speed in Excel-VBA. There are other things that can be disable like Events that also help if you have event specific triggers.
You might use something similar as:
Public roww
Sub Main()
Dim sht
roww = InputBox("Start Row", "Row to start", "", 8000, 6000)
If roww <> "" Then
Set sht = Sheets("Your sheet")
'this is a validation
Do Until sht.Cells(roww, "A").Value = ""
'this is other optional validation
If sht.Cells(roww, "Y").Text <> "" Then
[Perform this action]
roww = roww + 1
Else
roww = roww + 1
End If
Loop
Else
End If
MsgBox ("The Process has been completed")
End Sub
It should stop until the the last row with data.

Data input on excel: form sheet to database sheet

I'm using a dynamic button on one sheet to send data to a database/summary sheet within the same workbook in Excel.
I know I know. Should've done this using Access and queries, but I used the trusty brute-force method in the code shown below.
It works, but it's painstakingly slow. Please advise on how to perform this task with less demand on the processor.
'enter into database
NextSPGP.Value = Range("B7").Value
NextDate.Value = Format(Range("M7").Value, "mm/dd/yyyy")
NextStart.Value = Format(Range("A12").Value, "hh:mm")
NextFinish.Value = Format(Range("B12").Value, "hh:mm")
NextMix = Range("C12").Text
NextBatch.Value = Range("D12").Value
NextGrouter.Value = Range("J7").Value
NextPump.Value = Range("H7").Value
NextPass.Value = Range("F7").Value
NextDepth.Value = Range("E12").Value
NextSleeve.Value = Range("F12").Value
NextInitPress.Value = Range("G12").Value
NextFinalPress.Value = Range("H12").Value
NextFlow.Value = Range("I12").Value
NextVol.Value = Range("J12").Value
NextMove.Value = Range("K12").Value
NextComment.Value = Range("L12").Value
Instead of this
NextSPGP.Value = Range("B7").Value
NextDate.Value = Format(Range("M7").Value, "mm/dd/yyyy")
NextStart.Value = Format(Range("A12").Value, "hh:mm")
NextFinish.Value = Format(Range("B12").Value, "hh:mm")
NextMix = Range("C12").Text
NextBatch.Value = Range("D12").Value
NextGrouter.Value = Range("J7").Value
'etc...
You can do something like:
Dim Arr
Arr = Array(Range("B7").Value, Format(Range("M7").Value, "mm/dd/yyyy"), _
Format(Range("A12").Value, "hh:mm"),Format(Range("B12").Value, "hh:mm"), _
Range("C12").Text, Range("D12").Value, Range("J7").Value)
NextSPGP.Resize(1,UBound(Arr)+1).Value = Arr 'populate row in one shot
Tim Williams' suggestion with some minor editing. The database row had two cells needing data manipulation of a time (date) variable and pressure differential of initial to final pressures on our grout pumping operation. Success below:
MsgBox "Starting data input..."
enter log data into database
Dim NewData
Dim Duration As Date
Dim PressureDiff As Long
Duration = Format((Range("B12") - Range("A12")), "hh:mm")
PressureDiff = (Range("H12") - Range("G12"))
NewData = Array(Range("B7").Value, Format(Range("M7").Value, "mm/dd/yyyy"), Format(Range("A12").Value, "hh:mm"), _
Format(Range("B12").Value, "hh:mm"), Duration, Range("C12").Text, Range("D12").Value, Range("J7").Value, _
Range("H7").Value, Range("F7").Value, Range("E12").Value, Range("F12").Value, Range("G12").Value, _
Range("H12").Value, PressureDiff, Range("I12").Value, Range("J12").Value, Range("K12").Value, Range("L12").Value)
NextSPGP.Resize(1, UBound(NewData) + 1).Value = NewData
MsgBox "Data input complete!"
I usually start of my routines with something like this:
Dim bScreenUpdating As Boolean 'Screen Updating Flag
Dim bEnableEvents As Boolean
Dim lCalculation As Long
With Application
bScreenUpdating = .ScreenUpdating
bEnableEvents = .EnableEvents
lCalculation = .Calculation
.ScreenUpdating = False
.EnableEvents = False
.Calculation = xlCalculationManual
End With
...and end them with something like this:
With Application
.ScreenUpdating = bScreenUpdating
.EnableEvents = bEnableEvents
.Calculation = lCalculation
End With
...so that you turn things off that might slow progress, and restore the users environment afterwards.
Also, unless you have a reason to use .value, use .value2. See Excel MVP and Recalculation Guru Charles Williams' respone to the following thread:
What is the difference between .text, .value, and .value2?
Note that defining ranges in VBA code like you are doing is a recipe for disaster. As soon as someone inserts/deletes a row/column, all your VBA references are pointing at the wrong cells. I always give each of my ranges of interest a named range, and then reference that name in the VBA.
Also, you haven't given us much information about your workbook setup. How many values are we talking about here? Hundreds? Thousands? And is anything referencing the destination cells in the destination sheet? What else can you tell us about this workbook? i.e. is it a really big file with lots of formulas? What kinds of formulas? VLOOKUPS? OFFSET or other volatile functions? Are you writing these values into an Excel Table aka ListObject? These can really slow things down - particularly if you don't temporarily turn off calculation.

Resources