Search all columns of a spreadsheet for a unique value - vba

I am struggling with a starting point for my next question. Essentially, I need to be able to search an entire spreadsheet for a unique 13 digit number (which would be unknown so I can't reference it beforehand), find all the references to that number, copy the rows into a new work sheet and then look for the next 13 digit number until all of the different 13 digit references have been copied into new sheets. Now this number may be in column A/B but it also may not, we aren't given the data in set template which is why it needs to search the whole spreadsheet. Can anyone give me an idea as to where to start? I have the basis of a subroutine if I know the numbers beforehand but in this instance, we don't know these numbers just that they are in there. Please help?! This is a VBA solution that I need.
Sample Data
Now the unique number may not always be in Column B which is why the macro needs to be able to identify which column the 13 digit number is in before copying all the rows which relate to it. I hope that makes more sense.

Here is a generic FindAll function which can serve as a start. You will need to specify a region (eg. .UsedRange) to search and what you are searching for and it will return a range of all the cells that match.
Function FindAll(What, _
Optional SearchWhat As Variant, _
Optional LookIn, _
Optional LookAt, _
Optional SearchOrder, _
Optional SearchDirection As XlSearchDirection = xlNext, _
Optional MatchCase As Boolean = False, _
Optional MatchByte, _
Optional SearchFormat) As Range
'LookIn can be xlValues or xlFormulas, _
LookAt can be xlWhole or xlPart, _
SearchOrder can be xlByRows or xlByColumns, _
SearchDirection can be xlNext, xlPrevious, _
MatchCase, MatchByte, and SearchFormat can be True or False. _
Before using SearchFormat = True, specify the appropriate settings for the Application.FindFormat _
object; e.g. Application.FindFormat.NumberFormat = "General;-General;""-"""
Dim SrcRange As Range
If IsMissing(SearchWhat) Then
Set SrcRange = ActiveSheet.UsedRange
ElseIf TypeOf SearchWhat Is Range Then
Set SrcRange = IIf(SearchWhat.Cells.Count = 1, SearchWhat.Parent.UsedRange, SearchWhat)
ElseIf TypeOf SearchWhat Is Worksheet Then
Set SrcRange = SearchWhat.UsedRange
Else: SrcRange = ActiveSheet.UsedRange
End If
If SrcRange Is Nothing Then Exit Function
'get the first matching cell in the range first
With SrcRange.Areas(SrcRange.Areas.Count)
Dim FirstCell As Range: Set FirstCell = .Cells(.Cells.Count)
End With
Dim CurrRange As Range: Set CurrRange = SrcRange.Find(What:=What, After:=FirstCell, LookIn:=LookIn, LookAt:=LookAt, _
SearchDirection:=SearchDirection, MatchCase:=MatchCase, MatchByte:=MatchByte, SearchFormat:=SearchFormat)
If Not CurrRange Is Nothing Then
Set FindAll = CurrRange
Do
Set CurrRange = SrcRange.Find(What:=What, After:=CurrRange, LookIn:=LookIn, LookAt:=LookAt, _
SearchDirection:=SearchDirection, MatchCase:=MatchCase, MatchByte:=MatchByte, SearchFormat:=SearchFormat)
If CurrRange Is Nothing Then Exit Do
If Application.Intersect(FindAll, CurrRange) Is Nothing Then
Set FindAll = Application.Union(FindAll, CurrRange)
Else: Exit Do
End If
Loop
End If
End Function

Related

How to find cell containing string in entire worksheet

I would like to find a cell in a worksheet containing a specific string.
I won't know precisely how many columns or rows there will be in the spreadsheet, hence why I wanted to do it with CurrentRegion.
This is what I was trying:
=FIND("Data String", Range("A1").CurrentRegion)
You should have a look into the Microsoft References: Range.Find Method (Excel).
.Find(What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat)
Example:
Dim rngFound as Range
With Worksheets("MySheetName").Cells
Set rngFound = .Find("MySearchString", LookIn:=xlValues)
If Not rngFound Is Nothing Then
'something is found
else
'nothing found
End If
End With
searches the whole sheet
Try This
FindString = Sheets("Sheet1").Range("D1").Value
---------- This will select the next Cell in range with the inputbox value
Sub Find_First()
Dim FindString As String
Dim Rng As Range
FindString = InputBox("Enter a Search value")
If Trim(FindString) <> "" Then
With Sheets("Sheet1").Range("A:A")
Set Rng = .Find(What:=FindString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Application.Goto Rng, True
Else
MsgBox "Nothing found"
End If
End With
End If
End Sub
Find Value

Retrieve multiple values associated with single ID in excel

I'm curious if there is a simpler solution out there with respect to retrieving values associated with a single ID in excel.
I have explored the INDEX solution to look up multiple values in a list but that is not really dynamic and gives you the result in a vertical order rather than the horizontal order that I required. (see Results desired below)
The sample function i used was this
"=IF(ISERROR(SMALL(IF(IF(ISERROR(SEARCH($A$9,$A$1:$A$7)),FALSE,TRUE),ROW($A$1:$A$7)),ROW($C$1:$C$7))),"",INDEX($A$1:$C$7,SMALL(IF(IF(ISERROR(SEARCH($A$9,$A$1:$A$7)),FALSE,TRUE),ROW($A$1:$A$7)),ROW($C$1:$C$7)),3))"
*Ignore the references for this example.
I have two sheets that I'm working on and basically need to retrieve the values associated with a single ID from "Numbers Sheet" and store them on "Master Sheet" See images below for clearer explanation. The formula needs to find the subsequent number associated with the ID and put it on the subsequent column as shown below.
*note: any user ID can request for any number of tickets so it can range from 1-100 (just showing 3 as an example)
Appreciate any guidance from the excel masters here. The only other solution I can think of is to use a vba code to retrieve each value and store it in an array and then retrieve the value from the array. Let me know your thoughts!
Thanks in advance!
Master Sheet:
Numbers Sheet:
Results desired:
Put the following formula in cell C2[1] of your Master Sheet
{=IFERROR(INDEX(Numbers!$A:$C,SMALL(IF(Numbers!$A$1:$A$1000=$A2,ROW(Numbers!$A$1:$A$1000)),INT((COLUMN(A:A)-1)/2)+1),MOD(COLUMN(A:A)-1,2)+2),"")}
[1] I'm assuming it is row 2 since you have unfortunately not shown the row numbers.
The formula is an array formula. Input it into the cell without the curly brackets and confirm it with [Ctrl] + [Shift] + [Enter]. The curly brackets then will appear automatically.
Then fill the formula to right and downwards as needed.
you can try this code
Sub main()
Dim IdRng As Range, cell As Range, filtCell As Range
Dim i As Long
With Worksheets("Master Sheet")
Set IdRng = .Range("A1", .Cells(.Rows.Count, 1).End(xlUp)).SpecialCells(XlCellType.xlCellTypeConstants)
End With
With Worksheets("Numbers")
With .Cells(1, 1).CurrentRegion
For Each cell In IdRng
.AutoFilter field:=1, Criteria1:=cell.value '<--| filter it on current department value
If Application.WorksheetFunction.Subtotal(103, .Cells.Resize(, 1)) > 1 Then
For Each filtCell In .Offset(1, 1).Resize(.Rows.Count - 1, 1).SpecialCells(XlCellType.xlCellTypeVisible)
cell.End(xlToRight).Offset(, 1).Resize(, 2).value = filtCell.Resize(, 2).value
Next filtCell
End If
Next cell
End With
.AutoFilterMode = False
End With
With Worksheets("Master Sheet").Cells(1, 1).CurrentRegion.Rows(1)
.Insert
With .Offset(-1)
.Font.Bold = True
.Resize(, 2) = Array("ID", "Name")
For i = 1 To .Columns.Count - 2 Step 2
.Offset(, 1 + i).Resize(, 2) = Array("Description " & (i + 1) / 2, "Number " & (i + 1) / 2)
Next i
End With
End With
End Sub
VBA is probably a better route for this and using .Find and .FindNext is the way I would go.
Attached is a generic FindAll function, so you could look for all the cells containing the ID in question then process the cells one at a time.
Function FindAll(What, _
Optional SearchWhat As Variant, _
Optional LookIn, _
Optional LookAt, _
Optional SearchOrder, _
Optional SearchDirection As XlSearchDirection = xlNext, _
Optional MatchCase As Boolean = False, _
Optional MatchByte, _
Optional SearchFormat) As Range
'LookIn can be xlValues or xlFormulas, _
LookAt can be xlWhole or xlPart, _
SearchOrder can be xlByRows or xlByColumns, _
SearchDirection can be xlNext, xlPrevious, _
MatchCase, MatchByte, and SearchFormat can be True or False. _
Before using SearchFormat = True, specify the appropriate settings for the Application.FindFormat _
object; e.g. Application.FindFormat.NumberFormat = "General;-General;""-"""
Dim SrcRange As Range
If IsMissing(SearchWhat) Then
Set SrcRange = ActiveSheet.UsedRange
ElseIf TypeOf SearchWhat Is Range Then
Set SrcRange = IIf(SearchWhat.Cells.Count = 1, SearchWhat.Parent.UsedRange, SearchWhat)
ElseIf TypeOf SearchWhat Is Worksheet Then
Set SrcRange = SearchWhat.UsedRange
Else: Set SrcRange = ActiveSheet.UsedRange
End If
If SrcRange Is Nothing Then Exit Function
'get the first matching cell in the range first
With SrcRange.Areas(SrcRange.Areas.Count)
Dim FirstCell As Range: Set FirstCell = .Cells(.Cells.Count)
End With
Dim CurrRange As Range: Set CurrRange = SrcRange.Find(What:=What, After:=FirstCell, LookIn:=LookIn, LookAt:=LookAt, _
SearchDirection:=SearchDirection, MatchCase:=MatchCase, MatchByte:=MatchByte, SearchFormat:=SearchFormat)
If Not CurrRange Is Nothing Then
Set FindAll = CurrRange
Do
Set CurrRange = SrcRange.Find(What:=What, After:=CurrRange, LookIn:=LookIn, LookAt:=LookAt, _
SearchDirection:=SearchDirection, MatchCase:=MatchCase, MatchByte:=MatchByte, SearchFormat:=SearchFormat)
If CurrRange Is Nothing Then Exit Do
If Application.Intersect(FindAll, CurrRange) Is Nothing Then
Set FindAll = Application.Union(FindAll, CurrRange)
Else: Exit Do
End If
Loop
End If
End Function

Subroutine to fill in values in a range?

I've been trying to write a subroutine to find the first and last instances of a value in a row and fill the middle with the same value. However, I have trouble getting to work. I think the problem is with my range.find method to find the last instance. Can someone help me identify the error?
Sub ChangeBetween()
Dim FirstCell As Range
Dim LastCell As Range
Dim SearchTerm As String
Dim ChangeRange As Range
Dim ChangeCell As Range
'Define search term
SearchTerm = Range("A1").Value
'Find beginning and end of replacement range
Set FirstCell = Range("B2:I2").Find( _
What:=SearchTerm, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext)
Set LastCell = Range("B2:I2").Find( _
What:=SeartTerm, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
'Set replacement range
Set ChangeRange = Range(FirstCell, LastCell)
'Replace in range
For Each ChangeCell In ChangeRange
ChangeCell.Value = SearchTerm
Next ChangeCell
End Sub
I picked up a couple of (what I believe to be) logic holes by specifying the After:= parameter of the .Find method.
Option Explicit 'see footnote ¹
Sub ChangeBetween()
Dim firstCell As Range, lastCell As Range, searchTerm As String
With Worksheets("Sheet3")
'Define search term
searchTerm = .Range("A1").Value
'Find beginning and end of replacement range
With .Range("B2:I2")
'make sure there is at least one SearchTerm
If CBool(Application.CountIf(.Cells, searchTerm)) Then
Set firstCell = .Find(What:=searchTerm, _
After:=.Cells(.Columns.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext)
Set lastCell = .Find(What:=searchTerm, _
After:=.Cells(1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
.Parent.Range(firstCell, lastCell) = searchTerm
End If
End With
End With
End Sub
¹ Setting Require Variable Declaration within the VBE's Tools ► Options ► Editor property page will put the Option
Explicit statement at the top of each newly created code sheet. This
will avoid silly coding mistakes like misspellings as well as influencing you to use the correct variable type in the variable
declaration. Variables created on-the-fly without declaration are all of the variant/object type. Using Option Explicit is
widely considered 'best practice'.
Quoting you
Set LastCell = Range("B2:I2").Find( _
What:=SeartTerm, _ 'this is a typo your variable is SearchTerm
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
If I may, why to loop thorugh each cell from final and first?
'Replace in range
For Each ChangeCell In ChangeRange
ChangeCell.Value = SearchTerm
Next ChangeCell
Try:
ChangeRange.Value = SearchTerm

VBA .Find, dates, serial number dates, and the data type properties (.Text, .Value, .Value2)

Using the Range.Find Method to iterate through each value in one range to see if it exists in the other. Issue is that the format for both ranges is different (but all the values are ostensibly dates), which results in many "false negatives" (i.e., values are not matching when they should be). is it possible to control the data type for both the input and search ranges so that the system will compare apples-to-apples and successfully recognize matches?
Here is the data and code:
Sheet1 (custom format, "yyyy-mm-dd")
A1 2016-01-01
A2 2016-01-02
A3 2016-01-03
A4 2016-01-04
A5 2016-01-05
Sheet2 (text format)
A1 2016-01-01
A2 2016-01-03
A3 2016-01-05
Sheet3 (display as "yyyy-mm-dd")
[NO DATA]
Sheet4 (display as "yyyy-mm-dd")
[NO DATA]
code:
Sub FindTest()
Dim inputRange As Range
Dim searchRange As Range
Dim found As Range
Set inputRange = Worksheets(1).Cells(1, 1).Resize(7, 1)
Set searchRange = Worksheets(2).Cells(1, 1).Resize(5, 1)
For Each i In inputRange
Set found = searchRange.Find _
(What:=i, _
after:=Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If Not (found Is Nothing) Then
Worksheets(3).Cells(i.Row, i.Column) = i
Else
Worksheets(4).Cells(i.Row, i.Column) = i
End If
Next i
End Sub
For this to work, I assume that I need all the value in inputRange and searchRange to be compared as .Text, but I'm not sure to get there.
If you need to keep your data as true dates and text-that-looks-like-dates then format the true date values on the he fly to locate matches within the 'text-dates'.
Sub FindTest()
Dim inputRange As Range, i As Range
Dim searchRange As Range, found As Range
Set inputRange = Worksheets(1).Cells(2, 1).Resize(99, 1)
Set searchRange = Worksheets(2).Columns(1)
For Each i In inputRange
If IsDate(i) Then ' ▼ format into TXT here ▼
Set found = searchRange.Find(What:=Format(i.Value2, "yyyy-mm-dd"), _
after:=Cells(1), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not (found Is Nothing) Then
With Worksheets(3)
.Cells(i.Row, i.Column) = i.Address(external:=True)
.Cells(i.Row, i.Column + 1) = i.Address(external:=True)
End With
Else
With Worksheets(4)
.Cells(i.Row, i.Column) = i.Address(external:=True)
.Cells(i.Row, i.Column + 1) = i.Address(external:=True)
End With
End If
End If
Next i
End Sub
Alternately, the Range.Text property should provide the same answer. Apply the Trim command to make sure that the inputRange dates are not formatted with a trailing _) in the format mask.
Set found = searchRange.Find(What:=Trim(i.Text), _
after:=Cells(1), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
Addendum:
If there is a possibility of leading or trailing spaces in the searchRange, then change the parameters of your search to LookAt:=xlPart. While repairing the data beforehand would still be the better option, it will ignore extraneous characters that could interfere with a successful match.
If repairing the data can be considered an option then a blanket Range.TextToColumns method on the text values with a YMD xlColumnDataType will quickly convert your yyyy-mm-dd 'text-dates' into real dates.
With searchRange
.TextToColumns Destination: .Cells (1), DataType:=xlFixedWidth, _
FieldInfo:=Array(0, 5)
End With
This will remove leading/trailing extraneous characters and leave you with a column of true dates.
TextToColumn code (unsuccessful)
Sub FindTest()
Dim inputRange As Range
Dim i As Range
Dim searchRange As Range
Dim found As Range
Set inputRange = Worksheets(1).Cells(1, 1).Resize(7, 1)
Set searchRange = Worksheets(2).Cells(1, 1).Resize(5, 1)
searchRange.TextToColumns Destination:=Worksheets(2).Cells(1), DataType:=xlDelimited, FieldInfo:=Array(1, xlTextFormat)
For Each i In inputRange
Set found = searchRange.Find _
(What:=i, _
after:=Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If Not (found Is Nothing) Then
Worksheets(3).Cells(i.Row, i.Column) = i
Else
Worksheets(4).Cells(i.Row, i.Column) = i
End If
Next i
End Sub

Select a column based on various possible header names

What's the most efficient way of selecting a column based on a variety of different possible header names? For example, the following gives me the column with header "school":
Rows("1:1").Select
Selection.Find(What:="School", _
After:=ActiveCell, _
LookIn:=xlFormulas, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False).Activate
ActiveCell.Select
Range(ActiveCell, ActiveCell.Offset(6536, 0)).Select
However, "school" could be "college" in another workbook, or "institution" in another. Should I just place the above code within an if-then-else statement and replace "school" with the other possibilities, or is there a more efficient way? And yes, this assumes that none of the possible header names co-exist within the same workbook.
Find is already very efficient. What's not efficient is all those Select's.
I suggest you wrap your Find Header logic into a Function, and refactor your code to avoid Select.
Private Function GetColumn(Header() As Variant, _
Optional NumRows As Long = 0, _
Optional ws As Worksheet = Nothing, _
Optional wb As Workbook = Nothing) As Range
Dim rng As Range, cl As Range
Dim i As Long
If wb Is Nothing Then
Set wb = ActiveWorkbook
End If
If ws Is Nothing Then
Set ws = wb.ActiveSheet
End If
Set rng = ws.UsedRange.Rows(1)
For i = LBound(Header) To UBound(Header)
Set cl = rng.Find(What:=Header(i), _
After:=rng.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not cl Is Nothing Then
With ws
If NumRows = 0 Then
Set GetColumn = Range(cl, .Cells(.Rows.Count, cl.Column).End(xlUp))
Else
Set GetColumn = Range(cl, .Cells(NumRows, cl.Column))
End If
Exit Function
End With
End If
Next
Set GetColumn = Nothing
End Function
Call it like this
Dim rng As Range
Dim Headers() As Variant
Headers = Array("School", "Institution", "College")
' Active Workbook, Active Sheet
Set rng = GetColumn(Headers, 6536)
' All rows in specified column
' Specified sheet in Active workbook
Set rng = GetColumn(Headers, , Worksheets("SomeSheetName"))