【问题标题】:De-Stacking columns in Excel using VBA使用 VBA 在 Excel 中取消堆叠列
【发布时间】:2015-05-14 08:13:46
【问题描述】:

我有一个三列数据集,第一列是一组重复的 UUID,第二列是每个 UUID 的字符串响应,第三列是每个响应的代码。我需要将其分解为多组列,每个重复的 UUID 组一个。见下图:

我有:

UUID    RESPONSE    Resp. Code 
id1     String1     Code1
id2     String2     Code7
id3     String3     Code3
id1     String4     Code3
id2     String5     Code5
id3     String6     Code1

我需要:

UUID    RESPONSE    Resp. Code  RESPONSE    Resp. Code 
id1     String1     Code1       String4     Code3
id2     String2     Code7       String5     Code5
id3     String3     Code3       String6     Code1

请注意,虽然这里显示了 3 个 UUID,但我实际上处理的是 1377。

我试图为这个操作编写一个宏(粘贴在下面),但我完全是 VBA 和 Excel 宏的菜鸟,所以它很老套,甚至无法关闭我想要的功能。

    Sub DestackColumns()
    Dim rng As Range
    Dim iCell As Integer
    Dim lastCol As Integer
    Dim iCol As Integer

    Set rng = ActiveCell.CurrentRegion
    lastCol = rng.Rows(1).Columns.Count

    For iCell = 3 To rng.Rows.Count Step 3
        Range(Cells(1, iCell), Cells(2, iCell)).Cut
        ActiveSheet.Paste Destination:=Cells(lastCol, 1)
    Next iCell
    End Sub

感谢所有帮助!

【问题讨论】:

  • 这很简单,但是我们需要 UUID 列的模式............是否有 8 组值 1-7还是有 11 组值 1-23 等??
  • 共有69套。 UUID 值不是值,而是随机生成的字符串。谢谢!

标签: vba excel excel-2010 excel-2007


【解决方案1】:

实现此目的的 VBA 代码如下:

Sub DestackColumns()
    Dim Source As Worksheet
    Dim Output As Worksheet
    Dim DistArr As Variant
    Dim i As Integer
    Dim j As Integer
    Dim OutRow As Integer

    Set Source = ActiveSheet
    Sheets.Add After:=ActiveWorkbook.Sheets(ActiveSheet.Index)
    Set Output = ActiveSheet

    Output.Name = "Destack"
    Output.Range("A1").Value = "UUID"

    'Find distinct UUID's
    DistArr = ReturnDistinct(Source.Range("A2:" & Source.Cells(Rows.Count, 1).End(xlUp).Address))

    'Loop through distinct UUID's
    For i = LBound(DistArr) To UBound(DistArr)
        OutRow = Output.Cells(Rows.Count, 1).End(xlUp).Row + 1
        Output.Cells(OutRow, 1).Value = DistArr(i)

        'Loop source sheet
        For j = 2 To Source.Cells(Rows.Count, 1).End(xlUp).Row
            'IF UUID match
            If Source.Cells(j, 1).Value = DistArr(i) Then
                'Insert values
                Output.Cells(OutRow, Columns.Count).End(xlToLeft).Offset(0, 1).Value = Source.Cells(j, 2).Value
                Output.Cells(OutRow, Columns.Count).End(xlToLeft).Offset(0, 1).Value = Source.Cells(j, 3).Value
            End If
        Next j
    Next i

End Sub


Private Function ReturnDistinct(InpRng) As Variant
    Dim Cell As Range
    Dim i As Integer
    Dim DistCol As New Collection
    Dim DistArr()

    If TypeName(InpRng) <> "Range" Then Exit Function

    'Add all distinct values to collection
    For Each Cell In InpRng
        On Error Resume Next
        DistCol.Add Cell.Value, CStr(Cell.Value)
        On Error GoTo 0
    Next Cell

    'Write collection to array
    ReDim DistArr(1 To DistCol.Count)
    For i = 1 To DistCol.Count Step 1
        DistArr(i) = DistCol.Item(i)
    Next i

    ReturnDistinct = DistArr
End Function

此代码会将新数据结构放置在新工作表上(即不会覆盖您的原始数据),使用此代码您无需担心数据是否正确排序。

【讨论】:

  • 漂亮!非常感谢索伦!这非常有效(尽管它忽略了除第一个之外的所有标题,但手动输入这些标题很简单)。我会玩这个并继续发展我对宏的理解!
  • 实际上,我遇到了一个问题。最初测试您的代码时,我在我创建的一个小型测试数据集上运行它,以免在处理另一个项目时消耗处理能力。在完整数据集上运行它时,它会返回“400”错误,并且只会将 UUID 标头和第一个 UUID 粘贴到新工作表中。在仅包含前 1377 行(不应有重复)的集合上运行时,它仍然返回 400 错误并且仅返回前 571 行。有什么建议吗?感谢您迄今为止的帮助!
  • 添加错误catch返回错误描述后,400错误变为“Application-defined or object-defined error”。
【解决方案2】:

这里是一个有些不同的方法。我已经建立了一个名为 cUUID 的用户定义类。该类具有 UUID、Response、ResponseCode 和由成对的 Response 和 ResponseCode 组成的 Collection 的属性。

我们创建这个类对象的集合,其中集合的每个成员都是一个特定的 UUID(因为这就是您想要对它们进行分组的方式)。

代码遍历您的数据源,“即时”创建这些对象。然后我们创建一个包含所有结果的数组,并将这个数组写入不同的工作表。

如何更改这些工作表名称以及(如有必要)源数据和结果的位置应该在代码中很明显。

插入类模块后,必须选择它,F4 并将其重命名为 cUUID

类模块

Option Explicit
Private pUUID As String
Private pResponse As String
Private pRespCode As String
Private pCol As Collection

Public Property Get UUID() As String
    UUID = pUUID
End Property
Public Property Let UUID(Value As String)
    pUUID = Value
End Property

Public Property Get Response() As String
    Response = pResponse
End Property
Public Property Let Response(Value As String)
    pResponse = Value
End Property

Public Property Get RespCode() As String
    RespCode = pRespCode
End Property
Public Property Let RespCode(Value As String)
    pRespCode = Value
End Property

Public Property Get Col() As Collection
    Set Col = pCol
End Property

Public Sub Add(Resp1 As String, RC As String)
    Dim V(1 To 2) As Variant
    V(1) = Resp1
    V(2) = RC
    Col.Add V
End Sub

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


Private Sub Class_Terminate()
    Set pCol = Nothing
End Sub

常规模块

Option Explicit
Sub ConsolidateUUIDs()
    Dim cU As cUUID, colU As Collection
    Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
    Dim vSrc As Variant, vRes() As Variant
    Dim RespPairs As Long
    Dim I As Long, J As Long

Set wsSrc = Worksheets("Sheet1")
Set wsRes = Worksheets("Sheet2")
Set rRes = wsRes.Cells(1, 1)

With wsSrc
    vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, "C").End(xlUp))
End With

'Collect the data
Set colU = New Collection
RespPairs = 1
On Error Resume Next
For I = 2 To UBound(vSrc)
    Set cU = New cUUID
    With cU
        .UUID = vSrc(I, 1)
        .Response = vSrc(I, 2)
        .RespCode = vSrc(I, 3)
        .Add .Response, .RespCode
        colU.Add cU, CStr(.UUID)
        Select Case Err.Number
            Case 457
                Err.Clear
                colU(CStr(.UUID)).Add .Response, .RespCode
                J = colU(CStr(.UUID)).Col.Count
                RespPairs = IIf(J > RespPairs, J, RespPairs)
            Case Is <> 0
                Debug.Print Err.Number, Err.Description
                Stop
        End Select
    End With
Next I
On Error GoTo 0

'Sort Collection by UUID
CollectionBubbleSort colU, "UUID"

'Create Results Array
ReDim vRes(0 To colU.Count, 0 To RespPairs * 2)

'header row
vRes(0, 0) = "UUID"
For J = 0 To RespPairs - 1
    vRes(0, J * 2 + 1) = "RESPONSE"
    vRes(0, J * 2 + 2) = "Resp.Code"
Next J

'Data rows
For I = 1 To colU.Count
    With colU(I)
        vRes(I, 0) = .UUID
        For J = 1 To colU(I).Col.Count
            vRes(I, (J - 1) * 2 + 1) = colU(I).Col(J)(1)
            vRes(I, (J - 1) * 2 + 2) = colU(I).Col(J)(2)
        Next J
    End With
Next I

'Write the results array
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2) + 1)
With rRes
    .EntireColumn.Clear
    .Value = vRes
    With .Rows(1)
        .Font.Bold = True
        .HorizontalAlignment = xlCenter
    End With
    .EntireColumn.AutoFit
End With

End Sub

'-------------------------------------------------------
'Could use faster sort routine if necessary
Sub CollectionBubbleSort(TempCol As Collection, Optional Prop As String = "")
'Must manually insert element of collection to sort on in this version
    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 Prop = "" Then

            ' If the element is greater 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
Else
        If CallByName(TempCol(I), Prop, VbGet) > CallByName(TempCol(I + 1), Prop, VbGet) Then
                NoExchanges = False
                TempCol.Add TempCol(I), after:=I + 1
                TempCol.Remove I
            End If
End If
        Next I
    Loop While Not (NoExchanges)
End Sub

UUID 将按字母顺序排序。 该代码应该适用于不同数量的 UUID,以及对每个 UUID 的不同数量的响应。

【讨论】:

  • 它只对前 690 个 UUID 有效,但我只是剪掉了列表并运行了两次,效果很好!非常感谢!
  • @AidanLambert 很高兴它成功了。但我很好奇——第一个 690 之后发生了什么?你收到错误信息了吗?数据有什么不同吗?还有什么?这可能是一个简单的修复。
  • 嗨罗恩!对不起,我应该更具体。我完全不知道是什么导致了这种行为,但我怀疑这与我工作的机器的限制有关(一个老式的 MBP 为 mac 运行 excel)。我之前在这台机器上在大型数据集上运行宏时观察到类似的行为。
  • 没有返回错误,结果只是在我第一次运行时在第 554 个条目处结束(iirc,该文件已被编辑,我无法轻松验证确切数字:这个项目是有点超出了我公司通常的范围,我承认我们在版本控制方面做得很差)。第二次迭代(没有在第一次中分解数据)运行了 691。最后一次运行了 131,但那是因为只剩下这些了。
  • @AidanLambert 非常感谢您提供的信息。是否有机会获得原始数据的副本(敏感信息已编辑),所以我可以尝试在我的机器上运行它?我很惊讶它会在没有某种错误消息的情况下停止,除非选择要处理的区域的例程存在问题。
【解决方案3】:

您的示例代码表明您希望删除原始值以支持新矩阵。为此,我建议先在数据副本上运行它。

Sub stack_horizontally()
    Dim rw As Long, mrw As Long

    With ActiveSheet   '<-set this worksheet name properly!
        For rw = .Cells(Rows.Count, 1).End(xlUp).Row To 3 Step -1
            mrw = Application.Match(.Cells(rw, 1), .Columns(1), 0)
            If mrw < rw Then
                .Cells(mrw, Columns.Count).End(xlToLeft).Offset(0, 1) = .Cells(rw, 2).Value
                .Cells(mrw, Columns.Count).End(xlToLeft).Offset(0, 1) = .Cells(rw, 3).Value
                .Rows(rw).Delete
            End If
        Next rw
    End With
End Sub

我没有将标题填充到新列中,但这应该是一个小的手动操作。

【讨论】:

  • Søren 的代码给了我现在需要的东西,但我也会玩这个!谢谢!
  • 不用担心。顺便说一句,在您的原始代码中,像Cells(1, iCell) 这样的单元格引用的使用应该是Cells(iCell, 1)。你翻转了行和列。请参阅Range . Cells Property ( Excel ) 了解更多信息。
  • 这说明了很多。谢谢!
猜你喜欢
  • 1970-01-01
  • 2018-12-09
  • 1970-01-01
  • 2019-12-30
  • 1970-01-01
  • 2016-12-08
  • 2019-01-08
  • 2023-01-11
  • 1970-01-01
相关资源
最近更新 更多