Specify non equality in string comparison - excel

Within a procedure I am writing, I check all values of different columns of a table to be equal (or not equal) to a value.
Thing is I do not know in advance how many columns I will check and what values I will check and does not want to have to ask for another array of value (like 0 and 1) to specify if the value given in the first array is to be equal or NOT equal to the table's column's value.
Here is an example:
With Workbooks(path).Worksheets(sheetName)
Set tbl = .ListObjects(tableName)
i = 1
For Each col In fColumn
colRange(i) = tblSource.ListColumns(col).DataBodyRange
i = i + 1
Next
Dim flag As Boolean
For i = 1 To .ListRows.Count
flag = True
For j = 1 To fColumn.UBound
If colRange(i).Value <> fColumnVal(i) Then ' check if stored value is equal to specific value
flag = False
Exit For
End If
Next j
If flag = True Then
' do something
End If
Next i
So fColumn is an array of string which contains the name of the column to be checked, colRange is the respective data range, fColumnVal is the value to be tested.
How I approached my problem is for each row of the table, I test the cell value of the different columns to be tested (names in fColumn) against the value of fColumnVal.
In theory that would work w/o problem. However, it could be possible that someone would like to find values which are different of the value in fColumnVal such as:
<<Find every cell which are different of value "Hello">>
My question is, is it possible to encompass the idea of check if not equal in the value of fColumnVal and not in the line of code of the condition. Something similar to the use of "<>" in Excel's formula which is equivalent <<to different of "">>.
If that does not exist I will simply add another required variable to the sub which indicates how to test the value.
Thanks.

Related

Row index variable used with +1 in ListObject

I run the following:
For r = 2 To lastRowSan
useSan.Activate
searchCATID = Cells(r, LONameSan.ListColumns("CATID").Range.Column)
useOfCat = Cells(r, LONameSan.ListColumns("Total 6 months").Range.Column)
compList.Activate
searchVal = WorksheetFunction.XLookup(searchCATID, compList.ListObjects("completeList").ListColumns("CATID").Range, compList.ListObjects("completeList").ListColumns("CATID").Range, "NOK")
If searchVal <> "NOK" Then
compList.Activate
Set rowCatSearch = [completeList].Rows(WorksheetFunction.Match(searchCATID, [completeList[CATID]], 0))
rowCatID = rowCatSearch.Row
colNameUSe = "Number of uses - " & market
compList.ListObjects("completeList").ListColumns(colNameUSe).DataBodyRange(rowCatID) = useOfCat
End If
Next r
where useSan and compList are worksheets, LONameSan is a ListObject defined dynamically.
When I run the script, the matching values are found at the correct row index (the rowCatID = rowCatSearch.row is correct (I check step by step in Degug mode).
However, the line compList.ListObjects("completeList").ListColumns(colNameUSe).DataBodyRange(rowCatID) = useOfCat will put the useOfCat valmue in the next row, ie. in rowCatID + 1^
I have a second similar loop after this one which gives the exact same result.
Is this normal and due to the fact that I put the value in the databodyrange and that in this case, the row 1 is the first row of the databodyrange and does not count the real row 1 which is the Header?
Edit:
My answer is mostly valid, upon further inspection, the reason that your Match is providing the Worksheet Row is not because of the Match, but because of the range provided by the Table:
Set rowCatSearch = [completeList].Rows(WorksheetFunction.Match(searchCATID, [completeList[CATID]], 0))
The Match is actually giving the proper Row number you want, however because you have the match enclosed with the Table.Rows, it changes the Row.
Instead if you use:
rowCatID = WorksheetFunction.Match(searchCATID, [completeList[CATID]], 0)
that should give out the proper Row # you are looking for. Notice that I jumped directly to rowCatID, becasue the Set rowCatSearch is no longer needed by only using the Match function with the Table Column in it.
Original Answer:
This is because you are trying to assign a value using the "worksheet row" returned by the MATCH function. However the DataBodyRange is only requesting the row number of the DATABODY, in this case, Row 1 in the DataBodyRange is the First Row of the Data and not the first row of the Worksheet.
Example: in a table that starts with Headers on A1 and data from A2 to A5, Worksheet Row 5 is actually Row 4 in the DataBodyRange, but your MATCH Function is giving you the worksheet row because that's where it found the value. As long as the Table starts at Row 1 you can do a -1 on rowCatID and it should work.

How do I print a specific value in one column based on the value in another using VBA?

I have Excel data in column B (cells B2:B60) that contains email addresses (such as xxx#ahcptcare.com). In column D, I need to print "APC" if the value in B contains ahcptcare.com, or UDP if the value in B contains #upschyd.com, and so on for more email addresses.
I don't know how to look for the text after the # symbol in column B and return another value in column D. I started researching on RIGHT and FIND functions in VBA but I'm at loss on this one. I think I can use and IFS and RIGHT but I'm not sure how to.
How can I do this?
You can do this with a loop and two lines of code. If you are just checking for those 2 domains then
Dim r As Range
Set r = Sheets(1).Range("B2:B60")
Dim c As Range
'Loop through each cell in the range
For Each c In r
'Check for the first domain then check for the second.
If InStr(1, UCase(c.Value), "AHCPTCARE.COM", vbTextCompare) > 0 Then c.Offset(0, 2).Value = "APC"
If InStr(1, UCase(c.Value), "UPSCHYD.COM", vbTextCompare) > 0 Then c.Offset(0, 2).Value = "UDP"
Next
I'll go over how this would work for the following example data in Excel and hopefully it will teach you how to apply similar VBA code to your own Excel data. It's been a while since I've written VBA, so there may be more efficient ways to do this.
A
B
C
D
Email
Result
john1#abc.com
john2#xyx.com
john3#qrf.com
john4#wtf.com
john5#omg.com
john6#bbq.com
First you need to determine your business logic: what is it you are trying to do? Well, you have a range of data that you want to loop over, and, based on part of the data, print a specific string/value to another column in the same row as each data point, respectively.
In my case, I have data in cells B2:B7 of column B (Email), and I want to print a value to column D (Result).
The first thing we need to do in VBA is declare some variables for the data we are going to work with:
Dim rng As Range, cell As Range 'We are running through a range of cells, so the datatype of Range is appropriate here
Dim cellVal As Variant, comparisonVal As Variant 'Each cell will have text and special characters, so I just used the catch-all datatype of Variant here
Set rng = Range("B2:B7") 'This is just my range of data
Now that we've got some variables declared, you can start on your loop now. You need to loop through each cell in your range, so that's first... and there's not much better for looping through a set range of cells than a for each ... in loop in VBA.
I typically write the entire loop 'wrapper' first and then click inside it to start writing what will happen for each iteration.
For Each cell In rng
Next cell
Next, we'll jump inside that loop and write the steps we want to loop through. After you move to be inside the loop, we need to determine the value of the cell (but only the part after the # symbol) and store that value so that it can be compared against later. It's also good practice to check whether there is an # symbol in the first place... otherwise your code may go off the rails.
cellVal = cell.Value 'We're assigning this here rather than in the original variable declaration section because its value needs to change with each cell we loop through
If InStr(1, cellVal, "#") > 0 Then 'The InStr() function checks whether a specified value exists in a string
comparisonVal = Split(cellVal, "#")(1) 'Here we assign the value we want to use for comparison by using the Split() function to give us the value after the # symbol. Note that this may not work if the # symbol appears multiple times in a cell in column B. Lookup the Split() function for further information.
Else
MgBox ("No # symbol found! Exiting loop.") 'If we don't find an # symbol in one of the rows, we should probably alert the user. Dirty source data plagues even the best of us.
End If
OK! We're almost there. Now we just need to... actually apply the right Result value in column D based on the value in our comparisonVal variable. I don't know the extent of your data or business logic, but from what you've shared in your question, it sounds like you have some predetermined Result values to put into the Result column depending on the value of the Email column (column B).
When you have several predetermined routes you want to follow based on several possible scenarios, the best way to achieve that is often a switch statement (sometimes called a case statement). The syntax for that in VBA is:
Select Case [comparison]
Case [case value 1]
Case [case value 2]
...
Case Else
End Select
In our case (no pun intended...), we'll use comparisonVal (which contains just the part of each email address after the # symbol) and assign the appropriate value to column D of the current row. To do that assignment in a relative way, we'll use the Offset() function; you can look that up later for further reading if necessary. Remember, this bit of code also goes inside the foreach loop.
Select Case comparisonVal
Case "abc.com"
cell.Offset(0, 2).Value = "APC"
Case "xyz.com"
cell.Offset(0, 2).Value = "UDP"
Case "qrf.com"
cell.Offset(0, 2).Value = "THC"
Case "wtf.com"
cell.Offset(0, 2).Value = "IDK"
Case "omg.com"
cell.Offset(0, 2).Value = "LOL"
Case Else
cell.Offset(0, 2).Value = "Unknown Value"
End Select
Run all of that code inside your subroutine or function and you'll end up with the following data result (when you start with the data in the table above):
A
B
C
D
Email
Result
john1#abc.com
APC
john2#xyx.com
UDP
john3#qrf.com
THC
john4#wtf.com
IDK
john5#omg.com
LOL
john6#bbq.com
Unknown Value
Which should get you to 100% task completion!

Aggregate function in VBA Excel

I have table with Product name and Batch No. The product name is selected from a combo box frmmaster.cmbproduct.
Each product has a unique combination for batch number and the last digit of the batch number changes.
Sample table
The next batch number of a particular product is the last batch number +1. I have used aggregate function in excel to find the highest batch number. But the same aggregate function is throwing error in VBA.
Aggregate function for excel is =AGGREGATE(14,4,($B$2:$B$2000 =N12)*$C$2:$C$2000,1) where in N12 I am putting product name. Column B contains product name and column C contains batch number
Dim res As Long
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("Database")
With sh
'On Error Resume Next
res = WorksheetFunction.Aggregate(14, 4, ("R2C2:R2000C2" = FrmMaster.CmbProduct.Value) * "R2C3:R2000C3", 1)
MsgBox (res)
End With
The above code is throwing type mismatch error
I have changed the variable to hold value to variant and string but no result.
The reason for the type mismatch error is that the term ("R2C2:R2000C2" = FrmMaster.CmbProduct.Value) * "R2C3:R2000C3" tries multiplying a boolean value (result of ("R2C2:R2000C2" = FrmMaster.CmbProduct.Value)) with the string literal "R2C3:R2000C3". That of course is a type mismatch.
But there are other issues.
The term "R2C2:R2000C2" = FrmMaster.CmbProduct.Value compares the string "R2C2:R2000C2" to the FrmMaster.CmbProduct.Value. This always gets false except FrmMaster.CmbProduct.Value would be "R2C2:R2000C2". And it is not the same as $B$2:$B$2000=N12 in your array formula. There the $B$2:$B$2000 is a range of cell values which each gets compared to value of N12. But this is nothing what VBA is able to do. Even (sh.Range("$B$2:$B$2000") = FrmMaster.CmbProduct.Value) cannot work because this tries comparing an array (sh.Range("$B$2:$B$2000")) to a single value.
So the only way to evaluate an array formula having that kind of array operations in it is using Evaluate. WorksheetFunction cannot help here since it needs the single function parameters evaluated trough VBA first.
Example solving your problem:
Dim res As Long
Dim sh As Worksheet
Dim range1Address As String
Dim range2Address As String
Dim arrayFormula As String
Set sh = ThisWorkbook.Sheets("Database")
With sh
range1Address = .Range("B2:B2000").Address(RowAbsolute:=True, ColumnAbsolute:=True, ReferenceStyle:=xlA1, External:=True)
range2Address = .Range("C2:C2000").Address(RowAbsolute:=True, ColumnAbsolute:=True, ReferenceStyle:=xlA1, External:=True)
arrayFormula = "AGGREGATE(14,4,(" & range1Address & "=""" & FrmMaster.CmbProduct.Value & """)*" & range2Address & ",1)"
res = Evaluate(arrayFormula)
MsgBox res
End With
Getting range1Address and range2Address as external addresses (including workbook name and sheet name) from the ranges makes this independent of the active sheet.
I wonder if the formula below will be of help to you. It will return whatever number it finds associated with the lookup value +1.
=IFERROR(LOOKUP(2,1/($A1:$A$1=A2),$B1:$B$1),0)+1
First, the functionality. The function looks for the last occurrence of A2 above A2 which is in a range starting at absolute $A$1 and ending in relative $A1. The latter will expand as the formula copied down. Same system in column B. If nothing is found and error occurs, and if an error occurs the function returns 0. To the result of this 1 is added.
Now, if you would replace the 0 in the formula (which is the result in case of no precedent) with a number like 2022050 the formula would then return the correct result in the first row of your example and for all subsequent occurrences of "Amoxycillin Capsules 500".
It would return 1 on the line of Paracetamol tablets because there is no precedent. The 1 would stick out and perhaps you have a system by which you can fill in the missing numbers there.
The problem would be the same, regardless of whether you apply VBA or a worksheet function. You need a seed number to base increments on. All considered, the worksheet function looks easier to implement.

excel macro for checking the duplicate entries in coloumn

I looking for a macro which shall find the duplicate entries in the other column.
For example, the spreadsheet I've has more than 300 entries in both column A and B and the values assorted. Now, I need to find out the duplicate entries between the columns. Like westford xxxx there in the column B or not? Please help.
Column A Column B
WestFord xxxx 1.1/2.2 1.50 Direct Link
Direct Link 1.1/2.3 1.55 Westford xxxx
You can use the "Match(Lookup_Value, Lookup_Array, [Match_type])" function. The function returns a number, if the value is found and "N/A" if it is not. First check column B for each element in column A. Then check Column A for each element in Column B.
If the Match function isn't enough, since you have partial matches, you could write a user defined function that identifies whether a value exists in the array. Below is an example of a function that checks the first characters for matching.
Function StartsWith(InputStr As String, InArr As Range, Optional Chars As Integer = 5) As Boolean
Dim i As Integer
Dim compStr As String
Dim foundFlag As Boolean
For i = 1 To InArr.Rows.Count
If Len(InputStr) > Chars Then 'we check to make sure inputstr isn't longer
compStr = Left(InputStr, Chars) 'than then the number of characters we need
Else
compStr = InputStr 'if it is too long, then compare the whole thing
End If
If compStr = Left(InArr(i, 1), Len(compStr)) Then
foundFlag = True
Exit For
End If
Next i
StartsWith = foundFlag
End Function
If this code in a module in an open spreadsheet, you can use StartsWith like any other function. If you want further automation (like deleting the cells if a duplicate is found, you can write a sub to do that also!
The internet is FULL of VBA tutorials like this one.
Try a Google Search

Excel "UnCONCATENATE"/explode in function / Convert cell into array

I am trying to "Unconcatenate" a string in Excel 2010. Yes, I know that is not a real word. So pretty much; I have a cell that can not be split into multiple columns the cell looks like this:
Item 1, Item 2, Item 3
Now this cell may have 0-? items. I am wanting to compare that one cell against a column in another sheet. I believe I would need to use the match function to do this, but I need that first cell to be converted into an array in a function with the delimiter as the comma.
So far I have =MATCH(Each item in cell, SHEET2!A:A, 0)
Any help would be nice. I am aware of =Left and =Right, but I do not think these will work because the number of items in each cell may not be the same. Thanks
Edit:
Detailed discription:
In my first sheet I have a dropdown box. When you choose items it does a vlookup on sheet 2 on this item. When this happens I want it to also check if cell E in that row (item 1, item 2, item 3) match any of the individual cells in a column in sheet 3
The following code exposes VBA's Split function for worksheet use--it returns a row array of items that have been split using a specified delimiter. For example, if cell A1 contained the text "Item 1,Item 2"), EXPLODE(A1,",") would return an array with elements "Item 1" and "Item 2".
Function EXPLODE(str As String, Optional delimiter As Variant) As Variant
If IsMissing(delimiter) Then
delimiter = " "
End If
EXPLODE = Split(str, delimiter)
End Function
It is an array function. To use the returned elements in the spreadsheet:
Select the cells in which you want the "exploded" items show
Enter the function specifying the cell with the source string (or reference to the cell which contains the source) and the delimiter on which the split will be done
Complete the entry using the Control-Shift-Enter key combination.
Alternatively, individual elements can be chosen using the INDEX function--=INDEX(EXPLODE(A1,1,2) would return "Item 2" using the previous example. (Given a range or array, the INDEX function returns the value in the ith row and jth column.) This usage does not require the formula to be entered as an array formula.
For your use case, a combination with other functions would be in order. You have a string with multiple items of the form "aa, bb, cc" (the result of a VLOOKUP) and want to determine whether any of the elements of this string can be found as individual items in any of the cells in column A. You want a function that will return True if all of the elements are found, and False otherwise. The following formula achieves that result:
=SUM(SIGN(IFERROR(MATCH(TRIM(EXPLODE(D1,",")),$A:$A,0),0)))=COUNTA(EXPLODE(D1,","))
It is an array formula and needs to be entered with Control-Shift-Enter. Note that I used the contents of cell D1 in lieu of your lookup value. The TRIM function strips out any extraneous spaces between the elements of the string with multiple items.
(not really an answer, just trying to figure out what the question is)
Sheet 1 has a dropdown box with a number of items in, the selected item is used in a vlookup() looking at a table in sheet 2.
Sheet 2 has 2(+) columns, one which is an index used for a vlookup and the other which contains delimited lists.
Sheet 3 has 1(+) columns, each row has a value that may correspond to an item in one of the delimited lists in sheet 2.
When an item is selected in the dropdown box on sheet 1 I want to lookup the corresponding list in sheet 2 (using the vlookup) and then see if any of the items in that list exist in sheet 3.
Is this what your trying to do? If yes, what is the result of this search?
Boolean: True-Some matches found!, False-No Matches
Number: I found this many results
No? :(
Update
Doing that with just worksheet functions would be fairly tricky!
VBA is a much better choice. (the final step atleast)
Add the following code to a new module (not a worksheet or workbook module) and it will be available as a UDF on your worksheets.
This function takes a string (which is your delimited list) it is exploded inside the function so you dont have to worry about doing it.
I've not tested it, but in theory it should allow you to pass it a list. The function then should check sheet 3 for you and return true/false depending on weather the items are found or not.
I know that you've found an answer, but here is a working and slightly faster version of my function.
Public Function ValidateList(ByVal Target As Range) As Boolean
Dim Sheet As Worksheet: Set Sheet = ThisWorkbook.Worksheets("Sheet3") ' Check Sheet3 is the correct sheet
Dim List As Variant: List = Sheet.UsedRange.Columns("A").Value2 ' Set Column(A) to correct column
Dim Items() As String: Items = Split(Target.Value2, ",")
Dim Item As Long
Dim Key As String
Dim Result As Boolean: Result = False
Dim Search As Object: Set Search = CreateObject("Scripting.Dictionary")
For Item = LBound(Items) To UBound(Items)
Search.Add Trim(Items(Item)), False
Next Item
If Search.Count > 0 Then
' Target List has 1+ Items
For Item = LBound(List, 1) To UBound(List, 1)
Key = Trim(List(Item, 1))
If Search.Exists(Key) = True Then
Search.Remove Key
End If
If Search.Count = 0 Then
Result = True
Exit For
End If
Next Item
Else
' Target List is Empty
' Optionally set result to True here if empty list should return True
' Defaults to False
End If
ValidateList = Result
End Function

Resources