【问题标题】:Multi-Criteria Selection with VBA使用 VBA 进行多标准选择
【发布时间】:2019-01-02 06:54:07
【问题描述】:

我创建了一个宏,允许我根据文件名打开多个文件,并将工作表复制到另一个工作簿上的一个中。现在我想添加一些标准,我确定最后一行的数据。我用这个:

lstRow2 = alarms.Cells(alarms.Rows.Count, "A").End(xlUp).Row

现在我想遍历每一行并检查每行的列 G 是否包含字符串("condenser", "pump" 等),如果是,则复制行但不是整行,只有一系列列属于该行(例如,对于符合我的条件的每一行,复制这些列A-B-X-Z),最后将所有内容复制到另一张表中。

感谢您的帮助

【问题讨论】:

  • 添加了一种创新方法来解决您的问题。顺便说一句,因为这是您的第一篇文章,请查看 SO 并通过将其标记为已接受来帮助其他开发人员确定一个好的答案 - 请参阅 "Someone answers"
  • @T.M.你觉得my approach怎么样?
  • @ZevSpitz - 发现它很酷,直截了当。顺便说一句,我的呢?
  • 这个已解决的问题在Copying values AND color index in an array有一个稍作修改的后续问题

标签: vba excel


【解决方案1】:

灵活的多条件过滤解决方案

这种方法允许多条件搜索定义搜索数组并以高级方式使用Application.Index 函数。此解决方案只需几个步骤即可几乎完全避免循环ReDim s

  • [0] 定义条件数组,例如criteria = Array("condenser", "pump")
  • [1] 将数据 A:Z 分配给二维数据字段数组:v = ws.Range("A2:Z" & n),其中 n 是最后一行编号,ws 是设置的源工作表对象。 警告:如果您的基本数据包含任何日期格式,强烈建议使用.Value2 属性,而不是通过.Value 自动默认分配 - 有关详细信息,请参阅comment
  • [2] 搜索列 G(=7th col) 并通过 帮助函数 构建包含找到的行的数组:a = buildAr(v, 7, criteria)。李>
  • [3] 过滤基于此数组a 使用Application.Index 函数并将返回的列值减少到仅A,B,X,Z
  • [4] 仅使用一个命令将生成的数据字段数组v 写入目标工作表:例如ws2.Range("A2").Resize(UBound(v), UBound(v, 2)) = v,其中 ws2 为设置的目标工作表对象。

主程序MultiCriteria

Option Explicit                                 ' declaration head of code module
Dim howMany&                                    ' findings used in both procedures

Sub MultiCriteria()
' Purpose: copy defined columns of filtered rows
  Dim i&, j&, n&                                 ' row or column counters
  Dim a, v, criteria, temp                       ' all together variant
  Dim ws As Worksheet, ws2 As Worksheet          ' declare and set fully qualified references
  Set ws = ThisWorkbook.Worksheets("Sheet1")      ' <<~~ change to your SOURCE sheet name
  Set ws2 = ThisWorkbook.Worksheets("Sheet2")     ' <<~~ assign to your TARGET sheet name
' [0] define criteria
  criteria = Array("condenser", "pump")          ' <<~~ user defined criteria
' [1] Get data from A1:Z{n}
  n = ws.Range("A" & Rows.Count).End(xlUp).Row   ' find last row number n
  v = ws.Range("A2:Z" & n)                       ' get data cols A:Z and omit header row
' [2] build array containing found rows
  a = buildAr(v, 7, criteria)                    ' search in column G = 7
' [3a] Row Filter based on criteria
  v = Application.Transpose(Application.Index(v, _
      a, _
      Application.Evaluate("row(1:" & 26 & ")"))) ' all columns
' [3b] Column Filter A,B,X,Z
  v = Application.Transpose(Application.Transpose(Application.Index(v, _
      Application.Evaluate("row(1:" & UBound(a) - LBound(a) + 1 & ")"), _
      Array(1, 2, 24, 26))))                  ' only cols A,B,X,Z
' [3c] correct rows IF only one result row found or no one
  If howMany <= 1 Then v = correct(v)
' [4] Copy results array to target sheet, e.g. starting at A2
  ws2.Range("A2").offset(0, 0).Resize(UBound(v), UBound(v, 2)) = v
End Sub

检查过滤结果数组的可能添加

如果您想在 VB 编辑器的即时窗口中控制结果数组,您可以在上面的代码中添加以下部分 '[5]

' [5] [Show results in VB Editor's immediate window]
  Debug.Print "2-dim Array Boundaries (r,c): " & _
              LBound(v, 1) & " To " & UBound(v, 1) & ", " & _
              LBound(v, 2) & " To " & UBound(v, 2)
  For i = 1 To UBound(v)
        Debug.Print i, Join(Application.Index(v, i, 0), " | ")
  Next i

第一个辅助函数buildAr()

Function buildAr(v, ByVal vColumn&, criteria) As Variant
' Purpose: Helper function to check criteria array (e.g. "condenser","pump")
' Note:    called by main function MultiCriteria in section [2]
Dim found&, found2&, i&, n&, ar: ReDim ar(0 To UBound(v) - 1)
howMany = 0      ' reset boolean value to default
  For i = LBound(v) To UBound(v)
    found = 0
    On Error Resume Next    ' avoid not found error
    found = Application.Match(v(i, vColumn), criteria, 0)
    If found > 0 Then
       ar(n) = i
       n = n + 1
    End If
  Next i
  If n < 2 Then
     howMany = n: n = 2
  Else
     howMany = n
  End If
  ReDim Preserve ar(0 To n - 1)
  buildAr = ar
End Function

第二个辅助函数correct()

Function correct(v) As Variant
' Purpose: reduce array to one row without changing Dimension
' Note:    called by main function MultiCriteria in section [3c]
Dim j&, temp: If howMany > 1 Then Exit Function
ReDim temp(1 To 1, LBound(v, 2) To UBound(v, 2))
If howMany = 1 Then
   For j = 1 To UBound(v, 2): temp(1, j) = v(1, j): Next j
ElseIf howMany = 0 Then
   temp(1, 1) = "N/A# - No results found!"
End If
correct = temp
End Function

编辑 I. 由于您的评论

“在 GI 栏中有一个句子,例如(修理冷凝器),我希望一旦出现“冷凝器”这个词就意味着它尊重我尝试过的标准(“*冷凝器*”,“ cex") 就像文件名像 "book" 但它不适用于数组,有没有办法?"

只需将帮助函数buildAr() 中的逻辑更改为通过通配符 进行搜索,方法是对搜索词进行第二次循环 (citeria):

Function buildAr(v, ByVal vColumn&, criteria) As Variant
' Purpose: Helper function to check criteria array (e.g. "condenser","pump")
' Note:    called by main function MultiCriteria in section [2]
Dim found&, found2&, i&, j&, n&, ar: ReDim ar(0 To UBound(v) - 1)
howMany = 0      ' reset boolean value to default
  For i = LBound(v) To UBound(v)
    found = 0
    On Error Resume Next    ' avoid not found error
    '     ' ** original command commented out**
    '          found = Application.Match(v(i, vColumn), criteria, 0)
    For j = LBound(criteria) To UBound(criteria)
       found = Application.Match("*" & criteria(j) & "*", Split(v(i, vColumn) & " ", " "), 0)
       If found > 0 Then ar(n) = i: n = n + 1: Exit For
    Next j
  Next i
  If n < 2 Then
     howMany = n: n = 2
  Else
     howMany = n
  End If
  ReDim Preserve ar(0 To n - 1)
  buildAr = ar
End Function

编辑二。由于最后的评论 - 仅检查 X 列中的现有值

"...我看到了您所做的更改,但我想应用最后一个更简单的想法,(最后一条评论)不使用通配符,而是检查是否有值在 X 列中。”

只需将辅助函数中的逻辑挂起,仅通过测量第 24 列中修剪值的长度 (=X) 来检查现有值,并将主过程中的调用代码更改为

' [2] build array containing found rows
  a = buildAr2(v, 24)                            ' << check for value in column X = 24

注意:在这种情况下,不需要第 [0] 节定义标​​准。

辅助函数的第 2 版

Function buildAr2(v, ByVal vColumn&, Optional criteria) As Variant
' Purpose: Helper function to check for existing value e.g. in column 24 (=X)
' Note:    called by main function MultiCriteria in section [2]
Dim found&, found2&, i&, n&, ar: ReDim ar(0 To UBound(v) - 1)
howMany = 0      ' reset boolean value to default
  For i = LBound(v) To UBound(v)
    If Len(Trim(v(i, vColumn))) > 0 Then
       ar(n) = i
       n = n + 1
    End If
  Next i
  If n < 2 Then
     howMany = n: n = 2
  Else
     howMany = n
  End If
  ReDim Preserve ar(0 To n - 1)
  buildAr2 = ar
End Function

【讨论】:

  • 首先,我要感谢您花时间编写此代码,我尝试了它并且它有效,现在我正在尝试进行一些更改。例如,在 GI 列中有一个句子(对冷凝器进行修理),我希望一旦出现“冷凝器”这个词就意味着它尊重我尝试过的标准(“* 冷凝器*”,“cex ") 就像文件名像 "book" 但它不适用于数组,有没有一种方法?
  • 好的,我会考虑你的观点,我已经创建了一个新问题,但我喜欢你的方法,我想加深它。谢谢@T.M.
  • @Ibrahimatto 请注意,您的系统设置或默认的 excel 设置可能会决定日期的顺序。在您可能需要 yyyymmdd 的地方,您的计算机可能会告诉它 dd/mm/yyyy。您可以在事后格式化列以便快速修复,或在粘贴期间指定格式..
  • @Ibrahimatto 我建议在代码末尾格式化整个列。那些格式正确的看起来是一样的,那些不正确的会被纠正。由于使用您之前评论中的公式正确显示了日期,这似乎是合理的,因为数据在那里,只是需要看起来不同。我不肯定为什么 VBA 粘贴会做出这种改变;如果你在谷歌上遇到同样的问题,会有很多帖子,而且似乎都提出了我提出的相同建议……以确保你的设置正确(excel、系统、操作系统等),否则格式化数据。跨度>
  • @Ibrahimatto .Columns("K").NumberFormat = "dd/mm/yyyy" 应该这样做,前提是您将列固定为正确的数字...我随意选择了 K。跨度>
【解决方案2】:

我会创建一个 SQL 语句来使用 ADODB 从各种工作表中读取数据,然后使用 CopyFromRecordset 粘贴到目标工作表中。

添加对 Microsoft ActiveX 数据对象的引用(工具 -> 引用...)。 (选择最新版本;通常是 6.1)。

以下帮助函数将工作表名称作为Collection 返回给定 Excel 文件路径:

Function GetSheetNames(ByVal excelPath As String) As Collection
    Dim connectionString As String
    connectionString = _
        "Provider=Microsoft.ACE.OLEDB.12.0;" & _
        "Data Source=""" & excelPath & """;" & _
        "Extended Properties=""Excel 12.0;HDR=No"""            

    Dim conn As New ADODB.Connection
    conn.Open connectionString

    Dim schema As ADODB.Recordset
    Set schema = conn.OpenSchema(adSchemaTables)

    Dim sheetName As Variant
    Dim ret As New Collection
    For Each sheetname In schema.GetRows(, , "TABLE_NAME")
        ret.Add sheetName
    Next

    conn.Close
    Set GetSheetNames = ret
End Function

然后,您可以使用以下内容:

Dim paths As Variant
paths = Array("c:\path\to\first.xlsx", "c:\path\to\second.xlsx")

Dim terms As String
terms = "'" & Join(Array("condenser", "pump"), "', '") & "'"

Dim path As Variant
Dim sheetName As Variant
Dim sql As String
For Each path In paths
    For Each sheetName In GetSheetNames(path)
        If Len(sql) > 0 Then sql = sql & " UNION ALL "
        sql = sql & _
            "SELECT F1, F2, F24, F26 " & _
            "FROM [" & sheetName & "] " & _
                "IN """ & path & """ ""Excel 12.0;"" " & _
            "WHERE F7 IN (" & terms & ")"
    Next
Next

'We're connecting here to the current Excel file, but it doesn't really matter to which file we are connecting
Dim connectionString As String
connectionString = _
    "Provider=Microsoft.ACE.OLEDB.12.0;" & _
    "Data Source=""" & ActiveWorkbook.FullName & """;" & _
    "Extended Properties=""Excel 12.0;HDR=No"""     

Dim rs As New ADODB.Recordset
rs.Open sql, connectionString

Worksheets("Destination").Range("A1").CopyFromRecordset rs

【讨论】:

  • 基本上喜欢你的方法,因为它显示了另一种 +1。 - 然而似乎有一些问题:1)Join函数中的分隔符可能应该是"', '"(2)pathsheetName可能声明为Variant,outputFilePath未声明和未分配(3)辅助函数中的参数 excelPath 可能只有 Byval excelPath As Variant。你能再测试一下吗? (4) 在我的语言版本中,我收到错误号 -2147467259 'Tabelle2$' is no valid name calling rs.Open sql, connectionString.
  • 感谢您的帮助,但我对 vba 和 sql 以及编程知之甚少,所以我使用了帮助表单 TM,这对我来说似乎更简单,因为除了他的解释之外,我还设法理解了更多.但我仍然要感谢您为帮助我付出的时间和精力。
  • @T.M.我已经修复了前三个错误(您建议我对这段代码进行一次测试,这很好;我完全没有测试就写了它)。 RE第四期——它对我有用;你能把完整的 SQL 放在评论里吗?
  • 泽夫:SELECT F1, F2, F24, F26 FROM [Tabelle1$] IN "D:\Daten\Excel\_VBA Bsp\Stack\AllTogether.xlsx" "Excel 12.0;" WHERE F7 IN ('condenser', 'pump') UNION ALL SELECT F1, F2, F24, F26 FROM [Tabelle1$] IN "D:\Daten\Excel\_VBA Bsp\Stack\AllTogether.xlsx" "Excel 12.0;" WHERE F7 IN ('condenser', 'pump') UNION ALL SELECT F1, F2, F24, F26 FROM [Tabelle2$] IN "D:\Daten\Excel\_VBA Bsp\Stack\AllTogether.xlsx" "Excel 12.0;" WHERE F7 IN ('condenser', 'pump') UNION ALL SELECT F1, F2, F24, F26 FROM [Tabelle3$] IN "D:\Daten\Excel\_VBA Bsp\Stack\AllTogether.xlsx" "Excel 12.0;" WHERE F7 IN ('condenser', 'pump')
  • @T.M.请注意,如果您想使用您的标头而不是自动生成的标头(F1F2 等),您可以在连接字符串中指定 HDR=Yes(而不是 HDR=No
【解决方案3】:

可能是这样的:

j = 0
For i = To alarms.Rows.Count
   sheetname = "your sheet name"
   If (Sheets(sheetname).Cells(i, 7) = "condenser" Or Sheets(sheetname).Cells(i, 7) = "pump") Then
       j = j + 1
       Sheets(sheetname).Cells(i, 1).Copy Sheets("aff").Cells(j, 1) 
       Sheets(sheetname).Cells(i, 2).Copy Sheets("aff").Cells(j, 2) 
   End If
Next i

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-04
    • 2020-10-17
    • 2016-08-30
    • 1970-01-01
    • 1970-01-01
    • 2015-07-16
    • 1970-01-01
    相关资源
    最近更新 更多