How To Loop Nested If-Then Statements in VBA - vba

I am going to try to simplify my objective as well as add all of my vba as my OP was not clear.
I am writing a macro that is to be used to determine a commissions percentage based on a particular strategies (Tier1, Tier2, BPO or Enterprise), a Gross Margin range and contract year. This will need to be looped through about 5,000 rows of data in the final product. I have been trying to nest multiple If-Then statements to achieve my goal however it is not working.
Below is the table for the commissions rates that apply to each of the strategies and then the code that I wrote for this nested If-Then statement.
Looking to try to make this simpler and loop it through the entirety of the rows with data. Goal is to have each cell in column J return a Commission rate determined by the strategy in column i, year in column D and GM in column Z. The strategy has the potential to vary each row down the page.
Would I be better off creating a custom Function?
Kinda crazy task for a first time macro writer. Appreciate all the feedback I have gotten already and look forward to any other ideas to come.
enter image description here
enter image description here
enter image description here
My Code:
Where Column I = Strategy
Where Column D = Year
Where Column Z = Gross Margin
Where Column J = Result of If-Then
where Column C is a defined data set which determines the number of rows in the workbook.
Sub Define_Comm_Rate
Dim LastRow As Long
LastRow = Range("C" & Rows.Count).End(xlUp).Row
If Sheet1.Range("I2") = "BPO" And Sheet1.Range("Z2") >= 0.24 Then
If Sheet1.Range("D2") = 1 Then
Sheet1.Range("J2") = 0.4
Else
If Sheet1.Range("D2") = 2 Then
Sheet1.Range("J2") = 0.3
Else: Sheet1.Range("J2") = 0.15
End If
End If
End If
If Sheet1.Range("I2") = "BPO" And Sheet1.Range("Z2") >= 0.21 And Sheet1.Range("Z2") < 0.24 Then
If Sheet1.Range("D2") = 1 Then
Sheet1.Range("J2") = 0.35
Else
If Sheet1.Range("D2") = 2 Then
Sheet1.Range("J2") = 0.25
Else: Sheet1.Range("J2") = 0.1
End If
End If
End If
If Sheet1.Range("I2") = "BPO" And Sheet1.Range("Z2") >= 0.18 And Sheet1.Range("Z2") < 0.21 Then
If Sheet1.Range("D2") = 1 Then
Sheet1.Range("J2") = 0.3
Else
If Sheet1.Range("D2") = 2 Then
Sheet1.Range("J2") = 0.2
Else: Sheet1.Range("J2") = 0.05
End If
End If
End If
If Sheet1.Range("I2") = "BPO" And Sheet1.Range("Z2") < 0.18 Then
If Sheet1.Range("D2") = 1 Then
Sheet1.Range("J2") = 0.25
Else
If Sheet1.Range("D2") = 2 Then
Sheet1.Range("J2") = 0.15
Else: Sheet1.Range("J2") = 0.05
End If
End If
End If
If Sheet1.Range("I2") = "Enterprise24" Then
If Sheet1.Range("D2") = "1" Then
Sheet1.Range("J2") = 0.4
Else
If Sheet1.Range("D2") = 2 Then
Sheet1.Range("J2") = 0.3
Else: Sheet1.Range("J2") = 0.15
End If
End If
End If
If Sheet1.Range("I2") = "Enterprise21" Then
If Sheet1.Range("D2") = 1 Then
Sheet1.Range("J2") = 0.35
Else
If Sheet1.Range("D2") = 2 Then
Sheet1.Range("J2") = 0.25
Else: Sheet1.Range("J2") = 0.1
End If
End If
End If
If Sheet1.Range("I2") = "Enterprise18" Then
If Sheet1.Range("D2") = 1 Then
Sheet1.Range("J2") = 0.3
Else
If Sheet1.Range("D2") = 2 Then
Sheet1.Range("J2") = 0.2
Else: Sheet1.Range("J2") = 0.05
End If
End If
End If
If Sheet1.Range("I2") = "Enterprise00" Then
If Sheet1.Range("D2") = 1 Then
Sheet1.Range("J2") = 0.25
Else
If Sheet1.Range("D2") = 2 Then
Sheet1.Range("J2") = 0.15
Else: Sheet1.Range("J2") = 0.05
End If
End If
End If
If Sheet1.Range("I2") = "Tier1" Then
If Sheet1.Range("Z2") > 0.4 Then
Sheet1.Range("J2") = 0.5
Else
If Sheet1.Range("Z2") <= 0.4 And Sheet1.Range("Z2") > 0.25 Then
Sheet1.Range("J2") = (1 * Sheet1.Range("Z2")) + 0.1
Else
If Sheet1.Range("Z2") <= 0.25 And Sheet1.Range("Z2") > 0.075 Then
Sheet1.Range("J2") = (2 * Sheet1.Range("Z2")) - 0.15
Else
If Sheet1.Range("Z2") <= 0.075 And Sheet1.Range("Z2") > 0 Then
Sheet1.Range("J2") = 0
Else: Sheet1.Range("J2") = 0.5
End If
End If
End If
End If
End If
If Sheet1.Range("I2") = "Tier1-100" Then
If Sheet1.Range("Z2") > 0.4 Then
Sheet1.Range("J2") = 0.5
Else
If Sheet1.Range("Z2") <= 0.4 And Sheet1.Range("Z2") > 0.25 Then
Sheet1.Range("J2") = (1 * Sheet1.Range("Z2")) + 0.1
Else
If Sheet1.Range("Z2") <= 0.25 And Sheet1.Range("Z2") > 0.075 Then
Sheet1.Range("J2") = (2 * Sheet1.Range("Z2")) - 0.15
Else
If Sheet1.Range("Z2") <= 0.075 And Sheet1.Range("Z2") > 0 Then
Sheet1.Range("J2") = 0
Else: Sheet1.Range("J2") = 0.5
End If
End If
End If
End If
End If
If Sheet1.Range("I2") = "Tier2" Then
If Sheet1.Range("Z2") > 0.35 Then
Sheet1.Range("J2") = 0.5
Else
If Sheet1.Range("Z2") <= 0.35 And Sheet1.Range("Z2") > 0.25 Then
Sheet1.Range("J2") = (1 * Sheet1.Range("Z2")) + 0.15
Else
If Sheet1.Range("Z2") <= 0.25 And Sheet1.Range("Z2") > 0.05 Then
Sheet1.Range("J2") = (2 * Sheet1.Range("Z2")) - 0.1
Else
If Sheet1.Range("Z2") <= 0.05 And Sheet1.Range("Z2") > 0 Then
Sheet1.Range("J2") = 0
Else: Sheet1.Range("J2") = 0.5
End If
End If
End If
End If
End If
If Sheet1.Range("I2") = "Tier2-100" Then
If Sheet1.Range("Z2") > 0.35 Then
Sheet1.Range("J2") = 0.5
Else
If Sheet1.Range("Z2") <= 0.35 And Sheet1.Range("Z2") > 0.25 Then
Sheet1.Range("J2") = (1 * Sheet1.Range("Z2")) + 0.15
Else
If Sheet1.Range("Z2") <= 0.25 And Sheet1.Range("Z2") > 0.05 Then
Sheet1.Range("J2") = (2 * Sheet1.Range("Z2")) - 0.1
Else
If Sheet1.Range("Z2") <= 0.05 And Sheet1.Range("Z2") > 0 Then
Sheet1.Range("J2") = 0
Else: Sheet1.Range("J2") = 0.5
End If
End If
End If
End If
End If
Sheet1.Range("J2").AutoFill Destination:=Sheet1.Range("J2:J" & LastRow)
Application.Calculate
End Sub

I'm going to offer a non-VBA approach to this using INDIRECT, INDEX, MATCH, and a few tables. My thought is that instead of coding lots of nested IF's, with hard-coded values, in VBA, you should be able to do this with lookup tables. (Disclaimer: this was also a fun intellectual exercise.)
First, create a table similar to the Commissions Table you already have and name it your specific strategy, e.g. "BPO", under Formulas > Name Manager. I created mine on a separate sheet named "Tables". Note that I used 1 in row 1 as your max (and unrealistic) gross margin. I also added 1, 2, and 3 in cells B1, C1, and D1 respectively. You'd need to create similar tables for your other strategies, and put them under the BPO table.
Then in column J on your data tab, enter this formula: =INDEX(INDIRECT(I2),MATCH(Z2,INDIRECT(I2&"["&Z$1&"]"),-1),MATCH(D2,Tables!$A$1:$D$1,1))
This INDEX formula has 3 main parts:
INDIRECT(I2) - this returns the array comprising the table you have named "BPO" - so you know you're looking at the table appropriate to that particular strategy.
MATCH(Z2,INDIRECT(I2&"["&Z$1&"]"),-1) - this looks up your gross margin in column Z against the table (BPO), looking in the column[Gross Margin]. The last argument of MATCH (match type) is -1, meaning that it finds the smallest value that is greater than or equal to your gross margin (note that in the table, Gross Margin is sorted in descending order). So for example, if your Gross Margin is 0.22, the MATCH will return 0.2399.
MATCH(D2,Tables!$A$1:$D$1,1) - This looks up the year, and tries to find the largest value that is less than or equal to it. So if the year is 1, 2, or 3, the MATCH will return 1, 2, or 3, respectively, but if the year is greater than 3, the MATCH returns 3.
Columns AB and AC in the second screenshot are just the results of 2. and 3. above, included to show that the correct commission value is being returned. Note that the "Year Column" is not Year 2 or Year 3, but the 2nd or 3rd column in the BPO table, i.e. Year 1 or Year 2 respectively.

Thanks for all the input/feedback.
Due to added complexity of additional sales plans needing to be incorporated as well as needing the flexibility to add/remove sales plans at any time, I ended up writing a custom Function.
Function Commissions(Strategy As String, GM As Variant, YR As Variant) As Variant
If Strategy = "BPO" And GM >= 0.24 And YR = 1 Then
Commissions = 0.4
ElseIf Strategy = "BPO" And GM >= 0.24 And YR = 2 Then
Commissions = 0.3
ElseIf Strategy = "BPO" And GM >= 0.24 And YR >= 3 Then
Commissions = 0.15
ElseIf Strategy = "BPO" And GM >= 0.21 And GM < 0.24 And YR = 1 Then
Commissions = 0.35
ElseIf Strategy = "BPO" And GM >= 0.21 And GM < 0.24 And YR = 2 Then
Commissions = 0.25
ElseIf Strategy = "BPO" And GM >= 0.21 And GM < 0.24 And YR >= 3 Then
Commissions = 0.1
ElseIf Strategy = "BPO" And GM >= 0.18 And GM < 0.21 And YR = 1 Then
Commissions = 0.3
ElseIf Strategy = "BPO" And GM >= 0.18 And GM < 0.21 And YR = 2 Then
Commissions = 0.2
ElseIf Strategy = "BPO" And GM >= 0.18 And GM < 0.21 And YR >= 3 Then
Commissions = 0.05
ElseIf Strategy = "BPO" And GM < 0.18 And YR = 1 Then
Commissions = 0.25
ElseIf Strategy = "BPO" And GM < 0.18 And YR = 2 Then
Commissions = 0.15
ElseIf Strategy = "BPO" And GM < 0.18 And YR >= 3 Then
Commissions = 0.05
''all other strategies continued below....''
End If
End Function

Related

SQL - Update a table using LAG and based on another column

I have a dashboard with python and dash/plotly that receives inputs from the user and then run a query on Google BigQuery.
One of the queries updates a column (PAY_FT), uses other column (PAY_CLEAN) and a string input from the dashboard.
DECLARE cpi STRING DEFAULT "C";
UPDATE `xx.yy.zz` t
SET
PAY_FT = s.PAY_FT
FROM
(
SELECT
RN,
CASE
WHEN RN = 1 AND cpi = "S" AND PAY_SHALE_COR = "PAY" THEN 1
WHEN RN = 1 AND cpi = "C" AND PAY_CLEAN = "PAY" THEN 1
WHEN RN = 1 THEN 0
WHEN RN > 1 AND cpi = "S" AND PAY_SHALE_COR = "PAY" THEN 0.2 + (LAG(PAY_FT, 1) OVER(ORDER BY RN))
WHEN RN > 1 AND cpi = "C" AND PAY_CLEAN = "PAY" THEN 0.2 + (LAG(PAY_FT, 1) OVER(ORDER BY RN))
ELSE (LAG(PAY_FT, 1) OVER(ORDER BY RN))
END
AS PAY_FT
FROM
`xx.yy.zz`
WHERE
DEPTH_M IS NOT NULL
)
s
WHERE
t.RN = s.RN
But it's not working as intended. This query returns the following:
It works until DEPTH_M = 102.2, but after that it dows not return the 0.4 from the previous row. So, for example, if we had a "PAY" in DEPTH_M = 103.2, until 103.0 should return 0.4 and in 103.2 it should be 0.6.
How can I fix it?
Here's a sample of my data: SAMPLE.csv
My desired outpu should be:
DEPTH_M
PAY_CLEAN
PAY_FT
101.2
PAY
0.2
101.4
PAY
0.4
101.6
-
0.4
101.8
-
0.4
102.0
-
0.4
102.2
-
0.4
102.4
-
0.4
102.6
-
0.4
102.8
-
0.4
103.0
-
0.4
103.2
-
0.4
103.4
-
0.4
Let's say that we got another PAY on another row, the output should be:
DEPTH_M
PAY_CLEAN
PAY_FT
101.2
PAY
0.2
101.4
PAY
0.4
101.6
-
0.4
101.8
-
0.4
102.0
-
0.4
102.2
-
0.4
102.4
-
0.4
102.6
-
0.4
102.8
-
0.4
103.0
PAY
0.6
103.2
-
0.6
103.4
-
0.6
Thanks in advance.
on the second thought you can go with this query which is much simpler and does what you want and you don't need to check for RN :
SELECT RN
,0.2 * COUNT(CASE WHEN (cpi = "S" AND PAY_SHALE_COR = "PAY")
OR (cpi = "C" AND PAY_CLEAN = "PAY") THEN 1 END) OVER (ORDER BY RN)
+ SUM(CASE WHEN RN = 1 AND ((cpi = "S" AND PAY_SHALE_COR = "PAY")
OR (cpi = "C" AND PAY_CLEAN = "PAY")) THEN 0.8 ELSE 0 end) OVER () NEW_Pay_FT
FROM `xx.yy.zz`
WHERE DEPTH_M IS NOT NULL

VBA #Value Error in

I'm having a bit of a headache when it comes to VBA. I had tried to search online for an answer but to no luck. I have learned Python, but VBA is a different ballpark.
Dim X As String
Function GRADELETTER_PM(Num_Grade As Double)
X = "A"
If Num_Grade >= 0.93 Then 'finds corresponding letter to a grade'
X = "A"
MsgBox X
End If
If Num_Grade >= 0.9 Then
X = "A-"
End If
If Num_Grade >= 0.88 Then
X = "B+"
End If
If Num_Grade >= 0.83 Then
X = "B"
End If
If Num_Grade >= 0.8 Then
X = "B-"
End If
If Num_Grade >= 0.78 Then
X = "C+"
End If
If Num_Grade >= 0.73 Then
X = "C"
End If
If Num_Grade >= 0.7 Then
X = "C-"
End If
If Num_Grade >= 0.67 Then
X = "D+"
End If
If Num_Grade >= 0.6 Then
X = "D"
End If
If Num_Grade < 0.6 Then
X = "F"
End If
End Function
The program is supposed to calculate a grade to its letter. IE a 93% (input) is an "A" (Output) while a 64% is a "D". The only input is the grade. The sheet itself has multiple tables that do not aline themselves perfectly (ie, not the same Col x Row) and the formula will be used 40+ times on that one sheet when it works. Thanks in advance.
I would suggest using a Select Case statement, for example:
Function GRADELETTER_PM(ByVal Num_Grade As Double) As String
Select Case Num_Grade
Case Is >= 0.93: GRADELETTER_PM = "A"
Case Is >= 0.9: GRADELETTER_PM = "A-"
Case Is >= 0.88: GRADELETTER_PM = "B+"
Case Is >= 0.83: GRADELETTER_PM = "B"
Case Is >= 0.8: GRADELETTER_PM = "B-"
Case Is >= 0.78: GRADELETTER_PM = "C+"
Case Is >= 0.73: GRADELETTER_PM = "C"
Case Is >= 0.7: GRADELETTER_PM = "C-"
Case Is >= 0.67: GRADELETTER_PM = "D+"
Case Is >= 0.6: GRADELETTER_PM = "D"
Case Else: GRADELETTER_PM = "F"
End Select
End Function
Here is a reference.

Calculating discount in VB.NET

I'm trying to calculate discount based on a person's age category and amount of visits at a hair salon. It's just not working properly though. It doesn't calculate the proper discount until the second click and then it does some weird stuff if I keep pressing calculate. Just wondering where I am going wrong, thanks.
' Discount
If radAdult.Checked = True Then
discount = 0
ElseIf radChild.Checked = True Then
discount = totalPrice * 0.1
ElseIf radStudent.Checked = True Then
discount = totalPrice * 0.05
ElseIf radSenior.Checked = True Then
discount = totalPrice * 0.15
End If
' Additional discount
If txtClientVisits.Text >= 1 And txtClientVisits.Text <= 3 Then
additionalDiscount = 0
ElseIf txtClientVisits.Text >= 4 And txtClientVisits.Text <= 8 Then
additionalDiscount = totalPrice * 0.05
ElseIf txtClientVisits.Text >= 9 And txtClientVisits.Text <= 13 Then
additionalDiscount = totalPrice * 0.1
ElseIf txtClientVisits.Text >= 14 Then
additionalDiscount = totalPrice * 0.15
End If
totalPrice = baseRate + serviceRate - (discount + additionalDiscount)
The type of the txtClientVisits.Text property is a String. The comparison operators <, >, >=, and <= for strings perform a lexicographic comparison. VB will then convert 14 to the string "14" and so compare each digit one-by-one, which is not what you want.
(This is why I don't like VB.NET - because it performs these implicit conversions without warning you).
You will need to explicitly convert the number in the textbox to an actual number, then compare based on that:
Dim clientVisits As Integer = CInt( txtClientVisits.Text )
If clientVisits >= 1 AndAlso clientVisits < 4 Then
additionalDiscount = 0
ElseIf clientVisits >= 4 AndAlso clientVisits < 9 Then
additionalDiscount = totalPrice * 0.05
ElseIf clientVisits >= 9 AndAlso clientVisits < 14 Then
additionalDiscount = totalPrice * 0.1
ElseIf clientVisits >= 14 Then
additionalDiscount = totalPrice * 0.15
End If
I note you used inclusive boundary values. That works for integer values but will fail for continuous (floating-point) values. Note how I'm using >= and < instead of >= and <= to avoid that case.
Also, note I'm using the AndAlso operator which is short-circuiting (compared to And which is not). This isn't a functional change, it just means the program will run slightly faster.
Also, you don't need to do ElseIf radChild.Checked = True Then because the value of the .Checked property is already a boolean value, you can do this instead:
ElseIf radChild.Checked Then

Function does not work - shows 0

I have written a simple function that counts a company bonus. For some reason, it shows 0 and does not give an answer.
Function insurancebonus(penetration As Single, penetrationPlan As Single, _
renewalRate As Single, renewalPlan As Single, dealerRank As Single, _
premiumDynamic As Single) As Single
Dim Total As Single
Total = 0
If penetration >= penetrationPlan Then Total = Total + 0.01
If renewalRate >= renewalPlan Then Total = Total + 0.01
Select Case dealerRank
Case 1
Select Case premiumDynamic
Case 0 To 0.03: Total = Total + 0.01
Case 0.03 To 0.05: Total = Total + 0.02
Case 0.05 To 1: Total = Total + 0.03
End Select
Case 2
Select Case premiumDynamic
Case 0.02 To 0.04: Total = Total + 0.01
Case 0.04 To 0.08: Total = Total + 0.02
Case 0.08 To 1: Total = Total + 0.03
End Select
Case 3
Select Case premiumDynamic
Case 0.05 To 0.1: Total = Total + 0.01
Case 0.1 To 0.15: Total = Total + 0.02
Case 0.15 To 1: Total = Total + 0.05
End Select
Case 4
Select Case premiumDynamic
Case 0.1 To 0.17: Total = Total + 0.01
Case 0.17 To 0.25: Total = Total + 0.02
Case 0.25 To 1: Total = Total + 0.03
End Select
Case 5
Select Case premiumDynamic
Case 0.15 To 0.3: Total = Total + 0.01
Case 0.3 To 0.4: Total = Total + 0.02
Case 0.4 To 1: Total = Total + 0.03
End Select
End Select
End Function
You haven't told the function to return anything.
I assume you want to return the value of Total?
If that is the case, prior to the End Function add the following line:
insurancebonus = Total
This specifies that your function should return the value of Total.
As a very short and contrived example, this function simply returns 1.
Public Function test() As Integer
test = 1
End Function
The point is that you must assign the value to the name of your function.
For completeness, if your function returns an Object type you must use the Set keyword, such as:
Public Function testRange() As Range
Set testRange = Range("A1")
End Function
Which would return a Range object.

visual basics equal to or greater than

I am trying to write some code that says if textbox1 is equal to 0 between 10 then HandDecimal = 1. Else if textbox1 is equal to 10.1 through 100, then HandDecimal = 3. Else if textbox1 is equal to 100.1 and greater than HandDecimal = 5.
Here is my code, but it does not seem to work for me.
If WeightDecimal = 0 <= 10 Then
HandDecimal = 1
ElseIf WeightTextBox.Text = 10 <= 100 Then
HandDecimal = 3
ElseIf WeightTextBox.Text >= 100.1 Then
HandDecimal = 5
End If
How do I have to change the code to make it work?
Dim weight as Decimal = Decimal.Parse(WeightTextBox.Text)
If weight >= 0 AndAlso weight <= 10 Then
HandDecimal = 1
ElseIf weight > 10 AndAlso weight <= 100 Then
HandDecimal = 3
ElseIf weight > 100 Then
HandDecimal = 5
End If
Select Case statement with To operator
Select Case WeightDecimal
Case 0 To 10
HandDecimal = 1
Case 10.1 To 100
HandDecimal = 3
Case Else
HandDecimal = 5
End Select