Программа Speed ​​Up Matching в Excel VBA - PullRequest
0 голосов
/ 13 июня 2018

Я пишу код VBA в Excel, используя циклы для прохождения 10000 + строк .

Вот пример таблицы

Table_Matching_Example

А вот код, который я написал:

Sub Find_Matches()

    Dim wb As Workbook
    Dim xrow As Long

    Set wb = ActiveWorkbook
    wb.Worksheets("Data").Activate

    tCnt = Sheets("Data").UsedRange.Rows.Count
    Dim e, f, a, j, h As Range
    xrow = 2

    Application.ScreenUpdating = False
    Application.Calculation = xlManual

    For xrow = 2 To tCnt Step 1
        Set e = Range("E" & xrow)
        Set f = e.Offset(0, 1)
        Set a = e.Offset(0, -4)
        Set j = e.Offset(0, 5)
        Set h = e.Offset(0, 3)
        For Each Cell In Range("E2:E" & tCnt)
            If Cell.Value = e.Value Then
                If Cell.Offset(0, 1).Value = f.Value Then
                    If Cell.Offset(0, -4).Value = a.Value Then
                        If Cell.Offset(0, 5).Value = j.Value Then
                            If Cell.Offset(0, 3).Value = h.Value Then
                                If (e.Offset(0, 7).Value) + (Cell.Offset(0, 7).Value) = 0 Then
                                    Cell.EntireRow.Interior.Color = vbYellow
                                    e.EntireRow.Interior.Color = vbYellow
                                End If
                            End If
                        End If
                    End If
                End If
            End If
        Next
    Next
End Sub

Как вы можете себе представить, это занимает много времени, чтобы пройти 10000+ строк, и я хотел бынайти более быстрое решение.Должен быть метод, который я не думаю, чтобы избежать чрезмерного зацикливания

Вот условие:

Для каждой строки, если другая строка где-либо вфайл точно такой же:

  • Идентификатор покупателя (кол. E)
  • `# куплен (кол. F)
  • Идентификатор продукта (кол. A)
  • Оплата (столбец J)
  • Дата покупки (столбец H)

Затем, если сумма суммы (столбец L), эти двасовпадающая строка равна 0, затем закрасьте обе строки желтым цветом.

Обратите внимание, что дополнительные столбцы присутствуют и не сравниваются (например, столбец B), но все еще важны для документа и не могут быть удалены вупростите процесс.

При запуске предыдущего кода, в моем примере строки 2 и 5 подсвечиваются: Table_After_Running

Ответы [ 5 ]

0 голосов
/ 13 июня 2018

Спасибо всем за ваши ответы,

Решение Пола Бики действительно сработало, и я сейчас использую версию этого кода.

Но, просто чтобы оживитьВ дебатах, я думаю, я нашел другой способ обойти свой первый код, вдохновленный идеей Maciej о конкатенации ячеек и использовании CStr для сравнения значений и, конечно же, Vegard обрабатывает в памяти данные с использованием массивов вместо прохождения книги:

Sub Find_MatchesStr()

    Dim AmountArr(300) As Variant
    Dim rowArr(300) As Variant
    Dim ws As Worksheet
    Dim wb As Workbook
    Set ws = ThisWorkbook.Sheets("Data")
    ws.Activate
    Range("A1").Select

    rCnt = ws.Cells.SpecialCells(xlCellTypeLastCell).Row

    For i = 2 To rCnt
        If i = rCnt Then
            Exit For
        Else
        intCnt = 0
        strA = ws.Cells(i, 1).Value
        strE = ws.Cells(i, 5).Value
        strF = ws.Cells(i, 6).Value
        strH = ws.Cells(i, 8).Value
        strL = ws.Cells(i, 10).Value

        For j = i To rCnt - 1
            strSearchA = ws.Cells(j, 1).Value
            strSearchE = ws.Cells(j, 5).Value
            strSearchF = ws.Cells(j, 6).Value
            strSearchH = ws.Cells(j, 8).Value
            strSearchL = ws.Cells(j, 10).Value

            If CStr(strE) = CStr(strSearchE) And CStr(strA) = CStr(strSearchA) And CStr(strF) = CStr(strSearchF) And CStr(strH) = CStr(strSearchH) And CStr(strL) = CStr(strSearchL) Then

                AmountArr(k) = ws.Cells(j, 12).Value
                rowArr(k) = j
                intCnt = intCnt + 1
                k = k + 1
            Else
                Exit For
            End If
        Next
        strSum = 0
        For s = 0 To UBound(AmountArr)
            If AmountArr(s) <> "" Then
                strSum = strSum + AmountArr(s)
            Else
                Exit For
            End If
        Next
        strAppenRow = ""
        For b = 0 To UBound(rowArr)
            If rowArr(b) <> "" Then
                strAppenRow = strAppenRow & "" & rowArr(b) & "," & AmountArr(b) & ","
            Else
                Exit For
            End If
        Next

        If intCnt = 1 Then

        Else
            If strSum = 0 Then
                For rn = 0 To UBound(rowArr)
                    If rowArr(rn) <> "" Then
                        Let rRange = rowArr(rn) & ":" & rowArr(rn)
                        Rows(rRange).Select
                        Selection.Interior.Color = vbYellow
                    Else
                        Exit For
                    End If
                Next
            Else
                strvar = ""
                strvar = Split(strAppenRow, ",")
                For ik = 1 To UBound(strvar)
                    If strvar(ik) <> "" Then
                        strVal = CDbl(strvar(ik))
                        For ik1 = ik To UBound(strvar)
                            If strvar(ik1) <> "" Then
                                strVal1 = CDbl(strvar(ik1))
                                If strVal1 + strVal = 0 Then
                                    Let sRange1 = strvar(ik - 1) & ":" & strvar(ik - 1)
                                    Rows(sRange1).Select
                                    Selection.Interior.Color = vbYellow
                                    Let sRange = strvar(ik1 - 1) & ":" & strvar(ik1 - 1)
                                    Rows(sRange).Select
                                    Selection.Interior.Color = vbYellow
                                End If
                            Else
                                Exit For
                            End If
                            ik1 = ik1 + 1
                        Next
                    Else
                        Exit For
                    End If
                    ik = ik + 1
                Next
            End If
        End If
        i = i + (intCnt - 1)
        k = 0
        Erase AmountArr
        Erase rowArr
        End If
    Next
    Range("A1").Select

End Sub

У меня все еще есть некоторые ошибки (строки не выделены, когда они должны быть), приведенный выше кодне идеален, но я подумал, что было бы неплохо дать вам представление о том, куда я направлялся, прежде чем пришло решение Пола Бики.

Еще раз спасибо!

0 голосов
/ 13 июня 2018

Используются вложенные словари и массивы для проверки всех условий

Таймер с моими тестовыми данными: Rows: 100,001; Dupes: 70,000 - Time: 14.217 sec


Option Explicit

Public Sub FindMatches()
    Const E = 5, F = 6, A = 1, J = 10, H = 8, L = 12

    Dim ur As Range, x As Variant, ub As Long, d As Object, found As Object

    Set ur = ThisWorkbook.Worksheets("Data").UsedRange
    x = ur
    Set d = CreateObject("Scripting.Dictionary")
    Set found = CreateObject("Scripting.Dictionary")

    Dim r As Long, rId As String, itm As Variant, dupeRows As Object

    For r = ur.Row To ur.Rows.Count
        rId = x(r, E) & x(r, F) & x(r, A) & x(r, J) & x(r, H)
        If Not d.Exists(rId) Then
            Set dupeRows = CreateObject("Scripting.Dictionary")
            dupeRows(r) = 0
            Set d(rId) = dupeRows
        Else
            For Each itm In d(rId)
                If x(r, L) + x(itm, L) = 0 Then
                    found(r) = 0
                    found(itm) = 0
                End If
            Next
        End If
    Next
    Application.ScreenUpdating = False
    For Each itm In found
        ur.Range("A" & itm).EntireRow.Interior.Color = vbYellow
    Next
    Application.ScreenUpdating = True
End Sub

До

Before

После

After

0 голосов
/ 13 июня 2018

Если ваши данные только до столбца L, то используйте приведенный ниже код, я обнаружил, что для их запуска требуется меньше времени ...

Sub Duplicates()
    Application.ScreenUpdating = False
    Dim i As Long, lrow As Long
    lrow = Cells(Rows.Count, 1).End(xlUp).Row
    Range("O2") = "=A2&E2&F2&J2&L2"
    Range("P2") = "=COUNTIF(O:O,O2)"
    Range("O2:P" & lrow).FillDown
    Range("O2:O" & lrow).Copy
    Range("O2:O" & lrow).PasteSpecial xlPasteValues
    Application.CutCopyMode = False
    For i = 1 To lrow
        If Cells(i, 16) = 2 Then
            Cells(i, 16).EntireRow.Interior.Color = vbYellow
        End If
    Next
    Application.ScreenUpdating = True
    Range("O:P").Delete
    Range("A1").Select
    MsgBox "Done"
End Sub
0 голосов
/ 13 июня 2018

Ответ Мачей легко реализовать (если вы можете добавлять столбцы к своим данным, не прерывая ничего), и я бы порекомендовал его, если это возможно.

Однако, чтобы ответить на ваш вопрос, я внесу свой вкладрешение VBA также.Я проверил его на наборе данных, который немного меньше вашего, но я думаю, что он будет работать для вас.Обратите внимание, что вам, возможно, придется немного подправить его (с какой строки вы начинаете, имя таблицы и т. Д.), Чтобы она соответствовала вашей книге.

Наиболее примечательно, что сегмент, прокомментированный как «столбец помощника», - это то, что вам, скорее всего, придется корректировать - в настоящее время он сравнивает каждую ячейку между A и H для текущей строки, что вы можете или не можетехочу.

Я пытался включить небольшой комментарий в код, но это не так много.Основное изменение заключается в том, что я использую обработку массива в памяти, а не итерацию по диапазону листа (который для больших наборов данных должен быть экспоненциально быстрее).

Option Base 1
Option Explicit
' Uses ref Microsoft Scripting Runtime

Sub Find_Matches()
    Dim wb As Workbook, ws As Worksheet
    Dim xrow As Long, tCnt As Long
    Dim e As Range, f As Range, a As Range, j As Range, h As Range
    Dim sheetArr() As Variant, arr() As Variant
    Dim colorTheseYellow As New Dictionary, colorResults() As String, dictItem As Variant
    Dim arrSize As Long, i As Long, k As Long
    Dim c As Variant

    Set wb = ThisWorkbook
    Set ws = wb.Worksheets("Data")
    ws.Activate

    tCnt = ws.UsedRange.Rows.Count
    xrow = 2

    Application.ScreenUpdating = False
    Application.Calculation = xlManual

    ' Read range into an array so we process in-memory
    sheetArr = ws.Range("A2:H" & tCnt)
    arrSize = UBound(sheetArr, 1)

    ' Build new arr with "helper column"
    ReDim arr(1 To arrSize, 1 To 9)
    For i = 1 To arrSize
        For k = 1 To 8
            arr(i, k) = sheetArr(i, k)
            arr(i, 9) = CStr(arr(i, 9)) & CStr(arr(i, k)) ' "Helper column"
        Next k
    Next i

    ' Iterate over array & build collection to indicate yellow lines
    For i = LBound(arr, 1) To UBound(arr, 1)
        If Not colorTheseYellow.Exists(i) Then colorResults = Split(ReturnLines(arr(i, 9), arr), ";")
        For Each c In colorResults
            If Not colorTheseYellow.Exists(CLng(c)) Then colorTheseYellow.Add CLng(c), CLng(c)
        Next c
    Next i

    ' Enact row colors
    For Each dictItem In colorTheseYellow
        'Debug.Print "dict: "; dictItem
        If dictItem <> 0 Then ws.ListObjects(1).ListRows(CLng(dictItem)).Range.Interior.Color = vbYellow
    Next dictItem
End Sub


Function ReturnLines(ByVal s As String, ByRef arr() As Variant) As String
    ' Returns a "Index;Index" string indicating the index/indices where the second, third, etc. instance(s) of s was found
    ' Returns "0;0" if 1 or fewer matches

    Dim i As Long
    Dim j As Long
    Dim tmp As String
    ReturnLines = 0
    j = 0
    tmp = "0"

    'Debug.Print "arg: " & s

    For i = LBound(arr, 1) To UBound(arr, 1)
        If arr(i, 9) = s Then
            j = j + 1
            'Debug.Print "arr: " & arr(i, 9)
            'Debug.Print "ReturnLine: " & i
            tmp = tmp & ";" & CStr(i)
        End If
    Next i

    'If Left(tmp, 1) = ";" Then tmp = Mid(tmp, 2, Len(tmp) - 1)

    'Debug.Print "tmp: " & tmp
    If j >= 2 Then
        ReturnLines = tmp
    Else
        ReturnLines = "0;0"
    End If
End Function

В моем простом наборе данных это даетэтот результат (превосходно отмечен нарисованными от руки цветными индикаторами):

enter image description here

0 голосов
/ 13 июня 2018

Я предлагаю совершенно другой подход: добавьте к своим данным временный столбец, содержащий конкатенацию каждой ячейки в строке.Таким образом, вы получите:

A|B|C|D|E
1|Mr. Smith|500|A|1Mr. Smith500A

Затем используйте условное форматирование Excel для временного столбца, выделив дублирующиеся значения.Там у вас есть дублированные строки.Теперь нужно только использовать фильтр, чтобы проверить, какие из них имеют значения, равные нулю.

Вы можете использовать функцию CONCATENATE ;это требует, чтобы вы указали каждую ячейку отдельно, и вы не можете использовать диапазон, но в вашем случае (сравнивая только некоторые из столбцов) это кажется подходящим.

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