Second half VBA macro runs 3x faster if i don't run the first part before - excel

I have two macros:
Macro 1: copy-paste a large worksheet into the same workbook (basically myWorksheet.copy after:=lastWorksheet)
Macro 2: Iterate through data in new worksheet to unmerge any merged cells. Then set the value of all cells in each MergeArea to the value of the original merged cell.
Both macros run as they should.
The problem is the runtime. If I run Macro 1 and Macro 2 in the same 'session' (the way it has to be) Macro 2 has a runtime of roughly 250s.
If I run Macro 1, then restart Excel and run Macro 2, Macro 2 takes only 1/3 of the time (~70s).
I tried clearing the clipboard after running Macro 1 but it does nothing. Otherwise I'm out of ideas.
Macro 1:
Sub copySheet()
Dim ws As Worksheet
Application.DisplayAlerts = False
Set ws = ThisWorkbook.Worksheets("BigData")
ws.Copy After:=ThisWorkbook.Worksheets("BigData")
Application.DisplayAlerts = True
End Sub
Macro 2:
Sub unmergeCells()
Dim ws As Worksheet
Dim szArr() As Integer
Dim i%, k%, j%, col%
Dim strt&
'First row
Const fr% = 9
'First column
Const fc% = 12
'Last column
Const lc% = 135
'Last row
Const lr% = 1196
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Set ws = ThisWorkbook.Worksheets("BigData (2)")
strt = VBA.Timer
With ws
For col = fc To lc
'Some columns only have to be unmerged without changing any values
If IsNumeric(.Cells(fr - 1, col)) Then
.Range(.Cells(fr, col), .Cells(lr, col)).UnMerge
GoTo Skip_Ahead
End If
i = fr
k = 0
Do While i <= lr
ReDim Preserve szArr(k)
szArr(k) = i
i = i + .Cells(i, col).MergeArea.Cells.Count
k = k + 1
Loop
.Range(.Cells(fr, col), .Cells(lr, col)).UnMerge
For i = 0 To UBound(szArr) - 1
For j = szArr(i) + 1 To szArr(i + 1) - 1
.Cells(j, col) = "'" & .Cells(szArr(i), col)
Next j
Next i
Skip_Ahead:
Debug.Print col & ": " & Timer - strt
Next col
End With
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

Related

Match, Copy, Paste and clear takes a long time. How to speed up?

I am using below code in one workbook as the following:
(1) Match a range on SheetA against a range on SheetB.
(2) If the data found on SheetB, then some values will be inserted on SheetB and Sheet Log.
(3) The matched data (rows) on SheetB will be copied to Sheet Result and Autofit.
(4) The matched data (rows) on SheetB will be cleared. (cut & paste is not applicable).
The count of values on the first range in SheetA is normally 7 or 8 and this macro was as fast as it takes 2 seconds to finish all that steps.
I tried to put 146 values on the first range, but the macro turned to be very slow and it took 35 seconds to finish.
Please, how to speed up and optimize this macro?
Note: there is no problem at all to change match code or copy, paste, autofit and clear code.
Link for the full macro and sheet on the first comment.
Sub Match_Copy()
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
End With
'----------------------------- Match, Code
Dim Cell As Object, Match_A As Variant
For Each Cell In WorkOrder
Match_A = Application.Match(Cell.value, Auto_Data, 0)
If Not IsError(Match_A) Then
Cell.Offset(, 6).Resize(1, 3).value = Array("Close", Now, ws.name) 'Put Data of Close in every Area
If ws.name = "SheetB" Then 'Put Data of Close in Log Sheet
Sheets("Log").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Resize(1, 3).value = _
Array(Application.UserName, Now, Cell)
End If
End If
Next Cell
'----------------------------- Copy, Paste, AutoFit and Clear Code
Dim StatusColumn As Range
Set StatusColumn = ws.Range("G2", ws.Cells(Rows.Count, "G").End(xlUp))
For Each Cell In StatusColumn
If Cell.value = "Close" Then
Cell.EntireRow.Copy
Dim DestRng As Range
Set DestRng = Sheets("Result").Cells(Rows.Count, "A").End(xlUp).Offset(1)
DestRng.PasteSpecial xlPasteValues
DestRng.Rows.AutoFit
If DestRng.Rows.RowHeight < 45 Then DestRng.Rows.RowHeight = 45
End If
Next Cell
For Each Cell In StatusColumn
If Cell.value = "Close" Then
Cell.EntireRow.Clear
End If
Next Cell
'-----------------------------
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.EnableEvents = True
End With
End sub
Please, check the next adapted code. It uses arrays for faster iteration and for faster results return. Also, setting the row height for each cell consumes Excel resources. I commented some rows but no time now for everything. If something unclear, please do not hesitate to ask for clarifications:
Sub Run_Close()
Dim dStart As Double: dStart = Timer
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
End With
'------------------
Dim lastR As Long: lastR = Sheets("SheetA").Cells(Rows.Count, "A").End(xlUp).Row
Dim Auto_Data As Range: Set Auto_Data = Sheets("SheetA").Range("A2:A" & lastR)
Dim Count_Auto_Data As Long: Count_Auto_Data = WorksheetFunction.CountA(Auto_Data)
If Count_Auto_Data = 0 Then Exit Sub
With Auto_Data
.NumberFormat = "General"
.Value = .Value
End With
'------------------
Sheets("Result").AutoFilter.ShowAllData
Dim ws As Worksheet, arrWsFin, arrLog, k As Long
For Each ws In Sheets(Array("SheetB")) 'There are another 3 Sheets
ws.AutoFilter.ShowAllData
Dim LastRow As Long: LastRow = ws.Cells(Rows.Count, "A").End(xlUp).Row
Dim WorkOrder As Range: Set WorkOrder = ws.Range("A3:A" & LastRow)
Dim arrWO: arrWO = WorkOrder.Value2 'place the range in an array for faster iteration
ReDim arrWsFin(1 To LastRow, 1 To 3) 'redim array to keep the modifications in ws sheet
ReDim arrLog(1 To 3, 1 To LastRow): k = 1 'redim array to keep maximum modif of ws sheet
'----------------------------- Match, Code
Dim Cell As Object, Match_A As Variant, i As Long
For i = 1 To UBound(arrWO)
Match_A = Application.Match(arrWO(i, 1), Auto_Data, 0)
If Not IsError(Match_A) Then
arrWsFin(i, 1) = "Close": arrWsFin(i, 2) = Now: arrWsFin(i, 3) = ws.name
If ws.name = "SheetB" Then 'Put Data of Close in the array for further return at once
arrLog(1, k) = Application.UserName: arrLog(2, k) = Now: arrLog(3, k) = arrWO(i, 1): k = k + 1
End If
End If
Next i
ws.Range("G2").Resize(UBound(arrWsFin), UBound(arrWsFin, 2)).Value = arrWsFin
If k > 1 Then
ReDim Preserve arrLog(1 To 3, 1 To k - 1)
Sheets("Log").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Resize(UBound(arrLog, 2), UBound(arrLog)).Value = Application.Transpose(arrLog)
End If
'----------------------------- Copy, Paste and AutoFit, Code
Dim StatusColumn As Range, totRng As Range, lastCol As Long, arrSt, arrResult, arrRow, j As Long
lastR = ws.Cells(Rows.Count, "G").End(xlUp).Row
Set StatusColumn = ws.Range("G2", ws.Cells(Rows.Count, "G").End(xlUp))
arrSt = StatusColumn.Value2 'place the range in an array for faster iteration
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Set totRng = ws.Range("A2", ws.Cells(lastR, lastCol)) 'total range to extract the row slice
Dim rngClearCont As Range
ReDim arrResult(1 To lastCol, 1 To lastR): k = 1
For i = 1 To UBound(arrSt)
If arrSt(i, 1) = "Close" Then
arrRow = totRng.Rows(i).Value
'load arrResult array:
For j = 1 To lastCol
arrResult(j, k) = arrRow(1, j)
Next
k = k + 1
If rngClearCont Is Nothing Then
Set rngClearCont = StatusColumn.Cells(i) 'set the range necessary to clear rows at the end
Else
Set rngClearCont = Union(rngClearCont, StatusColumn.Cells(i))
End If
End If
Next i
If k > 1 Then
ReDim Preserve arrResult(1 To lastCol, 1 To k - 1)
With Sheets("Result").Cells(Rows.Count, "A").End(xlUp).Offset(1).Resize(UBound(arrResult, 2), _
UBound(arrResult))
.Value = Application.Transpose(arrResult)
.Rows.RowHeight = 45
End With
rngClearCont.EntireRow.ClearContents
End If
'-----------------------------
Next ws
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.EnableEvents = True
End With
MsgBox "Time taken: " & Format(Timer - dStart, "0.00s")
End Sub
It should take less than a second...
The root of your issue is that you are making many edits to the worksheet. One of the first ways to speed up VBA code is to reduce the number of times you write data to the sheet.
Rather than writing your data to the sheet every time in a For Each loop, add all of your data to an Array and then write that entire Array to the sheet(s) at once. This way, you don't have to write multiple times for every For Each loop, but only once.
I cannot guarantee that this is the only reason your code is "sub-optimal" but it's a good place to start to improve performance times.
While writing to the sheet does take time, the main problem here is the copy/paste part.
If you, after the row
Cell.Offset(, 6).Resize(1, 3).value = Array("Close", Now, ws.name) 'Put Data of Close in every Area
Put something like:
Sheets("Result").Cells(Rows.Count, "A").End(xlUp).Offset(1).Resize(, 9).value = Array(Cell, , , , , , "Close", Now, ws.name)
And then remove the copy/paste part completely, you should be able to run it almost instantly.

Loop input values in another sheet through cell reference then Copy formula value and paste it to another sheet using vba

[
I am working on two sheets
Sheets("UPDATER") = where input data is placed (more than 700 rows) and where data needs to be copied
Sheets("Historical_vol) = where i have kept the formulas (C9:N9) and input the records from updater sheet in cell A9 of historical_vol sheet.
The aim is to get the values of each of Underlyings pasted in sheets("updater").columns("AB") but the values get calculated from sheets("Historical_vol").column(C9:F9) then paste these values in sheet("updater")on each row starts from cell AD4.
Sub historical_vol()
Dim i As Long
Dim a As Worksheet
Dim b As Worksheet
lr = Worksheets("UPDATER").Cells(Rows.count, "AB").End(xlUp).Row
Set a = Worksheets("UPDATER")
Set b = Worksheets("Historical_vol")
For i = 4 To lr
b.Range("A9").value = a.Range("AB4" & i).value
'b.Range("C9":F9").Calculate
Application.Wait (Now + TimeValue("00:00:02"))
b.Range("C9:F9").Copy
NextRow = Cells(Rows.count, "AD").End(xlUp).Row + 1
Cells(NextRow, "AD").Select
ActiveSheet.Paste
Next i
End Sub
At first I notice the line b.Range("A9").value = a.Range("AB4" & i).value should be b.Range("A9").value = a.Range("AB" & i).value
As I see you are using Application.Wait, I assume it is for the calculation. So, I suggest using xlCalculationManual calculation and resetting it to auto once done as below.
Sub historical_vol()
Application.ScreenUpdating = True
'This will help to watch the status bar update
Application.Calculation = xlCalculationManual
Dim wb As Workbook, uPd As Worksheet, hV As Worksheet
Dim lr As Long, cl As Range
Set wb = ThisWorkbook
Set uPd = wb.Sheets("UPDATER")
Set hV = wb.Sheets("Historical_vol")
lr = uPd.Cells(Rows.Count, "AB").End(xlUp).Row
i = 0
For Each cl In uPd.Range("AB4:AB" & lr)
hV.Range("A9").Value = cl.Value
hV.Calculate
cl.Offset(0, 2).Resize(1, 4).Value = hV.Range("C9:F9").Value
i = i + 1
Application.StatusBar = i '& " / " & lr - 3
'View on status bar number of records completed out of total records (lr-3)
Next
Application.Calculation = xlCalculationAutomatic
End Sub

wrap text of a sheet with merged and not merged cells

I have a sheet with some cells are merged in rows, and some are not. I want to wrap all the cells and if rows contains merged cells, set the rows height to max of all cells height
In the excel file, you can find the sheet I am working with, what I want to have, the excel macro I wrote, what I get with that macro. I also put them here.
This is what I have: (column D is a hidden column)
This is what I want to have: (for the rest of the sheet see attached excel file)
I wrote an excel VBA macro to do the job, but there is no luck.
Sub MergeCells2()
Application.DisplayAlerts = False
Dim allRange As Range
Dim xCell As Range
On Error Resume Next
Dim i_row As Integer
Dim nRowsToMerge As Integer
Dim rangeToMerge As Range
Worksheets("What I have").Activate
LastCol = ActiveSheet.Range("a1").End(xlToRight).Column
LastRow = ActiveSheet.Cells(ActiveSheet.Rows.Count, LastCol).End(xlUp).Row
Set allRange = Application.Range("a1", ActiveSheet.Cells(LastRow, LastCol))
allRange.WrapText = True
If allRange Is Nothing Then Exit Sub
nRowsToMerge = 1
Set heightToSet = Range("A2").RowHeight
For i_row = 2 To LastRow
Set i_rowRange = allRange.Rows(i_row - 1)
If (allRange.Cells(i_row, 1) = "") Then
nRowsToMerge = nRowsToMerge + 1
ElseIf nRowsToMerge = 1 Then
heightToSet = i_rowRange.RowHeight
Else
Set rangeToMerge = ActiveSheet.Range(ActiveSheet.Cells(i_row - nRowsToMerge, 1), ActiveSheet.Cells(i_row - 1, LastCol))
For Each xCell In rangeToMerge
cellrow = xCell.Row
If (rangeToMerge.Cells(cellrow, 1) = "") Then
If xCell.Value = "" Then
Range(xCell, xCell.Offset(-1, 0)).Merge
End If
End If
Next
rangeToMerge.RowHeight = heightToSet
heightToSet = i_rowRange.RowHeight
nRowsToMerge = 1
End If
Next i_row
End Sub
This is what I get:
I don't know what is wrong with it and I have to say that I don't know much about VBA programming.
I hope I was clear with my question.
Please help, I am working on this for days now :(
Cheers,
Eda
The idea:
Start by wrapping all cells, and using AutoFit for all rows. This way Excel will automatically set the row height properly.
Loop through the rows merging the cells and dividing the height of the row with the wrapped text over the rows to be merged.
This is how:
Sub NewMerger()
Dim r As Long, rMax As Long, re As Long, cMax As Long, c As Long, n As Long, h As Single, mr As Long
Application.DisplayAlerts = False
'Create a copy of the input
Sheets("What I have").Copy After:=Sheets(Sheets.Count)
On Error Resume Next
Sheets("New Result").Delete
ActiveSheet.Name = "New Result"
'merge and use autofit to get the ideal row height
Cells().WrapText = True
Rows.AutoFit
'get max row and column
cMax = Cells(1, 1).End(xlToRight).Column
rMax = Cells(Rows.Count, 1).End(xlUp).Row
'loop through rows, bottom to top
For r = rMax To 2 Step -1
If Cells(r, 1).Value = "" Then
If re = 0 Then re = r 'If we don't have an end row, we do now!
ElseIf re > 0 Then 'If re has an end row and the current row is not empty (AKA start row)
h = Rows(r).RowHeight 'Get the row height of the start row
n = re - r + 1 'calculate the number of rows
If n > 0 Then Rows(r & ":" & re).RowHeight = h / n 'devide the row hight over all rows
For c = 1 To cMax 'And merge
For mr = re To r Step -1 'Merge only empty cells
If Cells(mr, c).Value = "" Then
Range(Cells(mr, c), Cells(mr - 1, c)).MergeCells = True
End If
Next
Next
re = 0 'We don't have an end row now
End If
Next
Application.DisplayAlerts = True
End Sub

Excel VBA macro works randomly

i need some advice regarding this macro.
This macro cuts and copies from "LATURAP" sheet, rows if specific conditions are met. exmpl. starts with number 170889 and so on.
Problem is that, when i run this macro, it will only works once when i have imported this to excel.
Can somebody explain what i'm missing here?
Sub Laturap()
Dim i As Integer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.EnableEvents = False
a = Worksheets("LATURAP").Cells(Rows.Count, "A").End(xlUp).Row
For i = 3 To a
'selection from LATURAP to 1708
If Left(Range("A" & i), 6) = 170889
Then
Worksheets("LATURAP").Range("A:J").Rows(i).Cut
Worksheets("1708").Activate
b = Worksheets("1708").Cells(Rows.Count, "A").End(xlUp).Row
Worksheets("1708").Cells(b + 1, 1).Select
Worksheets("1708").Paste
Worksheets("LATURAP").Activate
.........
You could try this(comments added in code)...
Sub Laturap()
Dim ws1 As Worksheet, ws2 As Worksheet, i As Long
Set ws1 = ThisWorkbook.Sheets("LATURAP")
Set ws2 = ThisWorkbook.Sheets("1708")
x = 1
With ws1 'wrap your code in the worksheet variable
For i = 3 To .Cells(.Rows.Count, 1).End(xlUp).Row 'you can assign the last row as a variable and use it, this cuts down the lines of code
If Left(.Range("A" & i), 6) = 170889 Then 'check the first 6 characters in each cell in Col A for the value
With .Range("A" & i).Resize(, 10) 'if a match select the range in the row from Col A to Col J using resize.
.Copy Destination:=ws2.Cells(Rows.Count, 1).End(xlUp).Offset(x, 1) 'copy the range pan paste to the first cell in ColB in ws2
.Clear 'clear the range in ws1
x = x + 1 'increases 1 to paste to the next empty row, must be within the If statement
End With
End If
Next i
End With
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.DisplayStatusBar = True
Application.EnableEvents = True
End Sub

Delete rows on two different sheets based on cell value in a more efficient way [VBA Excel]

I have a two different worksheets with the same number of rows each one. In column R I have "New" or "Old" depending on the row (this is a dynamic value). What I want to do is, if a row in Worksheet1 contains "Old" in column R, then delete that row in both Worksheet1 and Worksheet2.
Now, I have tried two codes for this:
Dim w1 As Worksheet
Dim w2 As Worksheet
Set w1= Worksheets("Sheet1")
Set w2= Worksheets("Sheet2")
'-----------------------------------------------------
'Code 1
'-----------------------------------------------------
Application.ScreenUpdating = False
For r = w1.UsedRange.Rows.Count To 1 Step -1
If Cells(r, "R") = "Old" Then
w1.Rows(r).EntireRow.Delete
w2.Rows(r).EntireRow.Delete
End If
Next r
Application.ScreenUpdating = True
'-----------------------------------------------------
'Code 2
'-----------------------------------------------------
Dim i As Long
i = 1
Application.ScreenUpdating = False
Do While i <= w1.Range("R1").CurrentRegion.Rows.Count
If InStr(1, w1.Cells(i, 18).Text, "Old", vbTextCompare) > 0 Then
w1.Cells(i, 1).EntireRow.Delete
w2.Cells(i, 1).EntireRow.Delete
Else
i = i + 1
End If
Loop
Application.ScreenUpdating = True
Usually I have +800 rows, so Code 1 works as desired but it sometimes takes too long, like 3 minutes. Code 2 gets stuck so far.
What is an efficient way of doing this?
Delete Rows In Sheets
Implementing Union should considerably speed up the process.
The Code
Sub DeleteRowsInSheets()
Const cSheet1 As Variant = "Sheet1" ' First Worksheet Name/Index
Const cSheet2 As Variant = "Sheet2" ' First Worksheet Name/Index
Const cVntCol As Variant = "R" ' Search Column Letter/Number
Const cStrCriteria As String = "Old" ' Search Criteria String
Dim rngU1 As Range ' Union Range 1
Dim rngU2 As Range ' Union Range 2
Dim LastUR As Long ' Last Used Row
Dim i As Long ' Row Counter
With Worksheets(cSheet1)
' Calculate Last Used Row.
If .Cells.Find("*", .Cells(.Rows.Count, .Columns.Count), -4123, , 1) _
Is Nothing Then Exit Sub
LastUR = .Cells.Find("*", , , , , 2).Row
' Add found cells to Union Ranges.
For i = 1 To LastUR
If StrComp(.Cells(i, cVntCol), cStrCriteria, vbTextCompare) = 0 Then
If Not rngU1 Is Nothing Then
Set rngU1 = Union(rngU1, .Cells(i, 1))
Set rngU2 = Union(rngU2, Worksheets(cSheet2).Cells(i, 1))
Else
Set rngU1 = .Cells(i, 1)
Set rngU2 = Worksheets(cSheet2).Cells(i, 1)
End If
End If
Next
End With
' Delete rows.
If Not rngU1 Is Nothing Then
rngU1.EntireRow.Delete ' Hidden = True
rngU2.EntireRow.Delete ' Hidden = True
Set rngU2 = Nothing
Set rngU1 = Nothing
End If
End Sub
I think that there could be lots of formulas. So Application.Calculation = xlManual at the begining and Application.Calculation = xlCalculationAutomatic at the end should be good idea too.
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
For r = w1.UsedRange.Rows.Count To 1 Step -1
If Cells(r, "R") = "Old" Then
w1.Rows(r).EntireRow.Delete
w2.Rows(r).EntireRow.Delete
End If
Next r
Application.ScreenUpdating = true
Application.Calculation = xlCalculationAutomatic

Resources