0

I am trying to find out the most efficient/optimal method to filter data I have. The data is seen here:

enter image description here

My goal here is to use drop down boxes to determine what data will be populated on a certain sheet. Basically I have narrowed it down to two ways. Both ways have to use VBA. First will use excel's autofilter function. It would filter certain columns based on criteria chosen by the user via dropdown boxes then the code will copy the filtered table over to another sheet. The second way is using VLOOKUP with a for loop to look through and get the data.

Which is more optimal and why? As well, if there is a way that I have not describe that would be better please let me know.

Community
  • 1
  • 1
  • 1
    `"what data will be populated on a certain sheet"` ... can you elaborate a bit on this? – Tim Biegeleisen Jan 26 '16 at 04:43
  • I think you can do it by excel function, which will automatically filter your data when new data inserted in main data sheet. – Harun24hr Jan 26 '16 at 05:02
  • You speak of 'optimal' but give no idea of 'scope'. How many rows? How many filter columns? How many filter criteria per column? See [this](http://stackoverflow.com/questions/34532282/can-advanced-filter-criteria-be-in-the-vba-rather-than-a-range). –  Jan 26 '16 at 05:16
  • @TimBiegeleisen, basically once the filter is applied the "new table" that will appear is the data that will be copied/populated on another sheet. – Louidgie Theoret Jan 26 '16 at 05:22
  • @Jeeped, Rows can reach up to 200. columns to filter by would be 2, and only 1 criteria per column. – Louidgie Theoret Jan 26 '16 at 05:23
  • Sounds like you need an [AutoFilter method](https://msdn.microsoft.com/en-us/library/office/aa221844.aspx) or (if yu can get your users to use it properly) an [Advanced Filter method](https://msdn.microsoft.com/en-us/library/office/aa221800(v=office.11).aspx). –  Jan 26 '16 at 05:27
  • You could setup a pivottable on that second sheet which has all table columns as row fields and shows no (sub)totals at all, that would duplicate your table entirely. Then you can add slicers to easily filter the pivottable. – jkpieterse Jan 26 '16 at 08:39

2 Answers2

0

FIND, find is your answer,

https://goo.gl/ThS44h

FIND is the most efficient matching way of excel, I don't really know why, but you should try the 5 possible ways and checking which is faster.

Im my opinion, slower to fastest would be:

  • 5 Loop through all rows and check if contains something
  • 4 Loop through all rows and select case before If
  • 3 Loop through all VLOOKUP results -> WorksheetFunction
  • 2 Loop through all IndexMatch results -> WorksheetFunction
  • 1 Loop through all found results with Find -> Range

I believe that somehow WorksheetFunction isn't executed in the same thread as VBA, but in a common thread for the Worksheet and Range somehow have aways faster methods.

KodornaRocks
  • 425
  • 3
  • 14
0

I wonder if you'd be better off looking at this task in a different way. At the moment you appear to be talking in terms of a database of employee details that you'd like to run a query on and then output the results to Excel. Obviously this would better suit a genuine database application but you could mimic that task in Excel using a worksheet as the data storage, a UserForm as the query interface and VBA to manage the data processing. Your question seems to want simply to automate some Excel keystrokes in an efficient manner, whereas it might be better to ask how VBA could create this mini database application.

In particular, for genuine efficiency, you really need to steer clear of multiple interrogations of Worksheet values, such as with VLOOKUP, FIND, MATCH etc. as reading values is time-consuming. You're usually better off reading the Worksheet database values just once into an array and then interrogating that array. Fortunately, these tasks are really quite straight forward in VBA.

So ... first of all, I'd create a UserForm with ListBoxes for each filter that you want to use. In the example below, I've just done three but you could do all of the columns if you wished. Make sure you set the MultiSelect property to 1-fmMultiSelectMulti:

enter image description here

In the code behind the UserForm, add the following (you can change the name of the listboxes if you wish, but I've used lboxCountry, lboxState and lboxDept):

Public Property Get Lbox(index As Long) As MSForms.ListBox
    Select Case index
        Case COUNTRY_ID: Set Lbox = lboxCountry
        Case STATE_ID: Set Lbox = lboxState
        Case DEPT_ID: Set Lbox = lboxDept
    End Select
End Property

Private Sub btnProcess_Click()
    Call OutputFilteredResults
End Sub

Now just add the data reading and data filtering procedures into a Module, like so:

Option Explicit
Public Const COUNTRY_ID As Integer = 1
Public Const STATE_ID As Integer = 2
Public Const DEPT_ID As Integer = 3
Public Const EMP_NUM_ID As Integer = 4
Public Const EMP_NAME_ID As Integer = 5
Public Const SALARY_ID As Integer = 6

Private mDb As Variant
Public Sub ReadDatabase()
    Dim lastRow As Long
    Dim r As Long
    Dim c As Long
    Dim uniques(1 To 6) As Collection
    Dim v As Variant

    'Read database from worksheet
    With ThisWorkbook.Worksheets("Sheet1") 'ammend as req'd
        lastRow = .Cells.Find(What:="*", _
                              After:=.Cells(1), _
                              LookIn:=xlFormulas, _
                              LookAt:=xlPart, _
                              SearchOrder:=xlByRows, _
                              SearchDirection:=xlPrevious, _
                              MatchCase:=False, _
                              SearchFormat:=False).Row

        mDb = .Range(.Cells(2, COUNTRY_ID), .Cells(lastRow, SALARY_ID)).Value2
    End With

    'Find unique values for filtering
    For r = 1 To UBound(mDb, 1)
        For c = 1 To 6
            If r = 1 Then Set uniques(c) = New Collection
            v = Empty: On Error Resume Next
            v = uniques(c).item(CStr(mDb(r, c))): On Error GoTo 0
            If IsEmpty(v) Then
                uniques(c).Add CStr(mDb(r, c)), CStr(mDb(r, c))
            End If
        Next
    Next

    'Populate the filter boxes
    For c = 1 To 3
        For Each v In uniques(c)
            UserForm1.Lbox(c).AddItem v
        Next
    Next
    UserForm1.Show False

End Sub
Public Sub OutputFilteredResults()
    Dim r As Long
    Dim c As Long
    Dim v As Variant
    Dim hits As Collection
    Dim output() As Variant

    'Acquire the target rows for output
    Set hits = New Collection
    For r = 1 To UBound(mDb, 1)
        If IsAMatch(r) Then hits.Add r
    Next

    'Transfer data to output array
    ReDim output(1 To hits.Count, 1 To 6)
    r = 1
    For Each v In hits
        For c = 1 To 6
            output(r, c) = mDb(v, c)
        Next
        r = r + 1
    Next

    'Write the output
    With ThisWorkbook.Worksheets("Sheet2")  'ammend as req'd
        .Cells.Clear
        .Cells(1, 1).Resize(, 6).Value = _
            ThisWorkbook.Worksheets("Sheet1").Cells(1, 1).Resize(, 6).Value2
        .Cells(2, 1).Resize(UBound(output, 1), UBound(output, 2)).Value = output
        .Activate
    End With

End Sub

Private Function IsAMatch(r As Long) As Boolean
    Dim i As Long
    Dim c As Long
    Dim found As Boolean

    'Loop through the Listboxes and test for a match in each
    For c = 1 To 3

        found = False

        With UserForm1.Lbox(c)
            For i = 0 To .ListCount - 1
                found = (.Selected(i) And .List(i) = CStr(mDb(r, c)))
                If found Then Exit For
            Next
        End With

        If Not found Then
            IsAMatch = False
            Exit Function
        End If

    Next

    IsAMatch = True

End Function

You could go further, such as by utilising LINQ syntax to simplify the query code, but this example should give you an idea of how you could think about VBA as more of an application solution.

Ambie
  • 4,872
  • 2
  • 12
  • 26