Pattern match and sum values in VBA - excel

I have a spreadsheet where users enter their three character name code corresponding to everyday with number of hours that they plan to spend in a specific task (A, B, C) within brackets. What I want is these needs to be summed up for every user and get populated in rows corresponding to their names everyday.
Currently I am using an custom vba function to perform this sum, however I wanted to know if this can be done directly with any custom formula or VLOOKUP. Any help is appreciated!
Edit: I have been using the function, one specific to each user (the three character code) to calculate this sum.
Function SumJON(Target As Range) As Double
Dim xCell As Range
Dim xSum As Double
xSum = 0
For Each xCell In Target
If xCell.Value <> "" And InStr(xCell.Value, "JON") > 0 Then
Test = Split(xCell.Value, "JON")
Test1 = Test(1)
Test2 = Split(Test1, ")")
Test3 = Test2(0)
xSum = xSum + Test3
End If
Next
SumJON = xSum
End Function

You can try the following, with the below setup of data:
Formula in B2:
=SUM(IF(ISNUMBER(SEARCH(MID($A2,LEN($A2)-3,3),B$6:B$8)),MID(B$6:B$8,SEARCH(MID($A2,LEN($A2)-3,3),B$6:B$8)+4,SEARCH(")",B$6:B$8,SEARCH(MID($A2,LEN($A2)-3,3),B$6:B$8))-SEARCH(MID($A2,LEN($A2)-3,3),B$6:B$8)-4),0)*1)
NOTE: This is an array formula and should be confirmed through CtrlShiftEnter
Drag formula down and right.
It will also work on expending the amount of names in the list AND with decimals:
Note that my system decimal point is a comma.
Breakdown:
To extract the searchvalue, JON, DOE and PRK we can make use of MID since you have a certain pattern to take into account. It would always have to be:
=MID($A2,LEN($A2)-3,3)
This value needs to be used in a SEARCH function which will return a position of our searchvalue in the A,B,C rows. Since it's an array formula it will return all these numeric positions or an error.
Therefor we need to use ISNUMBER to check if the searchvalue is actually found through SEARCH. If so it will return a TRUE and if not it will return FALSE.
Because this is part of our IFstatement, the formula will do another MID function for the TRUE values. Again we will have to make use of our searchvalue formula, but this time we know it will be in these strings and therefor we can use the numerical position as our starting position (+4, because these strings are 3 positions long + the opening paranthesis) in this second MID.
Now we have a starting position to get the lookupvalues from, we just need to know the first position of the next closing paranthesis, and we can do that through SEARCH from this exact position.
Multiplying it by 1 in the end would turn these values in true numerical lookupvalues, which finally can be summed through SUM
Et voila, we got the total of values between the paranthesis where the searchcriteria meets the current user.

Related

How to check whether a cell contains a number of search words

I am looking for answer on how to use correctly the "FIND"formula or any other formula that can help me finding string on one cell to another.
I use the find formula to find these:
=IF(FIND(A1,B1),"yes","no")
Col1 - Col2
A,B - A,B,C = formula works well
A,C - A,B,C = formula not working. Returning "#VALUE"
Thanks for your help.
FIND only checks if the full String from Col1 is contained in Col2.
if you want to check for all items, you would have to split the String by commas somehow.
This is a quirky alternative to the above presented UDF approach, if you can accept limitations to keeping the "relative distance" between the pattern and function cells involved. My approach involves defining Names for predefined formulas (containing relative cell references).
In the following sample code I assume that I want to use the find formula in cell C2 and my comma separated pattern is stored in the neighbouring cell left to it (relative!).
Now I can define the following "names" (press <cntrl> <F3>).
one: =LEFT(Sheet1!B2;FIND(",";Sheet1!B2)-1)
oneR: =MID(Sheet1!B2;FIND(",";Sheet1!B2)+1;LEN(Sheet1!B3))
two: =LEFT(oneR;FIND(",";oneR & ",")-1)
three: =MID(oneR;FIND(",";oneR & ",")+1;LEN(oneR))
You can extend the sample if you are interested in more than three parts. In the formula cell (C2, always the one directly to the right of the pattern cell) I can now write
=IFERROR(AND(FIND(one;D2);FIND(two;D2);FIND(three;D2));0;1)
to check whether all three words of the pattern occur in the target cell string (D2). The result is 1 if all three patterns are found, otherwise it returns 0.
If your okay with using VBA, above can be achieved using function, you can use following code as UDF.
Function FindExistence(firstStr As String, secStr As String) As String
Dim firstArr() As String, secArr() As String
Dim i As Long, j As Long
Dim fElem As Variant, sElem As Variant
Dim isExist As Boolean
firstArr = Split(firstStr, ",") 'comma separated values of first cell into array
secArr = Split(secStr, ",") 'comma separated values of second cell into array
For Each fElem In firstArr 'loop through each element in array of first array
isExist = False 'set boolean variable to False
For Each sElem In secArr 'loop through each element in array of second array
If fElem = sElem Then 'check if elements in both array are equal
isExist = True 'if elements are equal exit loop, check next element
Exit For
End If
Next sElem
If Not isExist Then Exit For 'if isExist is false exit all loop
Next fElem
If Not isExist Then 'if isExist is false return No else Yes
FindExistence = "No"
Else
FindExistence = "Yes"
End If
End Function
See image for reference.
On how to create function see this.
Instead of using two Exit For you can also use GoTo.
Here's a no-column-helper approach and guaranteed zero VBA.
I use an array formula as shown below:
= LEN(SUBSTITUTE(A2,",",""))
= SUM(
--ISNUMBER(
MATCH(
MID(
SUBSTITUTE(A2,",",""),
ROW(INDIRECT("1:"&LEN(SUBSTITUTE(A2,",","")))),
1),
MID(
SUBSTITUTE(B2,",",""),
ROW(INDIRECT("1:"&LEN(SUBSTITUTE(B2,",","")))),
1),
0)
)
)
How does it work?
First, we need a way to get all the characters in your string to an array so that we can check it one by one for matches. We do that using the formula:
MID(
SUBSTITUTE(A2,",",""),
ROW(INDIRECT("1:"&LEN(SUBSTITUTE(A2,",","")))),
1)
General logic is MID(<text>,<start_num>,<num_chars>) where instead of assigning one numerical value in <start_num>, we assign an array of numbers using ROW(INDIRECT("1:"&LEN(SUBSTITUTE(A2,",","")))). In a straightforward form, that is ROW([reference]) (e.g. ROW(1:10) gives us array of numbers 1 to 10, an array with 10 elements). But to make this formula work, we need to make sure that the size of our array is equal to the number of characters we are working on excluding , commas. So we eliminate the comma first by using SUBSTITUTE(A2,",","") and get the length of the resulting string in memory. For example if you have text A,C in cell A2 then SUBSTITUTE function will yield AC (in memory) and getting the length of that using LEN returns 2. Then we use that returned value as boundary for our array. We simply concatenate it with "1:" and to make it return a valid reference, we use INDIRECT function.
We repeat above for the string we search for matches (e.g. cell B2 contains A,B,C, we need to put that in an array too)
Conduct search using MATCH function. Here, we use the array of values we have generated using the 2 steps above. MATCH(<lookup_value>,<lookup_array>,[match_type]) is the general form. Fortunately, we can also use an array of values for <lookup_value> argument instead of a single value. So we use the array we yield in step 1 above as our lookup_values.
Next is we confirm how many matches we have using ISNUMBER. MATCH function returns a numerical value (which is the position of the matched value in the array) so we simply evaluate that using ISNUMBER. This returns an array of Boolean values {TRUE,FALSE,TRUE} but since we need to count it somehow, we use double negation -- to convert it to numbers which yields {1,0,1}.
Final step is just use SUM function to count the number of matches and compare it to the length of the search string without the commas. We confirm the formula using Ctrl+Shift+Enter
Alternatively, you can use an AND function in the final step to check if all matches are found.
Edit1: Formula using AND to directly evaluate if all matches are found.
= AND(
ISNUMBER(
MATCH(
MID(
SUBSTITUTE(A2,",",""),
ROW(INDIRECT("1:"&LEN(SUBSTITUTE(A2,",","")))),
1),
MID(
SUBSTITUTE(B2,",",""),
ROW(INDIRECT("1:"&LEN(SUBSTITUTE(B2,",","")))),
1),
0)
)
)
So this will return TRUE if all characters in cell A2 is found in all character set in cell B2. Now this works for uniform comma separated strings only (e.g A,K,E,G to be searched in A,B,C,D,E,F,G,H,I,J,K). This will fail on non-uniform comma separated strings (e.g. A,BC,D,EFG,H,I,J then trying to find BC,I). I hope this gets you going.

excel query append above row to cell

I've tried the concatenate function but it wasn't really useful.
I'm trying to accomplish, so here's a screenshot/picture
Basically I need to be able to input data for the score, then have the cell autofill the data based on the users and their score with the corresponding tile, my confusion is how to do this if/with multiple users if they're on the same tile.
OK... i don't know if you have a mechanism for Tile 1 knowing that it is Tile 1 etc., but what I did was to put the numbers onto another sheet in the same cells. I.e. on sheet Tiles cell B18 has 1. In this way we can just reference that cell rather than any hardcoded values in the formula...
I also decided that if there were no names with that score, then we would just want the tile name (e.g. Tile 1)
So these give the first part of the formula that can be written into B18 and copied to the range B18:F31:
=IF(Tiles!B18="","",IF(ISERROR(MATCH(Tiles!B18,$B$7:$H$7,0)),"Tile "&Tiles!B18,...))
What this is doing is:
checking the Tiles sheet - if the corresponding cell is empty, then this isn't a tile and we just put ""
using MATCH to see if that score is held by any user - if it isn't found then it gives an error so the IF(ISERROR(...)) will return just the tile name
the ... is where the two options come in...
OPTION 1 - Loooong Formula
Replace the ... by:
MID(IF($B$7=Tiles!B18,", "&$B$2,"")&IF($C$7=Tiles!B18,", "&$C$2,"")&IF($D$7=Tiles!B18,", "&$D$2,"")&IF($E$7=Tiles!B18,", "&$E$2,""),3,9999)
...and copy the &IF($E$7=Tiles!B18,", "&$E$2,"") section with each needed column for your 22 players.
So the unit is "if the player's score is the same as the tile number, give me ", name" otherwise blank" and we join these with the & operator.
This is wrapped in a MID(...,3,9999) so we get rid of the ", " at the start of the first name we find (the joined IF statements would give e.g. ", Aaron, infinite user").
This option is a bit painful to write (and read!) but it only has to be done once and the formula can be copied and pasted...
OPTION 2 - VBA Function and Array Formulas
Public Function JOIN(rng As Variant, Optional separator = ", ") As String
JOIN = ""
Dim v As String
For Each c In rng
If IsObject(c) Then
v = c.Text
Else
v = c
End If
If v <> "" Then
If JOIN = "" Then
JOIN = v
Else
JOIN = JOIN & separator & v
End If
End If
Next c
End Function
This function will join either a range of cells' values, or an array of strings. Using it with an array formula (entered in Excel using Ctrl+Shift+Enter) means that we can do a conditional join...
So the ... becomes:
JOIN(IF($B$7:$H$7=Tiles!B18,$B$2:$H$2,""))
For this, each value in the scores range $B$7:$H$7 will be checked against the tile number. If it doesn't match, we return "", otherwise we return the corresponding name (in range $B$2:$H$2). This will give us an array of blanks and names which JOIN will join...
This option means a macro enabled workbook and using "more complex" array formulas. But array formulas are great, and I advise everyone to play with them ;-). cpearson has a good intro: http://www.cpearson.com/excel/arrayformulas.aspx
Have fun!

Excel formula for count cells if not null, invalid or 0

I have a formula that I want to use to check if a cell does not have an 'invalid' value in it. However, it is also counting empty cells, and cells that have anything in it that isn't equal to zero:
=COUNTIF(A2:A200,"<>0")
This only checks if the cell does not have a value of "0". What can I add to it so that it will not count empty cells, or cells with values like:
#######
VALUE?
r
etc. All I want is to count how many cells have a number in them that does not equal 0, or an error.
The array formula below first counts all non-0 and non-null values and then subtracts the count of cells that contain errors.
You need to press CTRL + SHIFT + ENTER to properly execute this formula:
=COUNTIFS(A2:A200,"<>0", A2:A200,"<>"&"", A2:A200,"<>"&"NIL") - SUM(IF(ISERROR(A2:A200),1,"")) - SUM(IF(ISNA(A2:A200),1,""))
You can nest up to 7 valid or invalid entries. If you need to have more than that you should perhaps designate a column for your "black list" of entries that you can add to when an occurrence causes a count that you don't believe should be valid. For example:
=IF(ISERR(VLOOKUP(A1,Sheet1!E:E,1,FALSE))=FALSE,1,0)
Where column "E" is your list of values that are considered invalid. Drag this down next to your criteria and sum.
Edit: I wasn't aware of countifs. So you have a couple of solutions, here, depending on your preference.
This formula will count only numbers <> 0, excluding blanks, error messages, etc. BUT it will not exclude the cells that display ###### (but really contain a number) if the reason for that is a column that is too narrow, or a negative date or time value.
=SUMPRODUCT(--ISNUMBER(A2:A200))-COUNTIF(A2:A200,0)
If you really want to avoid counting cells that display ####### when the underlying contents is a number not equal to zero, you will need to use a UDF to act on the Text property of the cell. In addition, narrowing or widening the column to produce that affect will not trigger a calculation event that would update the formula, so you need to somehow do that in order to ensure the formula results are correct.
That is why I added Application.Volatile to the code, but it is still possible to produce a situation where the result of the formula does not agree with the display in the range being checked, at least until the next calculation event takes place.
To enter this User Defined Function (UDF), alt-F11 opens the Visual Basic Editor.
Ensure your project is highlighted in the Project Explorer window.
Then, from the top menu, select Insert/Module and
paste the code below into the window that opens.
To use this User Defined Function (UDF), enter a formula like
=CountNumbersNEZero(A2:A200)
in some cell.
Option Explicit
Function CountNumbersNEZero(rg As Range) As Long
Application.Volatile
Dim C As Range
Dim L As Double
For Each C In rg
If IsNumeric(C.Text) Then
If C.Text <> 0 Then L = L + 1
End If
Next C
CountNumbersNEZero = L
End Function

vector element operations in excel

I am trying to perform the following in one steps (one formula):
Strip a letter from a column of elements and add them up.
Example:
Data:
1x
2y
3x
I want to strip letters and add up numbers all in one formula.
I understand that I could have a helper column in which I strip letters x,y,z and then have a formula to add up the numbers, but I don't want to do this.
Thanks for any suggestions.
Assuming one entry per cell:
Is there only one letter at the end? If so, you can use:
=SUMPRODUCT(--LEFT(A1:A100,LEN(A1:A100)-1))
If there might be multiple letters at the end, a simple UDF would be simpler:
Option Explicit
Function AddStrings(rg As Range)
Dim L As Long
Dim I As Long
For I = 1 To rg.Count
L = L + Val(rg(I))
Next I
AddStrings = L
End Function
EDIT: If some of the cells might be blank, you can use either the UDF, or, if you prefer the formula, this array-entered formula:
=SUM(IFERROR(--LEFT(A1:A100,LEN(A1:A100)-1),0))
To array-enter a formula, after entering
the formula into the cell or formula bar, hold down
ctrl-shift while hitting enter. If you did this
correctly, Excel will place braces {...} around the formula.
Assuming that the format is consistent, you can do something like
=VALUE(LEFT(A1,1))+VALUE(MID(A1,4,1))+VALUE(MID(A1,7,1))
If the format is not consistent, things get more difficult. Let me know and I will expand the answer.
EDIT:
This function works with a variable length text, assuming that the fields are separated by the spaces and have one letter after the number:
Function AddValues(Text As String)
Dim Tokens() As String, I As Integer
Tokens = Split(Text)
For I = 0 To UBound(Tokens)
AddValues = AddValues + Val(Left(Tokens(I), Len(Tokens(I)) - 1))
Next I
End Function

Excel conditional formatting for the entire row with more than one formula

After 3 hours of searching I still didn't find an answer, here is what I am trying to do:
I am trying to fill with green any row that has WBS in it and with Red any row that has ACT in it and Blue any row that has EPR in it. It works for the first formula then when I try to add the second one every thing get messed up.
what i have understood is that you need to search a keyword in a row and if its found in any cell of that row then color it.
May be we can do it with conditional formatting but i have another idea. We can create a simple search function in Excel VBA. Something like this:
=search_row(A1:F1,"EPR")
The function will return 1 if EPR is found in any cell of specified row. Now if you create two different columns at the end of data columns, name first with WPS and second with EPR and write this function in it. Like
G1 =search_row(A1:F1,"WPS")
H1 =search_row(A1:F1,"EPR")
Drag it to end. Now sort the columns. First for WPS from higher to lower. Then color all rows having 1 in a single select. Similarly do the same with EPR (H1) column.
To use this function you can download the macro file from the following URL:
http://asimishaq.com/myfiles/SearchHighlight.xlsm
Now to run it first of all enable macros, and then re-open your data file and then open this macro file. As long as this macro file is opened you can use this function. Following is the VBA code if you want to create the macro yourself:
Function search_row(sRow As Range, Keyword As String)
Dim i As Integer
Dim Found As Integer
For i = 1 To sRow.Columns.Count
If InStr(1, LCase(sRow.Cells(1, i)), LCase(Keyword)) > 0 Then
search_row = 1
End If
Next
End Function
I had a go at making a function similar to asim-ishaq to determine if your search term exists in the row for fun :) then tried to apply it to highlighting rows, turns out I dont know how to use conditional formatting very well! Figured it out in the end, hopefully I've explained it well enough.
With this you will have to have (one) extra column at the end of your data to contain the result.
It might be possible to not require the extra column by putting the function inside the conditional formatting, however I couldn't get it to work (didn't try very hard). This isn't a great loss as it's much simpler to edit the formula if its on the workbook, instead of having to go through each conditional rule to edit it, should you need to edit it in the future.
To get the formatting to work you will need to create a number of rules (one per keyword)
You want to create a rule of the type shown below, in the formula box you need something along the lines of: =INDIRECT("D" & ROW())=0 where D is the column containing the result of the function below and 0 is the index of the keyword you're highlighting.
In my example, the formula in the D Column is: =SearchRow(An:Cn,"ABS","KBS","JBS") (where n is the row the formula is on)
Set the formatting as desired then press OK, when you return to the rule manager you will need to update the Applies to value, which should be a range that covers all the data you want to highlight. In my example it was $A$1:$C$3
My function below takes 2+ Arguments, The first is the range to search. The second (and any subsequent ones) are search terms.
The function will return a number. -1 for no matches and 0+ for the found search term. The number depends on the position in the arguments.
A1 = "ABS"
B1 = "SBA"
A2 = "SBA"
B2 = "ABS"
A3 = ""
B3 = ""
C1 = "=SearchRow(A1:B1, "ABS", "SBA")"
C2 = "=SearchRow(A2:B2, "ABS", "SBA")"
C3 = "=SearchRow(A3:B3, "ABS", "SBA")"
C1 > 0
C2 > 1
C3 > -1
The function will always return the first result, searching left to right comparing each cell to the Keywords in order. Using my example, if a cell contained "SBA ABS" the result would be 0 (for ABS). I guess your cells will probably only contain one keyword though so this shouldn't be a problem?
Public Function SearchRow(ByVal Row As Range, ParamArray Keyword() As Variant) As Integer
Dim Column As Integer
Dim Value As String
Dim Index As Integer
Dim Result As Integer
For Column = 1 To Row.Columns.Count
Value = LCase(Row.Cells(1, Column))
Result = -1
For Index = LBound(Keyword) To UBound(Keyword)
If InStr(1, Value, LCase(Keyword(Index))) > 0 Then
Result = Index
Exit For
End If
Next Index
If Result > -1 Then
Exit For
End If
Next Column
SearchRow = Result
End Function

Resources