【问题标题】:Combine Rows with duplicate values, merge cells if different合并具有重复值的行,如果不同则合并单元格
【发布时间】:2015-08-05 21:52:24
【问题描述】:

我有类似的问题 [合并具有重复值的行][1] Excel VBA - Combine rows with duplicate values in one cell and merge values in other cell

我有这种格式的数据(行已排序)


Pub     ID      CH      Ref
no      15      1      t2
no      15      1      t88
yes     15      2      t3
yes     15      2      t3
yes     15      2      t6

比较相邻行(比如第 4 行和第 5 行),如果 col 2 和 3 匹配,则如果 col 4 不同,则合并 col4,删除行。如果 col 2,3,4 匹配则删除行,不要合并 col 4


期望的输出

key     ID      CH      Text  
no      15      1       t2   t88
yes     15      2       t3   t6

第一个代码部分不能正常工作

Sub mergeCategoryValues()
    Dim lngRow As Long

    With ActiveSheet
        Dim columnToMatch1 As Integer: columnToMatch1 = 2
        Dim columnToMatch2 As Integer: columnToMatch2 = 3
        Dim columnToConcatenate As Integer: columnToConcatenate = 4


        lngRow = .Cells(65536, columnToMatch1).End(xlUp).row
        .Cells(columnToMatch1).CurrentRegion.Sort key1:=.Cells(columnToMatch1), Header:=xlYes
        .Cells(columnToMatch2).CurrentRegion.Sort key1:=.Cells(columnToMatch2), Header:=xlYes

        Do
            If .Cells(lngRow, columnToMatch1) = .Cells(lngRow - 1, columnToMatch1) Then 'check col 2 row lngRow, lngRow-1
              If .Cells(lngRow, columnToMatch2) = .Cells(lngRow - 1, columnToMatch2) Then 'check col 3 row lngRow, lngRow-1
                 If .Cells(lngRow - 1, columnToConcatenate) = .Cells(lngRow, columnToConcatenate) Then
                    Else
                    .Cells(lngRow - 1, columnToConcatenate) = .Cells(lngRow - 1, columnToConcatenate) & "; " & .Cells(lngRow, columnToConcatenate)
                 End If
                .Rows(lngRow).Delete
              End If
            End If
            lngRow = lngRow - 1
        Loop Until lngRow = 1
    End With

实际输出不正确,因为当单元格合并时 t3 将不匹配 t3;t6,我对 col 4 的比较仅适用于非常简单的情况。

实际输出

key ID  CH  Text
no  15  1   t2; t88
yes 15  2   t3; t3; t6

因此,我不得不添加这两个部分来拆分 Concatenate 单元格,然后删除重复项

'split cell in Col d to col e+ delimited by ;
        With Range("D2:D6", Range("D" & Rows.Count).End(xlUp))
            .Replace ";", " ", xlPart
            .TextToColumns other:=True
        End With

 'remove duplicates in each row

    Dim x, y(), i&, j&, k&, s$
    With ActiveSheet.UsedRange
        x = .Value: ReDim y(1 To UBound(x, 1), 1 To UBound(x, 2))
        For i = 1 To UBound(x)
            For j = 1 To UBound(x, 2)
                If Len(x(i, j)) Then
                    If InStr(s & "|", "|" & x(i, j) & "|") = 0 Then _
                       s = s & "|" & x(i, j): k = k + 1: y(i, k) = x(i, j)
                End If
            Next j: s = vbNullString: k = 0
        Next i
        .Value = y()
    End With
    End Sub

附加代码输出是

Pub ID  CH  Ref 
no  15  1   t2  t88
yes 15  2   t3  t6

问题:一定有比使用三种不同方法更容易做到这一点的方法吗?如果第 4 列项目不匹配,如何插入新列 5+?

注意:删除重复代码是从 excelforum 的用户 nilem 那里找到的。

编辑:如果 Col 2 和 3 匹配,则 Col 1 将始终相同。如果解决方案更简单,我们可以假设 Col 1 为空白并忽略数据。

我已经打印了书籍查找表,需要转换为一种简单的格式,该格式将用于使用 1960 年代语言的设备中,该语言的命令非常有限。我正在尝试预先格式化此数据,因此我只需要搜索包含所有信息的一行。

Col D 最终输出可以是带有分隔符的 col D 或 col D-K(最多只有 8 个 Ref),因为我将解析以在其他机器上使用。无论哪种方法都更容易。

【问题讨论】:

  • 我不太明白你的规则,或者为什么你必须对数据进行排序,但是,一般来说,我会使用用户定义的类和集合对象来处理每一行数据,并且然后结合结果得到输出。可能是Destacking Columns的修改
  • 您的文字表明您要比较第 2 列和第 3 列,合并重复项;但是您的示例表明您还想合并第 1 列的重复项。也许您可以更清楚地说明您的规则,并提供更全面的示例。
  • 我在帖子底部澄清了。
  • 谢谢。我发布了一个解决方案作为答案。

标签: vba excel


【解决方案1】:

删除行的规范做法是从底部开始,向顶部工作。以这种方式,不会跳过行。这里的技巧是在当前位置上方找到与列 B 和 C 匹配的行,并在删除行之前连接列 D 中的字符串。有几个很好的工作表公式可以获取两列匹配的行号。使用application.Evaluate 将其中一个付诸实践似乎是从 D 列收集值的最便捷方法。

Sub dedupe_and_collect()
    Dim rw As Long, mr As Long, wsn As String

    With ActiveSheet   '<- set this worksheet reference properly!
        wsn = .Name
        With .Cells(1, 1).CurrentRegion
            .RemoveDuplicates Columns:=Array(2, 3, 4), Header:=xlYes
        End With
        With .Cells(1, 1).CurrentRegion  'redefinition after duplicate removal
            For rw = .Rows.Count To 2 Step -1 'walk backwards when deleting rows
                If Application.CountIfs(.Columns(2), .Cells(rw, 2).Value, .Columns(3), .Cells(rw, 3).Value) > 1 Then
                    mr = Application.Evaluate("MIN(INDEX(ROW(1:" & rw & ")+(('" & wsn & "'!B1:B" & rw & "<>'" & wsn & "'!B" & rw & ")+('" & wsn & "'!C1:C" & rw & "<>'" & wsn & "'!C" & rw & "))*1E+99, , ))")
                    'concatenate column D
                    '.Cells(mr, 4) = .Cells(mr, 4).Value & "; " & .Cells(rw, 4).Value
                    'next free column from column D
                    .Cells(mr, Columns.Count).End(xlToLeft).Offset(0, 1) = .Cells(rw, 4).Value
                    .Rows(rw).EntireRow.Delete
                End If
            Next rw
        End With
    End With
End Sub

删除三列匹配的记录是使用 VBA 等效的 Date ► Data Tools ► Remove Duplicates 命令完成的。这仅考虑 B、C 和 D 列并删除较低的重复项(保留最接近第 1 行的重复项)。如果 A 列在这方面很重要,则必须添加额外的编码。

我不清楚您是否希望 D 列作为分隔字符串或单独的单元格作为最终结果。你能澄清一下吗?

【讨论】:

  • 我澄清了我的原始帖子。我们可以忽略 col 1,Col D final 可以是 col D-J 或在 Col D 中。
  • 谢谢吉普德。你能解释一下 mr 评估行中的逻辑吗? :) 此外,输出在 t2 和 t88 之间为 15 1 行留下空白 col。有什么办法可以在处理过程中删除空白?
  • a) 如果您收到一个空白列,那么单元格中有一个空格或其他一些值(可能是返回零长度字符串的公式),强制额外的列。建议您先清理数据或提供有关导致问题的原因的更多详细信息,以便解决问题。 b) 你不明白公式的哪一部分?这是相当标准的两列匹配,我已经在几十个 StackOverflow 答案中使用过 (examples)。
  • 我两周前才开始使用 VBA,所以这就是我问初学者问题的原因。我刚买了两本书,所以我会复习它们和你的例子。
  • 抱歉,它似乎在没有添加更多数据的空白列的情况下工作。我一定是在凌晨 2 点在餐桌上犯了错误。
【解决方案2】:

正如我在上面所写的,我将遍历数据并将内容收集到用户定义的对象中。这种方法不需要对数据进行排序;并省略重复的REF

用户定义对象的一个​​优点是它使调试更容易,因为您可以更清楚地看到自己所做的事情。

我们将 IDCH 相同的每一行组合起来,如果使用相同的键,则使用 Collection 对象的属性来引发错误。

只要将单个单元格中的 Ref 与分隔符组合在一起,与 D:K 列中的单个单元格相比,都可以简单地完成。我选择了分列,但将其更改为合并为一列将是微不足道的。

插入类模块后,必须重命名:cID_CH

您会注意到我将结果放在单独的工作表上。您可以覆盖原始数据,但我建议不要这样做。

类模块


Option Explicit
Private pID As Long
Private pCH As Long
Private pPUB As String
Private pREF As String
Private pcolREF As Collection

Public Property Get ID() As Long
    ID = pID
End Property
Public Property Let ID(Value As Long)
    pID = Value
End Property

Public Property Get CH() As Long
    CH = pCH
End Property
Public Property Let CH(Value As Long)
    pCH = Value
End Property

Public Property Get PUB() As String
    PUB = pPUB
End Property
Public Property Let PUB(Value As String)
    pPUB = Value
End Property

Public Property Get REF() As String
    REF = pREF
End Property
Public Property Let REF(Value As String)
    pREF = Value
End Property

Public Property Get colREF() As Collection
    Set colREF = pcolREF
End Property

Public Sub ADD(refVAL As String)
    On Error Resume Next
        pcolREF.ADD refVAL, refVAL
    On Error GoTo 0
End Sub

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

常规模块


Option Explicit
Sub CombineDUPS()
    Dim wsSRC As Worksheet, wsRES As Worksheet
    Dim vSRC As Variant, vRES() As Variant, rRES As Range
    Dim cI As cID_CH, colI As Collection
    Dim I As Long, J As Long
    Dim S As String

'Set source and results worksheets and results range
Set wsSRC = Worksheets("sheet1")
Set wsRES = Worksheets("sheet2")
Set rRES = wsRES.Cells(1, 1)

'Get Source data
With wsSRC
    vSRC = .Range("A2", .Cells(.Rows.Count, "D").End(xlUp))
End With

'Collect and combine data
Set colI = New Collection
On Error Resume Next
For I = 1 To UBound(vSRC, 1)
    Set cI = New cID_CH
    With cI
        .PUB = vSRC(I, 1)
        .ID = vSRC(I, 2)
        .CH = vSRC(I, 3)
        .REF = vSRC(I, 4)
        .ADD .REF
        S = CStr(.ID & "|" & .CH)
        colI.ADD cI, S
        If Err.Number = 457 Then
            Err.Clear
            colI(S).ADD .REF
        ElseIf Err.Number <> 0 Then
            Debug.Print Err.Number, Err.Description
            Stop
        End If
    End With
Next I
On Error GoTo 0

'Create and populate Results Array
ReDim vRES(0 To colI.Count, 1 To 11)

'Header row
vRES(0, 1) = "Pub"
vRES(0, 2) = "ID"
vRES(0, 3) = "CH"
vRES(0, 4) = "Ref"

'populate array
For I = 1 To colI.Count
    With colI(I)
        vRES(I, 1) = .PUB
        vRES(I, 2) = .ID
        vRES(I, 3) = .CH
        For J = 1 To .colREF.Count
            vRES(I, J + 3) = .colREF(J)
        Next J
    End With
Next I

'Write the results to the worksheet
Set rRES = rRES.Resize(UBound(vRES, 1) + 1, UBound(vRES, 2))
With rRES
    .EntireColumn.Clear
    .Value = vRES
    With .Rows(1)
        .Font.Bold = True
        .HorizontalAlignment = xlCenter
        Range(.Cells(4), .Cells(11)).HorizontalAlignment = xlCenterAcrossSelection
    End With
    .EntireColumn.AutoFit
End With

End Sub

原创

处理结果

【讨论】:

  • 谢谢!自从我使用 Java 以来已经有十几年了,所以在我编写类似的代码之前,我会看看是否可以让别人让我恢复 OO 类的速度。
  • @equalizer 可以肯定的是,上面的代码是 VBA,而不是 Java。您可以在 Chip Pearson 的网站页面Introduction To Classes 阅读有关 Excel-VBA 中的类的一些信息
【解决方案3】:

变体使用下面的字典

Sub test()
    Dim Dic As Object: Set Dic = CreateObject("Scripting.Dictionary")
    Dic.Comparemode = vbTextCompare
    Dim Cl As Range, x$, y$, i&, Key As Variant
    For Each Cl In Range("B2:B" & Cells(Rows.Count, "B").End(xlUp).Row)
        x = Cl.Value & "|" & Cl.Offset(, 1).Value
        y = Cl.Offset(, 2).Value
        If Not Dic.exists(x) Then
            Dic.Add x, Cl.Offset(, -1).Value & "|" & y & "|"
        ElseIf Dic.exists(x) And Not LCase(Dic(x)) Like "*|" & LCase(y) & "|*" Then
            Dic(x) = Dic(x) & "|" & y & "|"
        End If
    Next Cl
    Range("A2:D" & Cells(Rows.Count, "B").End(xlUp).Row).ClearContents
    i = 2
    For Each Key In Dic
        Cells(i, "A") = Split(Dic(Key), "|")(0)
        Range(Cells(i, "B"), Cells(i, "C")) = Split(Key, "|")
        Cells(i, "D") = Replace(Split(Replace(Dic(Key), "||", ";"), "|")(1), ":", ";")
        i = i + 1
    Next Key
    Set Dic = Nothing
End Sub

之前

之后

【讨论】:

  • 如果我们用 yes 15 2 t6 添加第 7 行,那么 15 2 的输出是 t3;t6;t6(t6 出现两次),你能验证一下吗?
  • @equalizer true,代码已经更新,现在一切正常
猜你喜欢
  • 1970-01-01
  • 2021-02-14
  • 2016-11-30
  • 1970-01-01
  • 1970-01-01
  • 2019-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多