I have a function that checks the rows underneath the current one depending on the unique ID. There can be up to 6 unique ideas under the current record (loop variable = i) that match the current record being checked in the loop. After this is done, the records underneath are checked for specific conditions (loop variable x). However, for some reason, I'm running into several issues. The first is that I had to set the range references inside of both loops, otherwise I got an error. The second is that, all of the stuff after the x loop seems to be outputting in the i loop that came before it. What am I doing wrong, and how can i make this function properly?
Please find my code below:
Function First_check()
dim i as long, x as long
Dim numComponents As Variant
Dim in1 As Range, in2 As Range, in3 As Range, in4 As Range, in5 As Range, _
in6 As Range, in7 As Range, in8 As Range, in9 As Range, in10 As Range, _
in11 As Range, in12 As Range, in13 As Range, in14 As Range, in15 As Range, _
in16 As Range, in17 As Range, in18 As Range, in19 As Range, in20 As Range
Dim out1 As Range, out2 As Range, out3 As Range, out4 As Range, out5 As Range, _
out6 As Range, out7 As Range, out8 As Range, out9 As Range, out10 As Range, _
out11 As Range, out12 As Range, out13 As Range, out14 As Range, out15 As Range, _
out16 As Range, out17 As Range, out18 As Range, out19 As Range, out20 As Range
Dim str, msg, oft, BTG, LOB, pdf, mht, emails, zip_rar, xls, doc, xls_doc, mrTT, lobVal, cmt1, giveURL, giveURLm As String
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
lastCol = Cells(1, Columns.Count).End(xlToLeft).Column
For i = 2 To lastRow
If Cells(i, 5).Value2 = Cells(i + 6, 5).Value2 Then
numComponents = 6
ElseIf Cells(i, 5).Value2 = Cells(i + 5, 5).Value2 Then
numComponents = 5
ElseIf Cells(i, 5).Value2 = Cells(i + 4, 5).Value2 Then
numComponents = 4
ElseIf Cells(i, 5).Value2 = Cells(i + 3, 5).Value2 Then
numComponents = 3
ElseIf Cells(i, 5).Value2 = Cells(i + 2, 5).Value2 Then
numComponents = 2
ElseIf Cells(i, 5).Value2 = Cells(i + 1, 5).Value2 Then
numComponents = 1
Else
numComponents = 0
End If
For x = i + 1 To i + numComponents
Set in1 = Cells(i, 11) 'test
Set in2 = Cells(i, 12)
Set in3 = Cells(i, 13)
Set in4 = Cells(i, 16) 'e
Set in5 = Cells(i, 37) 'target date
Set in6 = Cells(i, 38) 'target date end
Set in7 = Cells(i, 35) 'target date actual
Set in8 = Cells(i, 37) 'target date start
Set in9 = Cells(i, 38) 'target date end
Set in10 = Cells(x, 50) ' date start
Set in11 = Cells(x, 51) ' date end
Set in12 = Cells(i, 42) 'pro
Set in13 = Cells(i, 43) 'reco
Set in14 = Cells(x, 62) 'cert
Set in15 = Cells(x, 63) 'com
Set in16 = Cells(x, 64) 'comp
Set in17 = Cells(x, 49) 'uniqueID
'outs
Set out1 = Cells(i, 72) 'test
Set out2 = Cells(i, 73) '
Set out3 = Cells(i, 74) '
Set out4 = Cells(i, 75) 'e
Set out5 = Cells(i, 76) 'tar
Set out6 = Cells(i, 77) 'comp
Set out7 = Cells(i, 78) 'pro
Set out8 = Cells(i, 75) 'empty
Set out9 = Cells(i, 80) 'cer
Set out10 = Cells(i, 81) 'comp
Set out11 = Cells(i, 85) 'pre
Set out12 = Cells(i, 88) 'missing
Set out13 = Cells(i, 89) 'missing2
Set out14 = Cells(i, 71) 'uniqueID
'------ATTACHMENT SET
str = Cells(i, 46).Value2
msg = UBound(Split(str, ".msg"))
oft = UBound(Split(str, ".oft"))
BTG = UBound(Split(str, "BTG"))
LOB = UBound(Split(str, "LOB"))
pdf = UBound(Split(str, ".pdf"))
mht = UBound(Split(str, ".mht"))
emails = msg + oft + pdf + mht
zip_rar = UBound(Split(str, ".zip"))
xls = UBound(Split(str, ".xls"))
doc = UBound(Split(str, ".doc"))
xls_doc = xls Or doc
If (in8.Value2 <> in10.Value2) Or (in9.Value <> in11.Value2) Then 'date
out6.Value2 = Cells(x, 49).Value2 & ", " & out6.Value2
End If
If IsBlank(in14.Value2) Then 'Check cer
out9.Value2 = Cells(x, 49).Value2 & ", " & out9.Value2
End If
If IsBlank(in15.Value2) Or IsBlank(in16.Value2) Then 'check loc
out10.Value2 = Cells(x, 49).Value2 & ", " & out10.Value2
End If
If Not IsBlank(in17.Value2) Then
out14.Value2 = in17.Value2 & ", " & out14.Value2
End If
Next x
If Not IsBlank(out6.Value2) Then 'date
out6.Value2 = "Wrong dates"
out6.Value2 = fixtrail(out6.Value2)
End If
If Not IsBlank(out9.Value2) Then 'cert
out9.Value2 = "Cert Issue"
out9.Value2 = fixtrail(out9.Value2)
End If
If Not IsBlank(out10.Value2) Then 'comp
out10.Value2 = "Comp not found"
out10.Value2 = fixtrail(out10.Value2)
End If
If IsBlank(in1.Value2) Then
out1.Value2 = "Missing type"
End If
'
'many more checks happening that i omittied for brevity
'
If numComponents = 0 Then
Cells(i, 70).Value2 = "0"
Else
Cells(i, 70).Value2 = numComponents
End If
i = i + numComponents
Next i
End Function
The first idea that came to mind is using an array of Range objects to clean up the variable declarations:
Dim inRange(20) As Range
Dim outRange(20) As Range
'...
For x = i + 1 To i + numComponents
Set inRange(1) = Cells(i, 11)
Set inRange(2) = Cells(i, 12)
'...
Next
This will work especially well if you can get a formula for the cell numbers that map to each array position.
Additionally, we can improve variables around how the two loops are nested. The outer loop uses the i variable, while the inner loop uses the x variable. Since these are both looking at rows, I would re-name them as r0 and r1 (or rBase and rNested, rParent and rChild, rMaster and rDetail, etc) to help you understand what you're looking at with each index. I also see that some of the Range objects depend on the current i value, while other depend on x. You should be able to assign the i ranges above the inner loop, and save some CPU/memory work that way:
For irParent = 2 To LastRow
'...
Set inRange(1) = Cells(irParent, 11) 'test
Set inRange(2) = Cells(irParent, 12)
Set inRange(3) = Cells(irParent, 13)
Set inRange(4) = Cells(irParent, 16) 'e
'...
'If numComponents is 0, there are no child rows and this loop is skipped
For rChild = rParent + 1 To rParent + numComponents
Set inRange(10) = Cells(irChild, 50) ' date start
Set inRange(11) = Cells(irChild, 51) ' date end
'...
str = Cells(irParent, 46).Value2
msg = UBound(Split(str, ".msg"))
oft = UBound(Split(str, ".oft"))
'...
Next
irParent = irParent + numComponents
Next
Another thing is this method runs kind of long. You may want to abstract out some of the checks to a separate method, or a few separate methods that depend on what type of parent record you're looking at. Create methods that just accept the values needed for checking a particular kind of row, and then returns a single result for the check. This adds names to the code that help you understand what you're doing, as well as shorting the parent code to make it easier to read and understand at a high level more quickly.
As you make those other changes, you may want to start thinking in terms of creating Range objects that represent an entire row (or section from a row), so you can pass them to methods. This is especially true, as it appears many Range objects are currently used to hold values from single Cells. You can build strings to define non-contiguous Ranges that have the values needed for each row (including the parent cells when working in a child row). This will make building functions much easier, if you can have them simply accept a single Range object that you know has the correct cells in it.
This is also helpful because it minimizes instances where you copy from Excel Cells to memory. Moving data between VBA and Excel is a costly operation. It's usually better for performance to copy to or from a set of Cells in bulk, rather than one Cell at a time. This often holds even when it means using some extra memory. It also often helps reduce or simplify the total amount of code needed. Unfortunately, I'm too far out of VBA to show you an example.
Finally, notice my indentation. Professionals will do that consistently... even religiously. "Hacky" code does not. It's extremely helpful for spotting mistakes.
Related
I have tried to build a loop that pulls back certain data when it meets a criteria, then posts the results in my 'Main' sheet.
Unfortunately, when you run the macro it does not pull back all of the data.
However, and this in my opinion is super weird, when you step through it does.
There are no error messages at any point in the code and the code runs the whole way through if you step through/just run the macro.
I have posted my code below:
Sub Loop_Data()
'BR stands for Blank Row
Dim i As Integer, j As Integer, k As Integer, m As Integer, BRMAin As Integer, BRData As Integer, BRPhysNot As Integer, _
SearchRange As Range, strID As String, ExtEnd As Integer, FindRow As Range
BRMAin = Sheets("Main").Cells(Rows.Count, "W").End(xlUp).Row
BRData = Sheets("Data").Cells(Rows.Count, "A").End(xlUp).Row
BRPhysNot = Sheets("PhysNot").Cells(Rows.Count, "A").End(xlUp).Row
Set SearchRange = Sheets("Data").Range("A3:A" & BRData)
Sheets("CoData").Activate
'assign j for number of rows (units) and i to start at 6 (column J) and end at 21
For j = 2 To 48
i = 35
Do Until i = 52
'criteria
If Cells(j, i - 1).Interior.Color <> RGB(51, 51, 51) And Cells(j, i - 1) > 0 And Cells(j, i).Interior.Color = RGB(51, 51, 51) Then
'find duration o
m = 0
Do While Cells(j, i + m).Interior.Color = RGB(51, 51, 51)
m = m + 1
Loop
'check that the flagged is definitely matching criteria
If Cells(j, i + m) = 0 Then
'set string ID as the string of uni & period to find in the helper column of Data sheet
'set k as row which that occurs in
strID = Cells(1, i) & Cells(j, 3)
Set FindRow = SearchRange.Find(strID)
k = FindRow.Row
'Pull back data into main sheet
ThisWorkbook.Sheets("Main").Range("X" & BRMAin + 1) = Sheets("Data").Cells(k, 8)
ThisWorkbook.Sheets("Main").Range("V" & BRMAin + 1) = Sheets("Data").Cells(k, 4)
ThisWorkbook.Sheets("Main").Range("W" & BRMAin + 1) = Sheets("Data").Cells(k, 2)
ThisWorkbook.Sheets("Main").Range("Y" & BRMAin + 1) = m
ThisWorkbook.Sheets("Main").Range("Z" & BRMAin + 1) = Sheets("CoData").Cells(1, i)
End If
End If
i = i + 1
Loop
Next j
End Sub
If a Wait or DoEvents doesn't work, instead of using
Set FindRow = SearchRange.Find(strID)
k = FindRow.Row
You could go with
k = 0
For Each SearchCell In SearchRange
If SearchCell.Text = strID Then k = SearchCell.Row
Next
I'm not 100% sure, but I suspect it has to do with you having multiple sheets, but you aren't being specific about which sheet your ranges are calling to.
I'd add in call out to worksheets for each range and cell. See my code below and let me know if it helps.
Sub Loop_Data() 'loops through CoData Sheet
'BR stands for Blank Row
Dim wb As Workbook, wsData As Worksheet, wsMain As Worksheet, wsPhys As Worksheet, wsCoData As Worksheet
Dim i As Integer, j As Integer, k As Integer, m As Integer, BRMAin As Integer, BRData As Integer, BRPhysNot As Integer
Dim SearchRange As Range, strID As String, ExtEnd As Integer, FindRow As Range
Set wb = ThisWorkbook
Set wsData = wb.Sheets("Data")
Set wsMain = wb.Sheets("Main")
Set wsPhys = wb.Sheets("PhysNot")
Set wsCoData = wb.Sheets("CoData")
BRMAin = wsMain.Cells(Rows.Count, "W").End(xlUp).Row
BRData = wsData.Cells(Rows.Count, "A").End(xlUp).Row
BRPhysNot = wsPhys.Cells(Rows.Count, "A").End(xlUp).Row
Set SearchRange = wsData.Range("A3:A" & BRData)
wsCoData.Activate 'Not necessary to activate a sheet if you need to pull data from it if you link a range to a specific sheet.
'assign j for number of rows (units) and i to start at 6 (column J) and end at 21
For j = 2 To 48
i = 35
Do Until i = 52
'criteria
If wsCoData.Cells(j, i - 1).Interior.Color <> RGB(51, 51, 51) And wsCoData.Cells(j, i - 1) > 0 And wsCoData.Cells(j, i).Interior.Color = RGB(51, 51, 51) Then
'find duration o
m = 0
Do While wsCoData.Cells(j, i + m).Interior.Color = RGB(51, 51, 51)
m = m + 1
Loop
'check that the flagged is definitely matching criteria
If wsCoData.Cells(j, i + m) = 0 Then
'set string ID as the string of uni & period to find in the helper column of Data sheet
'set k as row which that occurs in
strID = wsCoData.Cells(1, i) & wsCoData.Cells(j, 3)
Set FindRow = SearchRange.Find(strID)
k = FindRow.Row
'Pull back data into main sheet
wsMain.Range("X" & BRMAin + 1) = wsData.Cells(k, 8)
wsMain.Range("V" & BRMAin + 1) = wsData.Cells(k, 4)
wsMain.Range("W" & BRMAin + 1) = wsData.Cells(k, 2)
wsMain.Range("Y" & BRMAin + 1) = m
wsMain.Range("Z" & BRMAin + 1) = wsCoData.Cells(1, i)
End If
End If
i = i + 1
Loop
Next j
End Sub
I had to guess on the unlabeled ranges, I just assumed they had to do with the CoData Worksheet since that is what you had active last.
Also, if it helps at all, I noticed you keep calling out to a specific color, you can make that a variable too so you don't have keep typing it so much. See below.
Dim grey as Long
grey = RGB(51, 51, 51)
'Colors are just stored as Longs, in some cases Integer will work, but its mostly safer to just always stick to Long.
'So your grey would equal 3355443: 51 + 51*256 + 51 *256*256
'Example Uses...
If wsCoData.Cells(j, i - 1).Interior.Color <> grey And wsCoData.Cells(j, i - 1) > 0 And wsCoData.Cells(j, i).Interior.Color = grey Then
'...Your code
End if
Do While Cells(j, i + m).Interior.Color = grey
m = m + 1
Loop
I have a very large data set (600,000 rows) of data structured in the following format:
1) There are around 60 products. One is a Total US number, while the others are for Manufacturers and are labled as KMFs. There are also some labeled as PCKGs(but aren't relevant for this question)
2) Each product is located in 60 different markets
3) Each market has 20 different locations
4) I have 12 metrics for which I need to calculate data in the following manner: Total US number - sum(KMFs) for each metric
I have written vba code for this but it is taking too long to run(around 20 minutes) I need to run similar code on at least 20 worksheets. I have tried various methods such as setting screenUpdating etc. to false. Here is my code. I am new to vba coding so I may have missed obvious things. Please let me know anything is unclear. Please help!
Sub beforeRunningCode()
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
ActiveSheet.DisplayPageBreaks = False
End Sub
Sub returnToOriginal()
Application.ScreenUpdating = screenUpdateState
Application.DisplayStatusBar = statusBarState
Application.Calculation = calcState
Application.EnableEvents = eventsState
ActiveSheet.DisplayPageBreaks = displayPageBreaksState
End Sub
Function LastRowFunc(Sheet) As Long
LastRowFunc = ActiveWorkbook.Worksheets(Sheet).Range("A2", Worksheets(Sheet).Range("A2").End(xlDown)).Rows.Count
End Function
Function LastColFunc(Sheet) As Long
With ActiveSheet
LastColFunc = ActiveWorkbook.Sheets(Sheet).Cells(1, .Columns.Count).End(xlToLeft).Column
End With
End Function
Sub AOCalculate()
Call beforeRunningCode 'Optimize Excel
Dim LastRow As Long
Dim LastCol As Long
Dim Period As String
Dim Sheet As String
Dim Arr(1 To 16)
Dim Count As Integer
Sheet = "Energy_LS_Bottler"
Period = "2016 WAVE 1 - 3 W/E 05/07"
LastRow = LastRowFunc(Sheet) 'Calculate last row for which data exists
LastCol = LastColFunc(Sheet) 'Calculate last column for which data exists
For Each Location In ActiveWorkbook.Sheets("Locations").Range("D7:D28").Value
For Each Market In ActiveWorkbook.Sheets("Markets").Range("A5:A92").Value
Count = Count + 1
Arr(1) = Market
Arr(2) = "AO"
Arr(3) = Location
Arr(4) = Period
With ActiveWorkbook.Sheets(Sheet) 'Filtering for KMF
.AutoFilterMode = False
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=17, Criteria1:="=KMF"
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=1, Criteria1:=Market
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=3, Criteria1:=Location
End With
For k = 5 To 16
Arr(k) = Application.WorksheetFunction.Sum(ActiveWorkbook.Sheets(Sheet).Range(Cells(1, k), Cells(LastRow, k)).SpecialCells(xlCellTypeVisible))
Next k
With ActiveWorkbook.Sheets(Sheet) ' filtering for Total US
.AutoFilterMode = False
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=17, Criteria1:="=Total US"
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=1, Criteria1:=Market
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=3, Criteria1:=Location
End With
For k = 5 To 16
Arr(k) = -Arr(k) + Application.WorksheetFunction.Sum(ActiveWorkbook.Sheets(Sheet).Range(Cells(1, k), Cells(LastRow, k)).SpecialCells(xlCellTypeVisible))
Next k
For j = 1 To 16
ActiveWorkbook.Sheets(Sheet).Cells(LastRow + Count, j).Value = Arr(j)
Next j
Erase Arr
Next
Next
ActiveWorkbook.Sheets(Sheet).AutoFilterMode = False
Call returnToOriginal
End Sub
[Edit]: Here is a link to a sample data set https://drive.google.com/file/d/0B3MkGa57h6g_WGl2WWlWekd4NU0/view?usp=sharing
I think that this will work (though I haven't had a chance to test it), and should be a lot faster:
Sub AOCalculate()
Call beforeRunningCode 'Optimize Excel
Dim LastRow As Long
Dim LastCol As Long
Dim Period As String
Dim Sheet As String
Dim Arr() '1 To 2000, 1 To 16)
Dim Count As Integer
Sheet = "Energy_LS_Bottler"
Period = "2016 WAVE 1 - 3 W/E 05/07"
LastRow = LastRowFunc(Sheet) 'Calculate last row for which data exists
LastCol = LastColFunc(Sheet) 'Calculate last column for which data exists
'copy all of the relevant cells to local arrays for speed
Dim Locations(), Markets(), data()
Markets = ActiveWorkbook.Sheets("Markets").Range("A5:A92").Value
Locations = ActiveWorkbook.Sheets("Locations").Range("D7:D28").Value
'(pretty sure the following line needs to localize the Cells() to .Cells())
data = ActiveWorkbook.Sheets(Sheet).Range(Cells(1, 1), Cells(LastRow, LastCol)).Value '**'
ReDim Arr(1 To UBound(Markets, 1) * UBound(Locations, 1), 16)
'make an index of pointers into our accumulation array
Dim counts As New Collection
Dim i As Long, l As Long, m As Long
For l = 1 To UBound(Locations, 1)
Location = Locations(l, 1) '**'
For m = 1 To UBound(Markets, 1)
Market = Markets(m, 1) '**'
i = i + 1
counts.Add i, CStr(Location) & "~" & CStr(Market)
'counts.Add NewAccumArray(Location, Market, Period), CStr(Location) & "~" & CStr(Market)
Arr(i, 1) = Market
Arr(i, 2) = "AO"
Arr(i, 3) = Location
Arr(i, 4) = Period
Next
Next
' go through each row and add it to the appropiate count in the array
Dim r As Long
Dim key As String, idx As Long
For r = 1 To UBound(data, 1)
key = CStr(data(r, 3)) & "~" & CStr(data(r, 1))
If data(r, 17) = "KMF" Then
idx = counts(key)
For k = 5 To 16
Arr(idx, k) = Arr(idx, k) - data(r, k)
Next k
Else
If data(r, 17) = "Total US" Then
idx = counts(key)
For k = 5 To 16
Arr(idx, k) = Arr(idx, k) + data(r, k)
Next k
End If
End If
Next r
' output the results
ActiveWorkbook.Sheets(Sheet).Range(Cells(LastRow + 1, 1), Cells(LastRow + Count, 16)).Value = Arr
ActiveWorkbook.Sheets(Sheet).AutoFilterMode = False
Call returnToOriginal
End Sub
Answering the query "What did I mean by this?"
'(pretty sure the following line needs to localize the Cells() to .Cells())
data = ActiveWorkbook.Sheets(Sheet).Range(Cells(1, 1), Cells(LastRow, LastCol)).Value '**'
The use of Cells(..) here is fundamentally unreliable and broken. this is because Cells(..) is really a shortcut for ActiveSheet.Cells(..) and the Active* properties are inherently slow and unreliable because they can change while the code is running. Worse, this code is assuming that ActiveSheet = Energy_LS_Blotter which is far from certain.
The correct way to write this line would be like this:
data = ActiveWorkbook.Sheets(Sheet).Range( _
ActiveWorkbook.Sheets(Sheet).Cells(1, 1), _
ActiveWorkbook.Sheets(Sheet).Cells(LastRow, LastCol) _
).Value
But that is long, ugly and inconvenient. An easier way would be to use either a Sheet variable, or a With:
With ActiveWorkbook.Sheets(Sheet)
data = .Range(.Cells(1, 1), .Cells(LastRow, LastCol)).Value
End With
ANY IDEA ??? I don't know why there's a type error especially that i've changed the type of both cells
Cells(i, 7).NumberFormat = "#"
Workbooks("template.xls").Worksheets("Introduction").Cells(j, 21).NumberFormat = "#"
If Left(Cells(i, 7), 13) = Workbooks("template.xls").Worksheets("Introduction").Cells(j, 21).Value
.
.
.
Code from comments:
Dim i As Long
Dim j As Integer
For j = 5 To derlig
For i = 2 To 4000
Cells(i, 2).NumberFormat = "#"
Workbooks("template.xls").Worksheets("Introduction").Cells(j, 21).NumberFormat = "#"
Workbooks("Cat export.xls").Worksheets("Items").Activate
If Left(Cells(i, 2), 13).Value = Workbooks("template.xls").Worksheets("Introduction").Cells(j, 21).Value Then
Workbooks("Cat export.xls").Worksheets("Items").Cells(i, 3) = Right(Workbooks("Cat export.xls").Worksheets("Items").Cells(i, 2), 5)
End If
Next
Next
The error is happening because you are not qualify all of your objects, so if any workbook or worksheet is active other than what you may expect, the code may not perform correctly.
It's best practice to always qualify objects in VBA and work directly with them.
See below:
Dim wbMyWB as Workbook, wbTemplate as Workbook
Set wbMyWB = Workbooks("myWB.xlsx") 'change as needed
Set wbTemplate = Workbooks("template.xls")
Dim wsMyWS as Worksheet, wsIntro as Worksheet
Set wsMyWS = wbMyWB.Worksheets("Sheet1") ' change as needed
Set wsIntro = wbTemplate.Worksheets("introduction")
'....
Dim rMyRange as Range, rIntro as Range
'assumes i and j are properly set to long or integer (or variant (hopefully not) type
'ideally i and j are both set to integer
rMyRange = wsMyWs.Cells(i,7)
rIntro = wsIntro.Cells(j,21)
rMyRange.NumberFormat = "#"
rIntro.NumberFormat = "#"
If Left(rMyRange,13).Value = rIntro.Value Then
'condition satisfied
End If
I cannot seem to solve this VBA riddle I've been working on, please help. I'm new at this and I'm probably over complicating it
Essentially, there are two worksheets - one titled Master and the other will be created fresh daily by date. The Master tab contains 10000 rows of historical data filled from Columns A:X. The other tab generally has about 300 rows of fresh data and also contains like Columns A:X, only with blank cells in Columns A:B. I'm trying to find matches with the master tab, and if so, populate the corresponding results in cells A and B from the master to the daily. If nothing, leave blank. It is crucial that Cells H:M and R:W are identical matches.
Below is my crazy attempt, Thank you in advance for helping
Sub Previous()
Dim u As Long
u = 2
Do While ActiveSheet.Cells(u, 6) <> ""
Dim i As Long
i = 2
Do While Worksheets("Master").Cells(i, 6) <> ""
If ActiveSheet.Range(Cells(u, 8), Cells(u, 13)) _
= Worksheets("Master").Range(Cells(i, 8), Cells(i, 13)) _
And ActiveSheet.Range(Cells(u, 18), Cells(u, 23)) _
= Worksheets("Master").Range(Cells(i, 18), Cells(i, 23)) _
And ActiveSheet.Cells(u, 2) = "" Then
ActiveSheet.Range(Cells(u, 1), Cells(u, 2)) _
= Worksheets("Master").Range(Cells(i, 1), Cells(i, 2))
Else: i = i + 1
End If
Loop
u = u + 1
i = 2
Loop
End Sub
First of all, I don't believe this snippet does what you think it does.
Worksheets("Master").Range(Cells(i, 8), Cells(i, 13))
In that snippet Cells(i,8) references the ActiveSheet, not Sheets("Master").
There is a note on this about halfway down the page on msdn's Range Object documentation.
You can simplify your code a great deal by assigning some worksheet variables.
dim actWs as Worksheet
dim mstWs as Worksheet
Set actWs = Activesheet
Set mstWs = Sheets("Master")
'then reference your ranges like this
mstWs.Cells(i,8)
But, that's not what is causing your runtime error.
Simply put, you can not compare ranges that way. You need to check the value of each cell, so you end up with another layer of nested loops.
dim u as long ' active sheet row counter
dim i as long ' master sheet row counter
dim c as long ' column counter
For u = 2 to actWs.Range("A" & .Rows.Count).End(xlUp).Row 'find last row in column "A" of active sheet
For i = 2 to mstWs.Range("A" & .Rows.Count).End(xlUp).Row 'find last row in column "A" of master sheet
For c = 8 to 13
If actWs.Cells(i,c) = mstWs.Cells(i,c) Then
'Do stuff
End if
next c 'next column
next i 'next master sheet row
next u 'next active sheet row
This is obviously a simplified version of what you'll need to do. Be careful of line continuations (" _ ") and code indentation. It's easy to trick yourself into thinking your program should flow in a way that it isn't. It would be advisable to store the value's you're checking for equality in variables to make it easier to read. You might more readily notice where you're going wrong.
Sub Previous()
Dim actWs As Worksheet
Set actWs = ActiveSheet
Dim mstWs As Worksheet
Set mstWs = Sheets("Master")
Dim u As Long
Dim i As Long
u = 2
Do While actWs.Cells(u, 6) <> ""
For i = 2 To mstWs.Range("C" & Rows.Count).End(xlUp).Row
If actWs.Cells(u, 8) = mstWs.Cells(i, 8) And actWs.Cells(u, 9) = mstWs.Cells(i, 9) And actWs.Cells(u, 10) = mstWs.Cells(i, 10) And actWs.Cells(u, 11) = mstWs.Cells(i, 11) And actWs.Cells(u, 12) = mstWs.Cells(i, 12) And actWs.Cells(u, 13) = mstWs.Cells(i, 13) And actWs.Cells(u, 18) = mstWs.Cells(i, 18) And actWs.Cells(u, 19) = mstWs.Cells(i, 19) And actWs.Cells(u, 20) = mstWs.Cells(i, 20) And actWs.Cells(u, 21) = mstWs.Cells(i, 21) And actWs.Cells(u, 22) = mstWs.Cells(i, 22) And actWs.Cells(u, 23) = mstWs.Cells(i, 23) Then
mstWs.Select
Range(Cells(i, 1), Cells(i, 2)).Select
Selection.Copy
actWs.Select
Range(Cells(u, 1), Cells(u, 2)).Select
actWs.Paste
End If
Next i
u = u + 1
Loop
End Sub
I created a macro in Excel to mergue duplicated rows:
The idea is that if 2 rows or more have the same A B C columns, I mergue their D columns removing ABC duplicates.
I need to do this, but checking more colums.
My macro looks like this:
processingRow = 2
Do Until Cells(processingRow, 1).Value = ""
i = processingRow + 1
Do Until Cells(i, 1).Value = ""
If Cells(processingRow, 8) = Cells(i, 8) And _
Cells(processingRow, 12) = Cells(i, 12) And _
Cells(processingRow, 7) = Cells(i, 7) And _
Cells(processingRow, 6) = Cells(i, 6) And _
Cells(processingRow, 5) = Cells(i, 5) And _
Cells(processingRow, 4) = Cells(i, 4) And _
Cells(processingRow, 3) = Cells(i, 3) And _
Cells(processingRow, 2) = Cells(i, 2) And _
Cells(processingRow, 1) = Cells(i, 1) Then
If Cells(i, 14) <> "" Then
Cells(processingRow, 14) = Cells(processingRow, 14) & "," & Cells(i, 14)
End If
Rows(i).Delete
End If
i = i + 1
Loop
processingRow = processingRow + 1
Loop
When running the macro with 500 rows, it takes a while, but its still reasonable. But I need to run this macro in a excel with more than 2500 rows, and it takes so much time that its not practical anymore.
This is my first macro in excel using VBA, so I was wondering if there is a faster way to process rows/cells, since accessing them individually seems extremelly slow.
Any ideas?
EDITED: I missed that you weren't checking every column to determine what was a duplicate. This should be closer now:
Sub Tester()
Dim rngCheck As Range, rw As Range
Dim dict As Object, k As String, rwDup As Range
Dim rngDel As Range, tmp
Set dict = CreateObject("scripting.dictionary")
With ActiveSheet
Set rngCheck = .Range(.Cells(2, 1), _
.Cells(Rows.Count, 1).End(xlUp)).Resize(, 14)
End With
For Each rw In rngCheck.Rows
k = RowKey(rw)
If dict.exists(k) Then
Set rwDup = dict(k)
tmp = rw.Cells(14).Value
If Len(tmp) > 0 Then
rwDup.Cells(14).Value = rwDup.Cells(14).Value & "," & tmp
End If
If rngDel Is Nothing Then
Set rngDel = rw
Else
Set rngDel = Application.Union(rngDel, rw)
End If
Else
dict.Add k, rw
End If
Next rw
If Not rngDel Is Nothing Then rngDel.EntireRow.Delete
End Sub
'create a "key" for the row by joining all columns to be checked
Function RowKey(rw As Range) As String
Dim arr, x As Long, sep As String, rv As String
arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 12)
For x = LBound(arr) To UBound(arr)
rv = rv & sep & rw.Cells(arr(x)).Value
sep = Chr(0)
Next x
RowKey = rv
End Function