Excel VBA - Invalid procedure call or argument (Error 5) - vba

I am having trouble finding the cause of the
Invalid procedure call or argument (Error 5)
that occurs on the last line of the table I am manipulating.
I have tried different tables of the same format and have found that they work just fine. This leads me to believe there is an issue with the specific row in the table
Relevant code:
VName.Value = RID.Value & " " & IIf(InStr(1, RID.Offset(columnOffset:=1).Value, vbCrLf) <> 0 And _
(InStr(1, RID.Offset(columnOffset:=1).Value, vbCrLf) - 1) >= 10, _
Left(RID.Offset(columnOffset:=1).Value, InStr(1, RID.Offset(columnOffset:=1).Value, vbCrLf) - 1), RID.Offset(columnOffset:=1).Value)
Full Code:
'~~~> For Each Row
'~~~> ID Row (offset by 2 columns) with SectionTitle (Cache A3) + ID starting with 0 on the header
Set RID = Nothing
Set SecT = Range("'Values'!$A$3")
Set RCount = .Range(.Cells(HC, 2), .Cells(.Cells(Rows.count, 2).End(xlUp).Row, 2))
IDCount = 0
For Each RID In RCount
'ID Req rows
RID.Offset(columnOffset:=-1).Value = SecT.Value & " " & IDCount
'~~~> Add ID, ReqName, Section to Values sheet where if ID is 0 then Type = Folder
Set VSection = Worksheets("Values").Cells(Worksheets("Values").Cells(Rows.count, 2).End(xlUp).Row + 1, 2)
VSection.EntireRow.ClearContents
Set VName = Worksheets("Values").Cells(Worksheets("Values").Cells(Rows.count, 2).End(xlUp).Row + 1, 3)
Set VType = Worksheets("Values").Cells(Worksheets("Values").Cells(Rows.count, 2).End(xlUp).Row + 1, 4)
Set VID = Worksheets("Values").Cells(Worksheets("Values").Cells(Rows.count, 2).End(xlUp).Row + 1, 5)
'Row = Header where IDCount = 0
If IDCount = 0 Then
VSection.Value = SecT.Value
VName.Value = SecT.Value
VType.Value = "Folder"
VID.Value = IDCount
'Row <> Header where IDCount > 0
ElseIf IDCount > 0 Then
VSection.Value = SecT.Value
VName.Value = RID.Value & " " & IIf(InStr(1, RID.Offset(columnOffset:=1).Value, vbCrLf) <> 0 And _
(InStr(1, RID.Offset(columnOffset:=1).Value, vbCrLf) - 1) >= 10, _
Left(RID.Offset(columnOffset:=1).Value, InStr(1, RID.Offset(columnOffset:=1).Value, vbCrLf) - 1), RID.Offset(columnOffset:=1).Value)
VName.WrapText = False
VID.Value = IDCount
End If
IDCount = IDCount + 1
Next RID
RID.Value = FLO.1170
RID.Offset(columnOffset:=1).Value = WORKITEM MANAGEMENT
triggers a
Invalid procedure call or argument (Error 5)
As there are no line breaks within "WORKITEM MANAGEMENT" it should use the full cell value and not the Left function. The other 113 rows worked fine.

It would seem that the IIF is evaluating both of the conditions, not ignoring the second when the first is false. If And always evaluates both. If then If only evaluates the second if the first is true. I suspect that the second criteria is causing the crash because it is being evaluated regardless of whether the first is true or not (in this case not) and Left cannot have a negative length. Change the IIF to a pair of nested If statements.
It also appears that you're are trying to compare Left(<string>, <length>) to the number 10. This may not be correct. If text that looks like a number is being returned out of the Left method then wrap it in CLng() or CDbl() to get an actual number.
Perhaps a .Split function would be a better choice. If Split cannot find the delimiter to actually split something, you will still have the original value as the zero-based element.
VName.Value = RID.Value & " " & Split(RID.Offset(columnOffset:=1).Value, Chr(10))(0)

Related

VBA – Alteration in a code

I have a code which performs different checks for 3 different columns. It works absolutely fine, but I want some alteration. Let’s see the code first.
Sub test()
On Error Resume Next
Dim cel As Range
Dim colCStr As String, colDStr As String, colEStr As String
Set ws = Sheets("Sheet1")
With ws
LastRow = .Cells(.Rows.count, "C").End(xlUp).row
For Each cel In .Range("C2:C" & LastRow)
'condition for Column C (cell not empty & characters in cell are alphabet)
For i = 1 To Len(cel)
If Not (Not IsEmpty(cel) And Asc(UCase(cel)) > 64 And Asc(UCase(cel)) < 91) Then
colCStr = colCStr & "," & cel.row
Exit For
End If
Next i
'condition for Column D (cell is numeric & length of cell value is 2 or 3)
If Not (IsNumeric(cel.Offset(0, 1)) And (Len(cel.Offset(0, 1)) = 2 Or Len(cel.Offset(0, 1)) = 3)) Then
colDStr = colDStr & "," & cel.Offset(0, 1).row
End If
'condition for Column E (cell is numeric & length of cell value is 7 or 8 or cell value is 0)
If Not (IsNumeric(cel.Offset(0, 2)) And (Len(cel.Offset(0, 2)) = 7 Or Len(cel.Offset(0, 2)) = 8) Or cel.Offset(0, 2) = 0) Then
colEStr = colEStr & "," & cel.Offset(0, 2).row
End If
Next cel
End With
'disply message box only if there's error
If Len(colCStr) > 0 Then
Sheets("Error_sheet").Range("A2" & row).Value = "Errors in Column C" & " : " & Mid(colCStr, 2, Len(colAStr))
If Len(colDStr) > 0 Then
Sheets("Error_sheet").Range("B2" & row).Value = "Errors in Column D" & " : " & Mid(colDStr, 2, Len(colDStr))
If Len(colEStr) > 0 Then
Sheets("Error_sheet").Range("C2" & row).Value = "Errors in Column E" & " : " & Mid(colEStr, 2, Len(colEStr))
Else
End If
End If
End Sub
The code performs following checks:
Column C: Cell not empty & characters in cell are alphabet (Actually I don’t want to perform any checks over here in Column C, but if I delete the lines of code which validate Column C the rest of code stops getting executed too).
Column D: Cell is numeric & length of cell value is 2 or 3 (I want the absolutely same checks).
Column E: Cell is numeric & length of cell value is 7 or 8 or cell value is 0 (I want the absolutely same checks).
I appreciate your time and efforts.
This version doesn't use Offset so it should be easier to update (and more efficient)
Option Explicit
Public Sub CheckColDandE()
Dim ws As Worksheet, lr As Long, arr As Variant, r As Long
Dim dOk As Boolean, eOk As Boolean, dErr As String, eErr As String
Set ws = ThisWorkbook.Worksheets("Sheet1")
lr = ws.Cells(ws.Rows.Count, "C").End(xlUp).Row
arr = ws.Range("D2:E" & lr)
For r = 1 To lr - 1
dOk = IsNumeric(arr(r, 1)) And arr(r, 1) > 9 And arr(r, 1) < 1000
eOk = IsNumeric(arr(r, 2))
eOk = eOk And (arr(r, 2) > 999999 And arr(r, 2) < 100000000 Or arr(r, 2) = 0)
If Not dOk Then dErr = dErr & r + 1 & ", "
If Not eOk Then eErr = eErr & r + 1 & ", "
Next
With ws.Range("D" & lr + 1 & ":E" & lr + 1)
.Value2 = vbNullString
If Len(dErr) > 0 Then .Cells(1) = "Rows with Errors: " & Left(dErr, Len(dErr) - 2)
If Len(eErr) > 0 Then .Cells(2) = "Rows with Errors: " & Left(eErr, Len(eErr) - 2)
End With
End Sub
Delete the following lines (and update your comments! The column names in comments and code ae not the same):
'condition for Column A (cell not empty & characters in cell are alphabet)
For i = 1 To Len(cel)
If Not (Not IsEmpty(cel) And Asc(UCase(cel)) > 64 And Asc(UCase(cel)) < 91) Then
colCStr = colCStr & "," & cel.row
Exit For
End If
Next i

Execution of internal loop - Error 'Type Mismatch'

I'm writing a macro for the first time and I have a problem with one of my Subs.
When it runs the first time when ROW = 3 it does the internal loop for the Col = 3 to Col = x-2 without any problem. However when ROW increase to 4 I get an Error on the first run of the internal Col loop (Col = 3).
The Error says Run-time error '13' and Type mismatch, however I don't understand why it would run thru ok on first execution of the loop when ROW = 3 but fail when ROW = 4.
For Row = 3 To y - 1
Col = 0
OutputCol = OutputCol + 1
For Col = 3 To x - 2
Cells(Col + 3, OutputCol).Value = "L" + Cells(5, Col + 1).Value + "C" + Cells(6, Row).Value + " " + "EXEC TRANEXEC, " + Line + Cells(5, Col + 1).Value + "," + Company + Cells(6, Row).Value
Next Col
Next Row
Any help greatly appreciated.
Thanks
Marty
Value is not necessarily a string. And #Comintern is right - don't redefine Row. Assuming you change Row to RowNum, try this in your loop:
Cells(Col + 3, OutputCol).Value = "L" & CStr(Cells(5, Col + 1).Value) & _
"C" & CStr(Cells(6, RowNum).Value) & " " & _
"EXEC TRANEXEC, " & Line & CStr(Cells(5, Col + 1).Value) & "," & _
Company & CStr(Cells(6, RowNum).Value)
The CStr() calls make sure that you get a string representation of Value, so that the string-concatenation operator & will always have strings to work with.
Edit
As #Mat's Mug noted, before the code snippet you included, you should be doing something like:
Dim ws as Worksheet
set ws = ActiveSheet
(or whatever worksheet you want instead of ActiveSheet). Then every call to Cells should be to ws.Cells instead. That way you will always know which worksheet you are referring to, and you won't have weird errors that depend on factors beyond your control.
Similarly, instead of ActiveWorkbook, do Dim wb as Workbook and use, e.g., wb.Sheets.

Searching for several exact substrings in one String

I have a problem I've been working on for a while but cannot seem to get there. I have a list of about 6000 material descriptions and I want to pull out a specific searched for word.
So if the description is 'Handschuhe-Wunder-20XV28', and the search word was 'Wunder', this material would then have a new column that said 'Wunder'. However, I might also want to search for the word 'Super', and I would like this to appear in the same column.
This search would only pick up the exact words, so if it was looking for 'Super', it wouldn't return a result if it found the word 'Superman'.
I had a formula which can do this:
=IF(AQ2=1,IF(SUM(IF(ISNUMBER(SEARCH(Search!A$2, K2)), IF(COUNT((IF(CODE(MID(K2, SEARCH(Search!A$2, K2)-1, 1))>122, 1, "")), (IF(CODE(MID(K2, SEARCH(Search!A$2, K2)-1, 1))<65, 1, "")), (IF(SEARCH(Search!A$2, K2)-1<1, 1, ""))), IF(COUNT((IF(CODE(MID(K2, SEARCH(Search!A$2, K2)+LEN(Search!A$2), 1))>122, 1, "")), (IF(CODE(MID(K2, SEARCH(Search!A$2, K2)+LEN(Search!A$2), 1))<65, 1, "")), (IF((SEARCH(Search!A$2, K2)+LEN(Search!A$2)+1)>LEN(K2), 1, ""))), 1, 0), 0), 0))>0,Search!A$2,0),"")
Where AQ2 contained the initial quick search:
=IF(IFERROR(SEARCH(Search!$A$2,'Raw Data Working'!K2),0)=0,"",1)
This was to improve efficiency as the first formula separates non-alphanumerical characters to find the exact word, after the second formula I posted works out whether it is worth search at all.
I tried recording this in visual basic, with the idea that there would be columns increasing as part of a for loop, with two columns for each search item. I would then somehow bring across the results of any searches into one column. However, when I record the long formula in VBA, despite including breaks, it still does not work.
I would be grateful for any help, suggestions or ideas. Quite simply, it is looking in one piece of text, to see whether the text includes either of several works. And they would have to be exact matches.
Thanks everyone!
Based on what has been discussed, the following should meet your needs or at least be a lot closer to what you imagine.
First is a function that accepts all the characters you wish to delimit the string with:
Note: This function is actually kind of awesome.
Function MultiSplitX(ByVal SourceText As String, RemoveBlankItems As Boolean, ParamArray Delimiters()) As String()
Dim a As Integer, b As Integer, n As Integer
Dim i As Integer: i = 33
Dim u As Variant, v As Variant
Dim tempArr() As String, finalArr() As String, fDelimiters() As String
If InStr(TypeName(Delimiters(0)), "()") <> 0 And LBound(Delimiters) = UBound(Delimiters) Then
ReDim fDelimiters(LBound(Delimiters(0)) To UBound(Delimiters(0))) 'If passing array vs array items then
For a = LBound(Delimiters(0)) To UBound(Delimiters(0)) 'build that array
fDelimiters(a) = Delimiters(0)(a)
Next a
Else
fDelimiters = Delimiters(0)
End If
Do While InStr(SourceText, Chr(i)) <> 0 'Find an unused character
i = i + 1
Loop
For a = LBound(fDelimiters) To UBound(fDelimiters) 'Sort Delimiters by length
For b = a + 1 To UBound(fDelimiters)
If Len(fDelimiters(a)) < Len(fDelimiters(b)) Then
u = fDelimiters(b)
fDelimiters(b) = fDelimiters(a)
fDelimiters(a) = u
End If
Next b
Next a
For Each v In fDelimiters 'Replace Delimiters with a common character
SourceText = Replace(SourceText, v, Chr(i))
Next
tempArr() = Split(SourceText, Chr(i)) 'Remove empty array items
If RemoveBlankItems = True Then
ReDim finalArr(LBound(tempArr) To UBound(tempArr))
n = LBound(tempArr)
For i = LBound(tempArr) To UBound(tempArr)
If tempArr(i) <> "" Then
finalArr(n) = tempArr(i)
n = n + 1
End If
Next i
n = n - 1
ReDim Preserve finalArr(LBound(tempArr) To n)
MultiSplitX = finalArr
Else: MultiSplitX = tempArr
End If
Erase finalArr
Erase tempArr
End Function
Next is the routine that finds all applicable matches:
Sub SearchDynamicDelimit()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets(2)
Dim strTest As New Collection
Dim udRange As Range: Set udRange = ws.Range("D2", ws.Cells(ws.Rows.Count, "D").End(xlUp)) '<~~Change to your Search Range
Dim myCell, myMatch, myString, i, delimiter, d, s, t, u, c
Dim temp() As String, myDelimiter() As String, delNotInSearch() As String, delInSearch() As String, tempCell As String
Dim delimitInSearch As Boolean: delimitInSearch = False
Dim delString As String, searchString As String
For Each myMatch In udRange
If myMatch.Value <> "" Then strTest.Add myMatch.Value
searchString = searchString & CStr(myMatch.Value)
Debug.Print myMatch.Value & " " & myMatch.Address
Next myMatch
ws.Range("B2", ws.Cells(ws.Rows.Count, "B")).Clear '<~~Change to where you want the results to populate
delString = "_|-|.|/|<|>|;|:|[|]|\|{|}| |(|,|)" '<~~Change to the delimiters you want. Separate them with any unique character.
myDelimiter() = Split(delString, "|") '<~~Make sure the unique character you chose above is the same here.
ReDim delNotInSearch(LBound(myDelimiter) To UBound(myDelimiter))
ReDim delInSearch(LBound(myDelimiter) To UBound(myDelimiter))
t = LBound(myDelimiter)
u = LBound(myDelimiter)
For s = LBound(myDelimiter) To UBound(myDelimiter)
If InStr(searchString, myDelimiter(s)) = 0 Then
delNotInSearch(t) = myDelimiter(s)
Debug.Print "delNotInSearch(" & t & ") = " & delNotInSearch(t)
t = t + 1
Else
delInSearch(u) = myDelimiter(s)
Debug.Print "delInSearch(" & u & ") = " & delInSearch(u)
u = u + 1
End If
Next s
t = t - 1
u = u - 1
If t <> -1 Then ReDim Preserve delNotInSearch(LBound(myDelimiter) To t)
If u <> -1 Then ReDim Preserve delInSearch(LBound(myDelimiter) To u)
If delInSearch(LBound(delInSearch)) <> "" Then delimitInSearch = True
If strTest.Count > 0 Then
For Each myCell In ws.Range("A2", ws.Cells(ws.Rows.Count, "A").End(xlUp)) '<~~Change to range being searched
If myCell.Value = "" Then GoTo SkipBlanks
If delimitInSearch = True Then
temp() = MultiSplitX(myCell.Value, True, delNotInSearch())
For i = 0 To UBound(temp)
For Each myString In strTest
If StrComp(temp(i), myString, vbTextCompare) = 0 Then
If ws.Range("B" & myCell.Row).Value = "" Then 'If you only want it to show 1 search result, remove the IF statement entirely
ws.Range("B" & myCell.Row).Value = temp(i) 'And keep this line only. Change "B" to where you want the results to go
Else: ws.Range("B" & myCell.Row).Value = ws.Range("B" & myCell.Row).Value & ", " & temp(i)
End If
End If
Next myString
Next i
Erase temp
End If
temp() = MultiSplitX(myCell.Value, True, delInSearch())
For i = 0 To UBound(temp)
For Each myString In strTest
If StrComp(temp(i), myString, vbTextCompare) = 0 Then
If ws.Range("B" & myCell.Row).Value = "" Then 'If you only want it to show 1 search result, remove the IF statement entirely
ws.Range("B" & myCell.Row).Value = temp(i) 'And keep this line only. Change "B" to where you want the results to go
Else: ws.Range("B" & myCell.Row).Value = ws.Range("B" & myCell.Row).Value & ", " & temp(i)
End If
End If
Next myString
Next i
Erase temp
SkipBlanks:
Next myCell
Else: MsgBox "Nothing found to search...", Title:="No Search Item"
End If
End Sub
On my sample workbook, I yielded the following results using the routine in conjuction with the MultiSplit function:
Notice Sich.Okay was properly found, even though "." being used as a delimiter.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ORIGINAL ANSWER BELOW FOR THOSE IT MAY HELP
If you want a method using VBA you could try something like this:
Sub ColorMatchingString()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets(1)
Dim strTest As Collection: Set strTest = New Collection
Dim udRange As Range: Set udRange = ws.Range("E1:G1") 'Define Search Ranges
Dim myCell, myMatch, myString, i, nextRR As Long
Dim temp() As String, tempLength As Integer, stringLength As Integer
nextRR = 3
For Each myMatch In udRange 'Build the collection with Search Range Values
strTest.Add myMatch.Value
Next myMatch
If ws.Range("E1").Value <> "" Or ws.Range("F1").Value <> "" Or ws.Range("G1").Value <> "" Then
For Each myCell In ws.Range("A1:A50")
temp() = Split(myCell.Text, "-")
startLength = 0
stringLength = 0
For i = 0 To UBound(temp)
tempLength = Len(temp(i))
stringLength = stringLength + tempLength + 2
For Each myString In strTest
If StrComp(temp(i), myString, vbTextCompare) = 0 Then
ws.Range("H" & nextRR).Value = myCell.Text
ws.Range("I" & nextRR).Value = myCell.Address
startLength = stringLength - tempLength - 2
ws.Range("H" & nextRR).Characters(startLength, tempLength).Font.Color = vbRed
nextRR = nextRR + 1
End If
Next myString
Next i
Erase temp
Next myCell
Else: MsgBox "Nothing found to search...", Title:="No Search Item"
End If
End Sub
What this will do is find your search items and show them in a new column, as well as show you where the item was found.
You could still keep a formula based approach, using an array formula. So, using the list being in e1:e4 and search criteria 1 in I1 and search criteria 2 in J1, I used the following
=INDEX($E$1:$E$4,SMALL(IF((NOT(ISERROR(SEARCH($I$1 & " ",$E$1:$E$4))))+(NOT(ISERROR(SEARCH($J$1 & " ",$E$1:$E$4)))),ROW($E$1:$E$4)),ROWS($E$1:$E1)))
and dragged down
Results can be seen in column G
Based on the comments, I've done the below array formula, this time, I've my data in A1:A5 and my search terms in D1 and D2.
=IFERROR(INDEX($A$1:$A$5 & " (" & $D$1 &")",SMALL(IF(NOT(ISERROR(SEARCH($D$1,$A$1:$A$5))),ROW($A$1:$A$5)),ROWS($B$1:$B1))),IFERROR(INDEX($A$1:$A$5 & " (" & $D$2 &")",SMALL(IF(NOT(ISERROR(SEARCH($D$2,$A$1:$A$5))),ROW($A$1:$A$5)),ROWS($B$1:$B1)-SUM(IF(NOT(ISERROR(SEARCH($D$1,$A$1:$A$5))),1,0)))),"<>"))
This looks as follows
This present two solutions:
1. Method to work with long formulas in VBA
2. VBA code to classify all Materials Description at once.
1. Method to work with long formulas in VBA
This formula is too long for VBA.
=IF(AQ2=1,IF(SUM(IF(ISNUMBER(SEARCH(Search!A$2, K2)), IF(COUNT((IF(CODE(MID(K2, SEARCH(Search!A$2, K2)-1, 1))>122, 1, "")), (IF(CODE(MID(K2, SEARCH(Search!A$2, K2)-1, 1))<65, 1, "")), (IF(SEARCH(Search!A$2, K2)-1<1, 1, ""))), IF(COUNT((IF(CODE(MID(K2, SEARCH(Search!A$2, K2)+LEN(Search!A$2), 1))>122, 1, "")), (IF(CODE(MID(K2, SEARCH(Search!A$2, K2)+LEN(Search!A$2), 1))<65, 1, "")), (IF((SEARCH(Search!A$2, K2)+LEN(Search!A$2)+1)>LEN(K2), 1, ""))), 1, 0), 0), 0))>0,Search!A$2,0),"")
To write long Formulas with VBA, we need to slice it down in several parts using variables.
So let’s first see the formula broken down by excel functions:
=IF(AQ2=1,
IF(
SUM(
IF(
ISNUMBER(SEARCH(Search!A$2, K2)),
IF(
COUNT(
(IF(CODE(MID(K2, SEARCH(Search!A$2, K2)-1, 1))>122, 1, "")),
(IF(CODE(MID(K2, SEARCH(Search!A$2, K2)-1, 1))<65, 1, "")),
(IF(SEARCH(Search!A$2, K2)-1<1, 1, ""))),
IF(
COUNT(
(IF(CODE(MID(K2, SEARCH(Search!A$2, K2)+LEN(Search!A$2), 1))>122, 1, "")),
(IF(CODE(MID(K2, SEARCH(Search!A$2, K2)+LEN(Search!A$2), 1))<65, 1, "")),
(IF((SEARCH(Search!A$2, K2)+LEN(Search!A$2)+1)>LEN(K2), 1, ""))
), 1, 0),
0),
0)
)>0,
Search!A$2,0)
,"")
This formula has basically 4 parts:
• It checks what you call the quick search in cell AQ2 - Part 1
=IF(AQ2=1,
IF(
SUM(
IF(
ISNUMBER(SEARCH(Search!A$2, K2)),
• Validates the character immediately before and after the word found:
Part 2 - before:
IF(
COUNT(
(IF(CODE(MID(K2, SEARCH(Search!A$2, K2)-1, 1))>122, 1, "")),
(IF(CODE(MID(K2, SEARCH(Search!A$2, K2)-1, 1))<65, 1, "")),
(IF(SEARCH(Search!A$2, K2)-1<1, 1, ""))),
Part 3 - after:
IF(
COUNT(
(IF(CODE(MID(K2, SEARCH(Search!A$2, K2)+LEN(Search!A$2), 1))>122, 1, "")),
(IF(CODE(MID(K2, SEARCH(Search!A$2, K2)+LEN(Search!A$2), 1))<65, 1, "")),
(IF((SEARCH(Search!A$2, K2)+LEN(Search!A$2)+1)>LEN(K2), 1, ""))
), 1, 0),
0),
0)
)>0,
• Then returns the result - Part 4
Search!A$2,0)
,"")
Following the same logic I have modified your formula:
=IF(
IF(OR(
IFERROR(CODE(TRIM(MID($K2,IFERROR(SEARCH(Search!A$2,$K2),0)+
IF(IFERROR(SEARCH(Search!A$2,$K2),0)=1,0,-1),1))),0)
<65,
IFERROR(CODE(TRIM(MID($K2,IFERROR(SEARCH(Search!A$2,$K2),0)+
IF(IFERROR(SEARCH(Search!A$2,$K2),0)=1,0,-1),1))),0)
>122),0,1)
+
IF(OR(
IFERROR(CODE(TRIM(MID($K2,IFERROR(SEARCH(Search!A$2,$K2),0)+
LEN(Search!A$2),1))),0)
<65,
IFERROR(CODE(TRIM(MID($K2,IFERROR(SEARCH(Search!A$2,$K2),0)+
LEN(Search!A$2),1))),0)
>122),0,1)
<>0,"",Search!A$2)
Now we can clearly see the fundamental parts of the formula:
• Getting the character immediately before:
IFERROR(CODE(TRIM(MID($K2,IFERROR(SEARCH(Search!A$2,$K2),0)+
IF(IFERROR(SEARCH(Search!A$2,$K2),0)=1,0,-1),1))),0)
• Getting the character immediately after:
IFERROR(CODE(TRIM(MID($K2,IFERROR(SEARCH(Search!A$2,$K2),0)+
LEN(Search!A$2),1))),0)
Now we use variables to define the formula, but first in order to make the formula flexible so it can be applied with any reference let’s modify the “hard coded” references with strings that can be replaced with the references obtained at run time:
Substitute $K2 with #Cll and Search!A$2 with #Srch
The final code is something like this:
Sub Vba_Long_Formula()
Dim sCll As String, sSrch As String
sCll = "$K2"
sSrch = "Search!A$2"
Dim sFmlIni As String, sFmlEnd As String 'Formulas for the before and after characters
'Chr(10) is used to ease reading by breaking the formula by line
sFmlIni = "IFERROR(CODE(TRIM(MID(#Cll,IFERROR(SEARCH(#Srch,#Cll),0)+" & Chr(10) & _
"IF(IFERROR(SEARCH(#Srch,#Cll),0)=1,0,-1),1))),0)"
sFmlEnd = "IFERROR(CODE(TRIM(MID(#Cll,IFERROR(SEARCH(#Srch,#Cll),0)+" & Chr(10) & _
"LEN(#Srch),1))),0)"
Dim sFml1 As String 'Formula to be applied
sFml1 = "=IF(" & Chr(10) & _
"IF(OR(" & Chr(10) & sFmlIni & "<65," & Chr(10) & sFmlIni & ">122),0,1)+" & Chr(10) & _
"IF(OR(" & Chr(10) & sFmlEnd & "<65," & Chr(10) & sFmlEnd & ">122),0,1)<>0,"""",#Srch)"
sFml1 = Replace(Replace(sFml1, "#Cll", sCll), "#Srch", sSrch)
ThisWorkbook.Sheets("Raw Data Working").Range("AR2:AR4").Formula = sFml1
End Sub
Hope the above solves the problem with the long formula in VBA.
2. VBA code to classify all Materials Description at once.
However, if you are using VBA then it’s more efficient to run the entire process using VBA to classify all the Material Descriptions with the corresponding Brands.
This code assumes the following (change as required):
List of Brands starts at 'Search'!A2
List of Materials Descriptions starts at 'Raw Data Working'!K2
Output of Brands in column 'Raw Data Working'!AP
Option Compare Text ‘Must have this at the top of the module
Option Explicit
Sub Brand_Classification()
Dim aBrands As Variant, rMaterials As Range, rResults As Range
Dim rFound As Range, blFound As Boolean, sFound As String
Dim sMaterial As String
Dim lLastRow As Long
Dim vItm As Variant
Dim iAsc As Integer, bPos As Byte
Dim b As Byte
Rem Set Array with Brands
With ThisWorkbook.Sheets("Search") 'Change as needed
lLastRow = .Columns("A:A").Cells(1 + .UsedRange.SpecialCells(xlLastCell).Row).End(xlUp).Row 'Change as needed
aBrands = .Range("A2:A" & lLastRow).Value2 'Change as needed
End With
With ThisWorkbook.Sheets("Raw Data Working") 'Change as needed
Rem Set Materials Description Range
lLastRow = .Columns("K:K").Cells(1 + .UsedRange.SpecialCells(xlLastCell).Row).End(xlUp).Row 'Change as needed
Set rMaterials = .Range("K2:K" & lLastRow) 'Change as needed
Rem Set Brand Results Range
Set rResults = .Range("AP2:AP" & lLastRow) 'Change as needed
Rem Clearing prior results
'rResults.ClearContents '}Choose one of
rResults.Value = Chr(39) '}these options
End With
Rem Search for Brands in Materials Description
For Each vItm In aBrands
If vItm <> Empty Then
With rMaterials
Set rFound = .Cells.Find(What:=vItm, After:=.Cells(.Cells.Count), _
LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
Rem Validate Value Found
If Not rFound Is Nothing Then
sFound = rFound.Address
Do
Rem Process Value Found
blFound = True
sMaterial = rFound.Value
For b = 1 To 2
Select Case b
Case 1
Rem Get Character Before Value
bPos = InStr(sMaterial, vItm)
bPos = -1 + bPos
Case 2
Rem Get Character After Value
bPos = InStr(sMaterial, vItm) + Len(vItm)
End Select
Rem Get Character
Select Case bPos
Case 0, Is > Len(sMaterial)
Case Else
Rem Validate Character
On Error Resume Next
iAsc = Asc(Mid(sMaterial, bPos, 1))
On Error GoTo 0
Select Case iAsc
Case 65 To 90, 97 To 122
blFound = False
End Select: End Select: Next
Rem Write Results
If blFound Then
With rResults.Cells(1 - rMaterials.Row + rFound.Row)
If .Value = Empty Then
.Value = vItm
Else
.Value = .Value & ", " & vItm
End If: End With: End If
Rem Search Next
Set rFound = .FindNext(After:=rFound)
If rFound.Address = sFound Then Exit Do
Loop: End If: End With: End If: Next
End Sub
Suggest to read the following pages to gain a deeper understanding of the resources used:
Do...Loop Statement,
For Each...Next Statement,
On Error Statement,
Option keyword,
Range Object (Excel),
Select Case Statement,
Variables & Constants,
With Statement,

Get the value between the parentheses, multiple matches in one string

My spreadsheet has a column with value like this string:
some text (text1) some test (text2) (text1)
How do I get all values between parentheses? The result I am looking for is:
text1, text2
Even if text1, text2... testn is present in the cell multiple times, I need it in the result only once.
I found a function GetParen here: Get the value between the brackets
It is helpful, but it gives the fist available value in the parentheses and ignores the rest.
It seems unwieldy to have one User Defined Function for individual entries and another for a collective result of all entries.
Paste the following into a standard module code sheet.
Function getBracketedText(str As String, _
Optional pos As Integer = 0, _
Optional delim As String = ", ", _
Optional dupes As Boolean = False)
Dim tmp As String, txt As String, a As Long, b As Long, p As Long, arr() As Variant
tmp = str
ReDim arr(1 To 1)
For b = 1 To (Len(tmp) - Len(Replace(tmp, Chr(40), vbNullString)))
p = InStr(p + 1, tmp, Chr(40))
txt = Trim(Mid(tmp, p + 1, InStr(p + 1, tmp, Chr(41)) - (p + 1)))
If UBound(Filter(arr, txt, True)) < 0 Or dupes Then '<~~ check for duplicates within the array
a = a + 1
ReDim Preserve arr(1 To a)
arr(UBound(arr)) = txt
End If
Next b
If CBool(pos) Then
getBracketedText = arr(pos)
Else
getBracketedText = Join(arr, delim)
End If
End Function
Use like any other native worksheet function. There are optional parameters to retrieve an individual element or a collection as well as changing the default <comma><space> delimiter.
    
This code works for me:
Sub takingTheText()
Dim iniP 'first parenthesis
Dim endP 'last parentehis
Dim myText 'the text
Dim txtLen
Dim i
Dim tmp
Dim j
myText = Range("A1").Value
txtLen = Len(myText)
j = 0
Do 'Loop in the text
i = i + 1 'a counter
iniP = InStr(1, myText, "(", 1) 'found the first occurence of the (
endP = InStr(1, myText, ")", 1) 'same as above
tmp = tmp & Right(Left(myText, i), 1) 'take the text garbage text
If i = iniP Then 'here comes the work
j = j + 1 'here take the cell index
myText = Replace(myText, tmp, "") 'remove the garbage text in front the first (
tmp = Left(myText, endP - iniP - 1) 'reuse the var to store the usefull text
Cells(1, 2).Value = Cells(1, 2).Value & Chr(10) & tmp 'store in the cell B1
'If you want to stored in separated cells use the below code
'Cells(j, 2).Value = tmp
myText = Replace(myText, tmp & ")", "", 1, 1) ' remove the garbage text from the main text
tmp = Empty 'empty the var
i = 0 'reset the main counter
End If
Loop While endP <> 0
End Sub
Result:
Please check and tellme if is ok.
Edit#1
Cells(1, 2).Value = Cells(1, 2).Value & Chr(10) & tmp this code store the text in separated lines inside the same cell, may be you want to use spaces between the resulting text because of chr(10) (also you can use chr(13)), then you can use Cells(1, 2).Value = Cells(1, 2).Value & " " & tmp, or use any other character instead the string inside the & symbols

Excel vba script: find() returns nothing on 2nd iteration

For active_row = 9 To last_row
ws1_func_loc = ThisWorkbook.Sheets(ws1).Cells(active_row, "C").Value
ws1_mat_id = ThisWorkbook.Sheets(ws1).Cells(active_row, "D").Value
ws1_mat_qty = ThisWorkbook.Sheets(ws1).Cells(active_row, "I").Value
ws1_reason2 = ""
zc_sum = WorksheetFunction.SumIfs(ThisWorkbook.Sheets(ws2).Range("F:F"), ThisWorkbook.Sheets(ws2).Range("K:K"), ws1_func_loc, ThisWorkbook.Sheets(ws2).Range("N:N"), ws1_mat_id, ThisWorkbook.Sheets(ws2).Range("S:S"), "ZC")
zk_sum = WorksheetFunction.SumIfs(ThisWorkbook.Sheets(ws2).Range("F:F"), ThisWorkbook.Sheets(ws2).Range("K:K"), ws1_func_loc, ThisWorkbook.Sheets(ws2).Range("N:N"), ws1_mat_id, ThisWorkbook.Sheets(ws2).Range("S:S"), "ZK")
'some other If conditions...
ElseIf zc_sum = 0 And zk_sum > 0 Then
row_match_count = WorksheetFunction.CountIf(ThisWorkbook.Sheets(ws2).Range("K:K"), ws1_func_loc)
Set found = ThisWorkbook.Sheets(ws2).Range("K:K").Find(What:=ws1_func_loc) 'find() found nothing on 2nd iteration
For i = 1 To row_match_count
If ThisWorkbook.Sheets(ws2).Cells(found.Row, "N").Value = ws1_mat_id And ThisWorkbook.Sheets(ws2).Cells(found.Row, "S") = "ZK" And Not found Is Nothing Then
ws1_reason2 = ws1_reason2 & Chr(10) & ThisWorkbook.Sheets(ws2).Cells(found.Row, "R").Value & ", " & "qty " & ThisWorkbook.Sheets(ws2).Cells(found.Row, "F").Value & ", " & ThisWorkbook.Sheets(ws2).Cells(found.Row, "U").Value & ", " & ThisWorkbook.Sheets(ws2).Cells(found.Row, "W").Value
Set found = Sheets(ws2).Range("K:K").FindNext(After:=found)
Else
Set found = Sheets(ws2).Range("K:K").FindNext(After:=found)
End If
Next i
ThisWorkbook.Sheets(ws1).Cells(active_row, "O").Value = ws1_reason2
ElseIf .......
I am trying to do a multi criteria search whereby I am finding all the rows in worksheet 2 (ws2) which matches the values in a specific row in worksheet 1 (ws2).
It works during the first For loop iteration, i.e. active_row = 9, but during the second iteration, i.e. when active_row = 10, Set found = ThisWorkbook.Sheets(ws2).Range("K:K").Find(What:=ws1_func_loc) returns Nothing.
But there is definitely at least a match because row_match_count comes up with a value greater than 0.
I finally figured out what's wrong.
There is a bunch of drop down list in ws2 which will filter the results.
If I used the drop down list to filter the rows, ThisWorkbook.Sheets(ws2).Range("K:K").Find(What:=ws1_func_loc) will only be able to see/find from the filtered results, while WorksheetFunction.CountIf(ThisWorkbook.Sheets(ws2).Range("K:K"), ws1_func_loc) is still able to see the whole worksheet.