I'm trying to run a macro but now it keeps freezing excel.
It runs with 10 cells, but when the macro is applied to almost two hundred, it freezes and crashes.
Sub eancheck()
Dim s1 As Worksheet, s2 As Worksheet
Dim Msg As String
Set s1 = Sheets("Sheet1")
Set s2 = Sheets("Sheet3")
Dim lr1 As Long, lr2 As Long
lr1 = s1.Range("A" & Rows.Count).End(xlUp).Row
lr2 = s2.Range("a" & Rows.Count).End(xlUp).Row
Dim i As Long, j As Long
Application.ScreenUpdating = False
For i = 2 To lr1
s1.Cells(i, "D").Interior.ColorIndex = 0
For j = 2 To lr2
If s2.Range("A" & j) = s1.Range("D" & i) Then
's1.Range("D" & i) = s2.Range("B" & j)
s1.Cells(i, "D").Interior.ColorIndex = 3
End If
Next j
Next i
Application.ScreenUpdating = True
End Sub
I'm having problems with other macros too, and i think is because of the size of the range. How can i fix it?
Note: The macro runs when searching 10 values in a sheet with two columns with almost 200.000 values each, but when instead of 10 is 200, crashes.
Use conditional formatting in your sheet1 with formula and apply it on range like D2:D5000 or whatever is suitable.
=COUNTIF(Sheet3!A2,D2)>0
Try Declaring all the required variables separately.
Use Application.ScreenUpdating = false in the beginning of the program.
Your first line of for loop can be outside the for loop as well.
Use Collections to make the checks.
For Example, I started with data like this on Sheet 1 Col A,
And data like this on Sheet 3 Col A.
And this is the Macro that I have,
Sub eancheck()
Application.ScreenUpdating = False
Dim s1 As Worksheet
Dim s2 As Worksheet
Dim Msg As String
Dim lr1 As Long
Dim lr2 As Long
Dim i As Long
Dim j As Long
Dim Sheet1ObjectsCol As Collection
Dim Sheet3ObjectsCol As Collection
Dim IdentifierCol As Collection
Set s1 = ThisWorkbook.Sheets("Sheet1")
Set s2 = ThisWorkbook.Sheets("Sheet3")
Set Sheet1ObjectsCol = New Collection
Set Sheet3ObjectsCol = New Collection
Set IdentifierCol = New Collection
lr1 = s1.Range("A" & Rows.Count).End(xlUp).Row
lr2 = s2.Range("a" & Rows.Count).End(xlUp).Row
s1.Range("D2" & ":" & "D" & lr1).Interior.ColorIndex = 0
'Load the collections
For i = 2 To lr1
Sheet1ObjectsCol.Add s1.Range("A" & i).Value
Next
'Load the collections
On Error Resume Next
For i = 2 To lr2
Sheet3ObjectsCol.Add s2.Range("A" & i).Value, CStr(s2.Range("A" & i).Value)
Next
'Create the Identifier Collection
For i = 1 To Sheet1ObjectsCol.Count
ColorValReq = 0
For j = 1 To Sheet3ObjectsCol.Count
If Sheet1ObjectsCol(i) = Sheet3ObjectsCol(j) Then
ColorValReq = 3
GoTo Idenitified
End If
Next
Idenitified:
IdentifierCol.Add ColorValReq
Next
For i = 1 To IdentifierCol.Count
j = i + 1
If IdentifierCol(i) = 3 then
s1.Range("D" & j).Interior.ColorIndex = IdentifierCol(i)
End if
Next
Application.ScreenUpdating = True
End Sub
And this is the output I got,
I am writing a VBA code on excel using loops to go through 10000+ lines.
Here is an example of the table
And here is the code I wrote :
Sub Find_Matches()
Dim wb As Workbook
Dim xrow As Long
Set wb = ActiveWorkbook
wb.Worksheets("Data").Activate
tCnt = Sheets("Data").UsedRange.Rows.Count
Dim e, f, a, j, h As Range
xrow = 2
Application.ScreenUpdating = False
Application.Calculation = xlManual
For xrow = 2 To tCnt Step 1
Set e = Range("E" & xrow)
Set f = e.Offset(0, 1)
Set a = e.Offset(0, -4)
Set j = e.Offset(0, 5)
Set h = e.Offset(0, 3)
For Each Cell In Range("E2:E" & tCnt)
If Cell.Value = e.Value Then
If Cell.Offset(0, 1).Value = f.Value Then
If Cell.Offset(0, -4).Value = a.Value Then
If Cell.Offset(0, 5).Value = j.Value Then
If Cell.Offset(0, 3).Value = h.Value Then
If (e.Offset(0, 7).Value) + (Cell.Offset(0, 7).Value) = 0 Then
Cell.EntireRow.Interior.Color = vbYellow
e.EntireRow.Interior.Color = vbYellow
End If
End If
End If
End If
End If
End If
Next
Next
End Sub
As you can imagine, this is taking a lot of time to go through 10000+ lines and I would like to find a faster solution. There must be a method I don't think to avoid the over looping
Here are the condition :
For each line, if another line anywhere in the file has the exact same
:
Buyer ID (col. E)
`# purchased (col. F)
Product ID (col.A)
Payment (col. J)
Date purchased (col. H)
Then, if the SUM of the Amount (col. L) the those two matching line is
0, then color both rows in yellow.
Note that extra columns are present and not being compared (eg- col. B) but are still important for the document and cannot be deleted to ease the process.
Running the previous code, in my example, row 2 & 5 get highlighted :
This is using nested dictionaries and arrays to check all conditions
Timer with my test data: Rows: 100,001; Dupes: 70,000 - Time: 14.217 sec
Option Explicit
Public Sub FindMatches()
Const E = 5, F = 6, A = 1, J = 10, H = 8, L = 12
Dim ur As Range, x As Variant, ub As Long, d As Object, found As Object
Set ur = ThisWorkbook.Worksheets("Data").UsedRange
x = ur
Set d = CreateObject("Scripting.Dictionary")
Set found = CreateObject("Scripting.Dictionary")
Dim r As Long, rId As String, itm As Variant, dupeRows As Object
For r = ur.Row To ur.Rows.Count
rId = x(r, E) & x(r, F) & x(r, A) & x(r, J) & x(r, H)
If Not d.Exists(rId) Then
Set dupeRows = CreateObject("Scripting.Dictionary")
dupeRows(r) = 0
Set d(rId) = dupeRows
Else
For Each itm In d(rId)
If x(r, L) + x(itm, L) = 0 Then
found(r) = 0
found(itm) = 0
End If
Next
End If
Next
Application.ScreenUpdating = False
For Each itm In found
ur.Range("A" & itm).EntireRow.Interior.Color = vbYellow
Next
Application.ScreenUpdating = True
End Sub
Before
After
I suggest a different approach altogether: add a temporary column to your data that contains a concatenation of each cell in the row. This way, you have:
A|B|C|D|E
1|Mr. Smith|500|A|1Mr. Smith500A
Then use Excel's conditional formatting on the temporary column, highlighting duplicate values. There you have your duplicated rows. Now it's only a matter of using a filter to check which ones have amounts equal to zero.
You can use the CONCATENATE function; it requires you to specify each cell separately and you can't use a range, but in your case (comparing only some of the columns) it seems like a good fit.
Maciej's answer is easy to implement (if you can add columns to your data without interrupting anything), and I would recommend it if possible.
However, for the sake of answering your question, I will contribute a VBA solution as well. I tested it on dataset that is a bit smaller than yours, but I think it will work for you. Note that you might have to tweak it a little (which row you start on, table name, etc) to fit your workbook.
Most notably, the segment commented with "Helper column" is something you most likely will have to adjust - currently, it compares every cell between A and H for the current row, which is something you may or may not want.
I've tried to include a little commentary in the code, but it's not much. The primary change is that I'm using in-memory processing of an array rather than iterating over a worksheet range (which for larger datasets should be exponentially faster).
Option Base 1
Option Explicit
' Uses ref Microsoft Scripting Runtime
Sub Find_Matches()
Dim wb As Workbook, ws As Worksheet
Dim xrow As Long, tCnt As Long
Dim e As Range, f As Range, a As Range, j As Range, h As Range
Dim sheetArr() As Variant, arr() As Variant
Dim colorTheseYellow As New Dictionary, colorResults() As String, dictItem As Variant
Dim arrSize As Long, i As Long, k As Long
Dim c As Variant
Set wb = ThisWorkbook
Set ws = wb.Worksheets("Data")
ws.Activate
tCnt = ws.UsedRange.Rows.Count
xrow = 2
Application.ScreenUpdating = False
Application.Calculation = xlManual
' Read range into an array so we process in-memory
sheetArr = ws.Range("A2:H" & tCnt)
arrSize = UBound(sheetArr, 1)
' Build new arr with "helper column"
ReDim arr(1 To arrSize, 1 To 9)
For i = 1 To arrSize
For k = 1 To 8
arr(i, k) = sheetArr(i, k)
arr(i, 9) = CStr(arr(i, 9)) & CStr(arr(i, k)) ' "Helper column"
Next k
Next i
' Iterate over array & build collection to indicate yellow lines
For i = LBound(arr, 1) To UBound(arr, 1)
If Not colorTheseYellow.Exists(i) Then colorResults = Split(ReturnLines(arr(i, 9), arr), ";")
For Each c In colorResults
If Not colorTheseYellow.Exists(CLng(c)) Then colorTheseYellow.Add CLng(c), CLng(c)
Next c
Next i
' Enact row colors
For Each dictItem In colorTheseYellow
'Debug.Print "dict: "; dictItem
If dictItem <> 0 Then ws.ListObjects(1).ListRows(CLng(dictItem)).Range.Interior.Color = vbYellow
Next dictItem
End Sub
Function ReturnLines(ByVal s As String, ByRef arr() As Variant) As String
' Returns a "Index;Index" string indicating the index/indices where the second, third, etc. instance(s) of s was found
' Returns "0;0" if 1 or fewer matches
Dim i As Long
Dim j As Long
Dim tmp As String
ReturnLines = 0
j = 0
tmp = "0"
'Debug.Print "arg: " & s
For i = LBound(arr, 1) To UBound(arr, 1)
If arr(i, 9) = s Then
j = j + 1
'Debug.Print "arr: " & arr(i, 9)
'Debug.Print "ReturnLine: " & i
tmp = tmp & ";" & CStr(i)
End If
Next i
'If Left(tmp, 1) = ";" Then tmp = Mid(tmp, 2, Len(tmp) - 1)
'Debug.Print "tmp: " & tmp
If j >= 2 Then
ReturnLines = tmp
Else
ReturnLines = "0;0"
End If
End Function
On my simple dataset, it yields this result (marked excellently with freehand-drawn color indicators):
Thanks everybody for your answers,
Paul Bica's solution actually worked and I am using a version of this code now.
But, just to animate the debate, I think I also found another way around my first code, inspired by Maciej's idea of concatenating the cells and using CStr to compare the values and, of course Vegard's in-memory processing by using arrays instead of going through the workbook :
Sub Find_MatchesStr()
Dim AmountArr(300) As Variant
Dim rowArr(300) As Variant
Dim ws As Worksheet
Dim wb As Workbook
Set ws = ThisWorkbook.Sheets("Data")
ws.Activate
Range("A1").Select
rCnt = ws.Cells.SpecialCells(xlCellTypeLastCell).Row
For i = 2 To rCnt
If i = rCnt Then
Exit For
Else
intCnt = 0
strA = ws.Cells(i, 1).Value
strE = ws.Cells(i, 5).Value
strF = ws.Cells(i, 6).Value
strH = ws.Cells(i, 8).Value
strL = ws.Cells(i, 10).Value
For j = i To rCnt - 1
strSearchA = ws.Cells(j, 1).Value
strSearchE = ws.Cells(j, 5).Value
strSearchF = ws.Cells(j, 6).Value
strSearchH = ws.Cells(j, 8).Value
strSearchL = ws.Cells(j, 10).Value
If CStr(strE) = CStr(strSearchE) And CStr(strA) = CStr(strSearchA) And CStr(strF) = CStr(strSearchF) And CStr(strH) = CStr(strSearchH) And CStr(strL) = CStr(strSearchL) Then
AmountArr(k) = ws.Cells(j, 12).Value
rowArr(k) = j
intCnt = intCnt + 1
k = k + 1
Else
Exit For
End If
Next
strSum = 0
For s = 0 To UBound(AmountArr)
If AmountArr(s) <> "" Then
strSum = strSum + AmountArr(s)
Else
Exit For
End If
Next
strAppenRow = ""
For b = 0 To UBound(rowArr)
If rowArr(b) <> "" Then
strAppenRow = strAppenRow & "" & rowArr(b) & "," & AmountArr(b) & ","
Else
Exit For
End If
Next
If intCnt = 1 Then
Else
If strSum = 0 Then
For rn = 0 To UBound(rowArr)
If rowArr(rn) <> "" Then
Let rRange = rowArr(rn) & ":" & rowArr(rn)
Rows(rRange).Select
Selection.Interior.Color = vbYellow
Else
Exit For
End If
Next
Else
strvar = ""
strvar = Split(strAppenRow, ",")
For ik = 1 To UBound(strvar)
If strvar(ik) <> "" Then
strVal = CDbl(strvar(ik))
For ik1 = ik To UBound(strvar)
If strvar(ik1) <> "" Then
strVal1 = CDbl(strvar(ik1))
If strVal1 + strVal = 0 Then
Let sRange1 = strvar(ik - 1) & ":" & strvar(ik - 1)
Rows(sRange1).Select
Selection.Interior.Color = vbYellow
Let sRange = strvar(ik1 - 1) & ":" & strvar(ik1 - 1)
Rows(sRange).Select
Selection.Interior.Color = vbYellow
End If
Else
Exit For
End If
ik1 = ik1 + 1
Next
Else
Exit For
End If
ik = ik + 1
Next
End If
End If
i = i + (intCnt - 1)
k = 0
Erase AmountArr
Erase rowArr
End If
Next
Range("A1").Select
End Sub
I still have some mistakes (rows not higlighted when they should be), the above code is not perfect, but I thought it'd be OK to give you an idea of where I was going before Paul Bica's solution came in.
Thanks again !
If your data is only till column L, then use below code, I found it is taking less time to run....
Sub Duplicates()
Application.ScreenUpdating = False
Dim i As Long, lrow As Long
lrow = Cells(Rows.Count, 1).End(xlUp).Row
Range("O2") = "=A2&E2&F2&J2&L2"
Range("P2") = "=COUNTIF(O:O,O2)"
Range("O2:P" & lrow).FillDown
Range("O2:O" & lrow).Copy
Range("O2:O" & lrow).PasteSpecial xlPasteValues
Application.CutCopyMode = False
For i = 1 To lrow
If Cells(i, 16) = 2 Then
Cells(i, 16).EntireRow.Interior.Color = vbYellow
End If
Next
Application.ScreenUpdating = True
Range("O:P").Delete
Range("A1").Select
MsgBox "Done"
End Sub
Background:
I am attempting to convert an old style of managing departments, facilities, and job titles at work to a new more convenient lookup table style format. Right now each set of data for new departments, facilities, and job titles are stored on separate sheets (a couple per week) and the sheet name is a date.
The issue:
The program runs fine for the majority of the workbook; however, I eventually get a
1004 - Application-defined or objected-defined error
When debugging, I find that my row value has to 1,048,577 and therefore caused the error. I am unsure how the iterating value is escaping my handling of it.
The code:
Sub cleanUp()
Dim wks As Worksheet
Dim wksNum As Long
Dim destWks As Worksheet
Dim rng As Range
Dim row As Long
Dim col As Long
Dim destRow As Long
Dim lastRow As Long
Dim itemType As String
Set destWks = ActiveWorkbook.Sheets("new")
destRow = 2
For wksNum = 1 To ActiveWorkbook.Sheets.Count
NextWks:
Set wks = ActiveWorkbook.Worksheets(wksNum)
If wks.Name = "new" Then
wksNum = wksNum + 1
GoTo NextWks
End If
lastRow = wks.Cells(wks.Rows.Count, 1).End(xlUp).row
For row = 1 To lastRow
NextRow:
Select Case wks.Cells(row, 1).Value
Case "New Hospitals"
itemType = "Hospital"
row = row + 1
GoTo NextRow
Case "New Departments"
itemType = "Department"
row = row + 1
GoTo NextRow
Case "New Job Titles"
itemType = "Job Title"
row = row + 1
GoTo NextRow
Case "none"
row = row + 1
GoTo NextRow
Case ""
row = row + 1
GoTo NextRow
End Select
destWks.Cells(destRow, 1).Value = wks.Name
destWks.Cells(destRow, 2).Value = itemType
wks.Range("A" & row & ":C" & row).Copy destWks.Range("C" & destRow)
destRow = destRow + 1
Next row
Next wksNum
End Sub
Example Sheet:
New Hospitals
None
New Departments
10 146 7205-DeptA
10 193 9178-DeptB
New Job Titles
004315 JobTitleA
A side question:
Is there a more elegant way of skipping to the next iteration of a for loop without using a GoTo statement. My thoughts are that these are causing my issue.
Thank you for any and all help.
The problem is if your data on any sheet ends with either of the group headings or none then it will start an eternal loop adding 1 to row until it reaches a row number that is not supported by the worksheet.
You have taken the loop outside the For Loop and so it does not stop.
It is better to test for the opposite and do the code instead of using the archaic Goto.
Use this:
Sub cleanUp()
Dim wks As Worksheet
Dim wksNum As Long
Dim destWks As Worksheet
Dim rng As Range
Dim row As Long
Dim col As Long
Dim destRow As Long
Dim lastRow As Long
Dim itemType As String
Set destWks = ActiveWorkbook.Sheets("new")
destRow = 2
For wksNum = 1 To ActiveWorkbook.Sheets.Count
Set wks = ActiveWorkbook.Worksheets(wksNum)
If wks.Name <> "new" Then
lastRow = wks.Cells(wks.Rows.Count, 1).End(xlUp).row
For row = 1 To lastRow
Select Case wks.Cells(row, 1).Value
Case "New Hospitals", "New Departments", "New Job Titles"
itemType = Replace(wks.Cells(row, 1).Value, "New ", "")
Case "None", ""
Case Else
destWks.Cells(destRow, 1).Value = wks.Name
destWks.Cells(destRow, 2).Value = itemType
wks.Range("A" & row & ":C" & row).Copy destWks.Range("C" & destRow)
destRow = destRow + 1
End Select
Next row
End If
Next wksNum
End Sub
Edited to simplify Select Case based on Scott Holtzman's now deleted answer
Output with provided data:
For DurcurrRowIn = 14 To .UsedRange.Row + .UsedRange.Rows.Count - 1
DurcurrRowIn = DurcurrRowIn + 1
Set DurlookFor = wb.ActiveSheet.Cells(currRowIn, "C") ' value to find
Set Durlookforin = wb.ActiveSheet.Range("C" & DurcurrRowIn & ":C500")
On Error Resume Next
DurStart = Application.WorksheetFunction.Index(wb.ActiveSheet.Range("F:F"), WorksheetFunction.Match(DurlookFor, Durlookforin, 0))
DurEnd = Application.WorksheetFunction.Index(wb.ActiveSheet.Range("G:G"), WorksheetFunction.Match(DurlookFor, Durlookforin, 0))
Dur1 = DurEnd - DurStart
Dur = Dur + Dur1
Next
Looking to perform an index/match to grab an employees id on the current row and look for it further down the column not including the row I'm currently in. Once it finds the same employee id, grab the start/end dates and determine their durations, then add that figure to the total duration number (dur). Currently not grabbing the dates, though it did work when my "Durlookforin" was just the whole column C, which doesn't really help. Will post my dimensions below.
Dim DurcurrRowIn As Long
Dim DurcurrColIn As Long
Dim DurcurrRowOut As Long
Dim wb As Workbook
Dim Dur As Long
Dim Dur1 As Long
Dim DurEnd As String
Dim DurStart As String
Dim DurlookFor As Range, Durlookforin As Range
Test File Here
Using Employee ID's as keys to a Dictionary makes it easy to add up the Durations.
Most of this code generates a report but this is all you need to get the Durations by Employee ID's
For x = 14 To lastRow
dicIDs(.Cells(x, "C").Value) = dicIDs(.Cells(x, "C").Value) + .Cells(x, "G").Value - .Cells(x, "F").Value
Next
Sample Data
Result
Sub GetDurations()
Application.ScreenUpdating = False
Const SOURCE_SHEETNAME As String = "Sheet1"
Dim lastRow As Long, x As Long
Dim dicIDs As Object
Set dicIDs = CreateObject("System.Collections.SortedList")
With Worksheets(SOURCE_SHEETNAME)
lastRow = .Range("C" & Rows.Count).End(xlUp).Row
For x = 14 To lastRow
dicIDs(.Cells(x, "C").Value) = dicIDs(.Cells(x, "C").Value) + .Cells(x, "G").Value - .Cells(x, "F").Value
Next
Worksheets.Add
.Range("A14:D" & lastRow).Copy Range("A2")
End With
Columns("D").ClearContents
Range("A1:D1") = Array("LastName", "FirstName", "Employee ID", "Duration")
lastRow = Range("A" & Rows.Count).End(xlUp).Row
Range("A1:C" & lastRow).RemoveDuplicates Columns:=Array(1, 2, 3)
lastRow = Range("A" & Rows.Count).End(xlUp).Row
For x = 2 To lastRow
Cells(x, "D").Value = dicIDs(Cells(x, "C").Value)
Next
Columns.AutoFit
Application.ScreenUpdating = True
End Sub
If you are not familiar with using a Scripting Dictionary then this is a must see Excel VBA Introduction Part 39 - Dictionaries
I have an excel sheet of which the data was jumbled: for example, the data that should have been in Columns AB and AC were instead in Columns B and C, but on the row after. I have the following written which moved the data from B and C to AB and AC respectively:
Dim rCell As Range
Dim rRng As Range
Set rRng = Sheet1.Range("A:A")
i = 1
lastRow = ActiveSheet.Cells(Rows.Count, "A").End(xlUp).Row
For Each rCell In rRng.Cells
If rCell.Value = "" Then
Range("AB" & i) = rCell.Offset(0, 1).Value
rCell.Offset(0, 1).ClearContents
End If
i = i + 1
If i = lastRow + 1 Then
Exit Sub
End If
Next rCell
End Sub
However, it doesn't fix the problem of the data being on the row BELOW the appropriate row now that they are in the right columns. I am new to VBA Macros so I would appreciate any help to make the data now align. I tried toggling the Offset parameter (-1,0) but it's not working.
Try something like this?
For i = Lastrow To 1 Step -1
' move data into cell AA from Cell A one row down
Cells(i, 27).Value = Cells(i + 1, 1).Value
Next
You don't need to loop through the range to accomplish what you're trying to do.
Try this instead:
Sub MoveBCtoAbAcUpOneRow()
Dim firstBRow As Integer
Dim lastBRow As Long
Dim firstCRow As Integer
Dim lastCRow As Long
' get the first row in both columns
If Range("B2").Value <> "" Then
firstBRow = 2
Else
firstBRow = Range("B1").End(xlDown).Row
End If
If Range("C2").Value <> "" Then
firstCRow = 2
Else
firstCRow = Range("C1").End(xlDown).Row
End If
' get the last row in both columns
lastBRow = Range("B" & ActiveSheet.Rows.Count).End(xlUp).Row
lastCRow = Range("C" & ActiveSheet.Rows.Count).End(xlUp).Row
' copy the data to the correct column, up one row
Range("B" & firstBRow & ":B" & lastBRow).Copy Range("AB" & firstBRow - 1)
Range("C" & firstCRow & ":C" & lastCRow).Copy Range("AC" & firstCRow - 1)
' clear the incorrect data
Range("B" & firstBRow & ":B" & lastBRow).ClearContents
Range("C" & firstCRow & ":C" & lastCRow).ClearContents
End Sub
Notes:
If the shape of data in each column is the same, you don't need to
find the first and last row for each. You'll only need one variable for each and one copy operation instead of 2.
Make sure you set variable declaration to required.
(Tools -> Options -> Require Variable Declaration) You may already be doing this, but I couldn't tell because it looks like the top of your Sub got truncated.