Reputation: 33
I have some experience in VBA but I am not an expert and need some advice on how to solve my problem. I have a database that I need to apply 2 filters on. I have the following code for the two filters:
Sub Filtering()
'Filter Plant
If IsEmpty(Worksheets("Material Planning").Range("D1")) = False Then
If Worksheets("Material Planning").Range("D1") = "All" Then
Worksheets("Inventory").Range("A:X").AutoFilter 'removes any filters
Else
Worksheets("Inventory").Range("A:X").AutoFilter Field:=1, Criteria1:=Worksheets("Material Planning").Range("D1")
End If
End If
'Filter SLoc
If IsEmpty(Worksheets("Material Planning").Range("D2")) = False Then
If Worksheets("Material Planning").Range("D2") = "All" Then
Worksheets("Inventory").Range("A:X").AutoFilter 'removes any filters
Else
Worksheets("Inventory").Range("A:X").AutoFilter Field:=2, Criteria1:=Worksheets("Material Planning").Range("D2")
End If
End If
End Sub
Once that is completed, I need to extract the Distinct Values and paste then into a different sheet. I know that the second half can be achieved by manipulating the following code:
Sub ExtractDistinct()
Dim lastrow As Long
lastrow = Worksheets("Inventory").Cells(Rows.Count, "H").End(xlUp).Row
Worksheets("Inventory").Range("H2:H" & lastrow).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=Worksheets("Dictionary").Range("D4"), _
Unique:=True
End Sub
Using the code above gives me the distinct value that I am looking for, but it does not do it to the filtered range that I had from the ".AutoFilter" code from before. It gives me the distinct values from the data unfiltered, and removes any filters that I had on the data.
I was able to get the ".autofilter" to filter the raw data in the worksheet I was using but having the data physically filtered in excel is not what I am looking for (Also seems to be taxing on the processing). I want to be able to filter the data from column A and B, then extract all of the distinct values from the Filtered Data From Column C and copy that to a separate sheet(not a new sheet).
Thank you for your help!
Upvotes: 1
Views: 540
Reputation: 6659
This proposed solution:
• Uses Select Case statement instead of nested IF
s
• Uses For Each...Next statement to avoid a double loop over Areas
and Cells
(i.e. For Each Cell in Range.Cells
)
• Does not validate the filtered values as they are the result of the criteria applied (i.e. the objective is to extract the unique values of whatever resulted from the filtering of the data).
• Uses a line like this: Dictionary.Item(Key) = Any value
where Key
= Cell.Value
, to ensure that only one record per Cell.Value
is added to the dictionary.
Sub Data_Filter_N_Extract()
Data_AutoFilter
Data_Extract_Unique
End Sub
Sub Data_AutoFilter()
Dim vCrt_A As Variant, vCrt_B As Variant
Rem Get Criteria
With ThisWorkbook.Worksheets("Material Planning")
vCrt_A = .Range("D1").Value
vCrt_B = .Range("D2").Value
End With
With ThisWorkbook.Worksheets("Inventory").Range("A:X")
Rem Filter Plant
Select Case vCrt_A
Case vbNullString 'NO ACTION!. Any filter already applied to column [A] will stay.
Case "All": .AutoFilter 'Removes all filters from the entire range [A:X]
Case Else: .AutoFilter Field:=1, Criteria1:=vCrt_A 'Apply filter as per [D1] value
End Select
Rem Filter SLoc
Select Case vCrt_B
Case vbNullString: Rem NO ACTION!. Any filter already applied to the column [B] will stay.
Case "All": .AutoFilter 'Removes all filters from the entire range [A:X]
Case Else: .AutoFilter Field:=2, Criteria1:=vCrt_B 'Apply filter as per [D2] value
End Select
End With
End Sub
Sub Data_Extract_Unique()
Dim Rng As Range
Dim Dtn As Object, Cll As Range
Dim lRow As Long, sMsg As String
Rem Set output cell & clear prior data
Set Rng = ThisWorkbook.Sheets("Dictionary").Range("D4")
With Rng
.Resize(-3 + .Worksheet.Rows.Count).ClearContents
.Value = "In progress…" 'Indicate that a process has started
End With
Rem Extract & post unique values
With ThisWorkbook.Sheets("Inventory").Columns("C:C")
Rem Get last row of columns [C] in Data
lRow = .Cells(Rows.Count).End(xlUp).Row
Rem Validate Last Row
Select Case lRow
Case 1 'Last row = 1 - Filter returned 0 records
sMsg = "Filtered data shows 0 records to extract!"
Case 2 'Last row = 2 - Filter returned 1 record
sMsg = "1 Unique value extracted from filtered data"
Rng.Value = .Cells(2).Value:
Case Else 'Last row = any other row - Filter returned several recorda
Rem Use a dictionary to filter out duplicated values
Set Dtn = CreateObject("Scripting.Dictionary")
With Range(.Cells(2), .Cells(lRow)).SpecialCells(xlCellTypeVisible)
For Each Cll In .Cells
Dtn.Item(Cll.Value) = Cll.Value
Next: End With
Rem Post Dictionary to the Output Range (Keys or Items - pick one)
With Dtn
sMsg = .Count & " Unique values extracted from filtered data"
Rem Any of these two lines would work as the Keys and Items are the same (pick one)
Rng.Resize(.Count).Value = Application.Transpose(.Keys)
'Rng.Resize(.Count).Value = Application.Transpose(.Items)
End With
End Select: End With
MsgBox sMsg, vbInformation, "Data Extract Unique"
End Sub
Upvotes: 1
Reputation: 54807
Option Explicit
Sub filterUnique()
' Declare a boolean which will indicate if successful.
Dim dataCopied As Boolean
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Define Criteria Values.
Dim Crit1 As Variant
Crit1 = wb.Worksheets("Material Planning").Range("D1").Value
Dim Crit2 As Variant
Crit2 = wb.Worksheets("Material Planning").Range("D2").Value
Application.ScreenUpdating = False
' Define Source Worksheet
Dim ws As Worksheet: Set ws = wb.Worksheets("Inventory")
' Remove AutoFilter.
ws.AutoFilterMode = False
' Define Souce Range (you may need to do it another way).
Dim srg As Range: Set srg = ws.Range("A1").CurrentRegion
' Apply filters to Source Range.
If Not IsEmpty(Crit1) Then
If Crit1 = "All" Then
srg.AutoFilter
Else
srg.AutoFilter Field:=1, Criteria1:=Crit1
End If
End If
If Not IsEmpty(Crit2) Then
If Crit2 = "All" Then
srg.AutoFilter
Else
srg.AutoFilter Field:=2, Criteria1:=Crit2
End If
End If
' Attempt to define Copy Range.
On Error Resume Next
Dim crg As Range
Set crg = srg.Columns(3).Resize(srg.Rows.Count - 1).Offset(1) _
.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
' If Copy Range was defined (i.e. a reference to it was created)...
If Not crg Is Nothing Then
' Write unique (distinct) values to Unique Dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim arg As Range
Dim cel As Range
Dim Key As Variant
For Each arg In crg.Areas
For Each cel In arg.Cells
Key = cel.Value
If Not IsError(Key) Then
If Len(Key) > 0 Then
dict(Key) = Empty
End If
End If
Next cel
Next arg
Dim rCount As Long: rCount = dict.Count
If rCount > 0 Then
' Write unique values from Unique Dictionary to Data Array.
Dim Data As Variant
Dim i As Long
ReDim Data(1 To rCount, 1 To 1)
For Each Key In dict.Keys
i = i + 1
Data(i, 1) = Key
Next Key
' Write values from Data Array to Dictionary Worksheet.
With wb.Worksheets("Dictionary").Range("D4")
.Resize(.Worksheet.Rows.Count - .Row + 1).ClearContents
.Resize(i).Value = Data
dataCopied = True
End With
End If
End If
Application.ScreenUpdating = True
If dataCopied Then
MsgBox "Unique values transferred.", vbInformation, "Success"
Else
MsgBox "Nothing transferred.", vbExclamation, "Fail?"
End If
End Sub
Upvotes: 0