How to improve the speed of VBA macro code? - vba

I do not have much experience with writing macros, and therefore need the help of this community for the following issue encountered:
My macro copies a range of values entered in a vertical range in one worksheet and then pastes the values horizontally (transpose) in another worksheet. It would in theory paste the values from the first sheet to first row of the second worksheet which does not have content. Since the first five rows have contents, it thus pastes the values to the sixth row.
The problem I have with the running of the macro is that I feel like it is too slow and I would therefore like it to run faster.
I have the same macro doing the same thing but that instead pastes the values to another worksheet to the first row, and it runs perfect.
My best guess is therefore that the second macro is running slow because it has to start pasting on the sixth row and there may be some contents on the first 5 rows that take a lot of time for the macro to go through (there a lot of cell references to other workbooks) to determine where the next row for pasting should be. That is my best guess though and since I hardly know anything about macros, I cannot say for sure what the problem is.
I hereby provide you with the code of my macro and sincerely hope that somebody can tell me what is making my macro slow and provide me with a solution as to how to make it run faster. I am thinking that a solution might potentially be that the macro should not consider the first five rows of data and start pasting immediately on row 6 for the first entry. Then on row 7 the next time, and etc. This might be a solution but I do not know how to write the code in a way that it would do that.
Thank you for taking time and helping me to find a solution, here is the code:
Sub Macro1()
Application.ScreenUpdating = False
Dim historyWks As Worksheet
Dim inputWks As Worksheet
Dim nextRow As Long
Dim oCol As Long
Dim myCopy As Range
Dim myTest As Range
Dim lRsp As Long
Set inputWks = wksPartsDataEntry
Set historyWks = Sheet11
'cells to copy from Input sheet - some contain formulas
Set myCopy = inputWks.Range("OrderEntry2")
With historyWks
nextRow = .Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0).Row
End With
With inputWks
Set myTest = myCopy.Offset(0, 2)
If Application.Count(myTest) > 0 Then
MsgBox "Please fill in all the cells!"
Exit Sub
End If
End With
With historyWks
With .Cells(nextRow, "A")
.Value = Now
.NumberFormat = "mm/dd/yyyy hh:mm:ss"
End With
.Cells(nextRow, "B").Value = Application.UserName
oCol = 3
myCopy.Copy
.Cells(nextRow, 3).PasteSpecial Paste:=xlPasteValues, Transpose:=True
Application.CutCopyMode = False
End With
'clear input cells that contain constants
With inputWks
On Error Resume Next
With myCopy.Cells.SpecialCells(xlCellTypeConstants)
.ClearContents
Application.GoTo .Cells(1) ', Scroll:=True
End With
On Error GoTo 0
End With
Application.ScreenUpdating = True
End Sub

Just reiterating what has already been said:
Option Explicit
Sub Macro1()
'turn off as much background processes as possible
With Excel.Application
.ScreenUpdating = False
.Calculation = Excel.xlCalculationManual
.EnableEvents = False
End With
Dim historyWks As Excel.Worksheet
Dim inputWks As Excel.Worksheet
Dim nextRow As Long
Dim oCol As Long
Dim myCopy As Excel.Range
Dim myTest As Excel.Range
Dim lRsp As Long
Set inputWks = wksPartsDataEntry
Set historyWks = Sheet11
'cells to copy from Input sheet - some contain formulas
Set myCopy = inputWks.Range("OrderEntry2")
With historyWks
nextRow = .Cells(.Rows.Count, 1).End(Excel.xlUp).Offset(1, 0).Row
End With
With inputWks
Set myTest = myCopy.Offset(0, 2)
If Excel.Application.Count(myTest) > 0 Then
MsgBox "Please fill in all the cells!"
GoTo QuickExit
End If
End With
With historyWks
With .Cells(nextRow, 1)
.Value = Now
.NumberFormat = "mm/dd/yyyy hh:mm:ss"
End With
.Cells(nextRow, 2).Value = Excel.Application.UserName
oCol = 3
myCopy.Copy
.Cells(nextRow, 3).PasteSpecial Paste:=Excel.xlPasteValues, Transpose:=True
Excel.Application.CutCopyMode = False
End With
'clear input cells that contain constants
With inputWks
On Error Resume Next
With myCopy.Cells.SpecialCells(Excel.xlCellTypeConstants)
.ClearContents
Excel.Application.Goto .Cells(1) ', Scroll:=True
End With
On Error GoTo 0
End With
Calculate
QuickExit
With Excel.Application
.ScreenUpdating = True
.Calculation = Excel.xlAutomatic
.EnableEvents = True
End With
End Sub
I'd step through the macro line-by-line to try to locate which line is slow.
Another alternative - although not sure if it'll speed things up - is to avoid the clipboard and lose the copy/paste so you'd apply a method like the following to move the data:
Option Explicit
Sub WithoutPastespecial()
'WORKING EXAMPLE
Dim firstRange As Range
Dim secondRange As Range
Set firstRange = ThisWorkbook.Worksheets("Cut Sheet").Range("S4:S2000")
With ThisWorkbook.Worksheets("Cutsheets")
Set secondRange = .Range("A" & .Rows.Count).End(Excel.xlUp).Offset(1)
End With
With firstRange
Set secondRange = secondRange.Resize(.Rows.Count, .Columns.Count)
End With
secondRange.Value = firstRange.Value
End Sub

Best way to improve performance based on my experience is to work on variables in code rather than accessing the spreadsheet every time you want to lookup a value.
Save any range you want to work with in a variable(variant) and then iterate through it as if it was the sheet.
dim maxRows as double
dim maxCols as integer.
dim data as variant
with someSheet
maxRows = .Cells(rows.count, 1).end(xlUp).row 'Max rows in sheet
maxCols = .Cells(1, columns.count).end(xlToLeft).column 'max columns in sheet
data = .Range(.Cells(1,1), .Cells(maxRows, maxCols)) 'copy range in a variable
end with
From here you can access the data variable as if it was the spreadsheet like - data(row, column) with MUCH MUCH faster read speed.

Please take a look at this article as well.
How to speed up calculation and improve performance...
By all means, Application.calculation= xlCalculationManual is usually the culprit. But we can notice that volatile Excel sheet functions can mostly kill your application on large scale of data processing and functional aspect.
Also, for your current code following post might not be directly relevant. I find it useful for tips on over-all Excel/VBA performance optimization.
75 Excel speeding up tips
PS: I don't have enough reputation to comment on your post. So added as an answer..

Just a few suggestions (would have posted as a comment but I guess I don't have the rep):
Try refering to cell addresses instead of named ranges (doubt this would be the cause but may be causing some hit to performance)
Do your workbook formulas contain links to other workbooks? Try testing the code on a file with broken links to see if it improves performance.
If neither of these are the issue, my guess is that if the formulas are overly complex, there is probably some processing overhead being added. Try the code on a file containing only values to see if there is any improved performance.

As suggested by a few others in the comments, you should definitely change Application.Calculation to xlCalculationManual and rememeber to set it back to xlcalculationAutomatic at the end. Also try setting Application.Screenupdating = False (and turning that back on again too). Also, bear in mind that .Copy is a very inefficient way to copy cell values - if you really just want the values, loop through the range setting .Value to the .Values in the old range. If you need all the formatting, you're probably stuck with .Copy.
When you turn off the calc/screen refresh flags, please remember to turn them back on in all circumstances (even when your program exits at a different point, or causes a runtime error). Otherwise all sorts of bad things will happen. :)

You can improve the speed by stopping calculation during changing cell value and after that you can enable it. please follow the link.
http://webtech-training.blogspot.in/2013/10/how-to-stop-heavy-formula-calculation.html

.Cells(nextRow, 3).PasteSpecial Paste:=xlPasteValues, Transpose:=True
Application.CutCopyMode = False
I wouldn't do that. Cut,copy & Paste operations are the costliest operations, in terms of processor utilization, in an operating system.
Instead, you could just assign the value from one cell / range to an another cell / range, as in
Cells(1,1) = Cells(1,2) or Range("A1") = Range("B1")
Hope you got my point..

Related

Copy paste Loop

I want to make J3:N3 = T2:X2, then I want to copy the cells formulas of T2:X2 and paste, jump one and paste and so on. The code runs fine, but does not apply my formulas in any of the cells. Any idea what I am doing wrong?
WsScenarios.Activate
Application.CutCopyMode = False
ActiveCell.FormulaR1C1 = "=R[1]C[-10]"
Range("T2").Select
Selection.AutoFill Destination:=Range("T2:X2"), Type:=xlFillDefault
Set RgnCopy = Range("T2:X2")
i = 4
Do While i <= LastRow
RgnCopy.Copy
Range(Cells(i, 20), Cells(i, 24)).PasteSpecial xlPasteFormulas
i = i + 2
Loop
Recording a macro using the Excel UI could be useful sometimes, but what matters more is to write the code in a way to get done what you want in a better way. Also you have to think about maintaning or developing the code more in future so I suggest to remove every element that is an indication of using the UI such as activating sheets, or getting inputs from the user through activecell, Select method etc.
As mentioned in one of the previous comments using Range("T2").FormulaR1C1 = "=R[1]C[-10]" would solve your problem, but if you consider two ranges as the origin and destination, then you can always do the job a lot faster and better with the least hassle to parametrise the code in a later time:
Sub CopyIt()
Dim rngOrigin As Range
Dim rngDestination As Range
Dim WS As Worksheet
Set WS = ActiveSheet
Set rngOrigin = WS.Range("T2:X2")
Set rngDestination = WS.Range("J3:N3")
rngDestination.NumberFormat = "General"
rngOrigin.Copy
rngDestination.PasteSpecial xlPasteFormulas
End Sub

Error Code 1004 When Copy/Pasting VBA Cant Solve

I have been looking at this for 5+ hours not being able to find a correct solution. This isn't my main deal just what I do in work to help out.
Basically I am copying from a sheet that has filtered rows to another sheet and placing it at the last row in column A to paste.
This worked perfectly find before I did some changes and now it's completely broken, any help be gratefully appreciated, here is the broken lump of spaghetti code....
Sheets("Working Sheet").Select
Selection.Copy
Sheets("Sent Items").Select
Dim LastRow As Long
With ActiveSheet
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
End With
Range("A" & LastRow).Select
ActiveSheet.Paste
Application.CutCopyMode = False
Columns("K:K").EntireColumn.AutoFit
Sheets("Sent Items").Select
It causes an error 1004 saying the size needs to be the same??? The paste causes the error. Any help is good have been looking for the answer.
you could refactor your code as follows:
Worksheets("Working Sheet").Select
Selection.SpecialCells(xlCellTypeVisible).Copy Destination:=Worksheets("Sent Items").Cells(Rows.Count, 1).End(xlUp).Offset(1)
or, if you are interested in pasting values only:
Dim area As Range
Worksheets("Working Sheet").Select
For Each area In Selection.SpecialCells(xlCellTypeVisible).Areas
Worksheets("Sent Items").Cells(Rows.Count, 1).End(xlUp).Offset(1).Resize(area.Rows.Count, area.Columns.Count).Value = area.Value
Next area
Since you are copying filtered rows, it's always a good practice to use the SpecialCells method.
See the refactored code below. Also, always best to avoid using select and work directly with objects.
Dim wsWorking as Worksheet
Set wsWorking = Sheets("Working Sheet")
With wsWorking
.Select
Selection.SpecialCells(xlCellTypeVisible).Copy
End With
Dim wsSent as Worksheet
Set wsSent = Sheets("Sent Items")
With wsSent
Dim LastRow As Long
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
.Range("A" & LastRow).PasteSpecial
.Columns("K:K").EntireColumn.AutoFit
.Range("A1").Select 'to make sure you end up on that sheet
End With

Excel Shift Data down after Insert

Hopefully someone can help me out here :(
In a sequence of workbooks (never a good idea :)), a user runs a macro which copies data from Workbook1 and inserts it using Insert Shift:=xlDown in Workbook2.
The problem is this: there is taller rows and a grouped textbox below the destination, and instead of shifting these down, the macro leaves the row size large and the textbox doesn't move.
I have set the textbox group to Move and size with cells and tried CopyOrigin:=xlFormatFromLeftOrAbove but it seems to make no difference.
Can somebody help please?
Thanks
EDIT
Here is the full code: (commented out original idea, added suggestion below)
Sub MakeQuote2()
Application.ScreenUpdating = False
Dim sourceRange As Range, loopRange As Range
Dim targetRange As Range
Dim FRow As Long
Dim m As Long
Dim p As Long
m = Sheets("Workbook1").Rows.Count
FRow = Sheets("Workbook1").Range("A" & m).End(xlUp).Row
Set sourceRange = ActiveSheet.Range("A9:E" & FRow)
Set targetRange = Workbooks.Open("C:\Users\j\Documents\Trial1.xltm").Sheets("Workbook2").Range("A4")
sourceRange.Copy
Sheets("Workbook2").Rows("4:4").EntireRow.Insert 'Select
'Selection.Insert 'Shift:=xlDown
p = FRow + 5
Sheets("Workbook2").Rows("4:" & p).Copy
Sheets("Workbook2").Rows("4:4").PasteSpecial xlPasteValues
Sheets("Workbook2").Range("A2").Select
Application.CutCopyMode = False
Workbooks("Copy.xlsm").Close SaveChanges:=False
Application.ScreenUpdating = True
End Sub
Thanks!
If you want below text boxes to move and size with the cells above then it does not suffice to use
.Insert Shift:=xlDown
Instead you need to use
.EntireRow.Insert
If you copy entire rows, paste will shift everything down. If your copy source has only several columns, the data shifts down but no rows format or objects shift with it. This is true in Excel, not only in VBA.
This code works for me (I changed some of the references to test it in my environment):
Set sourceRange = Sheets("Sheet2").Range("A9:E" & FRow).EntireRow '<-- Added EntireRow here.
Set targetRange = Sheets("Sheet1").Range("A4") '<-- This is never used.
sourceRange.Copy
Sheets("Sheet1").Rows("4:4").EntireRow.Insert
The only addition I made is to add EntireRow to the source range to copy. If you need only columns A:E I would suggest you insert blank rows according to FRow - 9, and then copy and paste A:E in the added rows.
Note that you are mixing up references Sheets("Workbook1"), ActiveSheet in your original code, and you never use targetRange.
Addition
As mentioned in the first note, to add blank rows before you paste only the relevant columns, you can use something like this code:
Sheets("Sheet1").Rows("4:" & FRow - 9 + 4).EntireRow.Insert
Set sourceRange = Sheets("Sheet2").Range("A9:E" & FRow)
Set targetRange = Sheets("Sheet1").Range("A4")
sourceRange.Copy
targetRange.PasteSpecial

VBA Transpose Dataset (Guidance) [duplicate]

This question already has answers here:
How to "flatten" or "collapse" a 2D Excel table into 1D?
(9 answers)
Closed 6 years ago.
Currently I have a data-set of 4000 rows with data arranged below:
The format it needs to be in is like this:
I have ignored the dates field or the X,Y,Z fields at the moment and just want to focus on the rows. I'm new to VBA still so please bear with my explanations.
My understanding of this is that I should use a variant to store the data as 1-dimensional arrays and then cycle through this via a for-loop.
This is what my code attempts to do (albeit clumsily):
Sub TransposeData()
Dim Last As Variant
Application.ScreenUpdating = False
prevCalcMode = Application.Calculation
Application.Calculation = xlCalculationManual
Last = Cells(Rows.Count, "L").End(xlUp).Row
'Go to the very bottom of row L and get the count
'For i = row Count - 1 from this and check what the value of L is
'If the value of L is greater than 0 Then ...
For i = Last To 1 Step -1
If (Cells(i, "L").Value) > 0 Then
range("D" & i & ":L" & i).Copy
Sheets("test").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Sheets("CVM").Select
End If
Next i
Application.Calculation = prevCalcMode
Application.ScreenUpdating = True
End Sub
However I am stuck at setting my 'range' variable as I don't know how to make it specific to each iteration. i.e. Range(i,L) This will not work obviously but I can't seem to think of another way around this.
Could you please point me in the right direction? I did look at a few other VBA questions regarding this but I couldn't apply the same methodology to my issue.
(Transpose a range in VBA)
Thank you!
EDIT: I now have my macro starting to work (yay!), but the loop keeps over-writing the data. Is there a way to check where the data was last pasted and make sure you paste in the next blank part of the column?
Seeing as you are new to VBA, as you said.
A few things:
Always use indexed based reference, like you used for range("D" & i & ":L" & i).Copy but then you did not use it for the PasteSpecial
Make sure you use referencing to the specific sheet you are wanting to operate out of, this way VBA doesnt need to assume anything
Try use descriptive variables this helps the next user really understand your code.
Also Use Option Explicit ALWAYS, I did no like it in the beginning but once I was used to typing correct variables for everything, like we should, its not an issue anymore. To have the Option Explicit on every module just go
Tool >> Options >> Require Variable Declaration
See answer below
Option Explicit
Sub TransposeData()
Application.ScreenUpdating = False
Dim PrevCalcMode As Variant
PrevCalcMode = Application.Calculation
Application.Calculation = xlCalculationManual
Dim DataSheet As Worksheet
Set DataSheet = ThisWorkbook.Sheets("CVM")
Dim DestinationSheet As Worksheet
Set DestinationSheet = ThisWorkbook.Sheets("test")
Dim DataSheetLastCell As Variant
With DataSheet
DataSheetLastCell = .Cells(.Rows.Count, "L").End(xlUp).Row
End With
Dim DataSheetRowRef As Long
Dim DestinationSheetNextFreeRow As Long
For DataSheetRowRef = 2 To DataSheetLastCell
If Not DataSheet.Cells(DataSheetRowRef, "L") = Empty Then
DataSheet.Range("D" & DataSheetRowRef & ":L" & DataSheetRowRef).Copy
With DestinationSheet
DestinationSheetNextFreeRow = .Cells(.Rows.Count, "B").End(xlUp).Row + 1
.Cells(DestinationSheetNextFreeRow, "B").PasteSpecial Transpose:=True
End With
End If
Next DataSheetRowRef
Application.ScreenUpdating = True
PrevCalcMode = Application.Calculation
End Sub

Copy multiple rows from one worksheet to another worksheet using macro/vba

I've looked around the forum and played with various options but not found a clear match for my problem:
My task is to copy data from a worksheet (called “workorders”) to a second worksheet (called “Assignments”). The data to be copied is from the “workorders” worksheet starting at cell range “E2, P2:S2”; and also copied from each row (same range) until column “P” is empty – (the number of rows to be copied can vary each time we need to run this macro so we can’t select a standard range) . Then pasted into the “Assignments” worksheet, starting at cell “A4”. I’ve used the forum so far to successfully copy a single row of date (from row 2) – I admit that’s the easy part, and I’ve used various versions of code to achieve this.
I’ve also tried some code (which I found via watching a youtube clip and modifying http://www.youtube.com/watch?v=PyNWL0DXXtQ )to allow me to run a loop which repeats the copy process for each required row in the “workorders” worksheet and then pastes the data into the “assignments” worksheet- but this is where I am not getting it right, I think I’m along the right lines and think I’m not far off but any help would be very useful.
Code examples below (first 2 only copy first row, 3rd example is where I’ve tried to loop and copy multiple rows:
Sub CopyTest1()
' CopyTest1 Macro
'copy data from workorders sheet
'Worksheets("workorders").Range("E2,P2,Q2,R2,S2").Copy
Worksheets("workorders").Range("E2, P2:S2").Copy
'paste data to assignments sheet
'sheets("assigments dc").Range("A4").Paste
Sheets("Assigments DC").Select
Range("A4").Select
ActiveSheet.Paste
Application.CutCopyMode = False
End Sub
Sub CopyTest2()
Sheets("workorders").Range("e2,p2,q2,r2,s2").Copy Sheets("assigments dc").Range("a4")
End Sub
Sub CopyTest3()
Dim xrow As Long
'Dim xrow As String
xrow = 2
Worksheets("workorders").Select
Dim lastrow As Long
lastrow = Cells(Rows.Count, 16).End(xlUp).Row
Do Until xrow = lastrow + 1
ActiveSheet.Cells(xrow, 16).Select
If ActiveCell.Text = Not Null Then
'Range("E2,P2,Q2,R2,S2").Copy
'Selection = Range("E2,P2,Q2,R2,S2").Copy
'Cells(xrow, 5).Copy
Cells(xrow, 5).Copy
Sheets("Assigments DC").Select
Range("A4").Select
ActiveSheet.Paste
Application.CutCopyMode = False
Sheets("workorders").Select
End If
xrow = xrow + 1
Loop
End Sub
Try this:
Sub LoopCopy()
Dim shWO As Worksheet, shAss As Worksheet
Dim WOLastRow As Long, Iter As Long
Dim RngToCopy As Range, RngToPaste As Range
With ThisWorkbook
Set shWO = .Sheets("Workorders") 'Modify as necessary.
Set shAss = .Sheets("Assignments") 'Modify as necessary.
End With
'Get the row index of the last populated row in column P.
'Change accordingly if you want to use another column as basis.
'Two versions of getting the last row are provided.
WOLastRow = shWO.Range("P2").End(xlDown).Row
'WOLastRow = shWO.Range("P" & Rows.Count).End(xlUp).Row
For Iter = 2 to WOLastRow
Set RngToPaste = shAss.Range("A" & (Iter + 2))
With shWO
Set RngToCopy = Union(.Range("E" & Iter), .Range("P" & Iter & ":S" & Iter))
RngToCopy.Copy RngToPaste
End With
Next Iter
End Sub
Read the comments first and test.
Let us know if this helps.
From what I see, you are only copying the cell in Column E. You could correct this by replacing Cells(xrow, 5).Copy with
Union(Sheets("workorders").Cells(xrow,5),Sheets("workorders").Range(Cells(xrow,"P"),Cells(xrow,"S")).Copy
However, using Select and Copy are not ideal. Instead, you can assign the value of the range directly:
Sheets("Assignments DC").Range("A4").Value = Union(Sheets("workorders").Cells(xrow,5),Sheets("workorders").Range(Cells(xrow,"P"),Cells(xrow,"S")).Value
More info on the Union method and why using Select is bad.
Is it even possible to run a line like this?
Worksheets("workorders").Range("E2, P2:S2").Copy
Each time I try different ways to copy/select a range which contains in my case, A3 and the range A34:C40 ("A3, A34:C40").Copy i get an error saying theres to many parameters.. Could this be because I'm running excel 2007?
Any tips or help would be greatly apreciated! :)