L oop через массив и вернуть несколько строк на основе одного или нескольких критериев поиска - PullRequest
0 голосов
/ 30 января 2020

Я перебираю ArrayDestination по двум столбцам (имя клиента и номер процесса).
Я перебираю ArraySourceData, чтобы найти совпадения (номер и сумма счета-фактуры) для указанных выше критериев поиска.

Если есть это совпадение, которое копируется в массив, и как только оба цикла заканчиваются sh, результаты копируются на лист.

Пока это работает, за исключением того, что l oop возвращает только первое совпадение.

Если у клиента несколько идентичных номеров процессов, l oop возвращает только первое совпадение для всех из них.

Моя переменная b выглядит немного как stati c, и я попытался ее подбодрить с 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

02/02/2020

Я переписал код во вложенном для l oop без массива. Этот код работает. Проблема в том, что в моих исходных данных есть дублированные номера процессов.

В моем примере я «вырезал и вставлял» уже найденные номера процессов в лист, называемый совпадениями. Это работает, НО я искал, чтобы разобрать все в массив из-за работы со 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

Ответы [ 2 ]

0 голосов
/ 02 февраля 2020

Если у вас есть 100 000 строк на листе SOURCEDATA и 10 000 строк на листе РЕЗУЛЬТАТОВ, то наличие 2 циклов - 1 000 000 000 итераций. Эффективным способом является использование словарного объекта с использованием ключа, созданного на основе двух критериев соответствия (col1 и col2), к которым присоединяется выбранный вами символ, например "~" (тильда) или "_" (подчеркивание) ). Отсканируйте лист SOURCEDATA один раз, чтобы построить «поиск» ключа к номеру строки. Затем отсканируйте лист РЕЗУЛЬТАТОВ один раз, объедините 2 поля, как и раньше, и, используя метод словаря .exists (ключ), чтобы найти совпадение, получите соответствующий номер строки в SOURCEDATA. Вот некоторый код для иллюстрации. Я протестировал его с 100 000 исходных строк и 10 000 строк результатов случайных данных, соответствующих ключам, и заполнение полей col 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
0 голосов
/ 31 января 2020

Я не уверен, что ты логик c прав. Если вы говорите, что вам нужно сопоставить 2 параметра, и эти два параметра могут содержать несколько объектов, то я не вижу, как вы можете сделать что-то кроме поиска первого или последнего вхождения. Разве вам не нужен третий параметр для различения guish совпадений?

Вы увидите в примере кода ниже, я предположил, что исходные данные имеют список счетов, которые последовательные и конечные данные имеют дублированные параметры клиента и процесса. В этом случае я предположил, что сопоставление накладной на листе назначения также должно быть последовательным, ie 2-е вхождение повторяющихся средств соответствует 2-му вхождению накладной. Итак, здесь «последовательность» становится третьим параметром, но ваш может отличаться.

Также может быть проще отформатировать ваши данные в иерархическую структуру:

customer -> process -> Счет

, так что вы можете увидеть, что происходит немного проще. Classes идеально подходят для этого. За вашим кодом трудно следовать, поскольку Exit For будет гарантировать только первое совпадение, а передача l oop повторяется в верхней границе массива ArraySourceData и обрабатывает ArrayDestination (я не вижу, что вы пытаюсь сделать там, если это не ошибка).

Чтобы показать вам, что я имею в виду, создайте три класса ( Insert ~> Module Module ) с именем 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

Без просмотра некоторых примеров данных, это трудно быть уверенным, но я подозреваю, моя точка зрения такова: если только комбинация из трех параметров делает что-то уникальным, вам нужно будет сопоставить эти три параметра.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...