Searching for several exact substrings in one String - vba
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,
Related
VBA Highlight multiple keywords with wildcard from text strings
Any help here would be appreciated please. The included VBA code almost meets the intended purpose, however, I need a solution that enables the use of wildcards and highlights all parameters contained between "##", "%%" or potentially other special characters (special characters included). For instance, lets say in the cell range B2:B10 we would find something like: Checked at ##date1## and ##hour1## But I want to be able to do a search and highlight using # * # or % * % within a selected determined cell range with the end result (bold being color): Checked at ##date1## and ##hour1## Sub HighlightStrings() Application.ScreenUpdating = False Dim Rng As Range Dim cFnd As String Dim xTmp As String Dim x As Long Dim m As Long Dim y As Long Dim xFNum As Integer Dim xArrFnd As Variant Dim xStr As String cFnd = InputBox("Please enter the text, separate them by comma:") If Len(cFnd) < 1 Then Exit Sub xArrFnd = Split(cFnd, ",") For Each Rng In Selection With Rng For xFNum = 0 To UBound(xArrFnd) xStr = xArrFnd(xFNum) y = Len(xStr) m = UBound(Split(Rng.Value, xStr)) If m > 0 Then xTmp = "" For x = 0 To m - 1 xTmp = xTmp & Split(Rng.Value, xStr)(x) .Characters(Start:=Len(xTmp) + 1, Length:=y).Font.ColorIndex = 3 xTmp = xTmp & xStr Next End If Next xFNum End With Next Rng Application.ScreenUpdating = True End Sub Thank you
Okay This seems to work for me. There is a limitation we can work on if required: the phrase to highlight must be padded with spaces on both sides. Option Explicit Option Base 0 Sub testreplace() Dim I As Integer 'Iteration Dim FlagNum As Integer 'Flag Number Dim RG As Range 'Whole range Dim CL As Range 'Each Cell Dim FlagChar As String 'Flag characters Dim ArrFlag 'Flag Char Array Dim TextTemp As String 'Cell Contents Set RG = Selection FlagChar = "##" FlagChar = InputBox("Enter 'Flag Characters' separated by a comma." & vbCrLf & vbCrLf & _ "Example:" & vbCrLf & vbCrLf & _ "##,%%,&&" & vbCrLf & _ "$$,XX", "Flag Characters to Highlight", "##,%%") ArrFlag = Split(FlagChar, ",") For Each CL In RG.Cells TextTemp = CL.Value For FlagNum = 0 To UBound(ArrFlag) For I = 1 To Len(TextTemp) 'Debug.Print "<<" & Mid(TextTemp, I, Len(ArrFlag(Flagnum)) + 1) & _ "=" & " " & ArrFlag(Flagnum) & ">>" If Mid(TextTemp, I, Len(ArrFlag(FlagNum)) + 1) = " " & ArrFlag(FlagNum) Then CL.Characters(I + 1, InStr(I, TextTemp, ArrFlag(FlagNum) & " ") + _ Len(ArrFlag(FlagNum)) - I).Font.ColorIndex = 3 End If Next I Next FlagNum Next CL End Sub Here's an example of it working:
Format pasted rows within userforum-textbox into concatenation or borderline?
I get a mismatch error in this line : row_str = Join(cell_rng, Chr(10)) Thank you. I am intermediate. I attached a piece of the code below: Dim last_row As String Dim last_col As String Dim office_str As String Dim lookupVal As String Dim i As Long Dim seperate_cells, cell_rng As Range Dim r As Range Dim row_str As String With Contacts For i = 2 To last_row Set cell_rng = Rows(i & ":" & i + 1) For Each r In cell_rng.Rows seperate_cells = cellsSeparator(r.SpecialCells(xlCellTypeConstants)) If row_str = "" Then row_str = Join(cell_rng, Chr(10)) Else row_str = row_str & vbLf & Join(cell_rng, Chr(10)) End If Next Debug.Print row_str Client_Finder.result.Text = Client_Finder.result.Text & vbLf & row_str Next i End With ````
Please try the next way. It will place the values of the necessary specific row in the text box, each value separated by " | ": Sub testSeparatorsBetweenRowCells() 'your existing code... Dim arr, rngR As Range For i = 2 To last_row lookupVal = cells(i, office_str) ' Compare ComboBox with the range from the spreadsheet If lookupVal = Office_Code Then Set rngR = rows(i & ":" & i).SpecialCells(xlCellTypeConstants) 'Set a range which will return all cells value in the row, except the empty ones arr = arrCells(rngR) 'call a function able to make an array from the range set in the above line Client_Finder.result.Text = Client_Finder.result.Text & vbLf & Join(arr, " | ") 'add the text obtained by joining the array to the next line of existing text End If Next i End Sub Function arrCells(rng As Range) As Variant Dim arr, Ar As Range, i As Long, C As Range ReDim arr(rng.cells.count - 1) 'ReDim the array to be filled as the range cells number. '- 1, because the array is 0 based... For Each Ar In rng.Areas 'iterate between the range areas For Each C In Ar.cells 'iterate between cells of each area arr(i) = C.value: i = i + 1 'put each cell value in the array Next Next arrCells = arr 'make the function returning the arr End Function If the text in the text box still goes on the next line, try making the text box property WordWrap False. If you cannot see all the text, make the textbox wider or decrease its font size. Please, test it and send some feedback. Edited: Please, try understanding the next piece of code, able to deal with copying more rows at once: Sub testCopyingMoreRows() Dim sh As Worksheet, i As Long, rng As Range, r As Range, arr, strRow As String Set sh = ActiveSheet i = 9 Set rng = sh.rows(i & ":" & i + 1) 'you ca select cells, rows (even not consecutive) and use: 'Set rng = Selection.EntireRow 'just uncomment this code line... 'extract rows and paste their contents (exept the empty cells) in Imediate Window For Each r In rng.rows arr = arrCells(r.SpecialCells(xlCellTypeConstants)) If strRow = "" Then strRow = Join(arr, " | ") Else strRow = strRow & vbLf & Join(arr, " | ") End If Next Debug.Print strRow 'instead returning in Imediate Window, you can do it in your text box (uncomment the next line): 'Client_Finder.result.Text = Client_Finder.result.Text & vbLf & strRow End Sub The code uses the same function arrCells...
VBA Chart range substract
I found this code, which add's one extra column to the chart each time it runs. Meaning first time it runs it shows week 1-7, secound time 1-8, next 1-9 and I would like it to show 2-7, 3-8, 4-9 ect. Sub ChartRangeAdd() On Error Resume Next Dim oCht As Chart, aFormulaOld As Variant, aFormulaNew As Variant Dim i As Long, s As Long Dim oRng As Range, sTmp As String, sBase As String Set oCht = ActiveSheet.ChartObjects(1).Chart oCht.Select For s = 1 To oCht.SeriesCollection.count sTmp = oCht.SeriesCollection(s).Formula sBase = Split(sTmp, "(")(0) & "(<FORMULA>)" ' "=SERIES(" & "<FORMULA>)" sTmp = Split(sTmp, "(")(1) ' "..., ..., ...)" aFormulaOld = Split(Left(sTmp, Len(sTmp) - 1), ",") ' "..., ..., ..." aFormulaNew = Array() ReDim aFormulaNew(UBound(aFormulaOld)) ' Process all series in the formula For i = 0 To UBound(aFormulaOld) Set oRng = Range(aFormulaOld(i)) ' Attempt to put the value into Range, keep the same if it's not valid Range If Err.Number = 0 Then Set oRng = oRng.Worksheet.Range(oRng, oRng.Offset(0, 1)) aFormulaNew(i) = oRng.Worksheet.Name & "!" & oRng.Address Else aFormulaNew(i) = aFormulaOld(i) Err.Clear End If Next i sTmp = Replace(sBase, "<FORMULA>", Join(aFormulaNew, ",")) Debug.Print "Series(" & s & ") from """ & oCht.SeriesCollection(s).Formula & """ to """ & sTmp & """" oCht.SeriesCollection(s).Formula = sTmp sTmp = "" Next s Set oCht = Nothing End Sub I want to do the opposite of this code, so instead of adding a column one column should be substracted. How can the code be modifued to do this? (LINK: VBA: Modify chart data range) Thank you!
Try changing the line Set oRng = oRng.Worksheet.Range(oRng, oRng.Offset(0, 1)) to Set oRng = oRng.Worksheet.Range(oRng.Offset(0, 1), oRng.Offset(0, 1))
Excel VBA - Randomly select 3 rows per username
I have a large list of tickets with a total of 6 different user names. What I need the code to do is randomly select 3 rows of data per user (18 total) and hide the rest of the rows, as I only need to see the selected rows. The code will be something like the below, but I am not sure how to write the "random" part. LastRow = Cells(Rows.Count, "A").End(xlUp).Row With Range("A2:F" & LastRow) *Select 3 random rows for user A* *Select 3 random rows for user B* *The same for C-F* *Hide all other rows* End With
Found this to be an interesting challenge. Something like this should work for you. Commented code for clarity. Sub tgr() 'Adjust these parameters as necessary Const sDataSheet As String = "Sheet1" Const sUserCol As String = "A" Const lHeaderRow As Long = 1 Const lShowRowsPerUser As Long = 3 Const bSortDataByUser As Boolean = False 'Declare variables Dim ws As Worksheet Dim rData As Range Dim rShow As Range Dim aData() As Variant Dim aUserRows() As Variant Dim lTotalUnqUsers As Long Dim lMaxUserRows As Long Dim i As Long, j As Long, k As Long Dim lRandIndex As Long 'Test if sDataSheet name provided exists in ActiveWorkbook On Error Resume Next Set ws = ActiveWorkbook.Sheets(sDataSheet) On Error GoTo 0 If ws Is Nothing Then MsgBox "No sheet named [" & sDataSheet & "] found in " & ActiveWorkbook.Name & Chr(10) & _ "Correct sDataSheet in code and try again." Exit Sub End If ws.Cells.EntireRow.Hidden = False 'Reset rows to show all data 'Work with the data range set by parameters With ws.Range(sUserCol & lHeaderRow + 1, ws.Cells(ws.Rows.Count, sUserCol).End(xlUp)) 'Verify data exists in specified location If .Row < lHeaderRow + 1 Then MsgBox "No data found in [" & sDataSheet & "]" & Chr(10) & _ "Verify column containing users is Column [" & sUserCol & "] or correct sUserCol in code." & Chr(10) & _ "Verify header row is Row [" & lHeaderRow & "] or correct lHeaderRow in code." & Chr(10) & _ "Once corrections have been made and data is available, try again." Exit Sub End If lTotalUnqUsers = Evaluate("SUMPRODUCT((" & .Address(external:=True) & "<>"""")/COUNTIF(" & .Address(external:=True) & "," & .Address(external:=True) & "&""""))") 'Get total unique users lMaxUserRows = Evaluate("max(countif(" & .Address(external:=True) & "," & .Address(external:=True) & "))") 'Get max rows per user If bSortDataByUser Then .Sort .Cells, xlAscending, Header:=xlNo 'If bSortByUser is set to True, then sort the data Set rData = .Cells 'Store the data in a range object for later use aData = .Value 'Load the data into an array to speed operations ReDim aUserRows(1 To lTotalUnqUsers, 1 To 3, 1 To lMaxUserRows) 'Ready the results array that random rows will be selected from End With 'Load all available rows into the results array, grouped by the user For i = LBound(aData, 1) To UBound(aData, 1) For j = LBound(aUserRows, 1) To UBound(aUserRows, 1) If IsEmpty(aUserRows(j, 1, 1)) Or aUserRows(j, 1, 1) = aData(i, 1) Then 'Find correct user If IsEmpty(aUserRows(j, 1, 1)) Then aUserRows(j, 1, 1) = aData(i, 1) 'If user isn't in results array yet, add it k = aUserRows(j, 2, 1) + 1 'Increment row counter for this user aUserRows(j, 2, 1) = k aUserRows(j, 3, k) = i + lHeaderRow 'Load this row into this user's group of rows Exit For End If Next j Next i 'Select random rows up to lShowRowsPerUser for each user from the grouped results array For j = LBound(aUserRows, 1) To UBound(aUserRows, 1) Do Randomize lRandIndex = Int(Rnd() * aUserRows(j, 2, 1)) + 1 If Not rShow Is Nothing Then Set rShow = Union(rShow, ws.Cells(aUserRows(j, 3, lRandIndex), sUserCol)) Else Set rShow = ws.Cells(aUserRows(j, 3, lRandIndex), sUserCol) End If Loop While rShow.Cells.Count < j * Application.Min(lShowRowsPerUser, aUserRows(j, 2, 1)) Next j rData.EntireRow.Hidden = True 'Hide all relevant rows rShow.EntireRow.Hidden = False 'Only show the rows that have been randomly selected End Sub
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