用于筛选具有特定值的数据表的Excel VBA代码

x6492ojm  于 2023-05-23  发布在  其他
关注(0)|答案(2)|浏览(178)

我有一个Excel,有两张纸。
名为“Items”的工作表包含一个名为“ItemsTable”的具有多个条目的列表。有多少人并不重要。我在它旁边添加了一个“过滤器”按钮以供以后用途:
Items sheet
其次,我有一个名为“Data”的工作表,其中有一个名为“DataTable”的表。在这个表中,我从ItemsTable中随机添加了条目(每个单元格都是一个下拉列表,我可以从ItemsTable中选择一个条目)。可以有空白单元格。填充时看起来像这样:
Data sheet
到目前为止的可用数据。我想从这里提取一些数据。首先,我手动过滤“Items”表中我想要使用的一些项目,例如这次只过滤梨和香蕉:
Items sheet filtered
现在我想要它做的是,当我点击“过滤器”按钮时,它应该接受选中的项目(在本例中为2),并检查这些项目是否同时出现在DataTable的行中。它们必须同时出现在一行上,在那一行上以任何顺序出现,只要它们都出现(或者我选择选择的项目数量)。这个想法是,我想创建一个名为“Stats”的新工作表(或者删除它的内容,如果这个工作表已经存在),并在该工作表上创建一个与“DataTable”表相同标题的新表,并将所有符合条件的行添加到新创建的表中。
因此,在这种情况下,将创建“Stats”工作表(或清除内容,如果它存在),并在其中添加一个表,其中包含以下行:
Stats sheet
我在vba中为这个过滤器按钮创建了一个宏:

Sub Filter()

End Sub

我已经尝试了很多方法来使用数组和循环,但每次都遇到一些问题。我甚至在尝试只使用我使用SpecialCells(xlCellTypeVisible)过滤的选定项时遇到了问题。我不会粘贴我尝试过的代码,因为它可能是无用的。有没有人可以帮我写一些必要的代码来执行这个命令?我会永远感激,因为我真的需要这个工作。

xwbd5t1u

xwbd5t1u1#

取过滤数据

Sub RetrieveFilterData()
 
    ' Define constants.
    
    ' Lookup
    Const LKP_SHEET As String = "Items"
    Const LKP_TABLE As Variant = 1 ' or e.g. "Items"
    Const LKP_COLUMN As Variant = "Items"
    ' Source
    Const SRC_SHEET As String = "Data"
    ' Destination
    Const DST_SHEET As String = "Stats"
    Const DST_TABLE As String = "Stats"
    Dim dColumns(): dColumns = VBA.Array(1, 2, 3, 4)
 
    Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
    
    ' Reference the lookup range (the filtered rows in the lookup column).
    
    Dim lws As Worksheet: Set lws = wb.Sheets(LKP_SHEET)
    Dim llo As ListObject: Set llo = lws.ListObjects(LKP_TABLE)
    Dim llc As ListColumn: Set llc = llo.ListColumns(LKP_COLUMN)
    Dim lrg As Range
    On Error Resume Next
        Set lrg = llc.DataBodyRange.SpecialCells(xlCellTypeVisible)
    On Error GoTo 0
    
    ' Reference the source worksheet.
    
    Dim sws As Worksheet: Set sws = wb.Sheets(SRC_SHEET)
    
    ' Delete the destination worksheet.
    
    Dim dws As Worksheet
    On Error Resume Next
        Set dws = wb.Sheets(DST_SHEET)
    On Error GoTo 0
    If Not dws Is Nothing Then
        Application.DisplayAlerts = False
            dws.Delete
        Application.DisplayAlerts = True
    End If
    
    ' Copy the source as the destination worksheet.
    
    sws.Copy After:=wb.Sheets(wb.Sheets.Count)
    Set dws = wb.Sheets(wb.Sheets.Count)
    dws.Name = DST_SHEET
    ' Assuming the 1st source or destination table:
    Dim dlo As ListObject: Set dlo = dws.ListObjects(1)
    dlo.Name = DST_TABLE
    ' Clear filters.
    If dlo.ShowAutoFilter Then
        If dlo.AutoFilter.FilterMode Then
            dlo.AutoFilter.ShowAllData
        End If
    End If
    
    ' If nothing was filtered.
    
    If lrg Is Nothing Then
        dlo.DataBodyRange.Delete
        MsgBox "Nothing to lookup.", vbExclamation
        Exit Sub
    End If
        
    ' Write the filtered strings to a dictionary.
    
    Dim lDict As Object: Set lDict = CreateObject("Scripting.Dictionary")
    lDict.CompareMode = vbTextCompare
        
    Dim lCell As Range, lStr As String
    For Each lCell In lrg.Cells
        lStr = CStr(lCell.Value)
        If Len(lStr) > 0 Then
            If Not lDict.Exists(lStr) Then
                lDict(lStr) = Empty
            End If
        End If
    Next lCell
        
    If lDict.Count = 0 Then
        dlo.DataBodyRange.Delete
        MsgBox "Only blanks found.", vbCritical
        Exit Sub
    End If
        
    ' Write the source or destination data to an array.
        
    Dim drg As Range: Set drg = dlo.DataBodyRange
    Dim dData(): dData = drg.Value
    
    ' Write the matching data to the top of the array.
    
    Dim nUpper As Long: nUpper = UBound(dColumns)
    Dim rCount As Long: rCount = UBound(dData, 1)
    Dim cCount As Long: cCount = UBound(dData, 2)
    
    Dim lKey, r As Long, dr As Long, c As Long, n As Long
    Dim dStr As String, IsNotFound As Boolean
    
    For r = 1 To rCount
        For Each lKey In lDict.Keys
            For n = 0 To nUpper
                c = dColumns(n)
                dStr = dData(r, c)
                If StrComp(dStr, lKey, vbTextCompare) = 0 Then
                    Exit For
                End If
            Next n
            If n > nUpper Then
                IsNotFound = True
                Exit For
            End If
        Next lKey
        If IsNotFound Then
            IsNotFound = False
        Else
            dr = dr + 1
            For c = 1 To cCount
                dData(dr, c) = dData(r, c)
            Next c
        End If
    Next r
                
    If dr = 0 Then
        dlo.DataBodyRange.Delete
        MsgBox "No matches found.", vbExclamation
        Exit Sub
    End If
    
    ' Write the matching data from the top of the array
    ' to the destination table.
    
    drg.Resize(dr, cCount).Value = dData
    
    ' Delete the remaining (table) rows.
    
    If dr < rCount Then
        drg.Resize(rCount - dr).Offset(dr).Delete xlShiftUp
    End If

    ' Inform.
   
    MsgBox "Filtered data retrieved.", vbInformation
    
End Sub
zaq34kh6

zaq34kh62#

一步一步在一个子

Option Explicit

Private Sub doTheCopy()
   Dim datat As ListObject, vis As Range, ar As Range, tar As Range
   Dim statWs As Worksheet, dataWS As Worksheet
   Dim lbr As Long, ubr As Long, lbc As Long, ubc As Long, rr As Long, cc As Long, fcnt As Integer, haveToFind As Integer
   Dim arr() As Variant, ln() As String, filterdStr As String, strRowsToCopy As String
   Const char160 = " "
   
   'FIND THE VISIBLE AREA
   Set vis = Me.ListObjects("ItemsTable").DataBodyRange.SpecialCells(xlCellTypeVisible)
   If vis Is Nothing Then Exit Sub
   
   'GET THE TWO OTHER SHEETS
   On Error Resume Next
   Set dataWS = Worksheets("Data")
   Set statWs = Worksheets("Stats")
   If dataWS Is Nothing Then GoTo Lexit
   Err.Clear

   'IF STATS SHEET DON'T EXIST => CREATE IT
   If statWs Is Nothing Then
      Set statWs = Worksheets.Add(, dataWS)
      If statWs Is Nothing Then MsgBox ("Can't create Stats Sheet"): GoTo Lexit
      statWs.Name = "Stats"
   End If

   'AFTER THIS POINT I DONT NEED RESUME NEXT
   Err.Clear
   On Error GoTo Lexit

   'GET DataTable
   Set datat = Worksheets("Data").ListObjects("DataTable")

   'POSITION IN STAT SHEET TO COPY THE TABLE => "A1"
   Set ar = statWs.Range("A1")
   
   'COPY THE TABLE AND NAME IT - CLEAR TABLE CONTENTS TO BE READY FOR COPY - IF NOT EXIST
   If ar.ListObject Is Nothing Then
      datat.Range.Copy ar
      ar.ListObject.Name = "StatTable"
   End If
   if Not ar.ListObject.DataBodyRange is Nothing Then
      ar.ListObject.DataBodyRange.Delete
   End If

   'MAKE A STRING WITH VALUES TO FIND, COUNT THEM
   filterdStr = char160
   For Each tar In vis
      For cc = 1 To tar.CountLarge
         If tar(cc) <> vbNullString Then
            filterdStr = filterdStr & tar(cc) & char160
            haveToFind = haveToFind + 1
         End If
      Next
   Next
   
   'SCAN LINE BY LINE THE TABLE AND IF TAKE -haveToFind- MATCHES THEN
   'ADD THE LINE NUMBER IN STRING
   arr() = datat.DataBodyRange
   ubr = UBound(arr, 1):   lbr = LBound(arr, 1)
   ubc = UBound(arr, 2):   lbc = LBound(arr, 2)
   For rr = lbr To ubr
      fcnt = 0
      For cc = lbc To ubc
         If arr(rr, cc) <> vbNullString Then
            If InStr(1, filterdStr, char160 & arr(rr, cc) & char160) > 0 Then fcnt = fcnt + 1
            If fcnt = haveToFind Then
               strRowsToCopy = strRowsToCopy & IIf(strRowsToCopy = vbNullString, "", " ") & rr
               GoTo LnextRow
            End If
         End If
      Next
LnextRow:
   Next

   'IF HAVE LINES TO COPY
   If strRowsToCopy <> vbNullString Then
      'SPLIT TO TAKE THE LINE NUMBERS
      ln = Split(strRowsToCopy)
      fcnt = 1
      'COPY THE LINES FROM SOURCE TABLE TO DESTINATION
      ubr = UBound(ln)
      For cc = LBound(ln) To ubr
         rr = Val(ln(cc))
         ar.ListObject.ListRows.Add
         datat.ListRows(rr).Range.Copy ar.ListObject.ListRows(fcnt).Range
         fcnt = fcnt + 1
      Next
   End If
Lexit:
   If Err.Number > 0 Then
      MsgBox ("doTheCopy>" & vbCrLf & Err.Description & vbCrLf & "error number> " & Err.Number)
   End If
   On Error GoTo 0
End Sub

Private Sub BT_FILTER_ITEMS_Click()
   Call doTheCopy
End Sub

第二个版本与精确搜索

Option Explicit

Private Sub doTheCopy(exact As Boolean)
   Dim datat As ListObject, vis As Range, ar As Range, tar As Range
   Dim statWs As Worksheet, dataWS As Worksheet
   Dim lbr As Long, ubr As Long, lbc As Long, ubc As Long, rr As Long, cc As Long, fcnt As Integer, haveToFind As Integer
   Dim arr() As Variant, ln() As String, filterdStr As String, strRowsToCopy As String
   Const char160 = " "
   
   'FIND THE VISIBLE AREA
   Set vis = Me.ListObjects("ItemsTable").DataBodyRange.SpecialCells(xlCellTypeVisible)
   If vis Is Nothing Then Exit Sub
   
   'GET THE TWO OTHER SHEETS
   On Error Resume Next
   Set dataWS = Worksheets("Data")
   Set statWs = Worksheets("Stats")
   If dataWS Is Nothing Then GoTo Lexit
   Err.Clear

   'IF STATS SHEET DON'T EXIST => CREATE IT
   If statWs Is Nothing Then
      Set statWs = Worksheets.Add(, dataWS)
      If statWs Is Nothing Then MsgBox ("Can't create Stats Sheet"): GoTo Lexit
      statWs.Name = "Stats"
   End If

   'AFTER THIS POINT I DONT NEED RESUME NEXT
   Err.Clear
   On Error GoTo Lexit

   'GET DataTable
   Set datat = Worksheets("Data").ListObjects("DataTable")

   'POSITION IN STAT SHEET TO COPY THE TABLE => "A1"
   Set ar = statWs.Range("A1")
   
   'COPY THE TABLE AND NAME IT - CLEAR TABLE CONTENTS TO BE READY FOR COPY - IF NOT EXIST
   If ar.ListObject Is Nothing Then
      datat.Range.Copy ar
      ar.ListObject.Name = "StatTable"
   End If
   ar.ListObject.DataBodyRange.Delete

   'MAKE A STRING WITH VALUES TO FIND, COUNT THEM
   filterdStr = char160
   For Each tar In vis
      For cc = 1 To tar.CountLarge
         If tar(cc) <> vbNullString Then
            filterdStr = filterdStr & tar(cc) & char160
            haveToFind = haveToFind + 1
         End If
      Next
   Next
   
   'SCAN LINE BY LINE THE TABLE AND IF TAKE -haveToFind- MATCHES THEN
   'ADD THE LINE NUMBER IN STRING
   arr() = datat.DataBodyRange
   ubr = UBound(arr, 1):   lbr = LBound(arr, 1)
   ubc = UBound(arr, 2):   lbc = LBound(arr, 2)
   For rr = lbr To ubr
      fcnt = 0
      For cc = lbc To ubc
         If arr(rr, cc) <> vbNullString Then
            If InStr(1, filterdStr, char160 & arr(rr, cc) & char160) > 0 Then
               fcnt = fcnt + 1
            Else
               If exact Then GoTo LnextRow
            End If
            If fcnt = haveToFind Then
               strRowsToCopy = strRowsToCopy & IIf(strRowsToCopy = vbNullString, "", " ") & rr
               GoTo LnextRow
            End If
         End If
      Next
LnextRow:
   Next

   'IF HAVE LINES TO COPY
   If strRowsToCopy <> vbNullString Then
      'SPLIT TO TAKE THE LINE NUMBERS
      ln = Split(strRowsToCopy)
      fcnt = 1
      'COPY THE LINES FROM SOURCE TABLE TO DESTINATION
      ubr = UBound(ln)
      For cc = LBound(ln) To ubr
         rr = Val(ln(cc))
         If ar.ListObject.ListRows.Count < fcnt Then
            ar.ListObject.ListRows.Add
         End If
         datat.ListRows(rr).Range.Copy ar.ListObject.ListRows(fcnt).Range
         fcnt = fcnt + 1
      Next
   End If
Lexit:
   If Err.Number > 0 Then
      MsgBox ("doTheCopy>" & vbCrLf & Err.Description & vbCrLf & "error number> " & Err.Number)
   End If
   On Error GoTo 0
End Sub

Private Sub BT_FILTER_ITEMS_Click()
   Call doTheCopy(Me.ExactSearch.value)
End Sub

相关问题