使用Excel VBA筛选多个条件

23

我在A列有8个变量,分别是1、2、3、4、5和A、B、C。
我的目标是过滤掉A、B、C,并仅显示1-5。

我可以使用以下代码实现:

My_Range.AutoFilter Field:=1, Criteria1:=Array("1", "2", "3","4","5"), _
    Operator:=xlFilterValues

但是代码实际上是过滤掉变量1到5并显示它们。

我希望做相反的操作,但要达到相同的结果,即过滤掉A、B、C并显示变量1到5。

我尝试了这段代码:

My_Range.AutoFilter Field:=1, Criteria1:=Array("<>A", "<>B", "<>C"), _
    Operator:=xlFilterValues

但它没有起作用。

为什么我不能使用这个代码?

它会出现以下错误:

运行时错误1004:范围类的自动筛选方法失败

我该怎么做才能完成这个任务?


可能是Excel VBA自动筛选除三个之外的所有内容的重复问题。 - user4039065
8个回答

33

我认为(通过尝试 - MSDN对此没有帮助)没有直接的方法来做到这一点。将Criteria1设置为一个Array,等同于使用下拉菜单中的复选框 - 就像您所说,它只会根据与数组中某个项目匹配的项过滤列表。

有趣的是,如果您在列表中有文字值"<>A""<>B",并对其进行筛选,宏录制器会出现以下代码:

Range.AutoFilter Field:=1, Criteria1:="=<>A", Operator:=xlOr, Criteria2:="=<>B"

这个功能可以运行。但是,如果您同时具有字面值"<>C"并对三个值进行过滤(使用复选框)录制宏时,宏记录器会精确地复制您的代码,然后出现错误。我想我应该把它称为错误-您可以使用UI进行某些筛选,但在VBA中无法进行。

无论如何,回到您的问题。可以过滤不等于某些条件的值,但只限于两个值,这对您不起作用:

Range("$A$1:$A$9").AutoFilter Field:=1, Criteria1:="<>A", Criteria2:="<>B", Operator:=xlAnd

根据具体问题,有几种可行的解决方法:

  1. 在B列中使用带有公式的“辅助列”,例如 =ISNUMBER(A2)=NOT(A2="A", A2="B", A2="C"),然后筛选出 TRUE
  2. 如果无法添加列,则可以使用自动筛选功能,并使用 Criteria1:=">-65535" (或任何你期望值更低的数) 进行过滤以排除非数值 - 假设这正是你想要的
  3. 编写一个 VBA 子程序来隐藏行(与自动筛选不完全相同,但可能已足够满足您的需求)。

例如:

Public Sub hideABCRows(rangeToFilter As Range)
  Dim oCurrentCell As Range
  On Error GoTo errHandler

  Application.ScreenUpdating = False
  For Each oCurrentCell In rangeToFilter.Cells
    If oCurrentCell.Value = "A" Or oCurrentCell.Value = "B" Or oCurrentCell.Value = "C" Then
      oCurrentCell.EntireRow.Hidden = True
    End If
  Next oCurrentCell

  Application.ScreenUpdating = True
  Exit Sub

errHandler:
    Application.ScreenUpdating = True
End Sub

1
非常感谢。这正是我在寻找的,一个信息丰富、有教育意义的答案。继续保持好工作。 - user4577989
嘿@aucuparia,我无法复制你提到的错误。记录的宏(只要有多于2个值被过滤和2个值未被过滤)总是只记录未被过滤的值:Criteria1:= Array("<>D", "<>E", "<>F", "<>G")。你还记得你是怎么出错的吗?我非常想看看。 - Cameron Critchlow

1
一个使用自动筛选的选项。
Option Explicit

Public Sub FilterOutMultiple()
    Dim ws As Worksheet, filterOut As Variant, toHide As Range

    Set ws = ActiveSheet
    If Application.WorksheetFunction.CountA(ws.Cells) = 0 Then Exit Sub 'Empty sheet

    filterOut = Split("A B C D E F G")

    Application.ScreenUpdating = False
    With ws.UsedRange.Columns("A")
        If ws.FilterMode Then .AutoFilter
       .AutoFilter Field:=1, Criteria1:=filterOut, Operator:=xlFilterValues
        With .SpecialCells(xlCellTypeVisible)
            If .CountLarge > 1 Then Set toHide = .Cells 'Remember unwanted (A, B, and C)
        End With
       .AutoFilter
        If Not toHide Is Nothing Then
            toHide.Rows.Hidden = True                   'Hide unwanted (A, B, and C)
           .Cells(1).Rows.Hidden = False                'Unhide header
        End If
    End With
    Application.ScreenUpdating = True
End Sub

1

我在互联网上没有找到任何解决方案,因此我自己实现了一个。

使用条件的自动筛选代码如下:

iColNumber = 1
Dim aFilterValueArray() As Variant
Call ConstructFilterValueArray(aFilterValueArray, iColNumber, Array("A", "B", "C"))

ActiveSheet.range(sRange).AutoFilter Field:=iColNumber _
    , Criteria1:=aFilterValueArray _
    , Operator:=xlFilterValues

事实上,ConstructFilterValueArray() 方法(不是函数)获取特定列中找到的所有不同值,并删除最后一个参数中存在的所有值。
该方法的VBA代码为:
'************************************************************
'* ConstructFilterValueArray()
'************************************************************

Sub ConstructFilterValueArray(a() As Variant, iCol As Integer, aRemoveArray As Variant)

    Dim aValue As New Collection
    Call GetDistinctColumnValue(aValue, iCol)
    Call RemoveValueList(aValue, aRemoveArray)
    Call CollectionToArray(a, aValue)

End Sub

'************************************************************
'* GetDistinctColumnValue()
'************************************************************

Sub GetDistinctColumnValue(ByRef aValue As Collection, iCol As Integer)

    Dim sValue As String

    iEmptyValueCount = 0
    iLastRow = ActiveSheet.UsedRange.Rows.Count

    Dim oSheet: Set oSheet = Sheets("X")

    Sheets("Data")
        .range(Cells(1, iCol), Cells(iLastRow, iCol)) _
            .AdvancedFilter Action:=xlFilterCopy _
                          , CopyToRange:=oSheet.range("A1") _
                          , Unique:=True

    iRow = 2
    Do While True
        sValue = Trim(oSheet.Cells(iRow, 1))
        If sValue = "" Then
            If iEmptyValueCount > 0 Then
                Exit Do
            End If
            iEmptyValueCount = iEmptyValueCount + 1
        End If

        aValue.Add sValue
        iRow = iRow + 1
    Loop

End Sub

'************************************************************
'* RemoveValueList()
'************************************************************

Sub RemoveValueList(ByRef aValue As Collection, aRemoveArray As Variant)

    For i = LBound(aRemoveArray) To UBound(aRemoveArray)
        sValue = aRemoveArray(i)
        iMax = aValue.Count
        For j = iMax To 0 Step -1
            If aValue(j) = sValue Then
                aValue.Remove (j)
                Exit For
            End If
        Next j
     Next i

End Sub

'************************************************************
'* CollectionToArray()
'************************************************************

Sub CollectionToArray(a() As Variant, c As Collection)

    iSize = c.Count - 1
    ReDim a(iSize)

    For i = 0 To iSize
        a(i) = c.Item(i + 1)
    Next

End Sub

这段代码可以改进,以返回字符串数组,但在VBA中使用数组并不容易。
注意:此代码仅在您定义了名为X的工作表时才有效,因为AdvancedFilter()中使用的CopyToRange参数需要一个Excel范围!
遗憾的是,微软没有实现这个解决方案,只需添加一个新的枚举,如xlNotFilterValues!或xlRegexMatch!

1

VBA的Filter函数的替代方法

作为对@schlebe最近回答的一种创新性替代方案,我尝试使用集成在VBA中的Filter函数,它允许将第三个参数设置为False以过滤掉给定的搜索字符串。所有"负面"搜索字符串(例如A、B、C)都定义在一个数组中。我将列A中的条件读入数据字段数组,并基本上执行连续过滤(A-C),以过滤掉这些项目。

代码

Sub FilterOut()
Dim ws  As Worksheet
Dim rng As Range, i As Integer, n As Long, v As Variant
' 1) define strings to be filtered out in array
  Dim a()                    ' declare as array
  a = Array("A", "B", "C")   ' << filter out values
' 2) define your sheetname and range (e.g. criteria in column A)
  Set ws = ThisWorkbook.Worksheets("FilterOut")
  n = ws.Range("A" & ws.Rows.Count).End(xlUp).row
  Set rng = ws.Range("A2:A" & n)
' 3) hide complete range rows temporarily
  rng.EntireRow.Hidden = True
' 4) set range to a variant 2-dim datafield array
  v = rng
' 5) code array items by appending row numbers
  For i = 1 To UBound(v): v(i, 1) = v(i, 1) & "#" & i + 1: Next i
' 6) transform to 1-dim array and FILTER OUT the first search string, e.g. "A"
  v = Filter(Application.Transpose(Application.Index(v, 0, 1)), a(0), False, False)
' 7) filter out each subsequent search string, i.e. "B" and "C"
  For i = 1 To UBound(a): v = Filter(v, a(i), False, False): Next i
' 8) get coded row numbers via split function and unhide valid rows
  For i = LBound(v) To UBound(v)
      ws.Range("A" & Split(v(i) & "#", "#")(1)).EntireRow.Hidden = False
  Next i
End Sub

0

这里提供一种选项,使用某个范围上编写的列表,填充将被过滤的数组。信息将被删除,然后列进行排序。

Sub Filter_Out_Values()

'Automation to remove some codes from the list
Dim ws, ws1 As Worksheet
Dim myArray() As Variant
Dim x, lastrow As Long
Dim cell As Range

Set ws = Worksheets("List")
Set ws1 = Worksheets(8)
lastrow = ws.Cells(Application.Rows.Count, 1).End(xlUp).Row

'Go through the list of codes to exclude
For Each cell In ws.Range("A2:A" & lastrow)

    If cell.Offset(0, 2).Value = "X" Then 'If the Code is associated with "X"
        ReDim Preserve myArray(x) 'Initiate array
        myArray(x) = CStr(cell.Value) 'Populate the array with the code
        x = x + 1 'Increase array capacity
        ReDim Preserve myArray(x) 'Redim array
    End If

Next cell

lastrow = ws1.Cells(Application.Rows.Count, 1).End(xlUp).Row
ws1.Range("C2:C" & lastrow).AutoFilter field:=3, Criteria1:=myArray, Operator:=xlFilterValues
ws1.Range("A2:Z" & lastrow).SpecialCells(xlCellTypeVisible).ClearContents
ws1.Range("A2:Z" & lastrow).AutoFilter field:=3

'Sort columns
lastrow = ws1.Cells(Application.Rows.Count, 1).End(xlUp).Row
'Sort with 2 criteria
With ws1.Range("A1:Z" & lastrow)
    .Resize(lastrow).Sort _
    key1:=ws1.Columns("B"), order1:=xlAscending, DataOption1:=xlSortNormal, _
    key2:=ws1.Columns("D"), order1:=xlAscending, DataOption1:=xlSortNormal, _
    Header:=xlYes, MatchCase:=False, Orientation:=xlTopToBottom, SortMethod:=xlPinYin
End With

End Sub

0

好的,我解决了。

这个问题多年来一直困扰着我,但我终于解决了。

我们需要做的就是查看实际在筛选范围内的所有值,如果它们不在我们要筛选的值列表中,就将它们添加到“筛选此项”列表中。

关于这段代码的注意事项:

  • 我编写了这个代码以作用于多个工作表,由于我在工作中没有时间更改,所以我不会更改。我相信你可以解决这个问题。
  • 我认为你不需要在Option base 1中工作……但我正在使用,如果你遇到问题……可能是这个原因。
  • 尽管它要检查和重新检查同样的数组很多次,但它的速度非常快。
  • 我相信有一种方法可以重新定义KeepArray,但我没有时间考虑它。
Option Explicit
Option Base 1
Sub FilterTable()
    
    Dim WS As Worksheet
    Dim L As Long
    Dim I As Long
    Dim N As Long
    Dim tbl As ListObject
    Dim tblName As String
    Dim filterArray
    Dim SrcArray
    Dim KeepArray(1 To 5000) ' you might be able to figure out a way to redim this easiely later on.. for now I'm just oversizing it.

    N = 0
    filterArray = Array("FilterMeOut007", _
                        "FilterMeOut006", _
                        "FilterMeOut005", _
                        "FilterMeOut004", _
                        "FilterMeOut003", _
                        "FilterMeOut002", _
                        "FilterMeOut001")


    For Each WS In ThisWorkbook.Worksheets
        Debug.Print WS.Name
        If Left(WS.Name, 4) = "AR -" Then
            With WS
                tblName = Replace(WS.Name, " ", "_")
                Set tbl = WS.ListObjects(tblName)
                SrcArray = tbl.ListColumns(1).DataBodyRange
                For I = 1 To UBound(SrcArray, 1)
                    If Not ExistsInArray(KeepArray, SrcArray(I, 1)) _
                        And Not ExistsInArray(filterArray, SrcArray(I, 1)) Then
                            N = N + 1
                            KeepArray(N) = SrcArray(I, 1)
                    End If
                Next I
                tbl.DataBodyRange.AutoFilter Field:=1, Criteria1:=KeepArray, Operator:=xlFilterValues
            End With
        End If
    Next WS
End Sub
Function ExistsInArray(arr, Val) As Boolean
    Dim I As Long
    ExistsInArray = False
    For I = LBound(arr) To UBound(arr)
        If arr(I) = Val Then
            ExistsInArray = True
            Exit Function
        End If
    Next I
End Function

请告诉我,如果您在使用过程中遇到任何错误,因为我希望在未来进行尽可能多的压力测试和调试,以使其尽可能便携。我预计会经常使用它。

0
这对我有用: 这是针对两个字段/列(9和10)的条件,它过滤掉第9列中值>0的行和第10列中值为4、7和8的行。lastrow是数据部分的行数。
ActiveSheet.Range("$A$1:$O$" & lastrow).AutoFilter Field:=9, Criteria1:=">0", Operator:=xlAnd
ActiveSheet.Range("$A$1:$O$" & lastrow).AutoFilter Field:=10, Criteria1:=Arr("4","7","8"), Operator:=xlFilterValues

0
请检查这个用于在范围内过滤值的代码;它是有效的。
Selection.AutoFilter field:=33, Criteria1:="<>Array(IN1R,IN2R,INDA)", Operator:=xlFilterValues

实际上,上面的代码并没有起作用。因此,我提供了一个循环来隐藏整行,每当活动单元格具有我正在搜索的值时。

For each cell in selection
    If cell.value = “IN1R” or cell.value = “INR2” or cell.value = “INDA” then
    Else
        Activecell.Entirerow.Hidden = True
    End if
Next

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接