【问题标题】:Removing duplicates based on their occurrence根据出现的重复项删除重复项
【发布时间】:2016-03-03 06:33:31
【问题描述】:

我想检查某个列 (W) 是否有重复项(出现次数存储在另一列 (AZ) 中),然后以这种方式删除所有行:

  • 在列中找到两次值 - 仅删除包含该值的一行。
  • 在该列中找到的值更多次 - 删除所有包含该值的行。

我的代码运行良好,但有时它并没有按照应有的方式删除所有重复项。有什么改进的想法吗?

编辑:更新后的代码效果非常好,只是它总是会遗漏一个重复项并且不会将其删除。

fin = ws.UsedRange.Rows.count

For i = 2 To fin
    ws.Range("AZ" & i).value = Application.WorksheetFunction.CountIf(ws.Range("W2:W" & fin), ws.Range("W" & i))
Next i

For j = fin To 2 Step -1
    If ws.Range("AZ" & j).value > 2 Then
        ws.Range("AZ" & j).EntireRow.Delete
        fin = ws.UsedRange.Rows.count
    ElseIf ws.Range("AZ" & j).value = 2 Then
        Set rng = Range("W:W").Find(Range("W" & j).value, , xlValues, xlWhole, , xlNext)
        rngRow = rng.Row
        If rngRow <> j Then
            ws.Range("AZ" & rngRow) = "1"
            ws.Range("AZ" & j).EntireRow.Delete
            fin = ws.UsedRange.Rows.count
        Else
            MsgBox "Error at row " & rngRow
        End If
    End If
Next j

【问题讨论】:

  • 您能描述一下您的代码何时无法成功删除重复项吗?
  • 我认为问题在于您在删除一些行后重新进行计数。这会改变一些计数,并可能导致对行进行重新分类。
  • 这背后的想法是我必须在删除第二次出现的重复项后重新进行计数,以便将第一次出现的重复项标记为非重复项 (1),并且以后不会被删除。至少我是这么想的。速度对我来说还不是一个大问题。我只需要将双重重复减少到一次(A,A到A) - 删除一个A,然后完全删除三重和更多重复(B,B,B到-; C,C,C,C, C to -) @RonRosenfeld
  • @Gussmayer,我在下面的回答正是这样做的,在第一个循环(AZ)上计数大于 2 的任何行都将被删除,删除除原始重复项之外的所有行,OR 负责这些通过计数并删除其中一个重复项,它很简单,不知道为什么新的答案对于一项简单的任务变得如此复杂......
  • @StevenMartin 增加了复杂性以提高速度。如果数据库比较小,问题不大;如果数据库需要删除数千行,那么复杂性就变得值得了。

标签: excel vba duplicates countif find-occurrences


【解决方案1】:

如果速度是个问题,这里有一个应该更快的方法,因为它创建要删除的行集合,然后删除它们。由于除了实际的行删除之外的所有操作都是在 VBA 中完成的,因此对工作表的来回调用要少得多。

如内联 cmets 中所述,可以加快例程的速度。 如果仍然太慢,根据工作表的大小,将整个工作表读入 VBA 数组可能是可行的;重复测试;将结果写回新数组并将其写入工作表。 (不过,如果您的工作表太大,此方法可能会耗尽内存)。

在任何情况下,我们都需要一个 Class Module(您必须将其重命名为 cPhrase),以及一个 Regular Module

类模块

Option Explicit
Private pPhrase As String
Private pCount As Long
Private pRowNums As Collection

Public Property Get Phrase() As String
    Phrase = pPhrase
End Property
Public Property Let Phrase(Value As String)
    pPhrase = Value
End Property

Public Property Get Count() As Long
    Count = pCount
End Property
Public Property Let Count(Value As Long)
    pCount = Value
End Property

Public Property Get RowNums() As Collection
    Set RowNums = pRowNums
End Property
Public Function ADDRowNum(Value As Long)
    pRowNums.Add Value
End Function


Private Sub Class_Initialize()
    Set pRowNums = New Collection
End Sub

常规模块

Option Explicit
Sub RemoveDuplicateRows()
    Dim wsSrc As Worksheet
    Dim vSrc As Variant
    Dim CP As cPhrases, colP As Collection, colRowNums As Collection
    Dim I As Long, K As Long
    Dim R As Range

'Data worksheet
Set wsSrc = Worksheets("sheet1")

'Read original data into VBA array
With wsSrc
    vSrc = .Range(.Cells(1, "W"), .Cells(.Rows.Count, "W").End(xlUp))
End With

'Collect list of items, counts and row numbers to delete
'Collection object will --> error when trying to add
'  duplicate key.  Use that error to increment the count

Set colP = New Collection
On Error Resume Next
For I = 2 To UBound(vSrc, 1)
        Set CP = New cPhrases
        With CP
            .Phrase = vSrc(I, 1)
            .Count = 1
            .ADDRowNum I

            colP.Add CP, CStr(.Phrase)
            Select Case Err.Number
                Case 457 'duplicate
                    With colP(CStr(.Phrase))
                        .Count = .Count + 1
                        .ADDRowNum I
                    End With
                    Err.Clear
                Case Is <> 0 'some other error.  Stop to debug
                    Debug.Print "Error: " & Err.Number, Err.Description
                    Stop
            End Select
        End With
Next I
On Error GoTo 0

'Rows to be deleted
Set colRowNums = New Collection
For I = 1 To colP.Count
    With colP(I)
        Select Case .Count
            Case 2
                colRowNums.Add .RowNums(2)
            Case Is > 2
                For K = 1 To .RowNums.Count
                    colRowNums.Add .RowNums(K)
                Next K
        End Select
    End With
Next I

'Revers Sort the collection of Row Numbers
'For speed, if necessary, could use
'   faster sort routine
RevCollBubbleSort colRowNums

'Delete Rows
'For speed, could create Unions of up to 30 rows at a time
Application.ScreenUpdating = False
With wsSrc
For I = 1 To colRowNums.Count
   .Rows(colRowNums(I)).Delete
Next I
End With

Application.ScreenUpdating = True

End Sub

'Could use faster sort routine if necessary
Sub RevCollBubbleSort(TempCol As Collection)
    Dim I As Long
    Dim NoExchanges As Boolean

    ' Loop until no more "exchanges" are made.
    Do
        NoExchanges = True

        ' Loop through each element in the array.
        For I = 1 To TempCol.Count - 1

            ' If the element is less than the element
            ' following it, exchange the two elements.
            If TempCol(I) < TempCol(I + 1) Then
                NoExchanges = False
                TempCol.Add TempCol(I), after:=I + 1
                TempCol.Remove I
            End If
        Next I
    Loop While Not (NoExchanges)
End Sub

【讨论】:

  • 我正在处理数千行 - 你的解决方案仍然可行吗?
  • @Gussmayer 我发布的解决方案应该适用于任意数量的行。你在尝试的时候遇到了什么问题?
【解决方案2】:

不需要在第二部分使用效率低下的第二个循环,只需像这样使用实时计数即可

fin = ws.UsedRange.Rows.count

For i = 2 To fin

    ws.Range("AZ" & i).value = Application.WorksheetFunction.CountIf(ws.Range("W2:W" & fin), ws.Range("W" & i))

Next i

For j = fin To 2 Step -1

    If ws.Range("AZ" & j).value > 2 OR Application.WorksheetFunction.CountIf(ws.Range("W2:W" & fin), ws.Range("W" & j)) = 2 Then

        ws.Range("AZ" & j).EntireRow.Delete

    End If
Next j

【讨论】:

  • 我需要重复出现一次才能保持不被删除(A,A 到 A)。但是,应完全删除所有三重和更多重复项(B、B、B、B 到 - )。
  • 这正是它的作用! ,如果原始计数(保存在单元格中)> 2 或者如果实时计数 = 2,则删除,您应该在写这样的评论之前尝试一下
【解决方案3】:

虽然您的逻辑基本上是合理的,但该方法并不是最有效的。 AutoFilter Method 可以快速删除所有大于 2 的计数,Range.RemoveDuplicates¹ method 随后可以快速删除 W 列中仍包含重复值的行之一。

Dim r As Long, c As Long
With ws
    If .AutoFilterMode Then .AutoFilterMode = False
    r = .Cells.SpecialCells(xlLastCell).Row
    c = Application.Max(52, .Cells.SpecialCells(xlLastCell).Column)
    With .Range("A1", .Cells(r, c))   '.UsedRange
        With .Columns(52)
            If IsEmpty(.Cells(1, 1)) Then .Cells(1, 1) = "count"
            With .Resize(.Rows.Count - 1, 1).Offset(1, 0)
                .Cells.FormulaR1C1 = "=COUNTIF(C[-29], RC[-29])"
                .Cells = .Cells.Value
            End With
            .AutoFilter field:=1, Criteria1:=">2"
            With .Resize(.Rows.Count - 1, 1).Offset(1, 0)
                If CBool(Application.Subtotal(103, .Cells)) Then
                    .SpecialCells(xlCellTypeVisible).EntireRow.Delete
                End If
            End With
            .AutoFilter
        End With
        .RemoveDuplicates Columns:=23, Header:=xlYes
    End With
End With

当您重写 AZ 列中的计数值时,您可能会将 3 个计数重写为 2 等。


¹ Range.RemoveDuplicates method 从下往上删除重复行。

【讨论】:

  • 我看不到您的代码如何将重复项减少到一次(A,A 到 A) - 删除一个 A,三重和更多重复项完全删除(B,B,B 到-;C、C、C、C、C 至 -)。你能再给我点提示吗?
  • @Gussmayer - 重写 COUNTIF 计数会将四元组减少为三元组、三元组、三元组等(如前所述),因此几乎所有重复项都将被重写为双倍(如上一段所述)。在重新阅读了 OP 的问题后,我决定这不是首选条件,并且重写可能是问题的根源,而不是即时更正。请参阅上面的编辑。
猜你喜欢
  • 2016-09-16
  • 2021-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-02
  • 2018-07-09
  • 2020-02-17
  • 2015-04-20
相关资源
最近更新 更多