End If without Block If error VBA - vba

I wrote this code to try and assign a value to a variable based on the value of another variable generated using vba's Rnd() function and if statements but for some reason its giving me the "end if without block if error." This is just a portion of the code, I iterate this process 5 times for the 5 different products and do 10000 iterations of the number generators aggregating the results. Initially I tried it this way nesting everything, but when that didnt work I tried doing single if statements and same deal. Any help with this would be awesome.
For i = 0 To 10000
ProdE = Rnd()
ProdF = Rnd()
ProdG = Rnd()
ProdH = Rnd()
ProdI = Rnd()
If ProdE <= 0.1 Then DaysLateE = 2
If 0.1 < ProdE <= 0.2 Then DaysLateE = 3
If 0.2 < ProdE <= 0.3 Then DaysLateE = 4
If 0.3 < ProdE <= 0.4 Then DaysLateE = 5
If 0.4 < ProdE <= 0.5 Then DaysLateE = 6
If 0.5 < ProdE <= 0.6 Then DaysLateE = 7
If 0.6 < ProdE <= 0.7 Then DaysLateE = 8
If 0.7 < ProdE <= 0.8 Then DaysLateE = 9
If 0.8 < ProdE <= 0.9 Then DaysLateE = 10
If 0.9 < ProdE <= 1 Then DaysLateE = 11
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
TotalDaysLateE = DaysLateE + 8
SumDaysLateE = SumDaysLateE + TotalDaysLateE
If TotalDaysLateE > 15 Then CountE = CountE + 1
End If

The syntax for If allows two variants:
"Inline"
If {bool-expression} Then {do something}
"Block"
If {bool-expression} Then
{do something}
End If
An End If token is illegal when you're using the "inline" syntax.
So this (i.e. removing the End If tokens) makes your code compilable again:
If ProdE <= 0.1 Then DaysLateE = 2
If 0.1 < ProdE <= 0.2 Then DaysLateE = 3
If 0.2 < ProdE <= 0.3 Then DaysLateE = 4
If 0.3 < ProdE <= 0.4 Then DaysLateE = 5
If 0.4 < ProdE <= 0.5 Then DaysLateE = 6
If 0.5 < ProdE <= 0.6 Then DaysLateE = 7
If 0.6 < ProdE <= 0.7 Then DaysLateE = 8
If 0.7 < ProdE <= 0.8 Then DaysLateE = 9
If 0.8 < ProdE <= 0.9 Then DaysLateE = 10
If 0.9 < ProdE <= 1 Then DaysLateE = 11
However, as #Rohan K suggested, a better option would be to use a Select Case construct, because right now, all these conditions are evaluated all the time - with a Select Case block, execution would exit the Select block after finding a matching condition, and as a bonus you gain readabiilty:
Select Case ProdE
Case Is <= 0.1
DaysLateE = 2
Case Is <= 0.2
DaysLateE = 3
Case Is <= 0.3
DaysLateE = 4
Case Is <= 0.4
DaysLateE = 5
Case Is <= 0.5
DaysLateE = 6
Case Is <= 0.6
DaysLateE = 7
Case Is <= 0.7
DaysLateE = 8
Case Is <= 0.8
DaysLateE = 9
Case Is <= 0.9
DaysLateE = 10
Case Is <= 1
DaysLateE = 11
Case Else
'DaysLateE = ??
End Select
So, what happens when ProdE is greater than or equal to 1? (hadn't read where ProdE came from, nevermind) It seems there's a straight linear relationship between the value of ProdE and DaysLateE - you could try to come up with a formula to calculate it instead.
This probably isn't perfect, but comes pretty close:
DaysLateE = Int(ProdE * 10 - 0.000000000001) + 2
And then you don't need If or Select blocks.

The problem is your final If statement there. It is completely valid syntax to have an If statement all on one line without an End If. So when you put that End If there it is expecting an If statement with lines after it.
These two would be valid without an error
If TotalDaysLate > 15 then CountE = CountE + 1
Or
If TotalDaysLate > 15 Then
CountE = CountE + 1
End If

Try this: I would suggest Use Select Case incases like this
For i = 0 To 10000
ProdE = Rnd()
ProdF = Rnd()
ProdG = Rnd()
ProdH = Rnd()
ProdI = Rnd()
If ProdE <= 0.1 Then
DaysLateE = 2
End If
If 0.1 < ProdE And ProdE <= 0.2 Then
DaysLateE = 3
End If
If 0.2 < ProdE And ProdE <= 0.3 Then
DaysLateE = 4
End If
If 0.3 < ProdE And ProdE <= 0.4 Then
DaysLateE = 5
End If
If 0.4 < ProdE And ProdE <= 0.5 Then
DaysLateE = 6
End If
If 0.5 < ProdE And ProdE <= 0.6 Then
DaysLateE = 7
End If
If 0.6 < ProdE And ProdE <= 0.7 Then
DaysLateE = 8
End If
If 0.7 < ProdE And ProdE <= 0.8 Then
DaysLateE = 9
End If
If 0.8 < ProdE And ProdE <= 0.9 Then
DaysLateE = 10
End If
If 0.9 < ProdE And ProdE <= 1 Then
DaysLateE = 11
End If
TotalDaysLateE = DaysLateE + 8
SumDaysLateE = SumDaysLateE + TotalDaysLateE
If TotalDaysLateE > 15 Then
CountE = CountE + 1
End If

When you use single line ifs you don't need to write the End if. Check this: https://msdn.microsoft.com/en-us/library/office/gg251599.aspx

Thinking about your branching and conditional path, and for 10000 iterations, I'd suggest to just Bifurcate your If... Then statement. Better yet, use this in conjunction with two smaller Case... Select for an easily readable combination of all the suggestions. FASTER!
If ProdE <= 0.5 Then
If ProdE <= 0.1 Then DaysLateE = 2
If 0.1 < ProdE <= 0.2 Then DaysLateE = 3
If 0.2 < ProdE <= 0.3 Then DaysLateE = 4
If 0.3 < ProdE <= 0.4 Then DaysLateE = 5
If 0.4 < ProdE <= 0.5 Then DaysLateE = 6
Else
If 0.5 < ProdE <= 0.6 Then DaysLateE = 7
If 0.6 < ProdE <= 0.7 Then DaysLateE = 8
If 0.7 < ProdE <= 0.8 Then DaysLateE = 9
If 0.8 < ProdE <= 0.9 Then DaysLateE = 10
If 0.9 < ProdE <= 1 Then DaysLateE = 11
End If

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

Recursive scalar function

I'm attempting to rebuild some VBA functions in SQL server 2016 and I'm having difficult making a recursive function work properly.
My attempt below is returning NULL and when I step through the function in debug #returnValue never seems to hold a value even though it appears to recurse correctly.
Can someone please advise how to fix this.
Example expected results:
select [dbo].TickDiff(1.01, 1.05) -- Expect result 4
select [dbo].TickDiff(2.02, 16) -- Expect result 121
Functions:
CREATE FUNCTION [dbo].TickDiff (#odds1 float, #odds2 float)
RETURNS float
AS
BEGIN
DECLARE #returnValue as float
IF (#odds1 > #odds2)
BEGIN
set #returnValue = #returnValue + [dbo].TickDiff([dbo].TicksDown(#odds1), #odds2) + 1
END
ELSE
IF (#odds1 < #odds2)
BEGIN
set #returnValue = #returnValue + [dbo].TickDiff([dbo].TicksUp(#odds1), #odds2) + 1
END
RETURN #returnValue;
END
GO
CREATE FUNCTION [dbo].TicksUp(
#odds float
)
RETURNS float
AS
BEGIN
RETURN
case
when #odds < 1 then 1.02
when #odds >= 1 and #odds <= 1.99 then #odds + 0.01
when #odds >= 2 and #odds <= 2.98 then #odds + 0.02
when #odds >= 3 and #odds <= 3.95 then #odds + 0.05
when #odds >= 4 and #odds <= 5.9 then #odds + 0.1
when #odds >= 6 and #odds <= 9.8 then #odds + 0.2
when #odds >= 10 and #odds <= 19.5 then #odds + 0.5
when #odds >= 20 and #odds <= 29 then #odds + 1.0
when #odds >= 30 and #odds <= 48 then #odds + 2.0
when #odds >= 50 and #odds <= 95 then #odds + 5.0
when #odds >= 100 and #odds < 1000 then #odds + 10
when #odds >= 1000 then 1000
end
END
GO
CREATE FUNCTION [dbo].TicksDown(
#odds float
)
RETURNS float
AS
BEGIN
RETURN
case
when #odds <= 1.01 then 1.01
when #odds >= 1.01 and #odds <= 2 then #odds - 0.01
when #odds >= 2.02 and #odds <= 3 then #odds - 0.02
when #odds >= 3.05 and #odds <= 4 then #odds - 0.05
when #odds >= 4.1 and #odds <= 6 then #odds - 0.1
when #odds >= 6.2 and #odds <= 10 then #odds - 0.2
when #odds >= 10.5 and #odds <= 20 then #odds - 0.5
when #odds >= 21 and #odds <= 30 then #odds - 1.0
when #odds >= 32 and #odds <= 50 then #odds - 2.0
when #odds >= 55 and #odds <= 100 then #odds - 5.0
when #odds >= 110 and #odds < 1000 then #odds - 10
when #odds >= 1000 then 990
end
END
GO
The VBA functions I'm trying to copy are below:
Function TicksDown(ByVal odds As Currency) As Currency
Dim IncrementOdds As Currency
Select Case odds
Case 1.01 To 2
IncrementOdds = 0.01
Case 2.02 To 3
IncrementOdds = 0.02
Case 3.05 To 4
IncrementOdds = 0.05
Case 4.1 To 6
IncrementOdds = 0.1
Case 6.2 To 10
IncrementOdds = 0.2
Case 10.5 To 20
IncrementOdds = 0.5
Case 21 To 30
IncrementOdds = 1
Case 32 To 50
IncrementOdds = 2
Case 55 To 100
IncrementOdds = 5
Case 110 To 1000
IncrementOdds = 10
End Select
If Math.Round(odds - IncrementOdds, 2) >= 1.01 Then
TicksDown = Math.Round(odds - IncrementOdds, 2)
Else
TicksDown = 1.01
End If
End Function
Function TicksUp(ByVal odds As Currency) As Currency
Dim IncrementOdds As Currency
Select Case odds
Case 1 To 1.99
IncrementOdds = 0.01
Case 2 To 2.98
IncrementOdds = 0.02
Case 3 To 3.95
IncrementOdds = 0.05
Case 4 To 5.9
IncrementOdds = 0.1
Case 6 To 9.8
IncrementOdds = 0.2
Case 10 To 19.5
IncrementOdds = 0.5
Case 20 To 29
IncrementOdds = 1
Case 30 To 48
IncrementOdds = 2
Case 50 To 95
IncrementOdds = 5
Case 100 To 1000
IncrementOdds = 10
End Select
If Math.Round(odds + IncrementOdds, 2) <= 1000 Then
TicksUp = Math.Round(odds + IncrementOdds, 2)
Else
TicksUp = 1000
End If
End Function
Function TickDiff(odds1 As Currency, odds2 As Currency) As Long
If odds1 > odds2 Then
odds1 = TicksDown(odds1)
TickDiff = TickDiff(odds1, odds2) + 1
ElseIf odds1 < odds2 Then
odds1 = TicksUp(odds1)
TickDiff = TickDiff(odds1, odds2) + 1
Else
'Found, Exit Recursive Function
End If
End Function
You need to set your #ReturnValue to zero:
DECLARE #returnValue as float = 0.0
That being said, your second example exceeds SQLs max recursion of 32. You may want to rewrite without recursion. Here's an example: http://www.sql-server-helper.com/error-messages/msg-217.aspx
Your #returnValue is not initialized , so it is null. And
set #returnValue = #returnValue + [dbo].TickDiff([dbo].TicksDown(#odds1), #odds2) + 1
evaluates to null.
Try
CREATE FUNCTION [dbo].TickDiff (#odds1 float, #odds2 float)
RETURNS float
AS
BEGIN
DECLARE #returnValue as float = 0.;
IF (#odds1 > #odds2)
BEGIN
set #returnValue = #returnValue + [dbo].TickDiff([dbo].TicksDown(#odds1), #odds2) + 1
END
ELSE
IF (#odds1 < #odds2)
BEGIN
set #returnValue = #returnValue + [dbo].TickDiff([dbo].TicksUp(#odds1), #odds2) + 1
END
RETURN #returnValue;
END
Also case expressions can be simplified because sql-server evaluates when conditions in the order specified.
CREATE FUNCTION [dbo].TicksUp(
#odds float
)
RETURNS float
AS
BEGIN
RETURN
case
when #odds < 1 then 1.02
when #odds <= 1.99 then #odds + 0.01
when #odds <= 2.98 then #odds + 0.02
when #odds <= 3.95 then #odds + 0.05
when #odds <= 5.9 then #odds + 0.1
when #odds <= 9.8 then #odds + 0.2
when #odds <= 19.5 then #odds + 0.5
when #odds <= 29 then #odds + 1.0
when #odds <= 48 then #odds + 2.0
when #odds <= 95 then #odds + 5.0
when #odds < 1000 then #odds + 10
when #odds >= 1000 then 1000
end
END

How To Loop Nested If-Then Statements in 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

Excel VBA function passing in null date causes #VALUE! error

I have a VBA function (DecTime) that I call passing in the value of a cell. The cell is formatted as custom hh:mm
in my cell the formula is "=DecTime(M6)"
If M6 is a time, eg 01:05 then it works fine, if it is null then I get #VALUE!
I am sure it's a simple solution but having spent the last hour trying lots of things from here and google I am baffled!
Here is my function :
Function DecTime(Optional time As Date = #12:00:00 AM#) As Single 'String
Dim Hours As Integer
Dim Minutes As Single
Dim HoursStr As String
Dim arrTime
'On Error Resume Next
'On Error GoTo error_handler
' HoursStr = Format(time, "h:mm")
' DecTime = HoursStr
If time = #12:00:00 AM# Then
' If HoursStr = "12:00" Then
' If IsEmpty(time) Then
' If IsEmpty(time) = True Then
' If IsNull(time) Then
' If arrTime.Count = 0 Then
' If InStr(0, time, ":") = 0 Then
' If IsDate(time) = False Then
DecTime = 88
' DecTime = HoursStr
Else
arrTime = Split(time, ":")
If arrTime(1) <= 0 Then
Minutes = 0
ElseIf arrTime(1) <= 5 Then
Minutes = 0.1
ElseIf arrTime(1) <= 10 Then
Minutes = 0.2
ElseIf arrTime(1) <= 15 Then
Minutes = 0.3
ElseIf arrTime(1) <= 20 Then
Minutes = 0.3
ElseIf arrTime(1) <= 25 Then
Minutes = 0.4
ElseIf arrTime(1) <= 30 Then
Minutes = 0.5
ElseIf arrTime(1) <= 35 Then
Minutes = 0.6
ElseIf arrTime(1) <= 40 Then
Minutes = 0.7
ElseIf arrTime(1) <= 45 Then
Minutes = 0.8
ElseIf arrTime(1) <= 50 Then
Minutes = 0.8
ElseIf arrTime(1) <= 55 Then
Minutes = 0.9
Else
Minutes = 0
End If
Hours = arrTime(0)
DecTime = Hours + Minutes
' DecTime = HoursStr
End If
'error_handler:
' DecTime = 99
'Resume Next
End Function
As you can see from the remarked code I have tried lots of different options to deal with a blank parameter passed in so if someone can tell me what I've done wrong I'd be very greatful!
I am a sql programmer so not much experience with VB
Assuming you want to return 0 if the cell is empty or doesn't contain a date, you could use:
Function DecTime(Optional time = #12:00:00 AM#) As Double
Dim Hours As Integer
Dim Minutes As Single
Dim arrTime
If Not IsDate(time) Then
DecTime = 0
ElseIf time = #12:00:00 AM# Then
DecTime = 0
Else
arrTime = Split(time, ":")
Select Case arrTime(1)
Case Is = 0
Minutes = 0
Case Is <= 5
Minutes = 0.1
Case Is <= 10
Minutes = 0.2
Case Is <= 20
Minutes = 0.3
Case Is <= 25
Minutes = 0.4
Case Is <= 30
Minutes = 0.5
Case Is <= 35
Minutes = 0.6
Case Is <= 40
Minutes = 0.7
Case Is <= 50
Minutes = 0.8
Case Is <= 55
Minutes = 0.9
Case Else
Minutes = 0
End Select
Hours = arrTime(0)
DecTime = Hours + Minutes
End If
End Function

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