Turning a multiple If statement formula into VBA - vba

I currently have the following formula inside a column of cells but as you can see its large and messy and I am afraid that the cells might get edited by someone working in the workbook accidentally.
So I have tried to code it in VBA but I keep getting a Run-Time Error '91'. And I am stuck as to what I need to adjust to make it operate.
thanks for your help
Current Formula
=IF(B7=$H$5,"1",IF(B7=$H$6,".75",IF(B7=$H$7,".75",IF(B7=$H$8,"1",IF(B7=$H$9,"1",IF(B7=$H$10,"1",IF(B7=$H$11,".5",IF(B7=$H$12,".5",IF(B7=$H$13,".5",IF(B7=$H$14,".5",IF(B7=$H$15,"1",IF(B7=$H$16,".75",IF(B7=$H$17,"1",IF(B7=$H$18,"1",IF(B7=$H$19,".75",IF(B7=$H$20,"1",IF(B7=$H$21,"1",IF(B7=$H$22,"1",IF(B7=$H$23,"1",IF(B7=$H$24,".75",IF(B7=$H$25,"1",IF(B7=$H$26,".75",IF(B7=$H$27,".5",IF(B7=$H$28,"1",IF(B7=$H$29,".75",IF(B7=$H$30,".5",IF(B7=$H$31,"1",IF(B7=$H$32,"1",IF(B7=$H$33,"1",IF(B7=$H$34,".5",IF(B7=$H$35,"1",IF(B7=$H$36,".25",IF(B7=$H$37,"1",IF(B7=$H$38,"1",IF(B7=$H$39,"1",IF(B7=$H$40,"1",IF(B7=$H$41,"1",IF(B7=$H$42,"1",IF(B7=$H$43,"1",IF(B7=$H$44,"1",IF(B7=$H$45,"1",IF(B7=$H$46,"1",IF(B7=$H$47,"1",IF(B7=$H$48,"1",IF(B7=$H$49,"1",IF(B7=$H$50,".5",IF(B7=$H$51,"1",IF(B7=$H$52,".25",IF(B7=$H$53,"1",IF(B7=$H$54,".75",IF(B7=$H$55,"1",IF(B7=$H$56,"1",IF(B7=$H$57,"1")))))))))))))))))))))))))))))))))))))))))))))))))))))
My attempt at turning it into VBA code
Sub Macro()
Dim Whole as long
Dim Third as long
Dim half as long
Dim quarter as long
Dim lookat as range
Dim answer as range
Whole = 1
third = .75
Half = .5
Quarter = .25
Lookat = Worksheets("sheet1".Range("B2:B300")
Answer = worksheets("Sheet1").range("C2:C300")
If Lookat = "AAAA" Or "AAAB" Or "AAAC" Or "AAAD" Or "AAAE" Or "AAAF" Or "AAAG" Or "AAAH" Or "AAAI" Or "AAAJ" Or "AAAK" Or "AAAL" Or "AAAM" Or "AAAN" Or "AAAO" Or "AAAP" Or "AAAQ" Or "AAAR" Or "AAAS" Or "AAAT" Or "AAAU" Or "AAAV" _
Or "AAAW" Or "AAAX" Or "AAAY" Or "AAAZ" Or "BBBA" Or "BBBB" Or "BBBC" Or "BBBD" Or "BBBE" Or "BBBF" Or "BBBG" Then
Answer.value=whole
ElseIf Lookat = "AAA" Or "AAB" Or "AAC" Or "AAD" Or "AAE" Or "AAF" Or "AAG" Or "AAH" Then
Answer.Value = Third
ElseIf Lookat = "AA" Or "AB" Or "AC" Or "AD" Or "AE" Or "AF" Or "AG" Or "AH" Then
Answer.Value = Half
ElseIf Lookat = "A" Or "B" Then
Answer.Value = Quarter
end if
End Sub

1 Protect your Workbook Link or hide it in a worksheet
2 Create your DataBase (your 'H' column)
3 In 'I' Column put your Weight [whole, third, half, quarter]. Sample:
+---------+--------+
| COL 'H' | HEIGHT |
+---------+--------+
| 6 | 1 |
| 9 | 0,5 |
| 4 | 0,75 |
| 6 | 1 |
| 8 | 0,5 |
| 1 | 0,75 |
| 5 | 1 |
| 4 | 0,5 |
| 5 | 0,75 |
| 7 | 1 |
| 4 | 0,5 |
| 9 | 0,75 |
| 1 | 1 |
| 8 | 0,5 |
| 1 | 0,75 |
| 5 | 1 |
| 1 | 0,5 |
| 4 | 0,75 |
| 4 | 1 |
| 1 | 0,5 |
| 7 | 0,75 |
+---------+--------+
4 Change your current formula to:
=VLOOKUP(B8,H:I,2,FALSE)
PS: You can try to name your Database to make your code better to understand Link.

Something like this may suit:
+----+-----+------+
| | A | B |
+----+-----+------+
| 1 | H10 | 1 |
| 2 | H14 | 0.5 |
| 3 | H15 | 1 |
| 4 | H16 | 0.75 |
| 5 | H18 | 1 |
| 6 | H19 | 0.75 |
| 7 | H23 | 1 |
| 8 | H24 | 0.75 |
| 9 | H25 | 1 |
| 10 | H26 | 0.75 |
| 11 | H27 | 0.5 |
| 12 | H28 | 1 |
| 13 | H29 | 0.75 |
| 14 | H30 | 0.5 |
| 15 | H33 | 1 |
| 16 | H34 | 0.5 |
| 17 | H35 | 1 |
| 18 | H36 | 0.25 |
| 19 | H49 | 1 |
| 20 | H5 | 1 |
| 21 | H50 | 0.5 |
| 22 | H51 | 1 |
| 23 | H52 | 0.25 |
| 24 | H53 | 1 |
| 25 | H54 | 0.75 |
| 26 | H57 | 1 |
| 27 | H7 | 0.75 |
+----+-----+------+
located in a sheet on its own in the same workbook and the array named say Vt of Workbook Scope.
This might then be used as the lookup table with a formula such as:
=VLOOKUP(A1,Vt,2)
where A1 contains a value such as "H10" for the purposes of this example.
It is important to sort A:B of the 'other' (to-be-hidden/protected) sheet in ascending order of ColumnA if to take advantage of a table that does not specifically equate every possible value. The lack of a fourth parameter in the VLOOKUP formula (though often causing problems!) means that an approximate match will be found where there is no exact match.

Related

Date comparison in Query function not returning result

I am using the below query
=QUERY(fulldata,"select avg(C) where A<>'Saturday'")
My Data looks like -
+-----------+-----------+----+-----+-----+-----+-----+---+-----+-----+-----+-----+-----+
| A | B | C | D | E | F | G | H | I | J | K | L | M |
+-----------+-----------+----+-----+-----+-----+-----+---+-----+-----+-----+-----+-----+
| Sunday | 1- Jan-18 | 6 | 12 | 0 | 1.5 | 2.5 | 2 | 0 | 0 | 0 | 0 | 0 |
| Monday | 2- Jan-18 | 6 | 0 | 9.5 | 4 | 0 | 0 | 0 | 2.5 | 2 | 0 | 0 |
| Tuesday | 3- Jan-18 | 7 | 0 | 7 | 0 | 0 | 2 | 0 | 4 | 1 | 3 | 0 |
| Wednesday | 4- Jan-18 | 7 | 1.5 | 6 | 1.5 | 0 | 0 | 0 | 2 | 0.5 | 5.5 | 0 |
| Thursday | 5- Jan-18 | 8 | 1 | 7.5 | 0 | 0 | 2 | 0 | 1.5 | 1 | 3 | 0 |
| Friday | 6- Jan-18 | 14 | 6 | 0 | 0 | 0 | 2 | 0 | 1.5 | 0.5 | 0 | 0 |
| Saturday | 7- Jan-18 | 9 | 0.5 | 0 | 0.5 | 1.5 | 2 | 3.5 | 4.5 | 0.5 | 1.5 | 0.5 |
+-----------+-----------+----+-----+-----+-----+-----+---+-----+-----+-----+-----+-----+
However I get no response from the query. The query runs without any issue but no results.
I simply want to find the average of C when A is not Saturday . Can anyone point where my query and/or logic is going wrong ?
Try adding the optional headers argument too (1 or 0).
PLEASE CHECK BELOW.
=QUERY(A:C,"select avg(C) where A NOT CONTAINS 'Saturday'")
For me #shashank padelkar's A seems not to work, but the following does:
=QUERY(A:C,"select avg(C) where not (A CONTAINS 'Saturday')")
(Assumes Sunday is in Row1.)
Mind you, so does OP's version work for me with fulldata replaced by A:C. So perhaps the real issue is something like trailing spaces or referencing the source. Also works for me if A1:M7 is named fulldata.
I found the answer.
My Day column was using =DAY(Col B) which was resulting into Sun, 31 Dec 99 but it was displaying it as Sunday because of the formatting done from toolbar.
So when I was comparing it with Saturday/Sunday it wasn't matching and hence no results.

Why is this WHERE NOT EXISTS() condition returning false pos/neg? (Itzik Ben-Gan identifying gaps)

I am learning Ben Itzik-Gans' 'solution 1' method of finding gaps to apply to a business problem.
Code here: http://rextester.com/CPXO58771
First, you start with a list of sequence values, some that break the sequence:
| A.seqval |
|----------|
| 2 |
| 3 |
| 11 |
| 12 |
| 13 |
| 31 |
| 33 |
| 34 |
| 35 |
| 42 |
And this query produces the results below:
SELECT
seqval + 1 AS start_range
,(SELECT MIN(B.seqval)
FROM dbo.NumSeq AS B
WHERE B.seqval > A.seqval) - 1 AS end_range
FROM
dbo.NumSeq AS A
WHERE 1=1
AND NOT EXISTS (SELECT *
FROM dbo.NumSeq AS B
WHERE B.seqval = A.seqval + 1)
AND seqval < (SELECT MAX(seqval) FROM dbo.NumSeq)
| start_range | end_range |
|-------------|-----------|
| 4 | 10 |
| 14 | 30 |
| 32 | 32 |
| 36 | 41 |
I am struggling to understand how the WHERE NOT EXISTS() filter is working. I went row-by-row to check the logic in the NOT EXISTS() subquery:
| A.seqval | A.seqval+1 | | B.seqval | | NOT EXISTS (SELECT * FROM NumSeq AS B WHERE B.seqval=A.seqval+1) |
|----------|------------|---|----------|---|-------------------------------------------------------------------------|
| 2 | 3 | | 2 | | TRUE- there is no A.seqval+1 = 2 |
| 3 | 4 | | 3 | | FALSE- there is a A.seqval+1 row = 3 |
| 11 | 12 | | 11 | | TRUE- there is no A.seqval+1 = 11 |
| 12 | 13 | | 12 | | FALSE- there is a A.seqval+1 row = 12 |
| 13 | 14 | | 13 | | FALSE- there is a A.seqval+1 row = 13 |
| 31 | 32 | | 31 | | ??? -there is no A.seqval+1 = 31, but this returns as TRUE in the query |
| 33 | 34 | | 33 | | TRUE- there is no A.seqval+1 = 33 |
| 34 | 35 | | 34 | | FALSE- there is a A.seqval+1 row = 34 |
| 35 | 36 | | 35 | | FALSE- there is a A.seqval+1 row = 35 |
| 42 | 43 | | 42 | | TRUE- there is no A.seqval+1 = 42 |
If NOT EXISTS() is returning TRUE for rows where a value doesn't exist, shouldn't they be included instead of excluded in the output?
How is seqval=31 identified as a true positive, when it is returned as FALSE?
I don't think you're handling the not in the not exists right in your stepthru.
Consider the first record 2+1 = 3. 3 exists in the data set. so NOT exists 3 = false not true. Set {A} and set {B} get compared on Set A.SeqNum+1 = B.SeqNum
A B Exists Not Exists
2 NEVER Needed as we always look A.SEQVAL+1
2+1 = 3 3 True False
3+1 = 4 NULL False True
31+1 = 32 NULL False True
42 NULL (never evaluated because of `where seqval < (SELECT MAX(seqval) FROM dbo.NumSeq)`
Carry this on to 31... 31+1 = 32.. 32 not exists in set .. TRUE. as it should be.
if we asked does 32 exist in the set it would be false. but since it's a not exists.... True.
+----------+------------+-------------------------------------+----------------------------------+--------+------------+-----------------------------------------+
| A.SeqVal | A.SeqVal+1 | Does A.SeqVal+1 exist in A.SEQ Val? | Since it exists do we return it? | SeqVal | Now Add 1 | Now subtract 1 from next value in A.Seq |
+----------+------------+-------------------------------------+----------------------------------+--------+------------+-----------------------------------------+
| 2 | 3 | Yes | No | | | |
| 3 | 4 | No | Yes | 3 | 4 | 10 |
| 11 | 12 | Yes | No | | | |
| 12 | 13 | Yes | No | | | |
| 13 | 14 | No | Yes | 12 | 13 | 30 |
| 31 | 32 | No | yes | 31 | 32 | 32 |
| 33 | 34 | Yes | No | | | |
| 34 | 35 | Yes | No | | | |
| 35 | 36 | No | Yes | 35 | 36 | 41 |
| 42 | 43 | No | Yes | 42 | Ignore max | |
+----------+------------+-------------------------------------+----------------------------------+--------+------------+-----------------------------------------+
Now Add one (why? because we know the next value in sequence is the first in a range missing)
Now subtract one from the next value in a.Seqval (why? because we know that value exists; but the one before it doesn't)

SQL Server - Dense/Rank not giving desired results

I'm trying to give a rank to two columns. Both columns are numeric values and one represents an X coordinate and the other a Y Coordinate.
My select statement is as follows:
SELECT
Dense_Rank() over (Partition by X,uniqueid Order by Y ASC) as Y_Rank,
Dense_Rank() over (Partition by Y,uniqueid Order by X ASC) as X_Rank
,[uniqueid]
,[X]
,[Y]
FROM xxxx.xxxxx
The idea being that the statement above would generate an x/y coordinate when concatenated. However my result set is not producing "unique" coordinates or if two rows have the same X but different Y values I'm getting X=1, Y=1 for both rows rather than X=1,Y=1 & X=1,Y=2.
My first question is why is this happening or what am I doing wrong and secondly it appears that rank()/Dense_Rank() doesn't look beyond the decimal, is that true?
I've cast the values to int to remove possible conflicts with floats
+--------+--------+-----+----+--------------------------------------+------------------+
| X_Rank | Y_Rank | X | Y | uniqueid | Slot_Side |
+--------+--------+-----+----+--------------------------------------+------------------+
| 1 | 1 | 29 | 4 | 00000000-0000-0000-0000-fffff27fdf27 | 1 |
| 1 | 2 | 29 | 45 | 00000000-0000-0000-0000-fffff27fdf27 | 1 |
| 1 | 1 | 52 | 6 | 00000000-0000-0000-0000-fffff27fdf2d | 1 |
| 2 | 1 | 236 | 6 | 00000000-0000-0000-0000-fffff27fdf2d | 1 |
| 1 | 1 | 33 | 45 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 1 | 1 | 5 | 3 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 2 | 1 | 55 | 3 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 2 | 1 | 83 | 45 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 3 | 1 | 133 | 45 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 3 | 1 | 105 | 3 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 4 | 1 | 155 | 3 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 4 | 1 | 183 | 45 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 5 | 1 | 233 | 45 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 5 | 1 | 205 | 3 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 6 | 1 | 255 | 3 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 6 | 1 | 283 | 45 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 7 | 1 | 333 | 45 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 7 | 1 | 305 | 3 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 8 | 1 | 355 | 3 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 8 | 1 | 383 | 45 | 00000000-0000-0000-0000-fffff27fe073 | 0 |
| 1 | 1 | 5 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
| 2 | 1 | 41 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
| 3 | 1 | 77 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
| 4 | 1 | 113 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
| 5 | 1 | 149 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
| 6 | 1 | 185 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
| 7 | 1 | 221 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
| 8 | 1 | 257 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
| 9 | 1 | 293 | 2 | 00000000-0000-0000-0000-fffff27fe074 | 0 |
+--------+--------+-----+----+--------------------------------------+------------------+
So I found the flaw in my logic.
Here is the corrected statement:
Select
Dense_Rank() over (Partition by Y,uniqueid Order by Y) as X_Rank,
Dense_Rank() over (Partition by uniqueid Order by Y) as Y_Rank
,[uniqueid]
,[X]
,[Y]
FROM xxxx.xxxxx
I was tricked by thinking my X_Rank statement was working when really it was a coincidence given the data I was using. The corrected statement gives more weight to the X_Rank by prioritizing or ranking/partitioning it based upon Y. The idea being that I really care about where the Y values change in relation to X, as this indicates the start of a new "Row". My original statement looked at both X and Y equally which returned undesired results. Once I made the relationship for X_Rank I only need to look at Y as it is ordered rather than what Y means in relation to X. Sorry for my poor explanation but at least it works. Thanks for the help regardless!

Series of conditional table and cell references

I have a reference table as such in Sheet2 of my workbook
|Score 1| | |Score 2 | | |
----------------------------------------------------------
| name | min | max | target | min | max | target |
----------------------------------------------------------
| jeff | 30 | 40 | 35 | 45 | 55 | 50 |
----------------------------------------------------------
| steve | 35 | 45 | 40 | 45 | 65 | 55 |
then in Sheet1 I have a list of scores for each name as such
| jeff | 1 | | | | steve | 3 | | |
------------------------------------------------------------
| jeff | 2 | | | | steve | 2 | | |
------------------------------------------------------------
| jeff | 2 | | | | steve | 3 | | |
------------------------------------------------------------
| jeff | 3 | | | | steve | 3 | | |
------------------------------------------------------------
| jeff | 1 | | | | steve | 2 | | |
------------------------------------------------------------
I am aware of simple lookups and offsetting values but I can't think of a way to do multiple references on different levels... Is there a way to in Sheet1 next to the scores have a function that looks up the score, then who the score is for, and then prints the corresponding min max and target values for that person with that score.
So if it sees 1 and then jeff, it returns 30 | 40 | 35 in the next 3 boxes. I would do this manually but the list is very long and is populated daily by an existing macro.
Use VLOOKUP with the name (jeff) and take the index (1) to calculate the column to take.

How to aggregate column on changing criteria in SQL (multiple SUMIFS)

Consider the following simplified example:
Table JobTitles
| PersonID | JobTitle | StartDate | EndDate |
|----------|----------|-----------|---------|
| A | A1 | 1 | 5 |
| A | A2 | 6 | 10 |
| A | A3 | 11 | 15 |
| B | B1 | 2 | 4 |
| B | B2 | 5 | 7 |
| B | B3 | 8 | 11 |
| C | C1 | 5 | 12 |
| C | C2 | 13 | 14 |
| C | C3 | 15 | 18 |
Table Transactions:
| PersonID | TransDate | Amt |
|----------|-----------|-----|
| A | 2 | 5 |
| A | 3 | 10 |
| A | 12 | 5 |
| A | 12 | 10 |
| B | 3 | 5 |
| B | 3 | 10 |
| B | 10 | 5 |
| C | 16 | 10 |
| C | 17 | 5 |
| C | 17 | 10 |
| C | 17 | 5 |
Desired Output:
| PersonID | JobTitle | StartDate | EndDate | Amt |
|----------|----------|-----------|---------|-----|
| A | A1 | 1 | 5 | 15 |
| A | A2 | 6 | 10 | 0 |
| A | A3 | 11 | 15 | 15 |
| B | B1 | 2 | 4 | 15 |
| B | B2 | 5 | 7 | 0 |
| B | B3 | 8 | 11 | 5 |
| C | C1 | 5 | 12 | 0 |
| C | C2 | 13 | 14 | 0 |
| C | C3 | 15 | 18 | 30 |
To me this is JobTitles LEFT OUTER JOIN Transactions with some type of moving criteria for the TransDate -- that is, I want to SUM Transaction.Amt if Transactions.TransDate is between JobTitles.StartDate and JobTitles.EndDate per each PersonID.
Feels like some type of partition or window function, but my SQL skills are not strong enough to create an elegant solution. In Excel, this equates to:
SUMIFS(Transaction[Amt], JobTitles[PersonID], Results[#[PersonID]], Transactions[TransDate], ">" & Results[#[StartDate]], Transactions[TransDate], "<=" & Results[#[EndDate]])
Moreover, I want to be able to perform this same logic over several flavors of Transaction tables.
The basic query is:
select jt.PersonID, jt.JobTitle, jt.StartDate, jt.EndDate, coalesce(sum(amt), 0) as amt
from JobTitles jt left join
Transactions t
on jt.PersonId = t.PersonId and
t.TransDate between jt.StartDate and jt.EndDate
group by jt.PersonID, jt.JobTitle, jt.StartDate, jt.EndDate;