【问题标题】:Excel VBA combine rows without lossExcel VBA合并行而不会丢失
【发布时间】:2016-08-25 01:11:48
【问题描述】:

我需要循环浏览 Excel 工作表上的行,然后将它们组合起来。基本上我一次比较第 1 行,如果它们满足我合并(组合?)它们的参数。

如果数据匹配我们什么都不做,如果有空我们填补空白,如果数据不同,则用逗号分隔符连接它。下面是我正在努力解决的代码部分。这是我第一次做 VBA,我试图找到解决方案,但几天来一直在旋转。

在此先感谢您的帮助。

编辑

用户@Ron Rosenfeld 发现了我犯的一个错误,这可能会增加一些混乱。我在上一段中提到的论点是我的代码中的 if 语句。我想查看单元票号是否与其下方单元格的值匹配,以及单元格日期是否与其下方单元格的值匹配。如果这两个值都匹配,那么我想尽可能优雅地合并数据。

我想要这个

成为

 For i = 2 To myRows.Count

'compare the current row to the one below it while matches are found. if the row under it is no longer a match then exit the loop

Do While doneComparing = False

If Cells(i, ticketNumberCell.Column).Value = Cells(i, ticketNumberCell.Column).Offset(1, 0).Value And Cells(i, dateCell.Column).Value = Cells(i, dateCell.Column).Offset(1, 0).Value Then
    'in here we merge, above we do our comparison
    'the below code is exceptionally slow. we need to speed it up or else this will not work.


'find a another way to merge since this doesnt do correctly anyway. The if statement is fine

'               For b = 1 To ColumnsCount
'                   Cells(i, b).Value = Cells(i, b).Offset(1, 0).Value '+ Cells(i, b).Value
'               Next
'                Rows(i).Offset(1, 0).Delete shift:=xlShiftUp
'
' Else
'     doneComparing = True

  End If
  Loop

'reset the flag for the next row
    doneComparing = False

  Next i

【问题讨论】:

  • 我会怎么做:1.) Make a class 为您的行提供每个列的属性。 2.) 将所有不会合并的属性合并到hash function。 3.) 将行读入Scripting.Dictionary,以哈希为键。 4.) 检查散列键,并将非散列属性与匹配项结合起来。 5.) 立即将整个内容写回工作表。
  • "if they meet the argument " 是什么意思?什么情况会导致合并两个不同的行?
  • 对于king 县,您有两个不同的codes(或一个缺席一个在场),但无论如何您都在合并行。您的结果表明您应该合并不同的列,但 countydate 是相同的。
  • @Ron Rosenfeld 抱歉让 Ron 感到困惑。我删除了我的 cmets,并更改了我的帖子以反映您发现的错误。我想要做的是将票号单元格与其下方的单元格进行比较,并将日期单元格与其下方的单元格进行比较。如果两者都有匹配的值,那么我想尽可能优雅地将数据组合成一行。接得好。抱歉信息不正确。
  • @Comintern 我明白你的意思,但我仍然认为我没有完全理解。可以举个例子吗?

标签: vba excel basic


【解决方案1】:

这是我在上面评论中提到的一个快速而肮脏的示例。我不知道数据代表什么,所以我只是将类称为“RenameMe”。这是类属性的样板代码:

'In RenameMe.cls
Option Explicit

Private mCounty As String
Private mDate As Date
Private mCode As String
Private mDescription As String
Private mTicket As String
Private mType As String

Public Property Let County(inValue As String)
    mCounty = inValue
End Property

Public Property Get County() As String
    County = mCounty
End Property

Public Property Let RecordDate(inValue As Date)
    mDate = inValue
End Property

Public Property Get RecordDate() As Date
    RecordDate = mDate
End Property

Public Property Let Code(inValue As String)
    mCode = inValue
End Property

Public Property Get Code() As String
    Code = mCode
End Property

Public Property Let Description(inValue As String)
    mDescription = inValue
End Property

Public Property Get Description() As String
    Description = mDescription
End Property

Public Property Let Ticket(inValue As String)
    mTicket = inValue
End Property

Public Property Get Ticket() As String
    Ticket = mTicket
End Property

Public Property Let RecordType(inValue As String)
    mType = inValue
End Property

Public Property Get RecordType() As String
    RecordType = mType
End Property

由于您将票证和日期视为不同记录的标准,因此这是一个简单的“散列”函数,可根据此生成唯一键:

'Also in RenameMe.cls
Public Property Get Hash() As String
    Hash = Ticket & CLng(Date)
End Property

最后是加载和组合的方法:

'Also in RenameMe.cls
Public Sub LoadRecord(sheet As Worksheet, loadRow As Long)
    County = sheet.Cells(loadRow, 1).Value
    RecordDate = sheet.Cells(loadRow, 2).Value
    Code = sheet.Cells(loadRow, 3).Value
    Description = sheet.Cells(loadRow, 4).Value
    Ticket = sheet.Cells(loadRow, 5).Value
    RecordType = sheet.Cells(loadRow, 6).Value
End Sub

Public Sub WriteRecord(sheet As Worksheet, writeRow As Long)
    sheet.Cells(writeRow, 1).Value = County
    sheet.Cells(writeRow, 2).Value = RecordDate
    sheet.Cells(writeRow, 3).Value = Code
    sheet.Cells(writeRow, 4).Value = Description
    sheet.Cells(writeRow, 5).Value = Ticket
    sheet.Cells(writeRow, 6).Value = RecordType
End Sub

Public Sub CombineWith(other As RenameMe)
    If Code <> other.Code Then
        Code = Code & "," & other.Code
    End If
    If Description <> other.Description Then
        Description = Description & "," & other.Description
    End If
    If County <> other.County Then
        County = County & "," & other.County
    End If
    If RecordType <> other.RecordType Then
        RecordType = RecordType & "," & other.RecordType
    End If
End Sub

执行工作的实际代码只是将行集加载到以哈希为键的字典中,然后组合键与现有记录匹配的那些:

'In a module
Public Sub CombineRecords()
    Dim data As Worksheet
    Dim records As New Scripting.Dictionary

    Set data = ActiveSheet
    Dim i As Long, record As RenameMe
    For i = 2 To data.Range("A" & data.Rows.Count).End(xlUp).Row
        Set record = New RenameMe
        record.LoadRecord data, i
        If records.Exists(record.Hash) Then
            records.Item(record.Hash).CombineWith record
        Else
            records.Add record.Hash, record
        End If
    Next

    'Output to new sheet. Can easily wipe the current data
    'and replace too.
    Set data = data.Parent.Worksheets.Add

    Dim key As Variant
    i = 1
    For Each key In records
        records.Item(key).WriteRecord data, i
        i = i + 1
    Next
End Sub

【讨论】:

  • 非常感谢,我将在早上尝试这个,如果它有效,我会回来的。这现在更清楚了。
  • @alphamalle - 忘了提及您需要添加对 Microsoft Scripting Runtime 的引用。它在工具->参考...菜单中。向下滚动找到它。
【解决方案2】:

这里是一个有些不同的方法。 “唯一行”存储在字典中,但我们也使用类中的字典来存储多个项目(如果它们应该保存在同一行)。完成后,然后将这些项目放在一起,用逗号分隔的字符串。

另外,通过使用这种方法,不需要预先对行进行排序。

可以使用内置的 VBA Collection 对象,但我认为,特别是对于这么多集合,使用 Dictionary 编码会更简单一些。

我们将确定唯一性的两个项目DateTicketNumber 组合为key

请注意,您需要重命名类模块:cRows,并且您还必须添加对 Microsoft Scripting Runtime 的引用。

类模块

Option Explicit

'Rename to cRows

Private pDT As Date
Private pTicket As String

Private pCounty As String
Private pCounties As Dictionary

Private pCode As String
Private pCodes As Dictionary

Private pDescription As String
Private pDescriptions As Dictionary

Private pTyp As String
Private pTyps As Dictionary

Private Sub Class_Initialize()
'use dictionaries to combine multiple items
    Set pCounties = New Dictionary
    Set pCodes = New Dictionary
    Set pDescriptions = New Dictionary
    Set pTyps = New Dictionary
End Sub

Public Property Get DT() As Date
    DT = pDT
End Property
Public Property Let DT(Value As Date)
    pDT = Value
End Property

Public Property Get Ticket() As String
    Ticket = pTicket
End Property
Public Property Let Ticket(Value As String)
    pTicket = Value
End Property

Public Property Get County() As String
    County = pCounty
End Property
Public Property Let County(Value As String)
    pCounty = Value
End Property
Public Property Get Counties() As Dictionary
    Set Counties = pCounties
End Property
Public Function ADDCounty(Value As String)
    If Value <> "" Then
        If Not pCounties.Exists(Value) Then _
        pCounties.Add Key:=Value, Item:=Value
    End If
End Function

Public Property Get Code() As String
    Code = pCode
End Property
Public Property Let Code(Value As String)
    pCode = Value
End Property
Public Property Get Codes() As Dictionary
    Set Codes = pCodes
End Property
Public Function ADDCode(Value As String)
    If Value <> "" Then
        If Not pCodes.Exists(Value) Then _
        pCodes.Add Value, Value
    End If
End Function


Public Property Get Description() As String
    Description = pDescription
End Property
Public Property Let Description(Value As String)
    pDescription = Value
End Property
Public Property Get Descriptions() As Dictionary
    Set Descriptions = pDescriptions
End Property
Public Function ADDDescription(Value As String)
    If Value <> "" Then
        If Not pDescriptions.Exists(Value) Then _
        pDescriptions.Add Value, Value
    End If
End Function

Public Property Get Typ() As String
    Typ = pTyp
End Property
Public Property Let Typ(Value As String)
    pTyp = Value
End Property
Public Property Get Typs() As Dictionary
    Set Typs = pTyps
End Property
Public Function ADDTyp(Value As String)
    If Value <> "" Then
        If Not pTyps.Exists(Value) Then _
        pTyps.Add Value, Value
    End If
End Function

Public Function CombineDicts(Dict As Dictionary) As String
    Dim W As Variant, S As String

    For Each W In Dict
        S = S & "," & Dict(W)
    Next W
    CombineDicts = Mid(S, 2)
End Function

常规模块

Option Explicit

'Add Reference (Tools/References) to Microsoft Scripting Runtime

Sub DeDupAndCombine()
    Dim cR As cRows, dR As Dictionary
    Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
    Dim vSrc As Variant, vRes As Variant
    Dim I As Long, J As Long, V As Variant, W As Variant
    Dim sKey As String

Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2")
    Set rRes = wsRes.Cells(1, 1)

'Get the Source data
With wsSrc
    vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=6)
End With

'collect and dedup
Set dR = New Dictionary
For I = 2 To UBound(vSrc, 1)
    Set cR = New cRows
    With cR
        .DT = vSrc(I, 2)
        .Ticket = vSrc(I, 5)
        sKey = .DT & "|" & .Ticket

        .County = vSrc(I, 1)
        .ADDCounty .County

        .Code = vSrc(I, 3)
        .ADDCode .Code

        .Description = vSrc(I, 4)
        .ADDDescription .Description

        .Typ = vSrc(I, 6)
        .ADDTyp .Typ

        If Not dR.Exists(sKey) Then
            dR.Add sKey, cR
        Else
            With dR(sKey)
                .ADDCounty cR.County
                .ADDCode cR.Code
                .ADDDescription cR.Description
                .ADDTyp cR.Typ
            End With
        End If
    End With
Next I

'Create the results
ReDim vRes(0 To dR.Count, 1 To 6)

'Headers
For I = 1 To 6
    vRes(0, I) = vSrc(1, I)
Next I

'data
I = 0
For Each V In dR
    I = I + 1
    With dR(V)
        vRes(I, 1) = .CombineDicts(.Counties)
        vRes(I, 2) = .DT
        vRes(I, 3) = .CombineDicts(.Codes)
        vRes(I, 4) = .CombineDicts(.Descriptions)
        vRes(I, 5) = .Ticket
        vRes(I, 6) = .CombineDicts(.Typs)
    End With
Next V

'Write the results
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
    End With
    .EntireColumn.AutoFit
End With

End Sub

如果速度是个问题,您可以尝试看看执行.RemoveDuplicates 方法是否会导致更快的执行时间。您也可以在写入工作表的段之前关闭ScreenUpdating

【讨论】:

    猜你喜欢
    • 2013-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-13
    • 2017-06-18
    • 1970-01-01
    相关资源
    最近更新 更多