Tables and loops VBA - excel

I am new to coding and have written some code to do some calculations within a table and fill in columns. I have it working for the first row within the table but I am having some trouble figuring out how to loop it so that it completes the calculations for every row within the table. Any help and advice would be greatly appreciated!
UPDATE:
Thanks for the Help! The code works perfectly for the first part provided here, I have tried to apply this to the other 2 parts, but am coming up with an error. I think due to the fact that I am trying to use a string as the input? I have tried without the quotation marks but all it returns is "#NAME?".
Sub CommandButton1_Click()
Dim tbl As ListObject
Set tbl = ThisWorkbook.Sheets("Data").ListObjects("Table1")
Dim formulaText As String
formulaText =
"=IF([#Reach]>=100000,5,IF([#Reach]>=50000,3,IF([#Reach]>=10000,2,1)))"
tbl.ListColumns("Media Significance").DataBodyRange.Formula = formulaText
Dim formulaText1 As String
formulaText1 = "=IF([#Headline Mentions]>="Yes",5,IF([#Exclusive
Mentions]>="Yes",3,1))"
tbl.ListColumns("Prominence Score").DataBodyRange.Formula = formulaText1
Dim formulaText2 As String
formulaText2 = "=IF([#Sentiment]>="Very Positive",2,IF([#Sentiment]>="Very
Negative",2,1))"
tbl.ListColumns("Very Positive/ Very Negative").DataBodyRange.Formula =
formulaText2
End Sub

Looping through each cell in a range is very slow, so you're either going to want to either load your data into an array first, or use a regular Excel formula + the FillDown function.
In this particular case, I'd recommend the second option, which will allow you to add your formula to a single cell and fill it down the rest of the column. Something like this should work:
Dim colNum As Long
With ThisWorkbook.Sheets("Example Sheet")
'Find last row in sheet
Dim lastRow As Long
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
'Add first formula
colNum = .Range("V2").Column
.Cells(2, colNum).Formula = "=IF(T2>=100000,5,IF(T2>=50000,3,IF(T2>=10000,2,1)))"
.Range(.Cells(2, colNum), .Cells(lastRow, colNum)).FillDown
End With
One problem with your current code is that the column letters are hard-coded. IE, you're expecting to find something called "Reach" in column L, and assuming that this will always be the case. However, if you ever add another column to the left of "Reach", it will break your code.
That's one reason why I'd probably recommend turning your range into a table object with descriptive column names. That should make your code much easier to read and maintain, like this:
Dim tbl As ListObject
Set tbl = ThisWorkbook.Sheets("Example Sheet").ListObjects("YourTable")
Dim formulaText As String
formulaText = "=IF([#Reach]>=100000,5,IF([#Reach]>=50000,3,IF([#Reach]>=10000,2,1)))"
tbl.ListColumns("Reach Analysis").DataBodyRange.Formula = formulaText

For starters, you have redundant criteria in your first If/ElseIf/End If statement.
This,
If Reach >= 100000 Then
Result = 5
ElseIf Reach < 100000 And Reach >= 50000 Then
Result = 3
ElseIf Reach < 50000 And Reach >= 10000 Then
Result = 2
ElseIf Reach < 10000 Then
Result = 1
End If
... can be written more succinctly as,
If Reach >= 100000 Then
Result = 5
ElseIf Reach >= 50000 Then
Result = 3
ElseIf Reach >= 10000 Then
Result = 2
Else
Result = 1
End If
These If/ElseIf/Else/End If conditions are resolved sequentially. Since you won't get into the second criteria unless Reach is less than 100000, there is no need to put that specification into the second criteria. The same logic can be applied for the remainder of the conditions.
Your second If/ElseIf/End If has an error in syntax.
ElseIf Headline = "No" And Exclusive = Yes Then
The Yes here should be quoted or the condition will be looking for a variable named Yes. Putting Option Explicit at the top of the module code sheet in the Declarations area will catch these errors quickly. You can also access the VBE's Tools, Options command and put a checkmark beside Require Variable Declaration and Option Explicit will be automatically put into the Declaration area of each new code sheet you create.

Related

How to multiply a range of values in Excel by a scalar variable using VBA?

I have implemented this method to multiply every array element by a number held in a variable. It is terribly slow.
Is there an accepted "fastest" way to multiply every element in a range by a constant? Or at least one which is not as slow? I have to do this 10 times and it takes a couple of minutes.
MultFactor = 10
For Each cell In Sheet1.Range("B3:B902")
cell.Value = cell.Value * MultFactor
Next cell
The solution cited in Multiply Entire Range By Value? multiplies by a constant (not a variable). If I use this code (changing the range from "A1:B10" to "B3:B902"), I get a nonsense answer.
Dim rngData As Range
Set rngData = Sheet12.Range("B3:B902")
rngData = Evaluate(rngData.Address & "*2")
My original values in B3:B902 are zero for the first 100 elements or so and then increase a bit and finally decrease and have another run of zeros, but what ends up in my range is a series of numbers that clobbers everything in my range. It begins at -224.5 and decreases by 0.5 all the way to the last cell.
-224.5
-224.0
-223.5
etc.
Even if that worked, how would I modify it to use the variable MultFactor?
This will be hundreds to thousands of times faster. The difference is that all of the calcs are done to a VBA array instead of directly to worksheet cells, one by one. Once the array is updated it is written back to the worksheet in one go. This reduces worksheet interaction to just two instances, reading the array and writing it. Reducing the number of instances that your VBA code touches anything on the worksheet side is critical to execution speed.
Sub Mozdzen()
Const FACTOR = 10
Const SOURCE = "B3:B902"
Dim i&, v
v = Sheet1.Range(SOURCE)
For i = 1 To UBound(v)
v(i, 1) = v(i, 1) * FACTOR
Next
Sheet1.Range(SOURCE) = v
End Sub
Building on the above idea, a better way to manage the code is to encapsulate the array multiplication with a dedicated function:
Sub Mozdzen()
Const FACTOR = 10
Const SOURCE = "B3:B902"
With Sheet2.Range(SOURCE)
.Value2 = ArrayMultiply(.Value2, FACTOR)
End With
End Sub
Function ArrayMultiply(a, multfactor#)
Dim i&
For i = 1 To UBound(a)
a(i, 1) = a(i, 1) * multfactor
Next
ArrayMultiply = a
End Function
You need:
rngData = Sheet12.Evaluate(rngData.Address & "*2")
since the address property doesn't include the sheet name by default (so your formula is evaluated in the context of the active sheet's range B3:B902)
Then it would need:
rngData = Sheet12.Evaluate(rngData.Address & "*" & MultFactor)
to add in your variable.

Excel - How do programmatically convert 'number stored as Text' to Number?

I'm looking for a simple Excel VBA or formula that can convert an entire row in Excel from 'number stored as Text' to an actual Number for vlookup reasons.
Can anyone point me in the right direction?
Better Approach
You should use INDEX(MATCH) instead of VLOOKUP because VLOOKUP behaves in an unpredictable manner which causes errors, such as the one you're presumably experiencing.
INDEX ( <return array> , MATCH ( <lookup value> , <lookup array> , 0) )
Using 0 as the last argument to MATCH means the match must be exact
Here is some more in-depth information on INDEX(MATCH)-ing
Further
Add zero +0 to convert a value to a number.
This can be (dangerously) extended with IFERROR() to turn non-numeric text into a zero:
=A2+0
=IFERROR(A2+0,0)
For the inverse, you can catenate an empty string &"" to force the value to be a string.
Notes
If 0 is not used as the last argument to MATCH, it will find all sorts of unexpected "matches" .. and worse, it may find a different value even when an exact match is present.
It often makes sense to do some extra work to determine if there are duplicates in the MATCH lookup column, otherwise the first value found will be returned (see example).
Help with MATCH comes from here, notably the matching logic the 3rd argument controls.
This should work if you add it before your vlookup or index/match lines:
Sheets("Sheet1").UsedRange.Value = Sheets("Sheet1").UsedRange.Value
I did find this, but does anyone have a formula as well?
Sub macro()
Range("F:F").Select 'specify the range which suits your purpose
With Selection
Selection.NumberFormat = "General"
.Value = .Value
End With
End Sub
http://www.ozgrid.com/forum/showthread.php?t=64027
Try this:
Sub ConvertToNumber()
Application.ScreenUpdating = False
Dim cl As Range
For Each cl In Selection.Cells
cl.Value = CInt(cl.Value)
Next cl
Application.ScreenUpdating = True
End Sub
To use it, simply select the relevant block of cells with the mouse, and then run the macro (Alt+F8 to bring up the dialogue box). It will go through each cell in the selected range and convert whatever value it holds into a number.
I wrote a custom vlookup function that doesn't care about data formats. Put this into a module in VBA and use = VLOOK instead of = VLOOKUP
Public Function VLook(sValue As String, rDest As Range, iColNo As Integer)
' custom vlookup that's insensitive to data formats
Dim iLastRow As Long
Dim wsDest As Worksheet
Set wsDest = Sheets(rDest.Parent.Name)
iLastRow = wsDest.Range(wsDest.Cells(100000, rDest.Column).Address).End(xlUp).Row
If iLastRow < rDest.Row + rDest.Rows.Count Then
For X = rDest.Column To rDest.Column + rDest.Columns.Count
If wsDest.Cells(100000, X).End(xlUp).Row > iLastRow Then iLastRow = wsDest.Cells(100000, X).End(xlUp).Row
Next X
End If
sValue = UCase(Application.Clean(Trim(sValue)))
For X = rDest.Row To iLastRow
If UCase(Application.Clean(Trim(wsDest.Cells(X, rDest.Column)))) = sValue Then
VLookDM = wsDest.Cells(X, rDest.Column + iColNo - 1)
Exit For
End If
Next X
End Function
The easiest way I can think of is using the built-in function =VALUE(TEXT_TO_CONVERT_TO_STRING).

Trying to expand VBA row align code to align all sheets

I'm working with an Excel report in which each month a new worksheet is added. Each row in the worksheet is for an employee, and the columns in that row is data related to them. Each week, the rows may vary, with names being added and removed.
I wrote the following VBA module to align the rows of 2 worksheets, adding blank rows as necessary, but I need to figure out a way to expand that so it aligns 12 worksheets, with multiple blank spaces between names as necessary. I'm not sure how to go about this, any suggestions?
Option Explicit
Sub Align()
Dim n As Long, a As Range, c As Range, x As Long
n = Cells.SpecialCells(11).Row
Set a = Worksheets("Jan").Range("A6:A200"): Set c = Worksheets("Feb").Range("A6:A200")
a(n + 1) = Chr(255): c(n + 1) = Chr(255)
a.Sort a(1), 1, Header:=xlNo
c.Sort c(1), 1, Header:=xlNo
Do
x = x + 1
If a(x) > c(x) Then
a(x).EntireRow.Insert xlShiftDown
ElseIf a(x) < c(x) Then
c(x).EntireRow.Insert xlShiftDown
End If
If x > 10 ^ 4 Then Exit Do
Loop Until a(x) = Chr(255) And c(x) = Chr(255)
a(x).ClearContents: c(x).ClearContents
End Sub
I do not believe any simple rearrangement of your existing code will meet your needs. I also believe this is too big a question to expect anyone to create an entire macro for you.
Below I outline the approach I would take to solving your problem. I suggest you try to solve each issue in turn. None of the code I give has been tested so I doubt it is error-free. Debugging my code should help you understand it. If you run into difficulties, you can come back to me with questions. However, it would be better to attempt to construct a new question including the code you cannot get working. With a single issue question, I believe you will get help more quickly than waiting for me to log in.
I hope this helps.
Issue 1 - Identifying the 12 worksheets
If the workbook only contains the 12 worksheets "Jan", "Feb" ... "Dec", then it is easy: worksheets 1 to 12. It does not matter if they are in the wrong sequence.
If the workbook contains other worksheets that are the first few worksheets of the workbook then it will be almost as easy: N to N+11.
If the other worksheets and the month worksheets are muddled, you will have to access then using an approach like this:
Dim InxMonth As Long
Dim InxWsht As Long
Dim WshtMonthName() As Variant
WshtMonthName = Array("Jan", "Feb", ... "Dec)
For InxMonth = 0 to 11
InxWsht = WshtMonthName(InxMonth)
With Worksheets(InxWsht)
:::::::
End with
Next
It might be better to use this approach anyway in case a user adds a new worksheet. This technique will work regardless of what other worksheets may exist.
Issue 2 - Get sorted list of names
You need a list in alphabetic order containing every name that appears in any worksheet. I can think of a number of approaches. I was taught: get the code working then make it faster, smoother or whatever. I have picked an approach that I think is easy to implement. Other approaches would be faster to execute but it does not sound as though you will be executing the code very often and there are only 12 worksheets. Your taking hours to debug complex code that will shave a few seconds off the run time is not a good use of your time.
Issue 3 - Sort the worksheets
You have code to sort a single worksheet. You need to put that code in a loop which you execute for each of the month worksheets.
Issue 4 - Create list of names
This approach is not very elegant and I can think of much faster approaches. However I think it is easy to understand what this code is doing.
I have initialised NameList to 200 entries because your code seem to assume that there are fewer than 200 employees. However the code enlarges the array if necessary.
Dim InxNameCrntMax as Long
Dim InxMonth As Long
Dim InxWsht As Long
Dim NameList() As String
Dim NextLowestName As String
Dim RowCrnt As Long
Dim WshtRowCrnt() As Long
ReDim NameList(6 to 200) ' 6 is first data row
InxNameCrntMax = 0
ReDim WshtRowCrnt(0 To 11)
' For each worksheet set the current row to the first data row
For InxMonth = 0 to 11
WshtRowCrnt(InxMonth) = 6
Next
Do While True
' Loop until every name in every worksheet has been added to NameList
NextLowestName = "~" ' Greater than any real name
' Examine the next row in each worksheet and find the lowest name
For InxMonth = 0 To 11
With Worksheets(WshtMonthName(InxMonth))
RowCrnt = WshtRowCrnt(InxMonth) ' Get next row for current worksheet
If .Cells(RowCrnt, "A") <> "" Then
' Not all names from current worksheet added to NameList
If NextLowestName > .Cells(RowCrnt, "A") Then
' This name comes before previous next lowest name
NextLowestName = .Cells(RowCrnt, "A")
End If
End If
End With
Next
If NextLowestName = "~" Then
' All names from all worksheets added to NameList
Exit Do
End If
' Add NextLowestName to NameList
InxNameCrntMax = InxNameCrntMax + 1
If InxNameCrntMax > UBound(NameList) Then
' NameList is full so enlarge it
ReDim Preserve NameList(6 To UBound(NameList) + 100)
End If
NameList(InxNameCrntMax) = NextLowestName
' Step the current row for every worksheet containing NextLowestName
For InxMonth = 0 To 11
With Worksheets(WshtMonthName(InxMonth))
RowCrnt = WshtRowCrnt(InxWsht) ' Get next row for current worksheet
If .Cells(RowCrnt, "A") = NextLowestName Then
WshtRowCrnt(InxWsht) = RowCrnt + 1
End If
End With
Next
Loop
Issue 5 - Using NameList
I initialised the size of NameList to (6 To 200) although it may have been enlarged so it could now be (6 To 300) or (6 To 400).
VBA is one of the few languages that does not require the lower bound of an array to be 0. It is worth taking advantage of this feature. I understand from your code that 6 is the first data row of the worksheets. That is why I set the lowest bound to 6; it means the element numbers match the row numbers.
InxNameCrntMax is the last used entry in NameList so we have something like:
NameList(6) = "Aardvark, Mary"
NameList(7) = "Antelope, John"
NameList(8) = "Bison, Jessica"
::::::
NameList(InxNameCrntMax) = "Zebra, Andrew"
So if for Worksheets("Jan") there is no Mary Aardvark, row 6 should be empty. If there is a John Antelope, his data belongs on row 7.
In your code, you use InsertRow to insert blank lines. I do not really like updating worksheets in situ because, if you mess up, you have to reload the data from a backup copy.
I would rather build worksheet "JanNew" from Jan", "FebNew" from "Feb" and so on. When all these new worksheets had been created, I would rename "Jan" to "JanOld" and so on and then I would rename "JanNew" to "Jan" and so on. Only when I was absolutely convinced I had moved the data correctly would I delete the old worksheets.
However, I have to admit your approach is easier. I leave you to decide what to do.

vba circular reference error in function

I'm trying myself on vba with little success. I would like to achieve a simple function that sums the content of a range of cells based on the beginning of the year till today. Unfortunately, I get back a "circular reference" error when I call the function, and I just can't see why. Any help will be appreciated.
Public Function til2day(r As Integer) As Long ''supposed to receive cell("row") as parameter
Dim c As Integer
Dim c1 As Integer
Dim c_here As Integer
Application.Volatile True
c_here = ActiveCell.Column
c = 0
c1 = 34 ''column contains 1/1/2013 date
Range("AH4:OM4").Activate ''contains a timeline
Do While ActiveCell.Offset(0, c).Value <> Date
c = c + 1
Loop
If ActiveCell.Offset(0, c).Value = Date Then
c = ActiveCell.Offset(0, c).Column
End If
til2day = Application.WorksheetFunction.Sum(Range(Cells(r, c1).Address, Cells(r, c).Address))
Range(Cells(r, c_here).Address).Activate
End Function
It is a really bad idea to use "activate" in a function; I can't explain exactly why this is, except that you are changing the selection of the cell during the calculation. In the following scenario this is going to cause a problem:
multiple cells are being calculated with this function, and
you use `Application.Volatile`, and
you refer to the active cell inside your function, and
you allow multi-threaded calculation,
Things will not happen in the order you expect, and at some point the active cell will be different than you thought. Function ends up referring to the cell it's in, and you have a circular reference. This doesn't happen when you run the debugger since it by definition runs as a single thread - which is why you can't find the problem then...
Here is a suggested rewrite of your function - it doesn't do any activating of cells, but attempts to maintain the same functionality:
Public Function til2day(r As Integer) As Long ''supposed to receive cell("row") as parameter
Dim c As Integer
Dim c1 As Integer
Dim dateRange as Range
Dime dateCell as Range
Application.Volatile True
c = 0
c1 = 34 ''column contains 1/1/2013 date
set dateRange = Range("AH4:OM4")
For Each dateCell in dateRange
If dateCell.Value = Date Then Exit For
Next dateCell
c = dateCell.Column
til2day = Application.WorksheetFunction.Sum(Range(Cells(r, c1).Address, Cells(r, c).Address))
End Function
Note: I attempted to reproduce the functionality of your function - but without a good example of the worksheet you are using, and the values you are expecting to return, it's hard to test. Please try to run this on your worksheet - and let me know if things don't work as you expected.
Note also that the SUMIF function could be used with good effect:
=SUMIF(range, criteria, sum_range)
In your case, use
=SUMIF($AH$4:$OM$4, "<=" & NOW(), $AH18:$OM18)
Where "18" is whatever row you need it to be (and when you drag the formula to a different row, it will continue to refer to the date row because of the $4 absolute reference, but calculate the sum for a different row because of the relative row reference in $AH18:$OM18.
An example of the use of this function (simplified range...)
As you can see, the function is summing columns C through F only since I did this on June 15th.

Setting a range to have a Macro run on all populated rows in a worksheet

I've pieced together a macro to allow me to calculate the cost of a story task by calculating the specific rate based on the developer assigned. I have the rate table on a second sheet. I am able to get a result for the cell that the macro is set to (Row 2), but want it to run on all rows. I know I have to set a generic range, but am not sure. How should I change the range declare to run on all rows?
Here is the code:
Sub GetCost()
Range("D2").Select
' Set Do loop to stop when an empty cell is reached.
Do Until IsEmpty(ActiveCell)
Dim Estimate As Integer, Assignee As String, RodRate As Integer, GarthRate As Integer, DerekRate As Integer, TotalCost As Integer
Estimate = ThisWorkbook.Worksheets("Sheet1").Range("D2").Value
Assignee = ThisWorkbook.Worksheets("Sheet1").Range("E2").Value
RodRate = ThisWorkbook.Worksheets("Sheet2").Range("B2").Value
GarthRate = ThisWorkbook.Worksheets("Sheet2").Range("B3").Value
DerekRate = ThisWorkbook.Worksheets("Sheet2").Range("B4").Value
If Assignee = "Rod" Then
TotalCost = Estimate * RodRate
ElseIf Assignee = "Garth" Then
TotalCost = Estimate * GarthRate
ElseIf Assignee = "Derek" Then
TotalCost = Estimate * DerekRate
Else
TotalCost = "0"
End If
ThisWorkbook.Worksheets("Sheet1").Range("F2").Formula = TotalCost
ActiveCell.Offset(1, 0).Select
Loop
End Sub
I have rewritten your code with explanations which I hope are enough for you to understand why. There is much more that I could say. I hope this is a good balance between too little and too much.
However, I have to point out that there are some excellent project management tools available. I do not believe this is a good use of your time.
Random points
On 32-bit computers, Long is better than Integer.
Do not declare your variables inside a loop. The scope of a variable declared inside a sub-routine is the
the sub-routine so declare them at the top of the sub-routine.
You can declare all your variables in a single Dim statement but I find it confusing unless there is a real association between two or more variable. I might have:
Dim RodRate As Long, GarthRate As Long, DerekRate As Long
because these variables are associated. However the trouble with this approach is that you will have to add MaryRate and JohnRate and AngelaRate when these people join your project.
You need an array:
Dim PersonRate(1 To 3) As Long
where PersonRate(1) = Rate for Rod, PersonRate(2) = Rate for Garth and PersonRate(3) = Rate for Derek.
But this is hardly any better. You want a table that can grow. So today:
Name Rate
Rod 20
Garth 25
Derek 15
Next week:
Name Rate
Rod 20
Garth 25
Derek 15
Mary 30
With this, you pick up the Assignee's name, run down the table until you find their name then look across for their rate.
I assume you have a table like this in Sheet2. You could keep going back to Sheet2 but better to load the table into an array.
We could have:
Dim PersonName() As String
Dim PersonRate() As Long
so PersonRate(2) gives the rate for PersonName(2).
Note in my first array declaration I wrote: PersonRate(1 To 3). This time, the brackets are empty. With PersonRate(1 To 3), I am saying I want exactly three entries in the array and this cannot be changed. With PersonRate(), I am saying I want an array but I will not know how many entries until run time.
I said we could have two arrays, PersonName() and PersonRate() and this is what I have done. This is an easy-to-understand approach but I do not think it is the best approach. I prefer structures. When you have got this macro working and before you start your next look up User Types which is the VBA name for a structure.
Consider:
With Sheets("Sheet2")
RowMax = .Cells(Rows.Count, "A").End(xlUp).Row
End With
There is a lot to explain here.
Cells means I want to address a cell within the active workbook. .Cells means I want to address a cell within the sheet identified in the With statement. This means I do not have to select Sheet1 or Sheet2 to look at their contents. Selecting worksheets is slow and the code tends to be more difficult to understand.
.Cells(Row, Column) identifies a cell. Row must be a number but column can be a number or a column code: A=1, B=2, Z=26, AA=27, etc.
Rows.Count returns the number of rows in a sheet for the version of Excel you are using. So .Cells(Rows.Count, "A") identifies the bottom of column "A".
End(xlUp) is the VBA equivalent of clicking Ctrl+UpArrow. If you are not familar with Ctrl+Arrow I suggest you play with these four controls. Note, these controls give easy to understand results with a rectangular table. However, if there are empty cells, the results can be strange.
Putting this together: .Cells(Rows.Count, "A").End(xlUp).Row means start at the bottom of column A, go up until you hit a cell with a value and return its row number. So this sets RowMax to the last row of the Rate table. When you add row 5 with Mary's name and rate, this code will automatically adjust.
Revised code
This should be enough to get you started. Welcome to the joys of programming.
' * Require all variables to be declared which means a misspelt name
' is not taken as an implicit declaration
Option Explicit
Sub GetCost()
Dim Estimate As Integer
Dim Assignee As String
Dim TotalCost As Integer
Dim PersonName() As String
Dim PersonRate() As String
Dim InxPerson As Long
Dim RowCrnt As Long
Dim RowMax As Long
' You can declare constants and use them in place of literals.
' You will see why later. I could have made these strings and
' used "A", "B", "D", "E" and "F" as the values. Change if that
' is easier for you.
Const ColS2Name As Long = 1
Const ColS2Rate As Long = 2
Const ColS1Estimate As Long = 4
Const ColS1Assignee As Long = 5
Const ColS1Total As Long = 6
' Before doing anything else we must load PersonName and PersonRate from
' Sheet2. I assume the structure of Sheet2 is:
' A B
' 1 Name Rate
' 2 Rod 20
' 3 Garth 25
' 4 Derek 15
With Sheets("Sheet2")
RowMax = .Cells(Rows.Count, ColS2Name).End(xlUp).Row
' I now know how big I want the the name and rate arrays to be
ReDim PersonName(1 To RowMax - 1)
ReDim PersonRate(1 To RowMax - 1)
' Load these arrays
For RowCrnt = 2 To RowMax
' I could have used 1 and 2 or "A" and "B" for the column
' but this is easier to understand particularly if you come
' back to this macro in six month's time.
PersonName(RowCrnt - 1) = .Cells(RowCrnt, ColS2Name).Value
PersonRate(RowCrnt - 1) = .Cells(RowCrnt, ColS2Rate).Value
Next
End With
With Sheets("Sheet1")
' I am using the same variable for rows in sheets Sheet1 and Sheet2.
' This is OK because I never look at Sheet1 and Sheet2 at the same time.
RowCrnt = 2
Do Until IsEmpty(.Cells(RowCrnt, ColS1Estimate))
Estimate = .Cells(RowCrnt, ColS1Estimate).Value
Assignee = .Cells(RowCrnt, ColS1Assignee).Value
.Cells(RowCrnt, ColS1Total).Value = 0
' Locate the Assignee in the PersonName array and
' extract the matching rate
For InxPerson = 1 To UBound(PersonName)
If PersonName(InxPerson) = Assignee Then
.Cells(RowCrnt, ColS1Total).Value = Estimate * PersonRate(InxPerson)
Exit For
End If
Next
RowCrnt = RowCrnt + 1
Loop
End With
End Sub
Tony's answer is a great solution and introduction to programming and very well written so I've +1 it. However unless I'm missing something code should always be the last resort in excel as it is very slow compared to formulas, I would have thought that a simple lookup would suffice, something like:
=D2*(vlookup(E2,'sheet2'!A:B,2,FALSE))
Copied down the column

Resources