VBA and Excel optimization of script, dealing with 700,000 rows - vba

Hello StackOverflowers,
I am currently working on a script that has one nested IF statement in it. When run it could potentially compute around 1.4m IF's.
I have run a test with a timer (not too sure on the accuracy of the timer in VBA) and crunching 1000 rows gives me a time of 10 seconds. 10 * 700 = 7000 seconds, which = 1.94 hours.
Can anyone give me any tips for optimisation when dealing with such large data sets?
My code is as follows
Sub itS1Capped()
Dim Start, Finish, TotalTime
Start = Timer
Dim c, d, j, lastRow
c = 1
'find how many rows
With Worksheets("Data")
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
'loop through all rows
For Each d In Worksheets("Data").Range("D2:D" & lastRow).Cells 'd = IT S0 Uncapped
j = Worksheets("Data").Range("J" & c + 1).Value 'IT Cap
If j <> 0 Then
If d > j Then
Worksheets("Data").Range("K" & c + 1).Value = j 'IT S1 Capped = j
Else
Worksheets("Data").Range("K" & c + 1).Value = d 'IT S1 Capped = d
End If
Else
Worksheets("Data").Range("K" & c + 1).Value = d 'IT S1 Capped = d
End If
c = c + 1
Next
Finish = Timer
TotalTime = Finish - Start
MsgBox TotalTime
End Sub

So I took inspiration from Mark Moore's use of arrays and found that using an array function rather than copying and pasting a plain function across a range is much faster. On my machine, Mark's procedure runs in 2.2 seconds, and the one below in 1.4 seconds.
Sub FormulaArray()
Dim iUsedRows As Long, rCell As Range, StartTimer As Double, Duration As Double
StartTimer = Timer
iUsedRows = ActiveSheet.UsedRange.Cells(ActiveSheet.UsedRange.Rows.Count, 1).Row
With Range(Cells(1, 11), Cells(iUsedRows, 11))
.FormulaArray = "=IF(J:J<>0,IF(D:D>J:J,J:J,D:D),D:D)"
.Copy
.PasteSpecial xlPasteValues
End With
Duration = StartTimer - Timer
MsgBox Format(Duration, "#0.0000") & " seconds to run"
End Sub

I am a bit old school and so "arrays" are your friend :-) Have had similar problems when I first took over looking after some pretty complex spreadsheets at work that did large numbers of validations. When working with large volumes of data, moving between the workbook and the data on the worksheet is not recommended, because each action is effectively an I/O (Input/ output) operation and these are very time consuming. It is massively more efficient to read all your data into an array, work with the array data and then write it back to the sheet at the end, this is effectively 2 I/O's instead of the 700,000 if you read the sheet data each time. As a rough example, I reduced our previous validation time down from 25 minutes to 4 seconds using this approach.
Sub ValidateSheet()
Dim DataRange As String
Dim SheetArray As Variant
Dim StartCol As String
Dim EndCol As String
Dim StartRow As Long ' long to cope with over 32k records
Dim lastrow As Long
Dim WorksheetToRead As String
Dim ArrayLoopCounter As Long
Dim Start, Finish, TotalTime
Start = Timer
'I use variables for the data range simply to allow it to be changed easily. My real code is actually paramatised so a single reusable procedure
'is used to populate all my arrays
'find how many rows
WorksheetToRead = "Data"
StartCol = "A"
EndCol = "Z"
StartRow = 1
lastrow = Sheets(WorksheetToRead).Cells(Rows.Count, "A").End(xlUp).Row
'set the range to be read into the array
DataRange = StartCol & Trim(Str(StartRow)) & ":" & EndCol & Trim(Str(StartRow - 1 + lastrow))
SheetArray = Worksheets(WorksheetToRead).Range(DataRange).Value ' read all the values at once from the Excel grid, put into an array
'Loop around the data
For ArrayLoopCounter = LBound(SheetArray, 1) To UBound(SheetArray, 1)
If SheetArray(ArrayLoopCounter, 10) <> 0 Then '10 is column J
'Compare D with J
If SheetArray(ArrayLoopCounter, 4) > SheetArray(ArrayLoopCounter, 10) Then '10 is column J
SheetArray(ArrayLoopCounter, 11) = SheetArray(ArrayLoopCounter, 10) 'set col K = Col J
Else
SheetArray(ArrayLoopCounter, 11) = SheetArray(ArrayLoopCounter, 4) 'set col K = Col D
End If
Else
SheetArray(ArrayLoopCounter, 11) = SheetArray(ArrayLoopCounter, 4) 'set col K = Col D
End If
Next ArrayLoopCounter
'Write the updated array back to the sheet
Worksheets(WorksheetToRead).Range(DataRange) = SheetArray
Finish = Timer
TotalTime = Finish - Start
MsgBox TotalTime
End Sub

I cannot test this right now, but i believe if you write a function to replace your nested IF statements, add it to Range("K2") with
Range("K2").Formula = ...
then copy it down to Cells(lastrow, "K"), copy all the functions and paste as values it'll be much faster.
Of course using
Application.Calculation = xlCalculationManual
and
Application.Calculation = xlCalculationAutomatic
like enemy suggested, along with
Application.screenupdate = false
Might make it slightly faster, but I think the function-copy-paste will make the biggest difference.
I don't have time to post updated code at the moment, but hopefully I'll get to it tomorrow.
Hope that helps!
EDIT: Here's the revised code
WARNING: I haven't been able to test this code yet. I'll do so tomorrow and revise if needed.
Sub FunctionCopyPaste()
Dim iLastRow as Integer
With Worksheets("Data")
iLastRow = .UsedRange.Cells(.UsedRange.Rows.Count,1).Row
.Range("K2").Formula = "=IF(J2<>0,IF(D2>J2,J2,D2),D2)"
.Range("K2").Copy Range(Cells(2,4), Cells(iLastRow,1).Row,4))
End With
With Range(Cells(2,4), Cells(iLastRow,4))
.Copy
.PasteSpecial xlPasteValues
End With
End Sub

I'm not sure if it will make a difference, but since you are timing it, I'd be interested to know.
I modified your code slightly. The main change is For each D in worksheets. Otherwise, I used Cells(row,col) instead of Range. Not that I expect that change to save time, I just thought you might like to see another way of defining cells, instead of concatenating letters and numbers.
note: with cells, you can use all variables, and numbers, with no letters. I just used letters to show you the similarities.
also, Since you have a c + 1 in every row, why not also just start on row 2, leave out the multiple (+1s) and go from there?
UN-TESTED
Sub itS1Capped()
Dim Start, Finish, TotalTime 'What are you declaring these variables as?
Dim c, d, j, lastRow
Start = Timer
'find how many rows
lastRow = Sheets("Data").Cells(Rows.Count, "A").End(xlUp).row
'loop through all rows
For c = 2 To lastRow 'c = IT S0 Uncapped (OLD d)
j = Sheets("Data").Cells(c, "J").Value 'IT Cap = Cells(c, 10)
If j <> 0 Then
If c > j Then
Sheets("Data").Cells(c, "K").Value = j 'IT S1 Capped = j
Else
Sheets("Data").Cells(c, "K").Value = c 'IT S1 Capped = c
End If
Else
Sheets("Data").Cells(c, "K").Value = c 'IT S1 Capped = c
End If
Next c
Finish = Timer
TotalTime = Finish - Start
MsgBox TotalTime
End Sub
edit: replaced d with c

Have you tried turning off automatic recalculation before you run your script?
Application.Calculation = xlCalculationManual
And then turn it back on when you're done?
Application.Calculation = xlCalculationAutomatic
That usually speeds up processing of lots of rows assuming you're not changing something that needs recalculating before you work on the next (or subsequent) rows.

Related

For loop while copy and pasting specific columns

I need a loop that will match and select different columns (not in sequential order) and paste them to another sheet all whilst keeping the condition in check. It would also be ideal if when the values get pasted that the formatting for the cell is not carried over, just the value.
Below is the code I am currently using:
Sub Test()
Application.ScreenUpdating = False
Sheets("DATA").Select
lr = Range("B" & Rows.Count).End(xlUp).Row
Range("P3").Select
For i = 3 To lr
If Cells(i, 2) <> "" Then Range(Cells(i, 7), Cells(i, 16), Cells(i, 26)).Copy
Sheets("Sheet2").Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
Next
Application.ScreenUpdating = True
End Sub
The problem is declaring the columns I want the loop to paste. I need the loop to run through the 16th column, check empty values, and then paste the index/matched value in the rows of columns 7,16,and 26 (so not in sequential order).. Any help would be appreciated.
The next code has to do what I understood you need. Please check it and confirm this aspect. It is very fast, working only in memory...
Sub PastingNextPage()
Dim sh As Worksheet, sh1 As Worksheet, arrIn As Variant, arrOut() As Variant
Dim lastRowIn As Long, lastRowOut As Long, nonEmpt As Long, rngP As Range, nrEl As Long
Dim i As Long, j As Long, P As Long
Set sh = Sheets("DATA"): lastRowIn = sh.Range("P" & sh.Rows.count).End(xlUp).Row
Set sh1 = Sheets("Sheet2"): lastRowOut = sh1.Range("A" & sh1.Rows.count).End(xlUp).Row + 1
arrIn = sh.Range("G2:Z" & lastRowIn).Value
nrEl = lastRowIn - Application.WorksheetFunction.CountIf(sh.Range("P2:P" & lastRowIn), "") - 2
P = 10 'column P:P number in the range starting with G:G column
ReDim arrOut(nrEl, 3) 'redim the array to keep the collected values
For i = 1 To lastRowIn - 1
If arrIn(i, P) <> "" Then
arrOut(j, 0) = arrIn(i, 1): arrOut(j, 1) = arrIn(i, P): arrOut(j, 2) = arrIn(i, 20)
j = j + 1
End If
Next i
sh1.Range(sh1.Cells(lastRowOut, "A"), sh1.Cells(lastRowOut + nrEl, "C")).Value = arrOut
End Sub
It does not select anything, you can run it activating any of the two involved sheets. I would recommend to be in "Sheet2" and see the result. If you want to repeat the test, its result will be added after the previous testing resulted rows...
If something unclear or not doing what you need, do not hesitate to ask for clarifications.

How to copy every row except every nth

In excel I would like to copy the date from one sheet to another one using macro in a way that it will copy everything until row 9, then it will skip row 10 and copy row 11 and 12, and then skip one again.
So it should not copy row 10,13,16,19, etc..
I have the following code
Dim i As Integer
i = 9
J = 1
K = 9
Do While i < 5000
If J = 3 Then
J = 0
Sheets("sheet1").Select
Rows(i).Select
Selection.Copy
Sheets("sheet2").Select
Cells(K, 1).Select
ActiveSheet.Paste
K = K + 1
End If
J = J + 1
i = i + 1
Loop
This code is copying everything till the 8th row and then every 3rd, can somebody help me how to modify that code?
Fastest way will be to Copy >> Paste the entire rows once, according to your criteria.
You can achieve it by merging all rows that needs to be copies to a Range object, in my code it's CopyRng, and you do that by using Application.Union.
Code
Option Explicit
Sub CopyCertailRows()
Dim i As Long
Dim CopyRng As Range
Application.ScreenUpdating = False
With Sheets("sheet1")
' first add the first 8 rows to the copied range
Set CopyRng = .Rows("1:8")
For i = 9 To 5000
If (i / 3) - Int(i / 3) <> 0 Then ' don't add to copied range the rows that divide by 3 without a remainder
Set CopyRng = Application.Union(CopyRng, .Rows(i))
End If
Next i
End With
' copy >> paste in 1- line
CopyRng.Copy Destination:=Sheets("sheet2").Range("A9")
Application.ScreenUpdating = True
End Sub
You could simplify this massively by using If i < 10 Or (i - 1) Mod 3 <> 0 Then... which will select the rows you're interested in. Like so:
Dim i As Integer, j As Integer
j = 0
Dim sourceSht As Worksheet
Dim destSht As Worksheet
Set sourceSht = Sheets("Sheet1")
Set destSht = Sheets("Sheet2")
For i = 1 To 5000
If i < 10 Or (i - 1) Mod 3 <> 0 Then
j = j + 1
sourceSht.Rows(i).Copy destSht.Rows(j)
End If
Next
Personally, I'd turn screen updating and calculations off before running this and enable them again after to reduce the time needed to perform the loop.
Also, as Michał suggests, unless your dataset happens to be exactly 5,000 rows, you might want to 'find' the last row of data before starting to further reduce the time needed.
All necessary comments in code:
'declare all variables, be consistent with lower/uppercases, use Long instead of Integeer (its stored as long anyway)
'use meaningful variable names
Dim i As Long, copyUntil As Long, currentRow As Long
copyUntil = 9
currentRow = 1
'copy all rows until we reach 9th row
For i = 1 To copyUntil
Sheets("sheet1").Rows(i).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Next
'now we will takes steps by 3, on every loop we will copy i-th row and next one, third will be omitted
'we also use currentRow variable to avoid empty rows in sheet2
'also, 5000 seems wrong, I'd recommend to determine last row, until which we will loop
'last row is often determined like Cells(Rows.Count, 1).End(xlUp).Row
For i = copyUntil + 2 To 5000 Step 3
Sheets("sheet1").Rows(i).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Sheets("sheet1").Rows(i + 1).Copy
Sheets("sheet2").Rows(currentRow).Paste
currentRow = currentRow + 1
Next
This code will only paste values. Let me know if any questions or if you really, really need the formatting I can tweak it.
Sub DoCopy()
'This code is pretty much specifit to your request/question, it will copy 1-9, skip 10, 13, 16....
'i for the loop, x for the row that will not be added, y to paste on the second sheet
Dim i, x, y As Long, divn As Integer
For i = 1 To 5000
If i < 10 Then
y = y + 1
Sheets("Sheet1").Rows(i).Copy
Sheets("Sheet2").Range("A" & y).PasteSpecial ''Paste values only
ElseIf i >= 10 Then
x = i - 10
If x Mod 3 <> 0 Then
y = y + 1
Sheets("Sheet1").Rows(i).Copy
Sheets("Sheet2").Range("A" & y).PasteSpecial ''Paste values only
Else
'Do nothing
End If
End If
Next i
End Sub

For loop setting Font and Interior of Range taking way too long

I have a sheet with a lot of data (almost 14.000 rows and 13 columns).
I am running a For loop within this sheet but it takes sometimes over 2 minutes to complete it. Also the application is not responding during the For loop.
Is there a way I can re-write my loop so it will run a lot faster?
Here is my code:
For counter = 1 To Rows.Count
If Cells(counter, 13).Value > 500 Then
Cells(counter, 13).Interior.ColorIndex = 37
Cells(counter, 13).Font.Color = Black
Cells(counter, 13).Font.Bold = True
End If
count = count + 1
Application.StatusBar = count
Next counter
Thanks in advance :).
Avoid looping through a range. You can speed up your code by looping through an array and do formatting after it. Furthermore you could split your loop for the status bar count into portions.
Code
Option Explicit
Public Sub Greater500()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("MySheet")
Dim v As Variant
Dim i As Long, n As Long, m As Long, r As Long
Dim t As Double
' stop watch
t = timer
' get last row in column M
n = ws.Range("M" & ws.Rows.Count).End(xlUp).Row
' get values to one based 2dim array
v = ws.Range("M1:M" & n).value
' clear existing colors over the WHOLE column to minimize file size
ws.Range("M:M").Interior.ColorIndex = xlColorIndexNone
For i = 1 To n
' avoid troubles with formula errors, e.g. divisions :/ zero
If IsError(v(i, 1)) Then
' check condition (neglecting date, string and boolean data types)
ElseIf Val(v(i, 1)) > 500 Then
ws.Cells(i, 13).Interior.ColorIndex = 37
ws.Cells(i, 13).Font.Color = vbBlack
ws.Cells(i, 13).Font.Bold = True
End If
Next i
MsgBox "Time needed: " & Format(timer - t, "0.00") & " seconds."
End Sub
Rows.Count includes every row, not just the ones with data. (1,048,576 rows in Excel 2016). The status bar shouldn't slow it down too much.
Sub test()
Dim c As Range, count As Integer
Worksheets("Sheet1").Activate
ActiveSheet.UsedRange.Select
For Each c In Application.Selection.Cells
If Cells(c.Row, 13).Value > 500 Then
Cells(c.Row, 13).Interior.ColorIndex = 37
Cells(c.Row, 13).Font.Color = Black
Cells(c.Row, 13).Font.Bold = True
count = count + 1
End If
Application.StatusBar = count
Next c
End Sub
The reason your code slows down is it takes all the rows when you're writing Rows.Count.
Try to limit your range and update the format at once at the very end which should fix your problem.
Below code takes 50000 cells and completes in more or less 8 seconds on my machine.
I also tried for each loop with almost same times.
Sub test()
Dim counter As Long
Dim count As Long
Dim st As Double
Dim et As Double
Dim tottime As Double
Dim rangetoformat As Range
'remove timer
st = Timer
For counter = 1 To 50000
If Not rangetoformat Is Nothing Then
If Cells(counter, 13).Value > 500 Then
Set rangetoformat = Union(rangetoformat, Cells(counter, 13))
End If
Else
Set rangetoformat = Cells(counter, 13)
End If
count = count + 1
Application.StatusBar = count
Next counter
rangetoformat.Cells.Interior.ColorIndex = 37
rangetoformat.Cells.Font.Color = Black
rangetoformat.Cells.Font.Bold = True
'remove timer
et = Timer
totaltime = et - st
MsgBox totaltime
End Sub

Converting 'For' into a 'For each' loop

I have some VBA code that looks at the last new row for other instances of entries in Columns D and E of a row in the worksheet. When both of the column instances are found, the macro copies the data from Column F of the existing row to Column F of the new row.
However, the macro is restrictive as it ends after finding the first instance of this. I would like the macro to loop until all instances are found.
I figured the best way would be to convert the For loop into a For each loop but can't seem to make any code attempts work. Any pointers would be very helpful!
Sub test()
Dim N As Long
N = Cells(Rows.Count, "D").End(xlUp).Row
Dim i As Long
d = Cells(N, "D").Value
e = Cells(N, "E").Value
For i = N - 1 To 1 Step -1
dt = Cells(i, "D").Value
et = Cells(i, "E").Value
If d = dt And e = et Then
Cells(N, "F").Value = Cells(i, "F").Value
End If
Next i
End Sub
I see no reason to move to For Each in your case.
What you should do is read everything from your sheet into arrays at once, then loop through those arrays. It's much more efficient than looping through cells. Same goes for writing to sheet -- that's slow and inefficient. Just write the end result once, rather than repeatedly writing to the sheet.
Example:
Sub test()
Dim d, e, dt, et, ft, x
Dim i As Long
Dim N As Long
'Read everything from sheet into arrays
N = Cells(Rows.Count, "D").End(xlUp).Row
d = Cells(N, "D").Value
e = Cells(N, "E").Value
dt = Range("D1").Resize(N, 1).Value
et = Range("E1").Resize(N, 1).Value
ft = Range("F1").Resize(N, 1).Value
'Loop through arrays
For i = N - 1 To 1 Step -1
If d = dt(i, 1) And e = et(i, 1) Then
x = ft(i, 1)
End If
Next i
'Write result back to sheet
Cells(N, "F").Value = x
End Sub
Right, working from Jean-François Corbett's answer, which stores the contents in arrays before proceeding for efficiency, but adapting it to check for all duplicate rows in a progressive fashion, bottom-up. You get something like this:
Public Sub FillDuplicates()
Dim lastRow As Integer
Dim dColumn As Variant, eColumn As Variant, fColumn As Variant
Dim rowAltered() As Boolean
'Find the last row in Column D with content
lastRow = Cells(Rows.Count, "D").End(xlUp).Row
'Acquire data from columns: D, E & F in to arrays
dColumn = Range("D1").Resize(lastRow, 1).Value
eColumn = Range("E1").Resize(lastRow, 1).Value
fColumn = Range("F1").Resize(lastRow, 1).Value
ReDim rowAltered(1 To lastRow)
'Loop through all rows from bottom to top, using each D/E column value as a key
For cKeyRow = lastRow To 1 Step -1
'Ignore rows that have already been replaced
If Not rowAltered(cKeyRow) Then
'Loop through all rows above current key row looking for matches
For cSearchRow = cKeyRow To 1 Step -1
'If the row is a match and has not previously been changed, alter it
If Not rowAltered(cSearchRow) And dColumn(cKeyRow, 1) = dColumn(cSearchRow, 1) And eColumn(cKeyRow, 1) = eColumn(cSearchRow, 1) Then
fColumn(cSearchRow, 1) = fColumn(cKeyRow, 1)
rowAltered(cSearchRow) = True
End If
Next cSearchRow
End If
Next cKeyRow
'Store the amended F column back in the spreadsheet
Range("F1").Resize(lastRow, 1) = fColumn
End Sub
Note, all the work with rowAltered to determine rows that have been processed simply saves processing time. It would not be necessary, as the bottom-to-top action of the process would replace future key row values with lower duplicates as it went. Just it will do the replacements multiple times for each further duplicate up the page. The rowAltered check prevents this.
If you left the data in the spreadsheet, then you could use .Find() methods perhaps on the columns to locate duplicates, rather than the inner loop. But I doubt it would be more efficient.
I would say that
sequentially processing a list - especially with exit conditions - are better done with classical loops (Do/Loop, While, For/Next)
to use For Each ... In / Next you need to have a collection (like a range, list of sheets - anything ending on 's'), and keep in mind that it is not guaranteed that this list is processed top-down-left-right ... there is no predefined or chooseable sequence.
So according to the logic you describe I see no point changing For/Next to For Each ... In/Next.
You need to keep track of the new Row, so that each time you find a duplicate, you increase the new Row by 1. To expand on your code:
Sub test()
Dim N As Long
Dim CurRow As Long
N = Cells(Rows.Count, "D").End(xlUp).Row
CurRow = N
Dim i As Long
d = Cells(N, "D").Value
e = Cells(N, "E").Value
For i = N - 1 To 1 Step -1
dt = Cells(i, "D").Value
et = Cells(i, "E").Value
If d = dt And e = et Then
Cells(CurRow, "F").Value = Cells(i, "F").Value
CurRow = CurRow + 1
End If
Next i
End Sub

Inefficient code that doesn't find matching data values

I have 3 issues with the following piece of code:
Intention of code: I have a table of data, 4 columns (F,G, H and I) wide and X rows long (X is typically between 5 and 400). I have a list of dates in column M, typically no more than 8 dates. Column H of table, contains dates as well. I want to find the dates that are in both columns (H and M) and whenever they appear, go to the same row in column I and set its value to zero, and the one after it (so if a match was in H100, then I100 and I101 would be zeroed).
issues with code: edited 1) as per feedback.
1) I have, using an if formula (=if(H100=M12,1,0), verified that there is one match, as how the spreadsheet sees it. The macro does not find this match, despite confirmation from the if formula. Cells I100 and I101 have nonzero values, when they should be zeroed.
2) the code runs, but takes about 3 minutes to go through 3 sheets of 180 rows of data. What can be done to make it run faster and more efficiently? It could have up to 30 sheets of data, and 400 rows (extreme example but possible, in this instance im happy to let it run a bit).
3) Assuming my data table before the macro is run, is 100 rows long, starting in row 12, after the macro, column I has nonzero values for 111 rows, and zeroes for the next 389. Is there a way I can prevent it from filling down zeroes, and leaving it blank?
I am using a correlate function afterwards on column I and there huge agreement of 0's with 0's is distorting this significantly. Thanks in advance,
Sub DeleteCells()
Dim ws As Worksheet
Dim cell As Range, search_cell As Range
Dim i As Long
Dim h As Long
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = "Cover" Then
For Each cell In ws.Range("H12:H500")
On Error Resume Next
h = ws.Range("G" & Rows.Count).End(xlUp).Row
i = ws.Range("L" & Rows.Count).End(xlUp).Row
Set search_cell = ws.Range("M12:M" & h).Find(what:=cell.Value, LookIn:=xlValues, lookat:=xlWhole)
On Error GoTo 0
If Not search_cell Is Nothing Then
ws.Range("I" & cell.Row).Value = 0
ws.Range("I" & cell.Row + 1).Value = 0
Set search_cell = Nothing
End If
Next cell
End If
Next ws
Application.ScreenUpdating = True
Set ws = Nothing: Set cell = Nothing: Set search_cell = Nothing
End Sub
EDIT: TESTED CODE, will work for 0, 1 row of data in H/M column starting from row 12?
EDIT: Updated the cell to handle case with 1 line of data, untested :|
I will give my solution first, this one should be much faster because it read the cells into memory first
Please comment if it doesn't work or you have further question
Sub DeleteCells()
Dim ws As Worksheet
Dim i As Long
Dim h As Long
Dim MColumn As Variant ' for convinence
Dim HColumn As Variant
Dim IColumn As Variant
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = "Cover" Then 'matching the target sheet
' matching the rows where column M's date matches column H's date
'starting row num is 12
With ws ' for simplifying the code
h = .Range("H" & .Rows.count).End(xlUp).Row
If h = 12 Then ' CASE for 1 row only
If Range("H12").Value = Range("M12").Value Then
Range("I12:I13").Value = ""
End If
ElseIf h < 12 Then
' do nothing
Else
ReDim HColumn(1 To h - 11, 1 To 1)
ReDim MColumn(1 To h - 11, 1 To 1)
ReDim IColumn(1 To h - 10, 1 To 1)
' copying the data from worksheet into 2D arrays
HColumn = .Range("H12:H" & h).Value
MColumn = .Range("M12:M" & h).Value
IColumn = .Range("I12:I" & h + 1).Value
For i = LBound(HColumn, 1) To UBound(HColumn, 1)
If Not IsEmpty(HColumn(i, 1)) And Not IsEmpty(MColumn(i, 1)) Then
If HColumn(i, 1) = MColumn(i, 1) Then
IColumn(i, 1) = ""
IColumn(i + 1, 1) = ""
End If
End If
Next i
'assigning back to worksheet cells
.Range("H12:H" & h).Value = HColumn
.Range("M12:M" & h).Value = MColumn
.Range("I12:I" & h + 1).Value = IColumn
End If
End With
End If
Next ws
Application.ScreenUpdating = True
End Sub