【问题标题】:excel vba transpose and duplicate headersexcel vba转置和重复标题
【发布时间】:2018-12-27 15:06:24
【问题描述】:

我有一个如下所示的 excel 表

    A        B       C
   1 name   company address
   2 john   apple   london
   3 jack   microsoft   kent
   4 ahmed  spacex  ca

但是我需要把它转换成下面的

    A
   1 name
   2 john
   3 company
   4 apple
   5 address
   6 london
   7 name
   8 jack
   9 company
   10 microsoft
   11 address
   12 kent
   13 name
   14 ahmed
   15 company
   16 spacex
   17 address
   18 ca

如何使用 VBA 来实现?主要问题似乎是重复名称之类的标题,因为每个名称都需要在一列中的所有标题上方都有一个标题,我们将不胜感激。

【问题讨论】:

  • 如果我正确地阅读了您的帖子,那么@chillin 拥有最好的答案,如果它有效,您应该接受它。您应该修改输出以使其看起来像实际结果。请下次尝试发布一些代码。

标签: excel vba multiple-columns transpose


【解决方案1】:

假设您的示例(在您的帖子中)位于名为 "Sheet2" 的工作表上,代码将尝试将转置后的数组输出到 E 列(因此您可能希望在运行之前保存/制作副本)。

Option Explicit

Private Sub TransposeWithRepeatingHeaders()

    With ThisWorkbook.Worksheets("Sheet2")

        Dim inputArray() As Variant
        inputArray = .Range("A1:C4").Value2

        Dim rowCountInOutput As Long
        ' Multiplied by two because each item will be preceded by a "header"
        rowCountInOutput = (UBound(inputArray, 1) - 1) * UBound(inputArray, 2) * 2

        Dim outputArray() As Variant
        ReDim outputArray(1 To rowCountInOutput, 1 To 1)

        Dim readRowIndex As Long
        Dim readColumnIndex As Long
        Dim writeIndex As Long

        For readRowIndex = (LBound(inputArray, 1) + 1) To UBound(inputArray, 1) ' Skip header on first row
            For readColumnIndex = LBound(inputArray, 2) To UBound(inputArray, 2)

                writeIndex = writeIndex + 1
                outputArray(writeIndex, 1) = inputArray(1, readColumnIndex) ' Assumes headers are on first row of inputArray

                writeIndex = writeIndex + 1
                outputArray(writeIndex, 1) = inputArray(readRowIndex, readColumnIndex)

            Next readColumnIndex
        Next readRowIndex

        .Range("E1").Resize(UBound(outputArray, 1), UBound(outputArray, 2)).Value2 = outputArray
    End With
End Sub

编辑:如果您需要处理更大的数组/范围,下面的代码可能是更好的方法。目前,堆叠数组将写入源数据右侧的两列(如果需要,请更改此设置)。

您可以调整常量MAXIMUM_CHUNK_SIZE(在任何给定时间要处理的最大行数)来查看您的机器可以处理什么。我想如果它太小,代码将需要更长的时间才能完成,如果它太大,您可能会遇到内存问题。 10000 可能是一个不错的起点,我不知道。

Option Explicit

Private Sub StackWithRepeatingHeaders()

    Const MAXIMUM_CHUNK_SIZE As Long = 10000 ' More specifically, the maximum number of rows to consume per iteration

    With ThisWorkbook.Worksheets("Sheet2")
        Dim inputRange As Range
        Set inputRange = .Range("A1:Z20000") ' Include headers please

        Dim columnHeaders As Variant
        columnHeaders = Application.Index(inputRange, 1, 0)

        Dim inputColumnCount As Long
        inputColumnCount = inputRange.Columns.Count

        ' Store only the "body", as "headers" are being stored in their own array
        Set inputRange = inputRange.Offset(1, 0).Resize(inputRange.Rows.Count - 1, inputColumnCount)

        Dim inputRowCount As Long
        inputRowCount = inputRange.Rows.Count

        Dim totalOutputRowCount As Long ' Multiplied by two because each item will be preceded by a "header"
        totalOutputRowCount = inputRowCount * inputColumnCount * 2

        If totalOutputRowCount > .Rows.Count Then
            MsgBox ("There are not enough rows in this sheet to stack this range (" & Format$(totalOutputRowCount, "#,###") & " rows required). Code will stop running now.")
            Exit Sub
        End If

        Dim firstOutputCell As Range ' Stack from this cell downward
        Set firstOutputCell = .Cells(1, inputRange.Columns(inputRange.Columns.Count).Column + 2) ' +2 could error if inputrange ends near last column of sheet
    End With

    Dim outputArray() As Variant
    ReDim outputArray(1 To (MAXIMUM_CHUNK_SIZE * inputColumnCount * 2), 1 To 1)

    Dim chunkStartIndex As Long
    For chunkStartIndex = 1 To inputRowCount

        Dim currentChunkSize As Long
        currentChunkSize = Application.Min(MAXIMUM_CHUNK_SIZE, inputRowCount - chunkStartIndex + 1)

        Dim inputArray() As Variant
        inputArray = inputRange.Offset(chunkStartIndex - 1, 0).Resize(currentChunkSize, inputColumnCount).Value2 ' -1 as 0-based

        If currentChunkSize <> MAXIMUM_CHUNK_SIZE Then
            ' Think this line will only run on the last iteration (when "remaining rows" might be < MAXIMUM_CHUNK_SIZE)
            ' Avoids needless Redims
            ReDim outputArray(1 To (currentChunkSize * inputColumnCount * 2), 1 To 1)
        End If

        Dim readRowIndex As Long
        Dim readColumnIndex As Long

        Dim arrayWriteIndex As Long
        arrayWriteIndex = 0

        For readRowIndex = 1 To currentChunkSize
            For readColumnIndex = 1 To inputColumnCount

                arrayWriteIndex = arrayWriteIndex + 1
                outputArray(arrayWriteIndex, 1) = columnHeaders(1, readColumnIndex)

                arrayWriteIndex = arrayWriteIndex + 1
                outputArray(arrayWriteIndex, 1) = inputArray(readRowIndex, readColumnIndex)

            Next readColumnIndex
        Next readRowIndex

        Dim sheetWriteIndex As Long
        firstOutputCell.Offset(sheetWriteIndex, 0).Resize(UBound(outputArray, 1), UBound(outputArray, 2)).Value2 = outputArray
        sheetWriteIndex = sheetWriteIndex + (currentChunkSize * inputColumnCount * 2)

        chunkStartIndex = chunkStartIndex + currentChunkSize - 1
    Next chunkStartIndex

End Sub

【讨论】:

  • 非常感谢chillin - 您的代码运行良好,但是当我将其缩放到一直到 Range("A1:J16372") 的工作表时,我得到一个内存不足错误 7
  • @MH731Z,我认为一种解决方案(针对这种规模的工作)是分块而不是一次性处理/使用inputArray。让我自己生成一些虚拟数据并回复您。
  • @MH731Z,已编辑答案以包含更多代码。尽可能尝试一下,看看您是否仍然遇到内存问题。在运行代码和/或在运行代码之前重新启动计算机时关闭后台应用程序也可能会有所帮助。
  • 不幸的是,即使我有超过 11gb 的可用内存 ram,我也会遇到同样的错误,而且它似乎正在 AB 列中创建一个条目
  • @MH731Z 奇怪的是我能够在更大的范围内运行第一个代码(比如 100k 行乘 20 列),所以我觉得第二段代码没有必要。我可以很好地处理数组,但由于行数不足,我无法将堆叠的数组写入工作表。尝试逐行执行代码(在编辑器中使用键盘上的F8 键)。这应该向您显示导致错误的行。另外,检查您是否有任何工作表事件 - 或将答案中的代码和一些测试数据(相同规模)放入新工作簿中,看看是否仍然出现错误。
【解决方案2】:

尝试将其调整为您的工作表名称:

Sub ReConfigure()
    Dim s1 As Worksheet, s2 As Worksheet, h1 As String, h2 As String, h3 As String
    Dim i As Long, j As Long, N As Long

    Set s1 = Sheets("Sheet1")
    Set s2 = Sheets("Sheet2")
    j = 1

    With s1
        h1 = .Range("A1")
        h2 = .Range("B1")
        h3 = .Range("C1")
        N = .Cells(Rows.Count, "A").End(xlUp).Row

        For i = 2 To N
            s2.Cells(j, 1) = h1
            j = j + 1
            s2.Cells(j, 1).Value = .Cells(i, 1).Value
            j = j + 1
            s2.Cells(j, 1) = h2
            j = j + 1
            s2.Cells(j, 1).Value = .Cells(i, 2).Value
            j = j + 1
            s2.Cells(j, 1).Value = h3
            j = j + 1
            s2.Cells(j, 1).Value = .Cells(i, 3).Value
            j = j + 1
        Next i
    End With
End Sub

我使用Sheet1 作为输入,Sheet2 作为输出。

【讨论】:

  • 谢谢,如何更改才能覆盖到 J 的所有列?
【解决方案3】:

您可以试试这个(将“mySheetName”更改为您的实际工作表名称):

Sub TransposeAndDuplicateHeaders()
    Dim arr As Variant

    With Worksheets("mySheetName")
        arr = .UsedRange.Value
        .UsedRange.ClearContents

        Dim i As Long, j As Long
        For i = 2 To UBound(arr, 1)
            For j = 1 To UBound(arr, 2)
                .Cells((i - 1) * UBound(arr, 2) + (j - 1) * 2 + 1, 1).Value = arr(1, j)
                .Cells((i - 1) * UBound(arr, 2) + (j - 1) * 2 + 2, 1).Value = arr(i, j)
            Next
        Next
    End With
End Sub

警告:这将清除“mySheetName”表的原始内容,因此请进行备份

【讨论】:

  • 我试过了,但即使我有 11gb 的可用内存,也会出现内存不足的错误
猜你喜欢
  • 1970-01-01
  • 2017-02-28
  • 1970-01-01
  • 2013-08-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-24
  • 2012-12-14
  • 1970-01-01
相关资源
最近更新 更多