Comparing Strings Producing Unexpected Results - excel

I have a list of data and I created a form to enter new data to be added to the list. Upon the click of a button it will take the information (name and email address) from the form and add it to the corresponding sheets in alphabetical order. There are linked cells involved so I can't just add this to the bottom and sort. Instead, I have it searching the last name cell in the correct sheets to insert a row into the correct location.
This was working as expected for the most part until I came along a possibly unique situation that I can't figure out.
Basically, I have an if statement checking to see if the name is a duplicate and afterwards checking to see if the if the new name should be inserted.
For i = 2 To lastrow
''^^IF STATEMENT CHECKING FOR DUPLICATE^^''
'''''''''''''''''''''''''''''''''''''''''''
'''vvIF STATEMENT CHECKING TO ADD DATAvv'''
ElseIf StrComp(lastname, searchl) = 1 And StrComp(lastname, searchl2) = -1 Then
Sheets("Master List").Range("A" & i).Offset(1).EntireRow.Insert (xlDown)
Sheets("Master List").Range("A" & i + 1).Value = firstname
Sheets("Master List").Range("B" & i + 1).Value = lastname
Sheets("Master List").Range("C" & i + 1).Value = fullname
Variables searchl and searchl2 are the last names from search rows i and i + 1, respectively.
My problem is that when I tried to add the last name "Kralik" it tried to insert the data between the last names "Day" and "de Castro"
Originally, I tried comparing the names using the line of code below:
ElseIf lastname > searchl And lastname < searchl2 Then
This executed the exact same way as the code outlined above. I then inserted a break point and decided to use the StrComp method for troubleshooting. Comparing "Kralik" to "Day" produced results expected but the problem occurs when comparing "Kralik" to "de Castro". For some reason, the code thinks "Kralik" is less than "de Castro" and enters the if statement to insert the data at that location. Even more head scratching for me is that I opened a new workbook and quickly typed "Kralik" into A1, "de Castro" into A2 and the formula "=A1>A2" into A3. The formula gave a result TRUE which is what I would have expected from VBA as well.
EDIT: After more tests, I think it must have something to do with the capitalization of "Kralik" vs. "de Castro" my code works as expected as long as the "k" in "Kralik" is uncapitalized. I will use the UCase method on my variables and come back with the results.
EDIT 2: Using UCase works as well. Outlined by GSerg's answer below as to why my original method was not working.

Excel formulas use case insensitive comparisons by default.
VBA uses case sensitive comparisons by default.
If you want case insensitive comparisons, either put
Option Compare Text
at the beginning of the code module to make all text comparisons in that code module case insensitive by default, or request a comparison type in each specific comparison:
ElseIf StrComp(lastname, searchl, vbTextCompare) = 1 And StrComp(lastname, searchl2, vbTextCompare) = -1 Then
On top of that, you should be using binary search in your particular case to find the position to insert. MATCH with match_type = 1 will return you position in a sorted list where the value should go.

Related

Possible faster VBA lookup with wildcards?

I need to perform a quite a lot of lookups with wildcards on the worksheet using a macro (mainly lookup for value & returning the value from another column though with proper adjustment it can be also just looking for a value with wildcard, and some lookups only as checks if the value exists in the dataset). My data can't be sorted and all the lookups are within a loop A or loops within loop A; wildcards are included mostly for condition "string begins with...". I often have to find a value in one row and find corresponding value in row N rows below or above.
I have a working code, but I wonder if it can be done faster. #response to comment about posting it on Code Review (sorry, I cannot comment yet :)) - preparation the whole code to posting would take a bit too much time for me, confidentiality etc, so I prefer to treat it as a general question to be worked on this example.
Example data (I can add more columns, if I need any helper column):
Example Data picture at Imgur
Assume 100 000 rows (max xPagesCount = 1000, typically around 400; all values for certain xPage is in one block). Due to a lot of possible rows with additional data I can't simply find one value and add numbers to the found row to find the other values by their position.
Example lookups to perform while looping through consecutive xPages (so, for each given xPage):
value in row just below row with "RESTRICTIONS:" text
find name (which is always given with height (column C) = 35)
find RSW number (which can be in several rows depending on page content, but always below name)
find all rows starting with the same four digits as RSW, in two formats: DDDD.LLL.DD and DDDD.DDDDD.DD (L letter, D digit) (I use internal loop here)
check if there is a text "MASTER" (or "MASTER " etc.)
find all values between values "DOCUMENTS:" and "OPTIONS:", which quantity can be from 1 to 50 (I use internal loop here)
I was wondering, what is the fastest way to do such lookups?
What I tried:
using a dictionary on all dataset (keys in column A or C with, values
col.D) but as dictionary can't work on wildcards, I had to add ifs
for not finding a key to perform additional Application.Match
lookup... and then realized it mostly worked on these Match lookups
and not sure I even need a dictionary. I also have duplicate values
within a page and dictionary was getting only first value, regardless
their position (for example, several attachments could have value 1).
The main use remained dict.exists("MASTER") but when I removed
dictionary and changed it to IsError(Application.Match(...)) the code
worked slightly faster.
Application.Match in whole range, typical example: Application.Match(xPage & "4???.*", sh.Range("A1:A" & LastRow), 0)
in few places I use If xValue Like "????.???.??" Then construction
I have dictionary lookups with ifs redirecting to Application.Match:
xValue = dict(xPage & "ATH.416")
If dict(xPage & "ATH.416") = "" Then xValue = Application.Match("ATH.*", Sheets(1).Range("D:D"), 0)
What I consider, but not sure it's worth the effort:
altering the code that at the beginning of the iteration I find the first and the last row for xPage, and then each later check is performed in this range
xStartPage = sh.Range("D" & Application.Match(xPage, sh.Range("A1:A" & LastRow), 0))
'or, I guess better:
xStartPage = xEndPage + 1
If xPage = xPagesCount Then
xEndPage = LastRow
Else
xEndPage = sh.Range("D" & Application.Match(xPage + 1, sh.Range("A1:A" & LastRow), 0) - 1)
End If
xValue = sh.Range("D" & Application.Match("4???.*", sh.Range("D" & xStartPage & ":D" & xEndPage), 0)).Value

Select Case with Complex Return Value (VBA 2007)

Using Excel 2007 and inexperienced with VBA.
I am trying to create a text generator where, given a particular type of entry, it returns a string of text AND a number of corresponding cells.
I have a lot of data to combine and somewhat complex text statements. The data I have here is sample data. The final file will combine multiple text strings ("one" and "two", etc) with multiple cells that need to be referenced based on the specific data input there (some instances require 8 different cells).
I was able to figure out how to do Select Case where it can look up the type of entry I need, and it returns a specific text string. Great! I also know I can combine things like "One" & "Two". But how do I combine the corresponding text for the line?
Where if the entry was "ID" I could return something that looked like "One 1" but combines "One " & K2 as the .Value to return?
Image shows the sample data
Image shows data for Columns I, J, and K.
Column I stores the entry type, J is where the data is returned, and K is the column with data to be referenced. Again, what's here is the simplified reference data.
I know this line is wrong. But I'm not sure what to put in place for K2 so it will correctly reference the cell, and then loop correctly for 300 times.
Case Is = "ID": .Value = "One" & (K2)
Am I close to something workable? What would you suggest? Is Select Case the wrong way to handle this problem?
I've tried a number of options, but I am inexperienced with VBA. I think whatever I implemented to fix the issue was likely done incorrectly.
Full code of what I have shown below.
Private Sub CommandButton1_Click()
Dim logtype As Range
For Each logtype In Range("I2:I302")
With logtype.Offset(0, 1)
Select Case logtype.Value
Case Is = "ID": .Value = "One" & K2
Case Is = "Phase": .Value = "Two"
Case Is = "Install New": .Value = "Three"
Case Is = "Install OH": .Value = "Four"
Case Is = "Install AR": .Value = "Five"
Case Is = "Insp": .Value = "Six"
Case Is = "LUI": .Value = "Seven"
Case Is = "": .Value = ""
Case Else: .Value = "Not Recognized"
End Select
End With
Next logtype
End Sub
EDIT:
What I put in a comment below, just formatted more clearly.
I want to create a macro that spits out the correct language of an entry based on the kind of work completed in a work order.
For example, ID or Install New and then return the correct language and reference data for the entry:
I2: ID
K2: Number (I'd type in the actual No.)
Returns:
"Complied with ID No. [K2]. No defects noted."
I4: Install New
L4: Actual part number
M4: etc...
N4: ...
O4: ...
Returns:
"Removed P/N [L4], S/N [M4]. Installed P/N [N4], S/N [O4]. Ops checked good."
Does that make sense? Thanks!
EDIT 2:
Hey everyone, I've tried tracking down alternative methods of solving this problem but I still haven't produced anything better than what I have here.
Any thoughts or help would be greatly appreciated. Thank you!
For each cell in Range("I2:I302")
cell.offset(0,1).value=cell.offset(0,1).text & cell.offset(0,2).text
next

VBA - populating cell value based on another cell's value (code not working)

I've tried to write the below code so depending on the value in cell worksheets "2. Survey" cell f11 then i want "yes" or no value to appear in my current tab cell e59 (current tab - 3. Unit Specification). Im currently getting run time error 9 but don't know why. (probably done something dumb)
Worksheets("3.Unit Specification").Range("e59").Formula = "=IF('2.
Survey'!F11=""Mains"",""Yes"","")"
run time error 9 is my current issue i just want the cell e59 to have the value yes or have no value (this is a drop down box though and the value is an exact match to the list so don't know if that is resulting in the problem).
You need to double up all the quotes. You missed the last pair.
Worksheets("3.Unit Specification").Range("e59").Formula = _
"=IF('2. Survey'!F11=""Mains"",""Yes"","""")"
Edit
'Non-formula approaches
If Worksheets("2. Survey").Range("F11").Value = "Mains" Then
Worksheets("3.Unit Specification").Range("e59").Value = "Yes"
Else
Worksheets("3.Unit Specification").Range("e59").Value = ""
End If
'OR
Worksheets("3.Unit Specification").Range("e59").Value = IIf(Worksheets("2. Survey").Range("F11").Value = "Mains", "Yes", "")

Returning multiple values using Vlookup in excel

I have an excel sheet set up to automatically calculate meetings per day by day of the week. I would like to write a formula to return all dates I have a meeting scheduled (comma separated preferably), but I am having some difficulty. Using Vlookup, I can only get it to return the first date.
For example, here is what my data looks like:
A B C
Initial Meetings Follow-up Meetings Date
1 1 7/29/2015
0 1 7/30/2015
1 1 7/31/2015
0 0 8/1/2015
0 0 8/2/2015
I would like to write a formula to return "7/29/2015, 7/31/2015" in one cell, and "7/29/2015, 7/30/2015, 7/31/2015" in another, but I seem to be stuck.
You can't do this with vLookup.
This can be done relatively easily in a VB script, but it would affect portability as many if not most users disable macros by default and in many cases users are prevented from using Macros because their company disables them and makes it policy that users should not use them.
If you are OK with Macros, you can put the following into a new module and then use =MultiVlookup(lookup_value,table_array, col_index_num) in the same way as you'd use vlookup and it should give you a comma separated list of multiple matches:
Public Function MultiVlookup(find_value, search_range, return_row)
Dim myval ' String to represent return value (comma-separated list)
Dim comma ' Bool to represent whether we need to prefix the next result with ", "
comma = False
'Debug.Print find_value.value, return_row
For Each rw In search_range.Rows ' Iterate through each row in the range
If rw.Cells(1, 1).value = find_value Then ' If we have found the lookup value...
If comma Then ' Add a comma if it's not the first value we're adding to the list
myval = myval + ", "
Else
comma = True
End If
myval = myval + Str(rw.Cells(1, return_row).value)
End If
Next
MultiVlookup = myval
End Function
This may not be the cleanest way of doing it, and it isn't a direct copy of vlookup (for instance it does not have a fourth "range lookup" argument as vlookup does), but it works for my test:
Finally my original suggestion (in case it helps others - it's not the exact solution to the question) was:
I've not tried it myself, but this link shows what I think you might be looking for.
Great code, but don't forget to add the following is you use Option Explicit:
Dim rw As Range
WHEELS

Prevent Partial Duplicates in Excel

I have a worksheet with products where the people in my office can add new positions. The problem we're running into is that the products have specifications but not everybody puts them in (or inputs them wrong).
Example:
"cool product 14C"
Is there a way to convert Data Valuation option so that it warns me now in case I put "very cool product 14B" or anything that contains an already existing string of characters (say, longer than 4), like "cool produKt 14C" but also "good product 15" and so on?
I know that I can prevent 100% matches using COUNTIF and spot words that start/end in the same way using LEFT/RIGHT but I need to spot partial matches within the entries as well.
Thanks a lot!
If you want to cover typo's, word wraps, figure permutations etc. maybe a SOUNDEX algorithm would suit to your problem. Here's an implementation for Excel ...
So if you insert this as a user defined function, and create a column =SOUNDEX(A1) for each product row, upon entry of a new product name you can filter for all product rows with same SOUNDEX value. You can further automate this by letting user enter the new name into a dialog form first, do the validation, present them a Combo Box dropdown with possible duplicates, etc. etc. etc.
edit:
small function to find parts of strings terminated by blanks in a range (in answer to your comment)
Function FindSplit(Arg As Range, LookRange As Range) As String
Dim LookFor() As String, LookCell As Range
Dim Idx As Long
LookFor = Split(Arg)
FindSplit = ""
For Idx = 0 To UBound(LookFor)
For Each LookCell In LookRange.Cells
If InStr(1, LookCell, LookFor(Idx)) <> 0 Then
If FindSplit <> "" Then FindSplit = FindSplit & ", "
FindSplit = FindSplit & LookFor(Idx) & ":" & LookCell.Row
End If
Next LookCell
Next Idx
If FindSplit = "" Then FindSplit = "Cool entry!"
End Function
This is a bit crude ... but what it does is the following
split a single cell argument in pieces and put it into an array --> split()
process each piece --> For Idx = ...
search another range for strings that contain the piece --> For Each ...
add piece and row number of cell where it was found into a result string
You can enter/copy this as a formula next to each cell input and know immediately if you've done a cool input or not.
Value of cell D8 is [asd:3, wer:4]
Note the use of absolute addressing in the start of lookup range; this way you can copy the formula well down.
edit 17-Mar-2015
further to comment Joanna 17-Mar-2015, if the search argument is part of the range you're scanning, e.g. =FINDSPLIT(C5; C1:C12) you want to make sure that the If Instr(...) doesn't hit if LookCell and LookFor(Idx) are really the same cell as this would create a false positive. So you would rewrite the statement to
...
...
If InStr(1, LookCell, LookFor(Idx)) <> 0 And _
Not (LookCell.Row = Arg.Row And LookCell.Column = Arg.Column) _
Then
hint
Do not use a complete column (e.g. $C:$C) as the second argument as the function tends to become very slow without further precautions

Resources