Help with a VBA Search function across multiple sheets to return a match

UMAKEMESIK

Active Member
Joined
Oct 3, 2005
Messages
378
TO ALL,

I have a workbook with multiple sheets.
The top level sheet is where I enter information to
Look for matching data on the following sheets.

I use Vlookup for an exact match when I know the part number and it works great.

I would like to add a search function for when I don’t know the part number
And am looking for a match of Length Width and depth –

The returned result would be:
Part number – Length – widty and depth.

I would also like to add a variance of + 1 and -1
So the result would find all units with the exact match and all those over and under by 1”

I would like this to run when the user enters a number and presses ENTER. ( or a click of a button would be ok to run the command)

So the user would enter 12 – 3 – 10 in three different cells all on the same line – Press Enter
And the result would show up possibly on the same line
But then all of the units over and under could show up right below.

I am pretty sure I will need VB for this as I think this goes beyond what Vlookup can accomplish.

I can explain more if needed.

Thanks in advance.
 
Martin, I received your current file and your note that you had further modified the layout.

Your revised layout is much better for data processing, since it uses these same fields consistently on the results sheet and all data sheets.


Excel 2013
ABCDEFG
10ENTER L X W X D -->>743CLICK BUTTON TO SEARCH --->>BUTTON
11
12Stock boxLengthWidthDepthDesign #/PlantFlute/Board
Stock Box Lookup


Here's some reworked code. It uses named ranges to identify the input cells and the results sheets headers.
This will allow you to make some additional changes to the results worksheet without the need to modify hard-coded address references in the VBA code.

Code:
Sub FindData()
'--purpose is to search multiple data sheets to find records
'    that are exact match to user input parameters and
'    near matches that are within specified variance.

'--assumptions about workbook setup:
'    search data is in contiguous range starting at A1 on each data sheet
'    each data sheet has headers and fields are same order as results header

 Dim bWarned As Boolean
 Dim adParams() As Double, adThisRecord() As Double
 Dim lNdx As Long, lRow As Long, lCol As Long, lNdxSheets As Long
 Dim lMatchResultCount As Long, lNearResultCount As Long
 Dim lParamCount As Long, lResultFieldsCount As Long
 Dim rParams As Range, rResultHeaders As Range, rSaveActiveCell As Range
 Dim avDataSheetNames() As Variant
 Dim vData As Variant, vMatchResults As Variant, vNearResults As Variant
 Dim wksData As Worksheet

 '--maximum variance to be considered near match
 Const dVARIANCE As Double = 1

 '--limit of matches to be returned
 Const lMAX_RESULTS As Long = 1000
  
 '--column nbr of length field in data sheets
 Const lFIRST_PARAM_COL As Long = 2
  
 '--identify data sheets to be searched
 avDataSheetNames = Array("Sheet2", "Sheet3", "Sheet4", _
   "Sheet5", "Sheet6", "Sheet7")

 '--find and validate worksheet ranges usef for search
 On Error Resume Next
 Set rParams = Names("inpStockBoxSizeSearch").RefersToRange
 Set rResultHeaders = Names("ptrStockBoxSizeSearchHeaders").RefersToRange
 On Error GoTo 0
 
 If rParams Is Nothing Or rResultHeaders Is Nothing Then
   MsgBox "Named ranges for input cells or headers not found."
   GoTo ExitProc
 End If
   
 If rParams.Rows.Count > 1 Or rResultHeaders.Rows.Count > 1 Then
   MsgBox "Named ranges for input cells or headers reference more than one row"
   GoTo ExitProc
 End If
 
 '--number of search parameters (length, width, depth)
 lParamCount = rParams.Columns.Count
 
 '--number of fields to be read for matching records
 lResultFieldsCount = rResultHeaders.Columns.Count
 
 '--clear previous search results
 With rResultHeaders
   .Parent.Rows(.Row + 1 & ":" & Rows.Count).ClearContents
 End With
 Set rSaveActiveCell = ActiveCell
 
'--read and validate search parameters
 If Application.CountA(rParams) = lParamCount And _
   Application.Count(rParams) = lParamCount Then
   '--write params to array that used to compare to records
   ReDim adParams(1 To lParamCount)
   For lNdx = 1 To lParamCount
      adParams(lNdx) = rParams(1, lNdx)
   Next lNdx
   
 Else
   MsgBox "Enter numeric values for Length, Width and Depth"
   GoTo ExitProc
 End If

 '--size array that will hold records meeting criteria
 ReDim vMatchResults(1 To lMAX_RESULTS, 1 To lResultFieldsCount)
 ReDim vNearResults(1 To lMAX_RESULTS, 1 To lResultFieldsCount)
 
 ReDim adThisRecord(1 To lParamCount)
 
 '--step through each data worksheet and find records that are matches
 '    or near matches to parameters
 
 For lNdxSheets = LBound(avDataSheetNames) To UBound(avDataSheetNames)
   On Error Resume Next
   Set wksData = Nothing
   Set wksData = Worksheets(avDataSheetNames(lNdxSheets))
   On Error GoTo 0
      
   If wksData Is Nothing Then
      '--warn about missing sheet, but continue search
      MsgBox "Sheet: " & avDataSheetNames(lNdxSheets) _
         & " not found"
   Else
      vData = wksData.Range("A1").CurrentRegion.Value
      If Not IsArray(vData) Then vData = Array(vData)
      If UBound(vData, 2) < lParamCount Then
         MsgBox "Not enough columns of data found in sheet: " _
            & wksData.Name
      Else
         For lRow = 2 To UBound(vData, 1)
           '--read each record's values to be compared vs parameters
            For lNdx = 1 To lParamCount
               On Error Resume Next
               adThisRecord(lNdx) = vData(lRow, lFIRST_PARAM_COL + lNdx - 1)
               If Err.Number <> 0 Then
                  Err.Clear
                  If Not bWarned Then
                     MsgBox "Non-numeric data found in numeric field: " & vbCr _
                        & "Sheet: " & wksData.Name & vbCr _
                        & "Row: " & lRow
                     bWarned = True
                  End If
               End If
               On Error GoTo 0
            Next lNdx
   
            If bIsMatch(dArr1:=adThisRecord, dArr2:=adParams) Then
               '--write exact match to next record
               lMatchResultCount = lMatchResultCount + 1
               If lMatchResultCount > lMAX_RESULTS Then
                  MsgBox "Matches found exceed maximum allowed"
                  GoTo ExitProc
               End If
               For lCol = 1 To lResultFieldsCount
                  vMatchResults(lMatchResultCount, lCol) = vData(lRow, lCol)
               Next lCol
                         
            ElseIf bIsNearMatch(dArr1:=adThisRecord, dArr2:=adParams, _
                dMaxVariance:=dVARIANCE) Then
               '--write near match to next record
                lNearResultCount = lNearResultCount + 1
               If lNearResultCount > lMAX_RESULTS Then
                  MsgBox "Near matches found exceed maximum allowed"
                  GoTo ExitProc
               End If
               For lCol = 1 To lResultFieldsCount
                  vNearResults(lNearResultCount, lCol) = vData(lRow, lCol)
               Next lCol
            End If
         Next lRow
      End If 'enough fields
   End If 'wksData exists

 Next lNdxSheets

 '--write results
 If lMatchResultCount Then
   rResultHeaders.Offset(1).Resize(lMatchResultCount, lResultFieldsCount) = vMatchResults
 Else
   MsgBox "No exact match meeting search parameters found"
 End If

 If lNearResultCount Then
   rResultHeaders.Offset(1).Offset(IIf(lMatchResultCount, lMatchResultCount, 1)) _
      .Resize(lNearResultCount, lResultFieldsCount) = vNearResults
 End If
 
 rSaveActiveCell.Select
 
ExitProc:
 Application.CutCopyMode = False
 Exit Sub
 
ErrorHandler:
 MsgBox Err.Number & "-" & Err.Description, vbExclamation, "Error"
 Resume ExitProc
End Sub

Private Function bIsMatch(ByRef dArr1() As Double, _
   ByRef dArr2() As Double) As Boolean
'--compares corresponding elements of each array.
'  returns True if all are equal, otherwise False.
'--assumes arrays have already been validated as vectors
'  with same lbound and size

 Dim bReturn As Boolean
 Dim lNdx As Long
 
 '--return true unless pair of non-matching elements found
 bReturn = True
 
 For lNdx = LBound(dArr1) To UBound(dArr1)
      If dArr1(lNdx) <> dArr2(lNdx) Then
         bReturn = False
         Exit For
      End If
 Next lNdx
 
 bIsMatch = bReturn
End Function

Private Function bIsNearMatch(ByRef dArr1() As Double, _
   ByRef dArr2() As Double, ByVal dMaxVariance As Double) As Boolean
'--compares corresponding elements of each array.
'  returns True if all are equal, otherwise False.
'--assumes arrays have already been validated as vectors
'  with same lbound and size

 Dim bReturn As Boolean
 Dim lNdx As Long
 
 '--return true unless pair of non-matching elements found
 bReturn = True
 
 For lNdx = LBound(dArr1) To UBound(dArr1)
      If Abs(dArr1(lNdx) - dArr2(lNdx)) > dMaxVariance Then
         bReturn = False
         Exit For
      End If
 Next lNdx
 
 bIsNearMatch = bReturn
End Function
 
Upvote 0

Excel Facts

How to show all formulas in Excel?
Press Ctrl+` to show all formulas. Press it again to toggle back to numbers. The grave accent is often under the tilde on US keyboards.
Martin, It's probably best if you send me a current copy of your file so that I don't misinterpret.

jERRY,

Received the sheet!!!

Most excellent - Thanks so much for the help.

One thing though, the search will not find anything on Sheet 1.

I have tested all sheets and they all work great except for the sheet 1

my sheet layouts are

the first one is titled Stoc kbox lookup ( this is the one the users will use)

then we start with Sheet1, sheet2, etc.......

Nothing on Sheet 1 will come up?

any thought?

thanks
 
Upvote 0
jERRY,

I have tested all sheets and they all work great except for the sheet 1

my sheet layouts are

the first one is titled Stoc kbox lookup ( this is the one the users will use)

then we start with Sheet1, sheet2, etc.......

Nothing on Sheet 1 will come up?

any thought?

In your earlier example file, Sheet1 wasn't a data sheet to be searched.

You can modify this list to include Sheet1 (and for any future changes you make- adding/deleting/renaming sheets).

Code:
'--identify data sheets to be searched
 avDataSheetNames = Array("Sheet2", "Sheet3", "Sheet4", _
   "Sheet5", "Sheet6", "Sheet7")
 
Upvote 0
In your earlier example file, Sheet1 wasn't a data sheet to be searched.

You can modify this list to include Sheet1 (and for any future changes you make- adding/deleting/renaming sheets).

Code:
'--identify data sheets to be searched
 avDataSheetNames = Array("Sheet2", "Sheet3", "Sheet4", _
   "Sheet5", "Sheet6", "Sheet7")


jERRY,

Just want to start off with THE SHEET WORKS GREAT!! tHANKS SO MUCH.

I like it the way it is now.

one of my fellow designers had asked if in the search the result could return any scenario in which the requested three numbers are found.

Right now it is linear.

length being the first entry
and so on.

then the results find anything close to within 1 in the same linear progression - l - w - d

Could the code look for anything with the three numbers input numbers
and return any combination of the numbers?

just a thought

because we can use a box in three different configurations.
 
Upvote 0
Martin, I'm glad to hear that worked for you.

If we sort the input parameters and compare it to sorted records in the data sheets, we can then do a linear comparison and it will provide the same results as if we compared every combination of the numbers.

I've added sorting to the code along with some other minor modifications.
Code:
Sub FindData()
'--purpose is to search multiple data sheets to find records
'    that are exact match to user input parameters and
'    near matches that are within specified variance.

'--assumptions about workbook setup:
'    search data is in contiguous range starting at A1 on each data sheet
'    each data sheet has headers and fields are same order as results header

 Dim bIsValidRecord As Boolean, bWarned As Boolean
 Dim adParams() As Double, adThisRecord() As Double
 Dim lNdx As Long, lRow As Long, lNdxSheets As Long
 Dim lMatchResultCount As Long, lNearResultCount As Long
 Dim lParamCount As Long, lResultFieldsCount As Long
 Dim rParams As Range, rResultHeaders As Range, rSaveActiveCell As Range
 Dim avDataSheetNames() As Variant
 Dim vData As Variant, vMatchResults As Variant, vNearResults As Variant
 Dim wksData As Worksheet

 '--maximum variance to be considered near match
 Const dVARIANCE As Double = 1

 '--limit of matches to be returned
 Const lMAX_RESULTS As Long = 1000
  
 '--column nbr of length field in data sheets
 Const lSTART_PARAM_COL As Long = 2
  
 '--identify data sheets to be searched
 avDataSheetNames = Array("Sheet1", "Sheet2", "Sheet3", "Sheet4", _
   "Sheet5", "Sheet6", "Sheet7")

 '--find and validate worksheet ranges used for search
 On Error Resume Next
 Set rParams = Names("inpStockBoxSizeSearch").RefersToRange
 Set rResultHeaders = Names("ptrStockBoxSizeSearchHeaders").RefersToRange
 On Error GoTo ErrHandler
 
 If rParams Is Nothing Or rResultHeaders Is Nothing Then
   MsgBox "Named ranges for input cells or headers not found."
   GoTo ExitProc
 End If
   
 If rParams.Rows.Count > 1 Or rResultHeaders.Rows.Count > 1 Then
   MsgBox "Named ranges for input cells or headers reference more than one row"
   GoTo ExitProc
 End If
 
 '--number of search parameters (length, width, depth)
 lParamCount = rParams.Columns.Count
 
 '--number of fields to be read for matching records
 lResultFieldsCount = rResultHeaders.Columns.Count
 
 '--clear previous search results
 With rResultHeaders
   .Parent.Rows(.Row + 1 & ":" & Rows.Count).ClearContents
 End With
 Set rSaveActiveCell = ActiveCell
 
'--read and validate search parameters
 If Application.CountA(rParams) = lParamCount And _
   Application.Count(rParams) = lParamCount Then
   '--write params to array that used to compare to records
   ReDim adParams(1 To lParamCount)
   For lNdx = 1 To lParamCount
      adParams(lNdx) = rParams(1, lNdx)
   Next lNdx
   '--sort descending-array is passed byRef
   Call BubbleSortDoubles(adNumbers:=adParams)
 Else
   MsgBox "Enter numeric values for Length, Width and Depth"
   GoTo ExitProc
 End If

 '--size array that will hold records meeting criteria
 ReDim vMatchResults(1 To lMAX_RESULTS, 1 To lResultFieldsCount)
 ReDim vNearResults(1 To lMAX_RESULTS, 1 To lResultFieldsCount)
 
 ReDim adThisRecord(1 To lParamCount)
 
 '--step through each data worksheet and find records that are matches
 '    or near matches to parameters
 
 For lNdxSheets = LBound(avDataSheetNames) To UBound(avDataSheetNames)
   On Error Resume Next
   Set wksData = Nothing
   Set wksData = Worksheets(avDataSheetNames(lNdxSheets))
   On Error GoTo ErrHandler
      
   If wksData Is Nothing Then
      '--warn about missing sheet, but continue search
      MsgBox "Sheet: " & avDataSheetNames(lNdxSheets) _
         & " not found"
   Else
      vData = wksData.Range("A1").CurrentRegion.Value
      If Not IsArray(vData) Then vData = Array(vData)
      If UBound(vData, 2) < lParamCount Then
         MsgBox "Not enough columns of data found in sheet: " _
            & wksData.Name
      Else
         For lRow = 2 To UBound(vData, 1)
           '--read each record's values to be compared vs parameters
           '   adThisRecord is passed byRef
            bIsValidRecord = bReadRecord(adThisRecord, vData, lRow, _
               lSTART_PARAM_COL)
   
            If Not bIsValidRecord Then
               If Not bWarned Then
                  MsgBox "Non-numeric data found in numeric field: " & vbCr _
                     & "Sheet: " & wksData.Name & vbCr _
                     & "Row: " & lRow
                  bWarned = True
               End If
            Else
               '--sort descending-array is passed byRef
               Call BubbleSortDoubles(adNumbers:=adThisRecord)
   
               '--search for matches- add to arrays
               If bIsMatch(dArr1:=adThisRecord, dArr2:=adParams) Then
                  '--write exact match to next record
                  If bAddResult(vResultArr:=vMatchResults, _
                        lNextNdx:=lMatchResultCount + 1, _
                        vData:=vData, lRow:=lRow) Then
                     lMatchResultCount = lMatchResultCount + 1
     
                  Else 'results exceed max
                     GoTo ExitProc
                  End If
   
                  ElseIf bIsNearMatch(dArr1:=adThisRecord, _
                     dArr2:=adParams, dMaxVariance:=dVARIANCE) Then
                     '--write near match to next record
                     If bAddResult(vResultArr:=vNearResults, _
                        lNextNdx:=lNearResultCount + 1, _
                        vData:=vData, lRow:=lRow) Then
                     lNearResultCount = lNearResultCount + 1
                  Else 'results exceed max
                     GoTo ExitProc
                  End If
               End If
            End If
         Next lRow
      End If 'enough fields
   End If 'wksData exists
 Next lNdxSheets

 '--write results
 If lMatchResultCount Then
   rResultHeaders.Offset(1).Resize(lMatchResultCount, lResultFieldsCount) = vMatchResults
 Else
   MsgBox "No exact match meeting search parameters found"
 End If

 If lNearResultCount Then
   rResultHeaders.Offset(1).Offset(IIf(lMatchResultCount, lMatchResultCount, 1)) _
      .Resize(lNearResultCount, lResultFieldsCount) = vNearResults
 End If
 
 rSaveActiveCell.Select
 
ExitProc:
 Application.CutCopyMode = False
 Exit Sub
 
ErrHandler:
 MsgBox Err.Number & "-" & Err.Description, vbExclamation, "Error"
 Resume ExitProc
End Sub

               
Private Function bAddResult(ByRef vResultArr As Variant, ByVal lNextNdx As Long, _
   ByVal vData As Variant, ByVal lRow As Long) As Boolean
 Dim bReturn As Boolean
 Dim lCol As Long
 
 bReturn = True
 If lNextNdx > UBound(vResultArr, 1) Then
   MsgBox "Results found exceed maximum allowed"
   bReturn = False
 Else
   For lCol = 1 To UBound(vResultArr, 2)
      vResultArr(lNextNdx, lCol) = vData(lRow, lCol)
   Next lCol
 End If
 bAddResult = bReturn
End Function

Private Function bIsMatch(ByRef dArr1() As Double, _
   ByRef dArr2() As Double) As Boolean
'--compares corresponding elements of each array.
'  returns True if all are equal, otherwise False.
'--assumes arrays have already been validated as vectors
'  with same lbound and size

 Dim bReturn As Boolean
 Dim lNdx As Long
 
 '--return true unless pair of non-matching elements found
 bReturn = True
 
 For lNdx = LBound(dArr1) To UBound(dArr1)
      If dArr1(lNdx) <> dArr2(lNdx) Then
         bReturn = False
         Exit For
      End If
 Next lNdx
 
 bIsMatch = bReturn
End Function

Private Function bIsNearMatch(ByRef dArr1() As Double, _
   ByRef dArr2() As Double, ByVal dMaxVariance As Double) As Boolean
'--compares corresponding elements of each array.
'  returns True if all are equal, otherwise False.
'--assumes arrays have already been validated as vectors
'  with same lbound and size

 Dim bReturn As Boolean
 Dim lNdx As Long
 
 '--return true unless pair of non-matching elements found
 bReturn = True
 
 For lNdx = LBound(dArr1) To UBound(dArr1)
      If Abs(dArr1(lNdx) - dArr2(lNdx)) > dMaxVariance Then
         bReturn = False
         Exit For
      End If
 Next lNdx
 
 bIsNearMatch = bReturn
End Function

Private Function bReadRecord(ByRef adThisRecord() As Double, _
   ByVal vData As Variant, ByVal lRow As Long, _
   ByVal lStartCol As Long) As Boolean

'--read indexed portion of record from 2D array vData
'  validate that all items in record are numeric
'  returns true if valid, else false

 Dim bReturn As Boolean
 Dim lNdx As Long
 Dim vItem As Variant
 
 bReturn = True
 For lNdx = 1 To UBound(adThisRecord)
   On Error Resume Next
   vItem = vData(lRow, lStartCol + lNdx - 1)
   If IsNumeric(vItem) Then
      adThisRecord(lNdx) = CDbl(vItem)
   Else
      bReturn = False
      Exit For
   End If
 Next lNdx
 bReadRecord = bReturn
End Function
       
Private Sub BubbleSortDoubles(ByRef adNumbers() As Double)
'--bubble sort used for array with few elements
'  items are sorted descending
  Dim dTemp As Double
  Dim i As Long, j As Long, lMax As Long

  lMax = UBound(adNumbers)
  For i = LBound(adNumbers) To lMax - 1
    For j = i + 1 To lMax
      If adNumbers(i) < adNumbers(j) Then
        dTemp = adNumbers(i)
        adNumbers(i) = adNumbers(j)
        adNumbers(j) = dTemp
      End If
    Next j
  Next i
End Sub
 
Upvote 0
Martin, I'm glad to hear that worked for you.

If we sort the input parameters and compare it to sorted records in the data sheets, we can then do a linear comparison and it will provide the same results as if we compared every combination of the numbers.

I've added sorting to the code along with some other minor modifications.
Code:
Sub FindData()
'--purpose is to search multiple data sheets to find records
'    that are exact match to user input parameters and
'    near matches that are within specified variance.

'--assumptions about workbook setup:
'    search data is in contiguous range starting at A1 on each data sheet
'    each data sheet has headers and fields are same order as results header

 Dim bIsValidRecord As Boolean, bWarned As Boolean
 Dim adParams() As Double, adThisRecord() As Double
 Dim lNdx As Long, lRow As Long, lNdxSheets As Long
 Dim lMatchResultCount As Long, lNearResultCount As Long
 Dim lParamCount As Long, lResultFieldsCount As Long
 Dim rParams As Range, rResultHeaders As Range, rSaveActiveCell As Range
 Dim avDataSheetNames() As Variant
 Dim vData As Variant, vMatchResults As Variant, vNearResults As Variant
 Dim wksData As Worksheet

 '--maximum variance to be considered near match
 Const dVARIANCE As Double = 1

 '--limit of matches to be returned
 Const lMAX_RESULTS As Long = 1000
  
 '--column nbr of length field in data sheets
 Const lSTART_PARAM_COL As Long = 2
  
 '--identify data sheets to be searched
 avDataSheetNames = Array("Sheet1", "Sheet2", "Sheet3", "Sheet4", _
   "Sheet5", "Sheet6", "Sheet7")

 '--find and validate worksheet ranges used for search
 On Error Resume Next
 Set rParams = Names("inpStockBoxSizeSearch").RefersToRange
 Set rResultHeaders = Names("ptrStockBoxSizeSearchHeaders").RefersToRange
 On Error GoTo ErrHandler
 
 If rParams Is Nothing Or rResultHeaders Is Nothing Then
   MsgBox "Named ranges for input cells or headers not found."
   GoTo ExitProc
 End If
   
 If rParams.Rows.Count > 1 Or rResultHeaders.Rows.Count > 1 Then
   MsgBox "Named ranges for input cells or headers reference more than one row"
   GoTo ExitProc
 End If
 
 '--number of search parameters (length, width, depth)
 lParamCount = rParams.Columns.Count
 
 '--number of fields to be read for matching records
 lResultFieldsCount = rResultHeaders.Columns.Count
 
 '--clear previous search results
 With rResultHeaders
   .Parent.Rows(.Row + 1 & ":" & Rows.Count).ClearContents
 End With
 Set rSaveActiveCell = ActiveCell
 
'--read and validate search parameters
 If Application.CountA(rParams) = lParamCount And _
   Application.Count(rParams) = lParamCount Then
   '--write params to array that used to compare to records
   ReDim adParams(1 To lParamCount)
   For lNdx = 1 To lParamCount
      adParams(lNdx) = rParams(1, lNdx)
   Next lNdx
   '--sort descending-array is passed byRef
   Call BubbleSortDoubles(adNumbers:=adParams)
 Else
   MsgBox "Enter numeric values for Length, Width and Depth"
   GoTo ExitProc
 End If

 '--size array that will hold records meeting criteria
 ReDim vMatchResults(1 To lMAX_RESULTS, 1 To lResultFieldsCount)
 ReDim vNearResults(1 To lMAX_RESULTS, 1 To lResultFieldsCount)
 
 ReDim adThisRecord(1 To lParamCount)
 
 '--step through each data worksheet and find records that are matches
 '    or near matches to parameters
 
 For lNdxSheets = LBound(avDataSheetNames) To UBound(avDataSheetNames)
   On Error Resume Next
   Set wksData = Nothing
   Set wksData = Worksheets(avDataSheetNames(lNdxSheets))
   On Error GoTo ErrHandler
      
   If wksData Is Nothing Then
      '--warn about missing sheet, but continue search
      MsgBox "Sheet: " & avDataSheetNames(lNdxSheets) _
         & " not found"
   Else
      vData = wksData.Range("A1").CurrentRegion.Value
      If Not IsArray(vData) Then vData = Array(vData)
      If UBound(vData, 2) < lParamCount Then
         MsgBox "Not enough columns of data found in sheet: " _
            & wksData.Name
      Else
         For lRow = 2 To UBound(vData, 1)
           '--read each record's values to be compared vs parameters
           '   adThisRecord is passed byRef
            bIsValidRecord = bReadRecord(adThisRecord, vData, lRow, _
               lSTART_PARAM_COL)
   
            If Not bIsValidRecord Then
               If Not bWarned Then
                  MsgBox "Non-numeric data found in numeric field: " & vbCr _
                     & "Sheet: " & wksData.Name & vbCr _
                     & "Row: " & lRow
                  bWarned = True
               End If
            Else
               '--sort descending-array is passed byRef
               Call BubbleSortDoubles(adNumbers:=adThisRecord)
   
               '--search for matches- add to arrays
               If bIsMatch(dArr1:=adThisRecord, dArr2:=adParams) Then
                  '--write exact match to next record
                  If bAddResult(vResultArr:=vMatchResults, _
                        lNextNdx:=lMatchResultCount + 1, _
                        vData:=vData, lRow:=lRow) Then
                     lMatchResultCount = lMatchResultCount + 1
     
                  Else 'results exceed max
                     GoTo ExitProc
                  End If
   
                  ElseIf bIsNearMatch(dArr1:=adThisRecord, _
                     dArr2:=adParams, dMaxVariance:=dVARIANCE) Then
                     '--write near match to next record
                     If bAddResult(vResultArr:=vNearResults, _
                        lNextNdx:=lNearResultCount + 1, _
                        vData:=vData, lRow:=lRow) Then
                     lNearResultCount = lNearResultCount + 1
                  Else 'results exceed max
                     GoTo ExitProc
                  End If
               End If
            End If
         Next lRow
      End If 'enough fields
   End If 'wksData exists
 Next lNdxSheets

 '--write results
 If lMatchResultCount Then
   rResultHeaders.Offset(1).Resize(lMatchResultCount, lResultFieldsCount) = vMatchResults
 Else
   MsgBox "No exact match meeting search parameters found"
 End If

 If lNearResultCount Then
   rResultHeaders.Offset(1).Offset(IIf(lMatchResultCount, lMatchResultCount, 1)) _
      .Resize(lNearResultCount, lResultFieldsCount) = vNearResults
 End If
 
 rSaveActiveCell.Select
 
ExitProc:
 Application.CutCopyMode = False
 Exit Sub
 
ErrHandler:
 MsgBox Err.Number & "-" & Err.Description, vbExclamation, "Error"
 Resume ExitProc
End Sub

               
Private Function bAddResult(ByRef vResultArr As Variant, ByVal lNextNdx As Long, _
   ByVal vData As Variant, ByVal lRow As Long) As Boolean
 Dim bReturn As Boolean
 Dim lCol As Long
 
 bReturn = True
 If lNextNdx > UBound(vResultArr, 1) Then
   MsgBox "Results found exceed maximum allowed"
   bReturn = False
 Else
   For lCol = 1 To UBound(vResultArr, 2)
      vResultArr(lNextNdx, lCol) = vData(lRow, lCol)
   Next lCol
 End If
 bAddResult = bReturn
End Function

Private Function bIsMatch(ByRef dArr1() As Double, _
   ByRef dArr2() As Double) As Boolean
'--compares corresponding elements of each array.
'  returns True if all are equal, otherwise False.
'--assumes arrays have already been validated as vectors
'  with same lbound and size

 Dim bReturn As Boolean
 Dim lNdx As Long
 
 '--return true unless pair of non-matching elements found
 bReturn = True
 
 For lNdx = LBound(dArr1) To UBound(dArr1)
      If dArr1(lNdx) <> dArr2(lNdx) Then
         bReturn = False
         Exit For
      End If
 Next lNdx
 
 bIsMatch = bReturn
End Function

Private Function bIsNearMatch(ByRef dArr1() As Double, _
   ByRef dArr2() As Double, ByVal dMaxVariance As Double) As Boolean
'--compares corresponding elements of each array.
'  returns True if all are equal, otherwise False.
'--assumes arrays have already been validated as vectors
'  with same lbound and size

 Dim bReturn As Boolean
 Dim lNdx As Long
 
 '--return true unless pair of non-matching elements found
 bReturn = True
 
 For lNdx = LBound(dArr1) To UBound(dArr1)
      If Abs(dArr1(lNdx) - dArr2(lNdx)) > dMaxVariance Then
         bReturn = False
         Exit For
      End If
 Next lNdx
 
 bIsNearMatch = bReturn
End Function

Private Function bReadRecord(ByRef adThisRecord() As Double, _
   ByVal vData As Variant, ByVal lRow As Long, _
   ByVal lStartCol As Long) As Boolean

'--read indexed portion of record from 2D array vData
'  validate that all items in record are numeric
'  returns true if valid, else false

 Dim bReturn As Boolean
 Dim lNdx As Long
 Dim vItem As Variant
 
 bReturn = True
 For lNdx = 1 To UBound(adThisRecord)
   On Error Resume Next
   vItem = vData(lRow, lStartCol + lNdx - 1)
   If IsNumeric(vItem) Then
      adThisRecord(lNdx) = CDbl(vItem)
   Else
      bReturn = False
      Exit For
   End If
 Next lNdx
 bReadRecord = bReturn
End Function
       
Private Sub BubbleSortDoubles(ByRef adNumbers() As Double)
'--bubble sort used for array with few elements
'  items are sorted descending
  Dim dTemp As Double
  Dim i As Long, j As Long, lMax As Long

  lMax = UBound(adNumbers)
  For i = LBound(adNumbers) To lMax - 1
    For j = i + 1 To lMax
      If adNumbers(i) < adNumbers(j) Then
        dTemp = adNumbers(i)
        adNumbers(i) = adNumbers(j)
        adNumbers(j) = dTemp
      End If
    Next j
  Next i
End Sub


Jerry,

worked like a charm, I add to add some protect and unprotect code as I have the workbook and worksheet protected.

by the help of mr.excel I have also added some code so that when a user hits enter the cursor does not go anywhere.

and it changes the uses settings back to normal after the code runs.

Again.

thanks so much!!!
 
Upvote 0
Jerry,

Just wanted to say the form you helped me with works great.

I did not want to put this on the open board since you wrote the code.

I had a question:

Without making too many changes to the code, just trying to see if this is easy to add to the existing code.

Can i add something like this.

If someone puts in a size of a box for example - 10 x 10 x 10 in the lower section of the form they will get an error dialog box with a custom msg.

i.e. " This box is only available in Santa Fe Springs as a Stock Box"

The form will still return all of the results it finds but the dialog box will come up first.

This is very specific to a size entered that will generate a dialog box with an error msg.

I have about 10 specific sizes that warrant a special dialog box.


--------------------------------------------------------------------------------------------

also, in the top section of the form. Users look for boxes by name.

i.e. The user will in put R56 and the size of the box is returned to them.


if the user puts in a stock box # that returns no results the associated cells to the right just come up blank.
This could lead some users to believe that the form is incomplete.

Can i add, if not to diffucult, an error dialog box that states, if a result is not found, something to affect of " This stock box does not exist"

This is based on a general overall search where no results are found.

If no reslult then..........






Martin, I'm glad to hear that worked for you.

If we sort the input parameters and compare it to sorted records in the data sheets, we can then do a linear comparison and it will provide the same results as if we compared every combination of the numbers.

I've added sorting to the code along with some other minor modifications.
Code:
Sub FindData()
'--purpose is to search multiple data sheets to find records
'    that are exact match to user input parameters and
'    near matches that are within specified variance.

'--assumptions about workbook setup:
'    search data is in contiguous range starting at A1 on each data sheet
'    each data sheet has headers and fields are same order as results header

 Dim bIsValidRecord As Boolean, bWarned As Boolean
 Dim adParams() As Double, adThisRecord() As Double
 Dim lNdx As Long, lRow As Long, lNdxSheets As Long
 Dim lMatchResultCount As Long, lNearResultCount As Long
 Dim lParamCount As Long, lResultFieldsCount As Long
 Dim rParams As Range, rResultHeaders As Range, rSaveActiveCell As Range
 Dim avDataSheetNames() As Variant
 Dim vData As Variant, vMatchResults As Variant, vNearResults As Variant
 Dim wksData As Worksheet

 '--maximum variance to be considered near match
 Const dVARIANCE As Double = 1

 '--limit of matches to be returned
 Const lMAX_RESULTS As Long = 1000
  
 '--column nbr of length field in data sheets
 Const lSTART_PARAM_COL As Long = 2
  
 '--identify data sheets to be searched
 avDataSheetNames = Array("Sheet1", "Sheet2", "Sheet3", "Sheet4", _
   "Sheet5", "Sheet6", "Sheet7")

 '--find and validate worksheet ranges used for search
 On Error Resume Next
 Set rParams = Names("inpStockBoxSizeSearch").RefersToRange
 Set rResultHeaders = Names("ptrStockBoxSizeSearchHeaders").RefersToRange
 On Error GoTo ErrHandler
 
 If rParams Is Nothing Or rResultHeaders Is Nothing Then
   MsgBox "Named ranges for input cells or headers not found."
   GoTo ExitProc
 End If
   
 If rParams.Rows.Count > 1 Or rResultHeaders.Rows.Count > 1 Then
   MsgBox "Named ranges for input cells or headers reference more than one row"
   GoTo ExitProc
 End If
 
 '--number of search parameters (length, width, depth)
 lParamCount = rParams.Columns.Count
 
 '--number of fields to be read for matching records
 lResultFieldsCount = rResultHeaders.Columns.Count
 
 '--clear previous search results
 With rResultHeaders
   .Parent.Rows(.Row + 1 & ":" & Rows.Count).ClearContents
 End With
 Set rSaveActiveCell = ActiveCell
 
'--read and validate search parameters
 If Application.CountA(rParams) = lParamCount And _
   Application.Count(rParams) = lParamCount Then
   '--write params to array that used to compare to records
   ReDim adParams(1 To lParamCount)
   For lNdx = 1 To lParamCount
      adParams(lNdx) = rParams(1, lNdx)
   Next lNdx
   '--sort descending-array is passed byRef
   Call BubbleSortDoubles(adNumbers:=adParams)
 Else
   MsgBox "Enter numeric values for Length, Width and Depth"
   GoTo ExitProc
 End If

 '--size array that will hold records meeting criteria
 ReDim vMatchResults(1 To lMAX_RESULTS, 1 To lResultFieldsCount)
 ReDim vNearResults(1 To lMAX_RESULTS, 1 To lResultFieldsCount)
 
 ReDim adThisRecord(1 To lParamCount)
 
 '--step through each data worksheet and find records that are matches
 '    or near matches to parameters
 
 For lNdxSheets = LBound(avDataSheetNames) To UBound(avDataSheetNames)
   On Error Resume Next
   Set wksData = Nothing
   Set wksData = Worksheets(avDataSheetNames(lNdxSheets))
   On Error GoTo ErrHandler
      
   If wksData Is Nothing Then
      '--warn about missing sheet, but continue search
      MsgBox "Sheet: " & avDataSheetNames(lNdxSheets) _
         & " not found"
   Else
      vData = wksData.Range("A1").CurrentRegion.Value
      If Not IsArray(vData) Then vData = Array(vData)
      If UBound(vData, 2) < lParamCount Then
         MsgBox "Not enough columns of data found in sheet: " _
            & wksData.Name
      Else
         For lRow = 2 To UBound(vData, 1)
           '--read each record's values to be compared vs parameters
           '   adThisRecord is passed byRef
            bIsValidRecord = bReadRecord(adThisRecord, vData, lRow, _
               lSTART_PARAM_COL)
   
            If Not bIsValidRecord Then
               If Not bWarned Then
                  MsgBox "Non-numeric data found in numeric field: " & vbCr _
                     & "Sheet: " & wksData.Name & vbCr _
                     & "Row: " & lRow
                  bWarned = True
               End If
            Else
               '--sort descending-array is passed byRef
               Call BubbleSortDoubles(adNumbers:=adThisRecord)
   
               '--search for matches- add to arrays
               If bIsMatch(dArr1:=adThisRecord, dArr2:=adParams) Then
                  '--write exact match to next record
                  If bAddResult(vResultArr:=vMatchResults, _
                        lNextNdx:=lMatchResultCount + 1, _
                        vData:=vData, lRow:=lRow) Then
                     lMatchResultCount = lMatchResultCount + 1
     
                  Else 'results exceed max
                     GoTo ExitProc
                  End If
   
                  ElseIf bIsNearMatch(dArr1:=adThisRecord, _
                     dArr2:=adParams, dMaxVariance:=dVARIANCE) Then
                     '--write near match to next record
                     If bAddResult(vResultArr:=vNearResults, _
                        lNextNdx:=lNearResultCount + 1, _
                        vData:=vData, lRow:=lRow) Then
                     lNearResultCount = lNearResultCount + 1
                  Else 'results exceed max
                     GoTo ExitProc
                  End If
               End If
            End If
         Next lRow
      End If 'enough fields
   End If 'wksData exists
 Next lNdxSheets

 '--write results
 If lMatchResultCount Then
   rResultHeaders.Offset(1).Resize(lMatchResultCount, lResultFieldsCount) = vMatchResults
 Else
   MsgBox "No exact match meeting search parameters found"
 End If

 If lNearResultCount Then
   rResultHeaders.Offset(1).Offset(IIf(lMatchResultCount, lMatchResultCount, 1)) _
      .Resize(lNearResultCount, lResultFieldsCount) = vNearResults
 End If
 
 rSaveActiveCell.Select
 
ExitProc:
 Application.CutCopyMode = False
 Exit Sub
 
ErrHandler:
 MsgBox Err.Number & "-" & Err.Description, vbExclamation, "Error"
 Resume ExitProc
End Sub

               
Private Function bAddResult(ByRef vResultArr As Variant, ByVal lNextNdx As Long, _
   ByVal vData As Variant, ByVal lRow As Long) As Boolean
 Dim bReturn As Boolean
 Dim lCol As Long
 
 bReturn = True
 If lNextNdx > UBound(vResultArr, 1) Then
   MsgBox "Results found exceed maximum allowed"
   bReturn = False
 Else
   For lCol = 1 To UBound(vResultArr, 2)
      vResultArr(lNextNdx, lCol) = vData(lRow, lCol)
   Next lCol
 End If
 bAddResult = bReturn
End Function

Private Function bIsMatch(ByRef dArr1() As Double, _
   ByRef dArr2() As Double) As Boolean
'--compares corresponding elements of each array.
'  returns True if all are equal, otherwise False.
'--assumes arrays have already been validated as vectors
'  with same lbound and size

 Dim bReturn As Boolean
 Dim lNdx As Long
 
 '--return true unless pair of non-matching elements found
 bReturn = True
 
 For lNdx = LBound(dArr1) To UBound(dArr1)
      If dArr1(lNdx) <> dArr2(lNdx) Then
         bReturn = False
         Exit For
      End If
 Next lNdx
 
 bIsMatch = bReturn
End Function

Private Function bIsNearMatch(ByRef dArr1() As Double, _
   ByRef dArr2() As Double, ByVal dMaxVariance As Double) As Boolean
'--compares corresponding elements of each array.
'  returns True if all are equal, otherwise False.
'--assumes arrays have already been validated as vectors
'  with same lbound and size

 Dim bReturn As Boolean
 Dim lNdx As Long
 
 '--return true unless pair of non-matching elements found
 bReturn = True
 
 For lNdx = LBound(dArr1) To UBound(dArr1)
      If Abs(dArr1(lNdx) - dArr2(lNdx)) > dMaxVariance Then
         bReturn = False
         Exit For
      End If
 Next lNdx
 
 bIsNearMatch = bReturn
End Function

Private Function bReadRecord(ByRef adThisRecord() As Double, _
   ByVal vData As Variant, ByVal lRow As Long, _
   ByVal lStartCol As Long) As Boolean

'--read indexed portion of record from 2D array vData
'  validate that all items in record are numeric
'  returns true if valid, else false

 Dim bReturn As Boolean
 Dim lNdx As Long
 Dim vItem As Variant
 
 bReturn = True
 For lNdx = 1 To UBound(adThisRecord)
   On Error Resume Next
   vItem = vData(lRow, lStartCol + lNdx - 1)
   If IsNumeric(vItem) Then
      adThisRecord(lNdx) = CDbl(vItem)
   Else
      bReturn = False
      Exit For
   End If
 Next lNdx
 bReadRecord = bReturn
End Function
       
Private Sub BubbleSortDoubles(ByRef adNumbers() As Double)
'--bubble sort used for array with few elements
'  items are sorted descending
  Dim dTemp As Double
  Dim i As Long, j As Long, lMax As Long

  lMax = UBound(adNumbers)
  For i = LBound(adNumbers) To lMax - 1
    For j = i + 1 To lMax
      If adNumbers(i) < adNumbers(j) Then
        dTemp = adNumbers(i)
        adNumbers(i) = adNumbers(j)
        adNumbers(j) = dTemp
      End If
    Next j
  Next i
End Sub
 
Upvote 0
Martin, Yes, I'll help with those changes. For customized exceptions that you describe, I'd recommend not hard coding the details into the code.

Instead you could make a lookup table in a worksheet in your book that lists the exceptions and the corresponding messages you want displayed.

You noted in your previous messages that you had made some modifications (unprotect/protecting, user cursor stays in cell, and perhaps others since then???).

Please post your current code so that we are in sync.
 
Upvote 0
Martin, I received the file you sent.

Your second request to have a Msgbox when no matches are found for a Stock # search could be handled by pasting this code into the sheet code module of Sheet "Stock Box Lookup"....

Code:
Private Sub Worksheet_Change(ByVal Target As Range)
 If Target.Address = "$A$5" Then
   If Len(Range("B5").Value) = 0 Then
      MsgBox "This stock box does not exist"
   End If
 End If
End Sub

Your first request is potentially more complicated depending on how you want to handle near matches and whether the sizes can match the "exception" in any order of L x W x D.
Please clarify your preference on that.

If you want the messages to essentially cover any of the Matches and Near Matches that are currently being returned by the Stock Box Size Search, then you have the potential for more than one MsgBox to appear before any of the search results are displayed. That can be done, but it might be easier for the user to follow if the search results appeared first, then any Msgbox(es) followed.
 
Upvote 0
Mr.Sullivan,

You have so graciously helped us develop the excel sheet in this feed. We have a +1/-1 variance in the search functions when searching for a particular size.

we would like to increase the variance to a +3/-3 in the search returns.

i.e when someone enters a box size of 10 x 10 x 10 the search will return all instances within a +3 and -3 window.

also, if you have a copy of the form, we would like to remove the variables in column E.

I think I can just delete the sheets or the content in them that column e pulls from.

or is there a better way.

Any help would be much appreciated.

thanks
 
Upvote 0

Forum statistics

Threads
1,223,762
Messages
6,174,357
Members
452,558
Latest member
jswan83

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top