Complicated multiple select case, variable declarations, range definitions - vba

I developed the code below for a budget template I'm creating from scratch. The purpose is to populate automatically actual GL data information into my assumptions tab in the workbook. I'm using one particular month as a test. I have set up the assumptions tab to have about 26 different sections of regional office information in order to determine an appropriate forecast.
Not all GLs are itemized. I've lumped the GLs for a particular expense (other admin) in categories. I have about 5 major categories of spend and the rest of the GLs are considered as "other". Since the categories are not titled exactly the same as the GL accounts I've had to create a map grid on a separate tab in the workbook to link category names with different GLs.
The end goal is to:
Cycle through each category type on the assumptions tab for each PM region office,
Calculate the total amount for each spend (i.e., Evictions) by PM office plus it's cost center in another workbook,
Calculate the total amount for each spend of Entity code only items from that same other workbook.
The code below only cycle through and calculates the spend for Eviction GLs. I'm looking to improve the code for performance improvement, easier future maintenance(flexibility), and efficiency. The end goal is to cycle through the different types of spend. As of right now, my solution to do that is to repeat the variable/range declarations substituting EvictionRg for the next spend, as well as adding another case.
I'm afraid the code will get too long and performance may be at risk. Any insight and guidance as how I can plan this, modify the code, etc. to help me do this will be greatly appreciated. I've been at this for three days trying to figure it out by actually drawing a process map and other methods to help me brainstorm and by reading other posts on SO. I'm afraid I'm at the end of my VBA knowledge.
Sub Try()
'Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim Wb1 As Workbook
Dim Wb2 As Workbook
Set Wb1 = Workbooks("SubModel Forecast_Other Admin v4.xlsm")
Set Wb2 = Workbooks("Feb15 PNL.xlsx")
Dim Wk4 As Worksheet
Set Wk4 = Wb1.Sheets("ASSUMPTIONS")
Dim Wk5 As Worksheet
Set Wk5 = Wb1.Sheets("Validation")
Dim Wk7 As Worksheet
Set Wk7 = Wb1.Sheets("GL Mapping")
Dim Wk1 As Worksheet
Set Wk1 = Wb2.Sheets("det")
Dim fname As String
fname = "Feb15 PNL"
With Wb1 '----submodel
With Wk5 '---validation tab
Dim CCCol As Long
Dim fRowCC As Long
Dim lRowCC As Long
CCCol = Wk5.Cells.Find("Cost Center", lookat:=xlWhole).Column
fRowCC = Wk5.Cells.Find("Cost Center", lookat:=xlWhole).Offset(1, 0).row
lRowCC = Wk5.Cells.Find("Cost Center", lookat:=xlWhole).End(xlDown).row
'---Determine cost center code column range and it's corresponding Region Office Name(ClinkRg)
Dim CCRg As Range
Set CCRg = Wk5.Range(Wk5.Cells(fRowCC, CCCol), Wk5.Cells(lRowCC, CCCol))
Dim CLinkRg As Range
Set CLinkRg = Wk5.Range(Wk5.Cells(fRowCC, CCCol).Offset(0, -1), Wk5.Cells(lRowCC, CCCol).Offset(0, -1))
End With '----closes W5 Validation tab
'----Grid that contains GL accounts and their category type
With Wk7
Dim MapGLCol As Long
MapGLCol = Wk7.Cells.Find("GL", lookat:=xlWhole).Column
Dim MapfRow As Long
MapfRow = Wk7.Cells.Find("GL", lookat:=xlWhole).Offset(1, 0).row
Dim MaplRow As Long
MaplRow = Wk7.Cells(rows.Count, MapGLCol).End(xlUp).row
Dim MapGLRg As Range
Set MapGLRg = Wk7.Range(Wk7.Cells(MapfRow, MapGLCol), Wk7.Cells(MapfRow, MapGLCol))
Dim TypeRg As Range
Set TypeRg = Wk7.Range(Wk7.Cells(MapfRow, MapGLCol).Offset(0, -1), Wk7.Cells(MaplRow, MapGLCol).Offset(0, -1))
End With '--closes wk7 - GL Mapping
End With '--closes Wb1 - SubModel file
'---------PNL wkb
With Wb2
With Wk1
'If Left(Wk2.Name, 5) = "By PM" Then
Dim OpsCol As Long
OpsCol = Wk1.Cells.Find("Property Manager", lookat:=xlWhole).Column
'Else
' OpsCol = Wk1.Cells.Find("Submarket", lookat:=xlWhole).Column
'End If
Dim FRow As Long
Dim LRow As Long
'Dim ExpCol As Long
Dim PropCodeCol As Long
'Dim Expense As String
'Expense = InputBox("Enter Expense GL")
'to locate begining and ending row of data on PNL report
'Identifies the column where the SubMarket names are located for lookup purposes
'Defines the expense GL column to lookup based on the inputbox above
FRow = Wk1.Cells.Find("66990000", lookat:=xlPart).Offset(2, 0).row
LRow = Wk1.Cells.Find("66990000", lookat:=xlPart).End(xlDown).Offset(-1, 0).row
'ExpCol = Wk1.Cells.Find(Expense, lookat:=xlPart).Column
PropCodeCol = Wk1.Cells.Find("Property Code", lookat:=xlWhole).Column
'Defines the Range of the PM
Dim OpsRg As Range
Set OpsRg = Wk1.Range(Wk1.Cells(FRow, OpsCol), Wk1.Cells(LRow, OpsCol))
'Defines the Range of the Property Codes
Dim PropCodeRg As Range
Set PropCodeRg = Wk1.Range(Wk1.Cells(FRow, PropCodeCol), Wk1.Cells(LRow, PropCodeCol))
'Defines the exact range of the expense column being analyzed
'Dim ExpRg As Range
'Set ExpRg = Wk1.Range(Wk1.Cells(FRow, ExpCol), Wk1.Cells(LRow, ExpCol))
'Defining range for GLs under Other Admin
Dim GLRow As Long
Dim BegGLCol As Long
Dim EndGLCol As Long
GLRow = Wk1.Cells.Find("66550000", lookat:=xlPart).row
BegGLCol = Wk1.Cells.Find("66550000", lookat:=xlPart).Column
EndGLCol = Wk1.Cells.Find("66990000", lookat:=xlPart).Column
Dim GLRg As Range
Set GLRg = Wk1.Range(Wk1.Cells(GLRow, BegGLCol), Wk1.Cells(GLRow, EndGLCol))
'----Find All GL accounts in WB1 Wk5 Validation Tab range TypeRg categorized as Evictions($)
'----Then Look up each GL account in the row with all the GLs in the current workbook PNL and Wk1
'----------Set that up as TempCell
'----------Set the range for the entire column of data for each GL and consolidate as one range 'EvictionRg'
'----------Purpose of this is to set up one range for all GL accounts categorized as Eviction GL accoutns
Dim c As Range
For Each c In TypeRg
If c = "Evictions ($)" Then
Dim TempCell As Range
Set TempCell = GLRg.Find(c.Offset(0, 1).Value, lookat:=xlWhole)
'MsgBox (TempCell)
Dim EvictionRg As Range
If EvictionRg Is Nothing Then
Set EvictionRg = Wk1.Range(Wk1.Cells(FRow, TempCell.Column), Wk1.Cells(LRow, TempCell.Column))
Else
Set EvictionRg = Union(EvictionRg, Wk1.Range(Wk1.Cells(FRow, TempCell.Column), Wk1.Cells(LRow, TempCell.Column)))
End If
End If
Next c
'---Sum up all the amounts under all the GL eviction accounts and set them as "z"
Dim z As Double
z = Application.WorksheetFunction.Sum(EvictionRg)
'---Define Ranges for All Entities, Cost Centers, Entities Not Cost Centers
'Define the range on the Property PNL workbook all items booked under an entity
Dim AllEntRg As Range
Dim cell As Range
For Each cell In OpsRg
If cell = "" Then
If AllEntRg Is Nothing Then
Set AllEntRg = Wk1.Cells(cell.row, PropCodeCol)
Else
Set AllEntRg = Union(AllEntRg, Wk1.Cells(cell.row, PropCodeCol))
End If
End If
Next cell
'Define the range of the property PNL workbook that are Entity codes that are NOT Cost Center Codes
'---Entity Codes and Cost Center Codes are within the AllEntRg
'---Create a new range in the Eviction GL Range that intersects
'---------the rows of the entity only codes and the eviction GL columns
With AllEntRg
Dim EntityRg As Range
Dim cl As Range
For Each cl In AllEntRg
If CCRg.Find(cl.Value, lookat:=xlWhole) Is Nothing Then
Dim cl2 As Range
For Each cl2 In EvictionRg '------extra
If cl2.row = cl.row Then '------extra
If EntityRg Is Nothing Then
Set EntityRg = cl2
Else
Set EntityRg = Union(EntityRg, cl2)
End If
End If
Next cl2
End If
Next cl
'MsgBox (EntityRg.Address)
Dim v As Double
v = Application.WorksheetFunction.Sum(EntityRg)
End With
'With AllEntRg
'Dim CostCRg As Range
'Dim r As Range
'For Each r In AllEntRg
' If Not CCRg.Find(r.Value, lookat:=xlWhole) Is Nothing Then
' Dim cl3 As Range
' For Each cl3 In EvictionRg
' If cl3.row = r.row Then
' If CostCRg Is Nothing Then
' Set CostCRg = cl3
' Else
' Set CostCRg = Union(CostCRg, cl3)
' End If
' End If
' Next cl3
' End If
'Next r
'End With
'MsgBox (CostCRg.Address)
'Define cell ranges for regional PM offices that contain more than one cost center ocde
With AllEntRg
If Not AllEntRg.Find("cahied", lookat:=xlWhole) Is Nothing Then
Dim n As Range
Set n = AllEntRg.Find("cahied", lookat:=xlWhole)
End If
'MsgBox (n.Address)
If Not AllEntRg.Find("cahrvr", lookat:=xlWhole) Is Nothing Then
Dim n2 As Range
Set n2 = AllEntRg.Find("cahrvr", lookat:=xlWhole)
'MsgBox (n2.Address)
End If
If Not AllEntRg.Find("atlnw", lookat:=xlWhole) Is Nothing Then
Dim an1 As Range
Set an1 = AllEntRg.Find("atlnw", lookat:=xlWhole)
'MsgBox (an1.Address)
End If
If Not AllEntRg.Find("atln", lookat:=xlWhole) Is Nothing Then
Dim an2 As Range
Set an2 = AllEntRg.Find("atln", lookat:=xlWhole)
'MsgBox (an2.Address)
End If
If Not AllEntRg.Find("atlse", lookat:=xlWhole) Is Nothing Then
Dim ae1 As Range
Set ae1 = AllEntRg.Find("atlse", lookat:=xlWhole)
'MsgBox (ae1.Address)
End If
If Not AllEntRg.Find("atle", lookat:=xlWhole) Is Nothing Then
Dim ae2 As Range
Set ae2 = AllEntRg.Find("atle", lookat:=xlWhole)
'MsgBox (ae2.Address)
End If
If Not AllEntRg.Find("atlsw", lookat:=xlWhole) Is Nothing Then
Dim as1 As Range
Set as1 = AllEntRg.Find("atlsw", lookat:=xlWhole)
'MsgBox (as1.Address)
End If
If Not AllEntRg.Find("atls", lookat:=xlWhole) Is Nothing Then
Dim as2 As Range
Set as2 = AllEntRg.Find("atls", lookat:=xlWhole)
'MsgBox (as2.Address)
End If
End With
'---Create a new range in the Eviction GL Range that intersects
'---------the rows of the specific cost center codes and the eviction GL columns
If Not n Is Nothing Or Not n2 Is Nothing Then
Dim n3 As Range
For Each n3 In EvictionRg
If n3.row = n.row Or n3.row = n2.row Then
Dim InlandRg As Range
If InlandRg Is Nothing Then
Set InlandRg = n3
Else
Set InlandRg = Union(InlandRg, n3)
End If
End If
Next n3
End If
Dim n3v As Double
n3v = Application.WorksheetFunction.Sum(InlandRg)
If Not an1 Is Nothing Or Not an2 Is Nothing Then
Dim an3 As Range
For Each an3 In EvictionRg
If an3.row = an1.row Or an3.row = an2.row Then
Dim ATLNrg As Range
If ATLNrg Is Nothing Then
Set ATLNrg = an3
Else
Set ATLNrg = Union(ATLNrg, an3)
End If
End If
Next an3
End If
Dim an3v As Double
an3v = Application.WorksheetFunction.Sum(ATLNrg)
If Not ae1 Is Nothing Or Not ae2 Is Nothing Then
Dim ae3 As Range
For Each ae3 In EvictionRg
If ae3.row = ae1.row Or ae3.row = ae2.row Then
Dim ATLErg As Range
If ATLErg Is Nothing Then
Set ATLErg = ae3
Else
Set ATLErg = Union(ATLErg, ae3)
End If
End If
Next ae3
End If
Dim ae3v As Double
ae3v = Application.WorksheetFunction.Sum(ATLErg)
If Not as1 Is Nothing Or Not as2 Is Nothing Then
Dim as3 As Range
For Each as3 In EvictionRg
If as3.row = as1.row Or as3.row = as2.row Then
Dim ATLSrg As Range
If ATLSrg Is Nothing Then
Set ATLSrg = as3
Else
Set ATLSrg = Union(ATLSrg, as3)
End If
End If
Next as3
End If
Dim as3v As Double
as3v = Application.WorksheetFunction.Sum(ATLSrg)
End With '---closes Wk1 (PNL report)
End With '--closes wb2
''--------Cycle through the different PM regional office section (column) in assumptions tab
'---------Identify where Evictions ($) is located
'---------calculate total eviction GL amounts for each section (by Entity code only, by PM + cost center code)
With Wb1
With Wk4
Wk4.Outline.ShowLevels RowLevels:=2
Dim dateRow As Long
dateRow = Wk4.Cells.Find("ACT", lookat:=xlWhole).Offset(1, 0).row
Dim fRow2 As Long
Dim AssumCol As Long
Dim lRow2 As Long
fRow2 = Wk4.Cells.Find("Global Assumptions", lookat:=xlWhole).row
AssumCol = Wk4.Cells.Find("Global Assumptions", lookat:=xlWhole).Column
lRow2 = Wk4.Cells(rows.Count, AssumCol).End(xlUp).row
Dim AssumptionRg As Range
Set AssumptionRg = Wk4.Range(Wk4.Cells(fRow2, AssumCol), Wk4.Cells(lRow2, AssumCol))
Dim r2 As Range
Dim isItem As Boolean
For Each r2 In AssumptionRg
Select Case r2
Case "Evictions ($)"
isItem = True
Dim PM As Range
Set PM = r2.End(xlUp)
'---If PM Label is Entity Level, Inland Empire or is one of the Atlanta PMs then
'-----IF Entity Level, the sum up the Entity Range for the Evictions
'-----IF Inland Empire, sum up Inland Empire properties and Inland Empire Cost Center entries
'-----IF Atlanta, the sum up Atlanta PMs and their cost center entries individually
If PM = "Tie-Out To Actuals" Or PM = "Entity Level Assumptions" _
Or PM = "Inland Empire" Or PM = "Atlanta East" _
Or PM = "Atlanta North" Or PM = "Atlanta South" Then
If PM = "Tie-Out To Actuals" Then
Wk4.Cells(r2.row, 4) = z
End If
If PM = "Entity Level Assumptions" Then
Wk4.Cells(r2.row, 4) = v
End If
If PM = "Inland Empire" Then
Wk4.Cells(r2.row, 4).Formula = _
"=SUMPRODUCT(('[" & fname & ".xlsx]det'!" & OpsRg.Address & "=" & PM.Address & ")*'[" & fname & ".xlsx]det'!" & EvictionRg.Address & ")" _
& "+" & n3v
Wk4.Cells(r2.row, 4).Value = Wk4.Cells(r2.row, 4).Value
End If
If PM = "Atlanta East" Then
Wk4.Cells(r2.row, 4).Formula = _
"=SUMPRODUCT(('[" & fname & ".xlsx]det'!" & OpsRg.Address & "=" & PM.Address & ")*'[" & fname & ".xlsx]det'!" & EvictionRg.Address & ")" _
& "+" & ae3v
End If
If PM = "Atlanta North" Then
Wk4.Cells(r2.row, 4).Formula = _
"=SUMPRODUCT(('[" & fname & ".xlsx]det'!" & OpsRg.Address & "=" & PM.Address & ")*'[" & fname & ".xlsx]det'!" & EvictionRg.Address & ")" _
& "+" & an3v
End If
If PM = "Atlanta South" Then
Wk4.Cells(r2.row, 4).Formula = _
"=SUMPRODUCT(('[" & fname & ".xlsx]det'!" & OpsRg.Address & "=" & PM.Address & ")*'[" & fname & ".xlsx]det'!" & EvictionRg.Address & ")" _
& "+" & as3v
End If
Else
Dim CCCodeRow As Long
Dim CCCodeCol As Long
CCCodeRow = CLinkRg.Find(PM.Value, lookat:=xlWhole).Offset(0, 1).row
CCCodeCol = CLinkRg.Find(PM.Value, lookat:=xlWhole).Offset(0, 1).Column
If Wk5.Cells(CCCodeRow, CCCodeCol).Value = "None" Then
Wk4.Cells(r2.row, 4).Formula = _
"=SUMPRODUCT(('[" & fname & ".xlsx]det'!" & OpsRg.Address & "=" & PM.Address & ")*'[" & fname & ".xlsx]det'!" & EvictionRg.Address & ")"
Else
Wk4.Cells(r2.row, 4).Formula = _
"=SUMPRODUCT(('[" & fname & ".xlsx]det'!" & OpsRg.Address & "=" & PM.Address & ")*'[" & fname & ".xlsx]det'!" & EvictionRg.Address & ")" _
& "+SUMPRODUCT(('[" & fname & ".xlsx]det'!" & PropCodeRg.Address & "=" & "Validation!" & Wk5.Cells(CCCodeRow, CCCodeCol).Address & ")*'[" & fname & ".xlsx]det'!" & EvictionRg.Address & ")"
End If
End If
End Select
Next r2
Set r2 = Nothing
Set Wk4 = Nothing
End With '---closes assumptions tab
End With '---workbook2
'Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True

Whew, that is quite the read! Although I agree with Comintern, I do see a pretty simple solution to trimming up your code, and making it easier to maintain.
I don't see a single function in the entire thing. If you're writing scripts that long, and not using them, you need to start... They'll change your life.
Lets take a simple block that I see repeated several (8) times. Note that I see several much larger blocks that are repeated throughout, but this one will be easy to learn on.
If Not AllEntRg.Find("atlsw", lookat:=xlWhole) Is Nothing Then
Dim as1 As Range
Set as1 = AllEntRg.Find("atlsw", lookat:=xlWhole)
End If
I only see three things that change from if to if in this part of the code, 2 on the input side are a range and a string, and it outputs a range if conditions are met. So you write a function like this, and place it somewhere in any module in the same workbook.
Public Function DefMultiCCPMRange(rngSearchRange as range, strSearchString as string)as range
If Not AllEndRg.Find(strSearchString, lookat:=xlWhole) Is Nothing Then
set DefMultiCCPMRange = rngSearchRange.Find(strSearchString, Lookat:=xlWhole)
End If
End Function
Now instead of rewriting this over and over.
If Not AllEntRg.Find("atlsw", lookat:=xlWhole) Is Nothing Then
Dim as1 As Range
Set as1 = AllEntRg.Find("atlsw", lookat:=xlWhole)
End If
You write this over and over.
Dim as1 as Range
set as1 = DefMultiCCPMRange(AllEndRg,"atlsw")
Also the lifetime of the variables that are used in the function, ends when the function ends, so your not storing every variable you use in memory for the entire duration of runtime.
That should take you a long way if you play with it.
I would also look into Arrays, Collections, and Dictionary items. They'll change your life too once you see where their power really lies. You could get creative and instead of declaring and setting that range 8 times, you could do a for loop, and put them all in one object named for the CC Code.
Dim arrCCCodes(3) as string 'change to arrCCCodes(7) for your 8 codes
arrCCCodes(0) = "cahied"
arrCCCodes(1) = "cahrvr"
arrCCCodes(2) = "atlnw"
arrCCCodes(3) = "atln"
'etc...
'add a reference to Microsoft scripting runtime
Dim odicCCRanges as New Dictionary
For i = 0 to UBound(arrCCCodes)
odicCCRanges.Add arrCCCodes(i), DefMultiCCPMRange(AllEndRg, arrCCCodes(i))
next
This will give you a dictionary object with 4 ranges in it (would be 8 in your actual code), not to mention lose a couple pages of code. You would call the values in the range like odicCCRanges("cahied").Item(1) or odicCCRanges(arrCCCodes(0)).Item(1). Here's where it adds to your project lifetime. If you need to add a new CC, you just change the arrCCCodes declaration to include one more item, then add it below, and the rest of our code will automatically pick it up, run the define ranges function, and add it to the dictionary.
Your code doesn't look all that bad, your testing for nulls, and declaring your vars, all good stuff. It's just all series scripted. Try stepping through your code, and watching the locals window in the VBA IDE. Particularly expand a range node after its set. It'll blow your mind what is actually in a Range Object.

You obviously have a lot of time invested in this but I really think you are over complicating things. Since all your code is doing is building ranges and then summing them I'm thinking you could do this with array formulas.

Related

VBA to Copy and Paste Based on Two Criterias

It's is possible to create a macro that do a mathematical sum?
I don't know how to explain. But I already saw another answers but I could not make work for me.
Here is what I trying to do:
I have this Worksheet
iTEM 1 [1]: https://i.stack.imgur.com/v7vXF.jpg
And I to put values as the image below. Make a search and make a mathematical sum in the "available" according with the group.
ITEM 2 [2]: https://i.stack.imgur.com/wQnxu.png
Here would be the result:
ITEM 3 [3]: https://i.stack.imgur.com/ify7J.png
To answer your question, tongue in cheek, Excel is very good at doing mathematical sums, and with the help of VBA it gains versatility. To prove the point, the code below doesn't only act upon your selection in the 'Update' sheet, it takes all the items in the 'Update' sheet and posts them to the 'Database' sheet. Click twice and it's done twice over. There is no break.
Option Explicit
Enum Nup ' Sheet Update
NupFirstDataRow = 2
NupName = 1 ' 1 = column A
NupGroup = 5
NupQty = 7
End Enum
Enum Ndt ' Sheet Data
NdtFirstDataRow = 2
NdtName = 1 ' 1 = column A
NdtGroup = 3
NdtQty ' = 4
NdtOffset = 3 ' NdtGroup + NdtOffset = Group2 column
End Enum
Sub UpdateQuantity()
' 09 Jan 2018
Dim WsUpdate As Worksheet ' Sheet where data are entered
Dim WsData As Worksheet ' Sheet where data are updated
Dim Rng As Range
Dim SearchRng As Range
Dim Itm As String ' an item's name
Dim Qty As Long ' Update quantity (designed for integers)
Dim Rt As Long ' target row in WsData
Dim Rl As Long ' last row in WsUpdate
Dim ClmOffset As Long ' helper
Dim R As Long ' row counter in WsUpdate
Dim Ct As Ndt ' column in WsData
Set WsUpdate = Worksheets("Update")
Set WsData = Worksheets("Database")
With WsData
Rl = .Cells(.Rows.Count, NupName).End(xlUp).Row
Set Rng = Range(.Cells(NdtFirstDataRow, NdtName), .Cells(Rl, NdtQty + NdtOffset))
End With
Application.ScreenUpdating = False
With WsUpdate
Rl = .Cells(.Rows.Count, NupName).End(xlUp).Row
For R = NupFirstDataRow To Rl
Itm = .Cells(R, NupName).Value
Set SearchRng = Range(Rng.Columns(NdtName), Rng.Columns(NdtName))
If CellAddress(Itm, SearchRng, Rt) Then
Itm = .Cells(R, NupGroup).Value
With WsData
Set SearchRng = Range(.Cells(R, NdtGroup), .Cells(R, NdtGroup + NdtOffset))
End With
If CellAddress(Itm, SearchRng, Ct) Then
Qty = Val(.Cells(R, NupQty).Value)
With WsData.Cells(Rt, Ct + 1)
Qty = Val(.Value) + Qty
.Value = Qty
End With
End If
End If
Next R
End With
Application.ScreenUpdating = True
End Sub
Private Function CellAddress(ByVal Itm As String, _
SearchRange As Range, _
Rc As Long) As Boolean
' 09 Jan 2018
' Rc is a return variable (either column or row = 0 if not found)
Dim ClmRng As Range
Dim Fnd As Range
Dim i As Long
With SearchRange
Set Fnd = .Find(What:=Itm, After:=.Cells(.Cells.Count), _
LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False)
If Fnd Is Nothing Then
Rc = 0
MsgBox "Item """ & Itm & """ wasn't found.", _
vbInformation, "Update failed"
Else
Rc = IIf(.Rows.Count > 1, Fnd.Row, Fnd.Column)
CellAddress = True
End If
End With
End Function
The enumerations at the top of the code control which columns and rows are used. You can modify these numbers. Observe that the quantity columns in the Database must be adjacent to the Group columns. The only other place in the code you may have to change concerns the names of the two worksheets. The code must be in a standard code module in the same workbook.

VBA Concatenate Cells from two columns based on criteria present in a third

I'm currently trying to combine two sets of data from two different columns based upon another set of data being present within a third column.
In this case I need basic country tags from column G (Strings of three letter codes) to be joined to the front of the string present in column H providing the string "Missing Audio" or "Missing Audio/Subs" is present in column F
My current fixed variable is that all data will start on the sixth row of their various columns however they will run to an undetermined point.
I've got various pieces of attempted code I'd be happy to post, but none of them seem to work and I'm beginning to get the impression that I've over complicated the process in my head and its creating a form of coders block! Any help offered would be massively appreciated
As Asked here is my attempt, trying to pull in a basic Concatenate script with the variable requirements:
Sub Notestidy()
Dim w1 As Worksheet
Dim c As Range, AC As String
Dim rng As Range, rng2 As Range, iRow As Integer, iCol As Integer, i As Integer
Application.ScreenUpdating = False
Set w1 = Worksheets("Sheet1")
Set rng = w1.Range("G6", w1.Range("G" & Rows.Count).End(X1up))
Set rng2 = w1.Range("H6", w1.Range("H" & Rows.Count).End(X1up))
For Each c In w1.Range("F6", w1.Range("F" & Rows.Count).End(xlUp))
AC = .String("Missing Audio", "Missing Audio/Subs")
On Error Resume Next
If c = Range Then
Dim varConctnt As Variant
For iRow = 1 To rng.Rows.Count: rng2.Rows.Count
For iCol = 1 To rng.Columns.Count: rng2.Columns.Count
If Not rng(iRow, iCol).Value = vbNullString Then
varConctnt = varConctnt & ", " & rng(iRow, iCol).Value
End If
Next iCol
If varConctnt = vbNullString Then MsgBox "Empty Array": GoTo skip1
MsgBox Mid(varConctnt, 2)
varConctnt = ""
skip1:
Next iRow
Application.ScreenUpdating = True
End Sub
New Code Attempt:
> Sub Notestidy()
>
> Dim w1 As Worksheet Dim rng As Range Dim FirstRow As Integer
>
>
>
> Set w1 = Worksheets("Sheet1") FirstRow = 0
>
>
> Set rng = w1.Range("F:F").Find(What:="Missing Audio",
> LookIn:=xlValues, _ LookAt:=xlPart, SearchDirection:=xlNext,
> MatchCase:=False) While Not rng Is Nothing If FirstRow <> rng.Row
> Then
> If FirstRow = 0 Then
> FirstRow = rng.Row
> End If
> w1.Range("H" & rng.Row) = w1.Range("G" & rng.Row) & w1.Range("H" & rng.Row)
> rng.FindNext After:=rng Else
> Set rng = Nothing End If Wend
>
> Application.ScreenUpdating = True
>
> End Sub
A couple of pointers to get you started:
never use Application.ScreenUpdating = False until your code works. It only hides what's going on and make it much more difficult to figure out issues
There are very rare and specific times you want to use On Error Resume Next, and this isn't one of them. All it does is mask the fact that a run-time error occurred. It allows you to handle specific errors that you expect might happen. That's not what you're after here.
Assignment of worksheets & ranges to variables instead of using Active... is excellent!
.String is not a valid method or object, and when not included in a With...End With block, the leading-dot notation is invalid
Combining multiple statements on a single line with the : is valid, and some people do it. My personal opinion is that it makes code more difficult to read and mentally parse, even though the compiler has no issue with it. This is especially true when you combine some code on one line with :, then have additional code to be executed in that code block that's not combined on that one line.
Proper and consistent indentation will help you figure out your code blocks and where to end a block that's begun.
Built in functions, like .Find() are much, much faster than looping through blocks of cells looking for things.
so...
Sub Notestidy()
Dim w1 As Worksheet
Dim rng As Range
Dim FirstRow as Integer
'Application.ScreenUpdating = False
Set w1 = Worksheets("Sheet1")
FirstRow = 0
'find "Missing Audio" in column F
set rng = w1.Range("F:F").Find (What:="Missing Audio", LookIn:=xlValues, _
LookAt:=xlPart) 'look at some additional parameters here
While Not rng is Nothing 'if we found it somewhere
If FirstRow <> rng.row Then 'we haven't wrapped back around to the first found item
If FirstRow = 0 then 'this is our first time through the loop
FirstRow = rng.row 'Store off the first found row
End If
'Assign H = F & H for this row.
w1.Range("H" & rng.row) = w1.Range("F" & rng.row) & w1.Range("H" & rng.row)
'------------------------
'update this line:
Set rng = rng.FindNext After:=rng 'find the next occurrence
Else
set rng = nothing 'we've wrapped around the search, let's get out
End If
Wend
Application.ScreenUpdating = True
End Sub
A couple of final notes:
Be aware that .Find() will use whatever parameters were last set in the search dialog box and that parameters set in code will be reflected in the dialog box. There's nothing wrong with this, you just need to understand that anything you don't explicitly set when calling it from code will leave you using whatever random setting happened to be from your last search. You may want to look at the MS Docs on .find() for all the parameters.
While on the topic of .Find() it will wrap around and find the first occurrence again. If you're not replacing what you're finding, you'll end up in an infinite loop, so you have to store off a marker of some sort (FirstRow, in this case) so you know when you get back there.
On the w1.Range("H" & rng.row) = line, you can add some sort of delimited in there between the country code (Col F) and the string in Col H if you want. I didn't know what you may have wanted, so I just slammed them together.
Step through your code in the debugger using the F8 key to execute 1 line of code at a time. That will help you understand what's going on in your code.
If I have interpreted your question correctly, I think you may have overcomplicated it!
Sub Notestidy()
Dim cc, sc, nc As String
Dim ws As Worksheet
Set ws = Sheet1
cc = "G"
sc = "H"
nc = "F"
Dim i, max As Integer
max = ws.UsedRange.Rows.Count
For i = 2 To max
If ws.Range(nc & i).Value = "Missing Audio" Or ws.Range(nc & i).Value = "Missing Audio/Subs" Then
ws.Range(sc & i).Value = ws.Range(cc & i).Value & " " & ws.Range(sc & i).Value
End If
Next i
Set ws = Nothing
End Sub
Edit: Or as per FreeMan's answer utilising the Find method:
Sub Notestidy()
Dim w1 As Worksheet
Dim result As Range
Dim FirstRow As Integer
Set w1 = Worksheets("Sheet1")
FirstRow = 0
With w1.Range("F:F")
Set result = .Find(What:="Missing Audio", LookIn:=xlValues, LookAt:=xlPart)
If Not result Is Nothing Then
FirstRow = result.Row
Do
w1.Range("H" & result.Row) = w1.Range("G" & result.Row) & w1.Range("H" & result.Row)
Set result = .FindNext(result)
Loop While Not result Is Nothing And result.Row <> FirstRow
End If
End With
Set w1 = Nothing
Set result = Nothing
End Sub

Create various ranges if cell is found or not found in another workbook

I have been struggling for a day and a half with my code. I have a spreadsheet with over 50 columns 18000 rows. I have been able to identify a smaller range of cells in column A defined by "AllEntRg" based on blank cells in column H(OpsCol). I'm stuck with my loops towards the bottom. For EntityRg, I am looping through each cell in "AllEntRg" and if it is Not found in Range CCRg which was defined in BudWb Wk4 Then I want to create a range of all of those cells. The next option, CostCRg, I want to define a range for all cells that ARE FOUND in CCrg.
I have tested this by selecting individual cells and it provides the results I'm looking for but when I have this in the loops I'm getting the following two results: For EntityRg, the range.address defined is the same as AllEntRg (this shouldn't be the case). For CostCRg, I'm getting an error. I'm not sure what I'm not defining correctly. I've been stuck here for quite a while and I have tried using Match Function as well. Again, individually it works but in the loop I'm getting these results which are not expected. I'm interested on the feedback I may receive. Thanks.
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim wb As Workbook
Dim BudWkb As Workbook
Dim Wk2 As Worksheet
Dim PNLWkb As Workbook
Dim fpath As String
Dim fname As String
Set BudWkb = Workbooks("SubModel Forecast_Other Admin v4.xlsm")
Set Wk2 = BudWkb.Sheets("By PM")
fname = "Feb15 PNL"
'fname = InputBox("Enter PNL File Name")
Dim Wk4 As Worksheet
Set Wk4 = BudWkb.Sheets("Validation")
With Wk4
Dim CCCol As Long
Dim fRowCC As Long
Dim lRowCC As Long
CCCol = Wk4.Cells.Find("Cost Center", lookat:=xlWhole).Column
fRowCC = Wk4.Cells.Find("Cost Center", lookat:=xlWhole).Offset(1, 0).row
lRowCC = Wk4.Cells.Find("Cost Center", lookat:=xlWhole).End(xlDown).row
Dim CCRg As Range
Set CCRg = Wk4.Range(Wk4.Cells(fRowCC, CCCol), Wk4.Cells(lRowCC, CCCol))
'MsgBox (CCRg.Address)
End With
Set PNLWkb = Workbooks("Feb15 PNL.xlsx")
Dim Wk1 As Worksheet
Set Wk1 = PNLWkb.Sheets("det")
With Wk1
If Left(Wk2.Name, 5) = "By PM" Then
Dim OpsCol As Long
OpsCol = Wk1.Cells.Find("Property Manager", lookat:=xlWhole).Column
Else
OpsCol = Wk1.Cells.Find("Submarket", lookat:=xlWhole).Column
End If
Dim FRow As Long
Dim lRow As Long
Dim ExpCol As Long
Dim PropCodeCol As Long
Dim Expense As String
Expense = InputBox("Enter Expense GL")
'to locate begining and ending row of data on PNL report
'Identifies the column where the SubMarket names are located for lookup purposes
'Defines the expense GL column to lookup based on the inputbox above
FRow = Wk1.Cells.Find("66990000", lookat:=xlPart).Offset(2, 0).row
lRow = Wk1.Cells.Find("66990000", lookat:=xlPart).End(xlDown).Offset(-1, 0).row
ExpCol = Wk1.Cells.Find(Expense, lookat:=xlPart).Column
PropCodeCol = Wk1.Cells.Find("Property Code", lookat:=xlWhole).Column
'Defines the Range of the PM or Sub-Market Names
Dim OpsRg As Range
Set OpsRg = Wk1.Range(Wk1.Cells(FRow, OpsCol), Wk1.Cells(lRow, OpsCol))
'Defines the Range of the Property Codes
Dim PropCodeRg As Range
Set PropCodeRg = Wk1.Range(Wk1.Cells(FRow, PropCodeCol), Wk1.Cells(lRow, PropCodeCol))
'Defines the exact range of the expense column being analyzed
Dim ExpRg As Range
Set ExpRg = Wk1.Range(Wk1.Cells(FRow, ExpCol), Wk1.Cells(lRow, ExpCol))
End With
Dim AllEntRg As Range
For Each Cell In OpsRg
If Cell = "" Then
If AllEntRg Is Nothing Then
Set AllEntRg = Cells(Cell.row, PropCodeCol)
Else
Set AllEntRg = Union(AllEntRg, Cells(Cell.row, PropCodeCol))
End If
'End If
End If
Next
MsgBox (AllEntRg.Address)
'MsgBox (Application.Match(Wk1.Cells(59, 1), CCRg, 0))
'Dim y
'y = Application.Match(Wk1.Cells(10, 1), CCRg, 0)
'If IsError(y) Then
'MsgBox ("pooopy error")
'End If
Dim EntityRg As Range
'Dim c As Range
For Each c In AllEntRg
'Dim z
'z = Application.Match(c, CCRg, 0)
If CCRg.Find(c.Value, lookat:=xlPart) Is Nothing Then
If EntityRg Is Nothing Then
Set EntityRg = c
Else
Set EntityRg = Union(EntityRg, c)
End If
End If
Next
MsgBox (EntityRg.Address)
Dim CostCRg As Range
Dim r As Range
For Each r In AllEntRg
If Not CCRg.Find(r.Value, lookat:=xlPart) Is Nothing Then
If CostCRg Is Nothing Then
Set CostCRg = r
Else
Set CostCRg = Union(CostCRg, r)
End If
End If
Next
MsgBox (CostCRg.Address)
Dim v As Double
v = Application.WorksheetFunction.Sum(EntityRg)
'SendKeys "{F9}"
MsgBox (v)
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
I have no means of running your code but I have reviewed it and have noticed some possible problems.
lRowCC = Wk4.Cells.Find("Cost Center", lookat:=xlWhole).End(xlDown).row
`.End(xlDown) is not a reliable method of finding the last row of a column. Read this answer of mine for an explanation: Excel vba – xlDown
You say: “For EntityRg, the range.address defined is the same as AllEntRg (this shouldn't be the case).”
Do you believe they are the same because EntityRg.Address = AllEntRg.Address?
EntityRg .Address will be a string of absolute cell and range addresses separated by commas. You may not be aware that this string has a maximum length of about 255. I cannot find any documentation but from my own experimentation, EntityRg .Address will be truncated to less than 256 such that there is no partial cell or range address.
Are you being fooled by the first 255 characters of these addresses matching?
Another possibility is that every use of CCRg.Find(c.Value, lookat:=xlPart) returns Nothing so EntityRgand AllEntRg are equal. You say CostCRg gives an error; is this because it is Nothing?
You have two loops searching CCRg for values in AllEntRg. One loop records the successes and one records the failures. Why not combine the loops into something like:
If CCRg.Find(c.Value, lookat:=xlPart) Is Nothing Then
If EntityRg Is Nothing Then
Set EntityRg = c
Else
Set EntityRg = Union(EntityRg, c)
End If
Else
If CostCRg Is Nothing Then
Set CostCRg = r
Else
Set CostCRg = Union(CostCRg, r)
End If
End If
I am concerned that For Each c In AllEntRg is not giving you what you expect. If you combine ranges with Union, it will tidy them up. So Union(Range("A2"), Range("A3", Range("A5"), Range("A6"), Range("A7")).Address is
"$A$2:$A$3,$A$5:$A$7" not "$A$2,$A$3,$A$5,$A$6,$A$7". My recollection is that For Each c In AllEntRg would not split "$A$2:$A$3" into separate cells.
Please use F8 to step through this loop to check that it is performing as you expect.
Hope this helps
Answer to problem described in comment
Your problem is you are not being consistent in you use of Withs and, in particular, you are not identifying which workbook you want to operate on.
Wk4 is explicitly specified to be within workbook BufdWkb and Wk1 is specified to be within PNLWkb.
However, in
Set AllEntRg = Cells(Cell.row, PropCodeCol)
you do not specify a worksheet or workbook for Cells. This is the equivalent of
Set AllEntRg = ActiveWorkbook.ActiveSheet.Cells(Cell.row, PropCodeCol)`
You need to write Set AllEntRg = .Cells(Cell.row, PropCodeCol) (note period before Cells) and include this code within the With Wk1 Block.

VBA Loop through rows but fails to exclude previous row contents

I have the following Loop which dispatches email for every used row in the worksheet starting from the second row.
The loop itself works and it dispatches email for every used row starting from the second row.
However, my loop fails to exclude the previous row contents and accumulate them in the each of the next rows' emails. I suspect it is to do with my cell referencing. Would be great if you can help with this :).
Sub TestEmail1()
Application.ScreenUpdating = False
Dim aOutlook As Object
Dim aEmail As Object
Dim rngeAddresses As Range, rngeCell As Range, strRecipients As String
Dim ccAddresses As Range, ccCell As Range, ccRecipients As String
Dim rngeSubject As Range, SubjectCell As Range, SubjectContent As Variant
Dim rngeBody As Range, bodyCell As Range, bodyContent As Variant
Dim Table1 As Range
Dim i As Integer
Set rng = ActiveSheet.UsedRange
LRow = rng.Rows.Count
For i = 2 To LRow
Set Table1 = Worksheets(1).Range("K1:R1")
Set Table2 = Worksheets(2).Range("K" & i & ":" & "R" & i)
Set aOutlook = CreateObject("Outlook.Application")
Set aEmail = aOutlook.CreateItem(0)
'set sheet to find address for e-mails as I have several people to
'mail to
Set rngeAddresses = ActiveSheet.Range("B" & i)
For Each rngeCell In rngeAddresses.Cells
strRecipients = strRecipients & ";" & rngeCell.Value
Next
Set ccAddresses = ActiveSheet.Range("C" & i)
For Each ccCell In ccAddresses.Cells
ccRecipients = ccRecipients & ";" & ccCell.Value
Next
Set rngeSubject = ActiveSheet.Range("D" & i)
For Each SubjectCell In rngeSubject.Cells
SubjectContent = SubjectContent & SubjectCell.Value
Next
Set rngeBody = ActiveSheet.Range("E" & i)
For Each bodyCell In rngeBody.Cells
bodyContent = bodyContent & bodyCell.Value
Next
'set Importance
'aEmail.Importance = 2
'Set Subject
aEmail.Subject = rngeSubject
'Set Body for mail
'aEmail.Body = bodyContent
aEmail.HTMLBody = bodyContent & "<br><br><br>" & RangetoHTML_ (Table1)
aEmail.To = strRecipients
aEmail.CC = ccRecipients
aEmail.Send
Next i
Exit Sub
End Sub
Yes, you are accumulating longer and longer strings in SubjectContent, bodyContent and the other similar variables. Each time you pass through the For i = 2 to lRow loop you add the value of the specified cell in the current row to the associated Content variable.
For some reason you've also got an inner loop for each email area, e.g, For Each bodyCell in rgneBody.Cells. If I'm reading your code correctly none of those loops are needed.
So, taking into account the above, I would change:
Set rngeBody = ActiveSheet.Range("E" & i)
For Each bodyCell In rngeBody.Cells
bodyContent = bodyContent & bodyCell.Value
Next
to:
bodyContent = ActiveSheet.Range("E" & i)
You can use the intermediate rngeBody variable if you think it's more readable, but it's not necesssary.
Repeat the above for your other email area variables.

excel macro code is skipping every 2nd line

the below code is to take an employee name, (Column A) andput the range ("A:J") of that row into a new sheet of that employee, if they dont have a sheet, then make one and ad the heading. However, it is skipping every second line, which is causing the line that it is scanning the name on, and the line it is copying from to be different (ie:Employees are going in the wrong sheets, and only 1/2 are getting moved)
Any help would be great
Set rngEmpSales = wsSales.Range("A2", wsSales.Range("A" & Rows.Count).End(xlUp).Address)
Dim wsSales As Worksheet, wsDesSales As Worksheet
Set wsSales = ThisWorkbook.Sheets("Sales")
Dim SalesCount as Range
For Each SalesCount In rngEmpSales
On Error Resume Next
Set wsDesSales = ThisWorkbook.Sheets(Trim(SalesCount.Value))
On Error GoTo 0
If wsDesSales Is Nothing Then
Set wsDesSales = ThisWorkbook.Sheets.Add(after:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
wsDesSales.Name = SalesCount.Value
End If
SalesCount(1 - (SalesCount.Row - 1)).Range("A1:J1").Copy wsDesSales.Range("K2")
SalesCount.Range("A" & SalesCount.Row & ":J" & SalesCount.Row).Copy wsDesSales.Range("K" & Rows.Count).End(xlUp).Offset(1, 0)
Set wsDesSales = Nothing
End If
Next SalesCount
Is this what you are trying? (UNTESTED)
Sub Sample()
Dim wsSales As Worksheet, wsDesSales As Worksheet
Dim rngEmpSales As Range, SalesCount As Range
Dim shName As String
Dim lRow As Long, i As Long
Set wsSales = ThisWorkbook.Sheets("Sales")
With wsSales
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set rngEmpSales = .Range("A2:A" & lRow)
For i = 2 To lRow
shName = Trim(.Range("A" & i).Value)
On Error Resume Next
Set wsDesSales = ThisWorkbook.Sheets(shName)
On Error GoTo 0
If wsDesSales Is Nothing Then
Set wsDesSales = ThisWorkbook.Sheets.Add(after:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
wsDesSales.Name = shName
End If
.Range("A1:J1").Copy wsDesSales.Range("K2")
.Range("A" & i & ":J" & i).Copy wsDesSales.Range("K" & _
wsDesSales.Rows.Count).End(xlUp).Offset(1, 0)
Set wsDesSales = Nothing
Next i
End With
End Sub
You should use
wssales.Range("A" & SalesCount.Row & ":J" & SalesCount.Row) instead of SalesCount.Range("A" & SalesCount.Row & ":J" & SalesCount.Row)
and
wssales.Range("A1:J1").Copy instead of
SalesCount(1 - (SalesCount.Row - 1)).Range("A1:J1").Copy
The reason is SalesCount itself is a range, when you apply another .Range after it, it will take the relative position.
e.g. Range("A2").Range("A1:J1") becomes Range("A2:J2") and Range("B2").Range("B2:K2") becomes Range("B2:K2")