Arrays 循环遍历数组并基于一个或多个搜索条件返回多行

Arrays 循环遍历数组并基于一个或多个搜索条件返回多行,arrays,excel,vba,search,Arrays,Excel,Vba,Search,我通过两列(客户名称和流程编号)循环执行ArrayDestination。 我正在通过ArraySourceData查找上述搜索条件的匹配项(发票号和金额) 如果存在匹配项,则将其复制到数组中,并在两个循环完成后将结果复制到工作表中 到目前为止,除了循环只返回第一个匹配项外,它仍然有效 如果客户有多个相同的流程编号,则循环仅返回所有流程编号的第一个匹配项 我的b变量看起来有点静态,我试着用b=b+1来使它振作起来 为了简单起见,我没有发布创建数组部分的帖子。它起作用了。如果需要,我可以提供 Su

我通过两列(客户名称和流程编号)循环执行ArrayDestination。
我正在通过ArraySourceData查找上述搜索条件的匹配项(发票号和金额)

如果存在匹配项,则将其复制到数组中,并在两个循环完成后将结果复制到工作表中

到目前为止,除了循环只返回第一个匹配项外,它仍然有效

如果客户有多个相同的流程编号,则循环仅返回所有流程编号的第一个匹配项

我的b变量看起来有点静态,我试着用b=b+1来使它振作起来

为了简单起见,我没有发布创建数组部分的帖子。它起作用了。如果需要,我可以提供

Sub search_loop_arrray()

For a = 2 To UBound(ArraySourceData)
    varCustomerName = ArraySourceData(a, 3)
    varProcessNumber = ArraySourceData(a, 5)

    For b = 2 To UBound(ArrayDestination)
        If ArrayDestination(b, 3) = varCustomerName And _
          ArrayDestination(b, 8) = varProcessNumber Then

            ArrayDestination(b, 9) = ArraySourceData(a, 11)
            ArrayDestination(b, 10) = ArraySourceData(a, 12)

            Exit For
        End If
    Next b
Next a

'transfer data (invoice number and amount) from ArrayDestination to wsDestination (Column 9 and 10)
For a = 2 To UBound(ArraySourceData)
    For b = 9 To 10
        wsDestination.Cells(a, b).Value = ArrayDestination(a, b)
    Next b
Next a

End Sub

2020年2月2日

我在没有数组的嵌套for循环中重写了代码。这个代码有效。问题是源数据中存在重复的进程号

在我的示例中,我“剪切并粘贴”了一张名为“巧合”的表格中已经找到的流程编号。它正在工作,但由于要处理100.000+行和20+列,我希望将所有内容解析为一个数组

我不知道我的“复制到临时巧合表”在数组中是否有意义

Sub find_invoice()

Dim wsSourceData As Worksheet
Dim wsResults As Worksheet
Dim wsCoincidences As Worksheet

Dim varCustomer As String
Dim varProcessNumber As Long
Dim varInvoiceNumber As Long
Dim varSDlastrow As Integer
Dim varRElastrow As Long
Dim varCIlastrow As Long
Dim varCounterResults As Long

Set wsResults = ThisWorkbook.Sheets("RESULTS")
Set wsSourceData = ThisWorkbook.Sheets("SOURCEDATA")
Set wsCoincidences = ThisWorkbook.Sheets("COINCIDENCES")

varSDlastrow = wsSourceData.Cells(Rows.Count, 1).End(xlUp).Row
varRElastrow = wsResults.Cells(Rows.Count, 1).End(xlUp).Row
varCIlastrow = wsCoincidences.Cells(Rows.Count, 1).End(xlUp).Row

For i = 2 To varRElastrow
    varCustomer = wsResults.Cells(i, 1)
    varProcessNumber = wsResults.Cells(i, 2)

    For j = 2 To varSDlastrow
        If wsSourceData.Cells(j, 1) = varCustomer And wsSourceData.Cells(j, 2) = varProcessNumber Then
            wsResults.Cells(i, 3) = wsSourceData.Cells(j, 3)
            wsResults.Cells(i, 4) = wsSourceData.Cells(j, 4)
            wsCoincidences.Rows(varCIlastrow).EntireRow.Value = wsSourceData.Rows(j).EntireRow.Value
            wsSourceData.Rows(j).EntireRow.Delete
            varCIlastrow = varCIlastrow + 1

            Exit For
        End If

    Next j
Next i

End Sub

我不确定你的逻辑是否正确。如果您说需要匹配两个参数,并且几个实体可以包含这两个参数,那么除了查找第一个或最后一个引用之外,我看不出您还能做什么。您不需要第三个参数来区分匹配项吗

您将在下面的示例代码中看到,我假设源数据具有连续的发票列表,而目标数据具有重复的客户和流程参数。在这种情况下,我假设目的地工作表上匹配的发票也应该是连续的,即重复的第二次出现意味着匹配发票的第二次出现。所以这里,“序列”变成了第三个参数,但是你的可能不同

将数据格式化为层次结构也可能更容易:

客户->流程->发票

所以你可以更容易地看到发生了什么<代码>类非常适合于此。您的代码很难理解,因为
退出
将保证只进行第一次匹配,并且传输循环在
数组源数据
的上限上迭代,但仍处理
数组目标
(我看不到您在那里试图做什么,除非是错误)

为了向您展示我的意思,创建三个类(Insert~>classmodule),称为cCustomer、cProcess和cInvoice。将以下代码添加到每个:

cCustomer

Option Explicit

Public Name As String
Public Processes As Collection
Public Sub AddInvoice(processNum As String, invoiceNum As String, invAmount As Double)
    Dim process As cProcess
    Dim invoice As cInvoice

    On Error Resume Next
    Set process = Processes(processNum)
    On Error GoTo 0
    If process Is Nothing Then
        Set process = New cProcess
        With process
            .ProcessNumber = processNum
            Processes.Add process, .ProcessNumber
        End With
    End If

    Set invoice = New cInvoice
    With invoice
        .InvoiceNumber = invoiceNum
        .Amount = invAmount
        process.Invoices.Add invoice
    End With

End Sub

Public Function GetProcess(num As String) As cProcess
    On Error Resume Next
    Set GetProcess = Processes(num)
End Function
Private Sub Class_Initialize()
    Set Processes = New Collection
End Sub
cProcess

Option Explicit

Public ProcessNumber As String
Public Invoices As Collection
Public CurrentInvoiceCount As Long

Private Sub Class_Initialize()
    Set Invoices = New Collection
End Sub
cInvoice

Option Explicit

Public InvoiceNumber As String
Public Amount As Double
Public ArrayIndex As Long
模块中的以下例程将输出如上所述的数据:

Dim customers As Collection
Dim customer As cCustomer
Dim process As cProcess
Dim invoice As cInvoice
Dim srcData As Variant, dstData As Variant
Dim output() As Variant

Dim i As Long

'Populate the source data array.
'Note: just an example here, use whatever array populating code you have.
With Sheet1 'I've put some dummy data in my Sheet1.
    srcData = _
        .Range( _
                .Cells(2, "A"), _
                .Cells(.Rows.Count, "A").End(xlUp)) _
        .Resize(, 12) _
        .Value2
End With

'Populate the destination data array.
'Note: just an example here, use whatever array populating code you have.
With Sheet2 'I've put some dummy data in my Sheet2.
    dstData = _
        .Range( _
                .Cells(2, "A"), _
                .Cells(.Rows.Count, "A").End(xlUp)) _
        .Resize(, 10) _
        .Value2
End With

'Convert source array to heirarchical collections.
Set customers = New Collection
For i = 1 To UBound(srcData, 1)
    Set customer = Nothing: On Error Resume Next
    Set customer = customers(CStr(srcData(i, 3))): On Error GoTo 0
    If customer Is Nothing Then
        Set customer = New cCustomer
        With customer
            .Name = CStr(srcData(i, 3))
            customers.Add customer, .Name
        End With
    End If
    customer.AddInvoice CStr(srcData(i, 5)), CStr(srcData(i, 11)), CDbl(srcData(i, 12))
Next

'Match destination array.
For i = 1 To UBound(dstData, 1)
    Set customer = Nothing: On Error Resume Next
    Set customer = customers(CStr(dstData(i, 3))): On Error GoTo 0
    If Not customer Is Nothing Then
        Set process = customer.GetProcess(CStr(dstData(i, 8)))
        If Not process Is Nothing Then
            With process
                .CurrentInvoiceCount = .CurrentInvoiceCount + 1
                If .CurrentInvoiceCount > .Invoices.Count Then
                    MsgBox "No further invoices for [cust=" & customer.Name & ";" & process.ProcessNumber & "]"
                Else
                    Set invoice = .Invoices(.CurrentInvoiceCount)
                    invoice.ArrayIndex = i
                End If
            End With
        End If
    End If
Next

'Populate the output array.
ReDim output(1 To UBound(dstData, 1), 1 To 2)
For Each customer In customers
    For Each process In customer.Processes
        For Each invoice In process.Invoices
            With invoice
                If .ArrayIndex > 0 Then
                    output(.ArrayIndex, 1) = .InvoiceNumber
                    output(.ArrayIndex, 2) = .Amount
                End If
            End With
        Next
    Next
Next

'Write array to worksheet
Sheet2.Cells(2, 9).Resize(UBound(output, 1), UBound(output, 2)).Value = output

在没有看到一些样本数据的情况下,很难确定,但我怀疑我的观点是:如果只有三个参数的组合才能使某些东西独一无二,然后您需要匹配这三个参数。

如果SOURCEDATA sheet上有100000行,结果sheet上有10000行,那么有2个循环就是100000000次迭代。有效的方法是使用一个基于2个匹配条件(col1和col2)构造的键,该键由您选择的字符(如“~”(波浪号)或“\”(下划线)连接。扫描SOURCEDATA sheet一次,以“查找”关键行编号。然后扫描结果表一次,像以前一样连接这两个字段,并使用dictionary.exists(key)方法查找匹配项将为您提供SOURCEDATA上的相关行号。下面是一些代码来说明。我用100000个源行和10000个结果行对它进行了测试,这些随机数据与键匹配,在结果表上填写C列和D列大约需要3秒钟。为性能数据添加一个名为RUNLOG的工作表。它看起来有很多代码,但大部分都是日志

Option Explicit

Sub find_invoice2()

    Const MSG As Boolean = False ' TRUE to show message boxes
    Const RUNLOG As Boolean = False ' TRUE to log matches, no match etc

    Dim wb As Workbook, start As Single, finish As Single
    start = Timer
    Set wb = ThisWorkbook

    ' set up sheets
    Dim wsSourceData As Worksheet, wsResults As Worksheet, wsLog As Worksheet, wsMatch
    With wb
        Set wsResults = .Sheets("RESULTS")
        Set wsSourceData = .Sheets("SOURCEDATA")
        Set wsMatch = .Sheets("COINCIDENCES")
        Set wsLog = .Sheets("RUNLOG")
    End With

    ' find last row of source and results
    Dim lastRowSource As Long, lastRowResults As Long, lastRowLog As Long, lastRowMatch
    lastRowSource = wsSourceData.Cells(Rows.Count, 1).End(xlUp).Row
    lastRowResults = wsResults.Cells(Rows.Count, 1).End(xlUp).Row
    lastRowMatch = wsMatch.Cells(Rows.Count, 1).End(xlUp).Row

    ' set up log sheets
    wsLog.Cells.Clear
    wsLog.Range("A1:E1") = Array("Source Row", "Result Row", "Customer~Process", "Message", "Date Time")
    wsLog.Cells(2, 4) = "Started"
    wsLog.Cells(2, 5) = Time

    lastRowLog = 3

    ' create lookup from Source
    ' key = Name~ProcessID, value = array row
    Dim dict As Object, sKey As String, iRow As Long
    Set dict = CreateObject("scripting.dictionary")

    With wsSourceData
    For iRow = 2 To lastRowSource
        sKey = CStr(.Cells(iRow, 1)) & "~" & CStr(.Cells(iRow, 2)) ' customer~process
        If Len(sKey) > 1 Then ' skip blanks lines if any
            If dict.exists(sKey) Then

                dict.Item(sKey) = dict.Item(sKey) & "_" & CStr(iRow)

                If MSG Then MsgBox "Ignoring duplicate key in Source Data " & sKey, vbCritical
                If RUNLOG Then
                With wsLog.Cells(lastRowLog, 1)
                    .Offset(0, 0) = iRow
                    .Offset(0, 2) = sKey
                    .Offset(0, 3) = "Source : Ignoring duplicate key "
                    .Offset(0, 4) = Time
                End With
                lastRowLog = lastRowLog + 1
                End If
            Else
                dict.Add sKey, iRow
                'Debug.Print "Dict add", sKey, iRow
            End If
        End If
    Next
    End With
    If MSG Then MsgBox dict.Count & " records added to dictionary"

    wsLog.Cells(lastRowLog, 4) = "Dictionary Built Keys Count = " & dict.Count
    wsLog.Cells(lastRowLog, 5) = Time
    lastRowLog = lastRowLog + 1 ' blank line to seperate results

    ' scan results sheet
    Dim sDict As String, countMatch As Long, countNoMatch As Long, sMsg As String
    Dim ar As Variant, i As Long
    countMatch = 0: countNoMatch = 0

    Application.ScreenUpdating = False
    With wsResults
    For iRow = 2 To lastRowResults
        sKey = CStr(.Cells(iRow, 1)) & "~" & CStr(.Cells(iRow, 2)) ' customer~process
        If Len(sKey) > 1 Then 'skip blanks lines if any
            If dict.exists(sKey) Then

                ' split string to get multiple lines
                sDict = dict(sKey)
                ar = Split(sDict, "_")
                .Cells(iRow, 3).Value = UBound(ar) + 1
                For i = 0 To UBound(ar)
                  .Cells(iRow, 4).Offset(0, i) = ar(i)
                Next

                lastRowMatch = lastRowMatch + 1
                countMatch = countMatch + 1

                If RUNLOG Then
                    With wsLog.Cells(lastRowLog, 1)
                        .Offset(0, 0) = sDict
                        .Offset(0, 1) = iRow
                        .Offset(0, 2) = sKey
                        .Offset(0, 3) = "Match - Source record deleted"
                        .Offset(0, 4) = Time
                    End With
                    lastRowLog = lastRowLog + 1
                End If
                'Debug.Print iRow,sDict, sKey,
            Else
                ' no match
                If MSG Then MsgBox "Results Row " & iRow & ": NO match for " & sKey, vbExclamation, "NO match"
                countNoMatch = countNoMatch + 1
                If RUNLOG Then
                    With wsLog.Cells(lastRowLog, 1)
                        .Offset(0, 1) = iRow
                        .Offset(0, 2) = sKey
                        .Offset(0, 3) = "Results : NO match"
                        .Offset(0, 4) = Time
                        .EntireRow.Interior.Color = vbYellow
                    End With
                    .Cells(iRow, 3).Resize(1, 2).Interior.Color = vbYellow
                    lastRowLog = lastRowLog + 1
                    'Debug.Print iRow, sDict, sKey,
                End If
            End If
        End If
    Next
    End With
    Application.ScreenUpdating = True

    wsLog.Cells(lastRowLog, 4) = "Program Ended Rows Scanned = " & lastRowResults - 1
    wsLog.Cells(lastRowLog, 5) = Time
    wsLog.Columns.AutoFit
    wsLog.Activate
    wsLog.Columns("A:B").HorizontalAlignment = xlCenter
    wsLog.Range("A1").Select

    ' result
    finish = Timer
    sMsg = "Matched  = " & countMatch & vbCrLf _
         & "NO match = " & countNoMatch & vbCrLf _
         & "Run time (secs) = " & Int(finish - start)
    MsgBox sMsg, vbInformation, "Results"

End Sub

不要硬编码数组的下限,使用
LBound
。He BigBen,感谢您的快速响应。您的意思是:对于a=LBound(ArraySourceData)到UBound(ArraySourceData)?我试过了,但没什么不同。它是否应该遍历所有行?是的,这是完全正确的。不幸的是,它仍然只返回第一个匹配。还有其他想法吗?看来退出会导致它只找到第一个匹配项?这是一个非常有用的答案,我会记住你的代码和建议,因为这是我将来想去的地方。目前,这超出了我的vba知识范围,而且还不可能,因为我正在处理非常不一致的数据。我编辑了我的问题,因为我现在更清楚实际的问题是什么。这绝对令人惊讶。我一直害怕字典,但以我自己的例子来看,它不再像火箭科学了。然而,我必须进入它,以充分了解发生了什么。你的代码的问题是-我最初的问题。它返回第一个匹配项并忽略其他匹配项。假设一个客户有4行相同的流程编号,这些编号属于不同的发票。然后,代码应该返回n个匹配项,而不仅仅是第一个。我知道这很难理解,如果你不熟悉我的项目,可能没有意义。非常感谢你的帮助和时间!!!我完全不知所措。@Chris哪个表源或结果有重复的键,或者两者都有?它们都可以有重复的键。ok,我已经编辑了我的代码。它的工作原理是使用下划线ie 123_456_789构建多个匹配行的字符串。结果表现在以C列显示匹配数,以D列、E列、F列等显示实际行数