Search code examples
arraysvbaexcelmultidimensional-arrayexcel-2007

How can I search for multiple values using multidimensional Array?


This code is now working to search multiple values in multiple sheets. How can I fix it to support searching multiple values at the same time without having to write every one . For example, I want to put in column A all my search values, and then I click on search, and it should search and give the value for all of them at the same time. What should I change in the code to do this function? Please see the code and the images.

 Dim i, j, k, l, m, n, no_sheets As Variant
 Dim key, cursor, sheetname As Variant
 Dim flag As Variant
 Dim sheet1_count, sheet1_row, row_count As Integer
 Dim Arr() As Variant

     sheet1_count = WorksheetFunction.CountA(ThisWorkbook.Worksheets("sheet1").Range("A:A"))

     no_sheets = 3 ' Number of sheets
     k = 2
     sheet1_row = sheet1_count 'My start in result sheet

     key = ThisWorkbook.Worksheets("sheet1").Range("A" & sheet1_count) ' The value that the user will put in searching sheet in column A

     For i = 2 To no_sheets ' sheet2 then sheet3 then sheet4 then sheet5 ..etc
         flag = False
         sheetname = "Sheet" & i
         row_count = WorksheetFunction.CountA(ThisWorkbook.Worksheets(sheetname).Range("A:A")) ' It's a counter that will contain the range of row A in each sheet
     For j = 1 To row_count 'I'll start from row 1 until the last sheet
         cursor = ThisWorkbook.Worksheets(sheetname).Range("A" & j) 'Searching in column A in each sheet (1st row - last row) and put the value in this variable
             If key = cursor Then ' If the entering value in sheet1 equal the value that we have in current sheet, do the following
             ' Copying the data

             flag = True ' The data found

                  ThisWorkbook.Worksheets("sheet1").Range("A" & sheet1_row) = ThisWorkbook.Worksheets(sheetname).Range("A" & j)
                  ThisWorkbook.Worksheets("sheet1").Range("B" & sheet1_row) = ThisWorkbook.Worksheets(sheetname).Range("B" & j)
                  ThisWorkbook.Worksheets("sheet1").Range("C" & sheet1_row) = ThisWorkbook.Worksheets(sheetname).Range("C" & j)
                  ThisWorkbook.Worksheets("sheet1").Range("D" & sheet1_row) = ThisWorkbook.Worksheets(sheetname).Range("D" & j)
                  ThisWorkbook.Worksheets("sheet1").Range("E" & sheet1_row) = ThisWorkbook.Worksheets(sheetname).Range("E" & j)
                  ThisWorkbook.Worksheets("sheet1").Range("F" & sheet1_row) = ThisWorkbook.Worksheets(sheetname).Range("F" & j)

                 sheet1_row = sheet1_row + 1
                Else

         End If
     Next j 'Go to the next row
Next i 'Go to the next sheet
    MsgBox "finished, Do another search..!"


            If key <> cursor Then
              flag = False  ' If the value not found

                  ThisWorkbook.Worksheets("sheet1").Range("B" & sheet1_row) = "Not found"
                  ThisWorkbook.Worksheets("sheet1").Range("C" & sheet1_row) = "Not found"
                  ThisWorkbook.Worksheets("sheet1").Range("D" & sheet1_row) = "Not found"
                  ThisWorkbook.Worksheets("sheet1").Range("E" & sheet1_row) = "Not found"



            End If



       End Sub

   Sub MatchUnMatch_Click()
Dim i, j, k, l, m, n As Integer
Dim ListA_count, ListB_count, ListC_count, ListD_count, ListE_count As Integer
Dim key, cursor As String
Dim flag As Boolean

ListA_count = WorksheetFunction.CountA(ThisWorkbook.Worksheets("MatchUnmatch").Range("A:A"))
ListB_count = WorksheetFunction.CountA(ThisWorkbook.Worksheets("MatchUnmatch").Range("B:B"))
'ListA_count = ThisWorkbook.Worksheets("MatchUnMatch").Range("A2").End(xlDown).Row
'MsgBox ListA_count & " " & ListB_count
'=======================================================================================================
'
'
' Matching Logic for List 'A' and List 'B'
'
'
'=======================================================================================================
k = 2
For i = 2 To ListA_count
    key = ThisWorkbook.Worksheets("MatchUnMatch").Range("A" & i)

    For j = 1 To ListB_count
        cursor = ThisWorkbook.Worksheets("MatchUnMatch").Range("B" & j)
        'MsgBox "Key=" & Key & " Cursor=" & cursor
        If key = cursor Then
            ThisWorkbook.Worksheets("MatchUnMatch").Range("C" & k) = key
            k = k + 1
            Exit For
        End If
    Next j
Next i

'=======================================================================================================
'
'
' List 'A' items not in List 'B'
'
'
'=======================================================================================================
ListC_count = WorksheetFunction.CountA(ThisWorkbook.Worksheets("MatchUnmatch").Range("C:C"))

k = 2
For i = 2 To ListA_count
    key = ThisWorkbook.Worksheets("MatchUnMatch").Range("A" & i)
    flag = False
    For j = 1 To ListC_count
        cursor = ThisWorkbook.Worksheets("MatchUnMatch").Range("C" & j)
        If key = cursor Then
            flag = True
            Exit For
        End If
    Next j
    If flag = False Then
        ThisWorkbook.Worksheets("MatchUnMatch").Range("D" & k) = key
        k = k + 1
    End If
Next i

'=======================================================================================================
'
'
' List 'B' items not in List 'A'
'
'
'=======================================================================================================
k = 2

For i = 2 To ListB_count
    key = ThisWorkbook.Worksheets("MatchUnMatch").Range("B" & i)
    flag = False
    For j = 1 To ListC_count
        cursor = ThisWorkbook.Worksheets("MatchUnMatch").Range("C" & j)
        If key = cursor Then
            flag = True
            Exit For
        End If
    Next j
    If flag = False Then
        ThisWorkbook.Worksheets("MatchUnMatch").Range("E" & k) = key
        k = k + 1
    End If
Next i
End sub

see the image please, to understand what I meanI want to put in row A in search sheet (first sheet) many numbers and then I want to click on search button one time only that should give me all the values at the same time.I don't want to click one search more than one time. I want someone to fix it for me please. As soon as possible :(


Solution

  • (*) updated after OP's requested functionality to save data from previous runs and have numbers not found in "data" sheets marked as "NOT FOUND"

    (**) updated after OP's request to handle a variable number of columns

    (***) updated to fix FindItems() function to handle non contiguous cells range

    (****) updated to fix iRow updating in sub Main()

    (*****) updated to have items to be searched in sheets whose cell "A1" has same content as that of "base" sheets

    (******) updated to have items to be searched in column A of all data sheets, whatever the header of that column

    While I was doing my code, Cornel's already given you an answer which is ok

    however should you ever want to manage:

    • any different number of "data" Sheets (i.e.: sheets to seek for item number in its column "A" and gather relevant data from adjacent columns)

    • multiple occurrences of a "number" in any "data" sheet

    • (*) functionality to save previous data already in "base" sheet resulting from previous runs

    • (*) functionality to mark "NOT FOUND" in "base" sheet when number not found on any "data" sheet

    • (**) functionality to handle a variable number of columns

    then you may want to use the following code

    Option Explicit
    
    Sub main()
    
    Dim items() As Variant, itemToFind As Variant
    Dim itemsNumber As Long, previousDataNumber As Long, dataShtNumber As Long, iRow As Long, i As Long, j As Integer
    Dim itemsSht As Worksheet, dataShts() As Worksheet
    Dim rngToCopy As Range
    Dim itemFound As Boolean
    Dim columnsNumberToCopyAndPaste As Long
    
    columnsNumberToCopyAndPaste = 7 '<== here you set the number of columns to be copied form "data" sheet and pasted in "base" sheet
    
    Set itemsSht = ThisWorkbook.Worksheets("Sheet1") ' this is the "base" sheet you take "numbers" from its column A, starting at row 2
    
    Call GetItems(itemsSht, items(), itemsNumber, previousDataNumber) ' gather all "numbers" to be searched for in "data" sheets
    
    Call GetDataWorksheets(dataShts(), ThisWorkbook, "Sheet1", dataShtNumber) ' gather all "data" sheets
    
    iRow = 1
    For i = 1 To itemsNumber 'loop through "numbers"
    
        itemToFind = items(i) ' "number" to be searched for in "data" sheets
        itemFound = False
        For j = 1 To dataShtNumber 'loop through "data" worksheets
    
            Set rngToCopy = FindItems(dataShts(j), itemToFind, 1, columnsNumberToCopyAndPaste) ' get "data" sheet column 1 cells with "number" along with 'columnsNumberToCopyAndPaste-1' adjacents cells
    
            If Not rngToCopy Is Nothing Then ' if found any occurrence of the "number" ...
                rngToCopy.Copy itemsSht.Cells(1, 1).Offset(previousDataNumber + iRow) ' ... copy it and paste into "base" sheet
                iRow = iRow + rngToCopy.Count / columnsNumberToCopyAndPaste 'update "base" sheet row offset to paste subsequent cells, if any
                itemFound = True
            End If
    
        Next j
        If Not itemFound Then 'if NOT found any occurrence of the "number" ...
            itemsSht.Cells(1, 1).Offset(previousDataNumber + iRow).Value = itemToFind
            itemsSht.Cells(1, 2).Offset(previousDataNumber + iRow).Resize(1, columnsNumberToCopyAndPaste - 1).Value = "NOT FOUND"
            iRow = iRow + 1
        End If
    
    Next i
    
    itemsSht.Columns.AutoFit
    
    End Sub
    
    
    Sub GetItems(itemsSht As Worksheet, items() As Variant, itemsNumber As Long, previousDataNumber As Long)
    
    With itemsSht
        previousDataNumber = .Cells(.Rows.Count, 2).End(xlUp).Row - 1
        itemsNumber = .Cells(.Rows.Count, 1).End(xlUp).Row - 1 - previousDataNumber
        ReDim items(1 To itemsNumber) As Variant
        With .Cells(2 + previousDataNumber, 1).Resize(itemsNumber)
            If itemsNumber = 1 Then
                items(1) = .Value
            Else
                items = WorksheetFunction.Transpose(.Value)
            End If
        End With
    End With
    
    End Sub
    
    
    Function FindItems(sht As Worksheet, itemToFind As Variant, columnToSearchFor As Long, columnsToCopy As Long) As Range
    Dim cell As Range, unionRng As Range
    Dim firstAddress As String
    
    With sht.Columns(columnToSearchFor)
        Set cell = .Find(What:=itemToFind, LookAt:=xlWhole)
        If Not cell Is Nothing Then
            firstAddress = cell.Address
            Set unionRng = cell.Resize(, columnsToCopy)
            Do
                Set unionRng = Union(unionRng, cell.Resize(, columnsToCopy))
    
                Set cell = .FindNext(cell)
            Loop While Not cell Is Nothing And cell.Address <> firstAddress
            Set FindItems = unionRng
        End If
    End With
    
    End Function
    
    
    Sub GetDataWorksheets(shts() As Worksheet, wb As Workbook, noShtName As String, nShts As Long)
    Dim sht As Worksheet
    
    For Each sht In wb.Worksheets
        With sht
            If .Name <> noShtName Then
                nShts = nShts + 1
                ReDim Preserve shts(1 To nShts) As Worksheet
                Set shts(nShts) = sht
            End If
        End With
    Next sht
    
    End Sub
    

    (*) Actually I added a previousDataNumber variable to track data already there at the time the routine runs

    (**) in columnsNumberToCopyAndPaste = 5 you set the number of columns to be handled

    I split it into a "main" sub and some other "helper" subs or function in order to have clear and more maintainable/changeable code.

    this habit has always helped me much more than I could ever expect at my beginnings, when I was used to code looong subs