【问题标题】:Convert row with columns of data into column with multiple rows in Excel在Excel中将具有数据列的行转换为具有多行的列
【发布时间】:2011-04-11 12:54:22
【问题描述】:

I hv 数据行:-

TAG   SKU   SIZE   GRADE   LOCATION
A001  123    12      A       X1
A002  789    13      B       X3
A003  456    15      C       X5

我需要把它转换成:-

A001   123  SIZE 12
A001   123  GRADE A
A001   123  LOCATION X1
A002   789  SIZE 13
A002   789  GRADE B
A002   789  LOCATION X3
A003   456  SIZE 15
A003   456  GRADE C
A003   456  LOCATION X5

我使用了以下内容(基于 Ben McCormack 于 2009 年 11 月 23 日发布的建议),但没有产生上述结果:-

Sub NormalizeSheet()
Dim wsOriginal As Worksheet
Dim wsNormalized As Worksheet
Dim strKey As String
Dim clnHeader As Collection
Dim lngColumnCounter As Long
Dim lngRowCounterOriginal As Long
Dim lngRowCounterNormalized As Long
Dim rngCurrent As Range
Dim varColumn As Variant

Set wsOriginal = ThisWorkbook.Worksheets("Original")     'This is the name of your original worksheet'
Set wsNormalized = ThisWorkbook.Worksheets("Normalized") 'This is the name of the new worksheet'
Set clnHeader = New Collection

wsNormalized.Cells.ClearContents        'This deletes the contents of the destination worksheet'

lngColumnCounter = 2
lngRowCounterOriginal = 1
Set rngCurrent = wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter)

' We'll loop through just the headers to get a collection of header names'
Do Until IsEmpty(rngCurrent.Value)
    clnHeader.Add rngCurrent.Value, CStr(lngColumnCounter)
    lngColumnCounter = lngColumnCounter + 1
    Set rngCurrent = wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter)
Loop

'Here we'll reset our Row Counter and loop through the entire data set'
lngRowCounterOriginal = 2
lngRowCounterNormalized = 1
lngColumnCounter = 1

Do While Not IsEmpty(wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter))

    Set rngCurrent = wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter)
    strKey = rngCurrent.Value ' Get the key value from the current cell'
    lngColumnCounter = 2

    'This next loop parses the denormalized values for each row'
    Do While Not IsEmpty(wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter))
        Set rngCurrent = wsOriginal.Cells(lngRowCounterOriginal, lngColumnCounter)

        'We're going to check to see if the current value'
        'is equal to NULL. If it is, we won't add it to'
        'the Normalized Table.'
        If rngCurrent.Value = "NULL" Then
            'Skip it'
        Else
            'Add this item to the normalized sheet'
            wsNormalized.Range("A" & lngRowCounterNormalized).Value = strKey
            wsNormalized.Range("B" & lngRowCounterNormalized).Value = clnHeader(CStr(lngColumnCounter))
            wsNormalized.Range("C" & lngRowCounterNormalized).Value = rngCurrent.Value
            lngRowCounterNormalized = lngRowCounterNormalized + 1
        End If

        lngColumnCounter = lngColumnCounter + 1
    Loop
    lngRowCounterOriginal = lngRowCounterOriginal + 1
    lngColumnCounter = 1    'We reset the column counter here because we're on a new row'
Loop



End Sub

【问题讨论】:

  • 您可以将 Excel 文件转换为 CSV 并用您喜欢的语言执行逻辑
  • Bob,你有很多人帮助你构建项目的各个部分,但你没有接受或投票赞成一个答案。给我们一点功劳怎么样?
  • 非常乐意这样做,但我可以在哪里或如何做到这一点?
  • 所有的都在常见问题中解释:stackoverflow.com/faq
  • “投票需要 15 声望”...我猜我还是个新手。

标签: excel vba pivot-table


【解决方案1】:

这是一种直接从工作表转到工作表的方法。如果数据集太大而可用内存太小而无法使用数组,这可能是必要的。它可能很慢。

它使用与 reOrgV1 相同的调用参数,以及几乎相同的逻辑。

已更新以将“缺陷”添加到属性中。输入看起来像:

TAG     SKU   SIZE GRADE LOCATION DEFECTS
A001    123    12   A       X1      3
A002    789    13   B       X3      5
A003    456    15   C       X5      7

这是代码。

Public Sub reOrgV2(inSource As Range, inTarget As Range)
'' This version works directly on the worksheet
'' and transfers the result directly to the target
'' given as the top-left cell of the result.

'' **** Changed to add "Defects"
    Dim resNames()
    Dim propNum As Integer
    Dim srcRows As Integer
    Dim resRows As Integer
    Dim i As Integer
    Dim j As Integer
    Dim g As Integer

    '' Shape the result
    resNames = Array("Size", "Grade", "Location", "Defects")
    propNum = 1 + UBound(resNames)

    '' Row counts
    srcRows = inSource.Rows.Count
    resRows = srcRows * propNum

    '' re-org and transfer source to result range
    inTarget = inTarget.Resize(resRows, 4)
    g = 1
    For i = 1 To srcRows
        For j = 0 To 3
            inTarget.Item(g + j, 1) = inSource.Item(i, 1)      '' Tag
            inTarget.Item(g + j, 2) = inSource.Item(i, 2)      '' SKU
            inTarget.Item(g + j, 3) = resNames(j)              '' Property
            inTarget.Item(g + j, 4) = inSource.Item(i, j + 3)  '' Value
        Next j
        g = g + propNum
    Next i
End Sub

这是修改后的调用范围更广。

'' Call ReOrgV2 with input and output ranges
Public Sub test4()
    Dim i As Integer
    i = Range("InData!A:A").Find("").Row - 2
    reOrgV2 Range("InData!A2").Resize(i, 6), [OutData!A1]
End Sub

【讨论】:

  • 对于 reOrgV1,它在 InData 的第 3 行之后停止。对于 reOrgV2,我 rcv'd 运行时错误'13':类型不匹配与 inTarget.Item(g + j, 2) = Int(inSource.Item(i, 2)) 突出显示。
  • 很好,两者都在工作。但是,输出需要调整为 TAG 的 A 列,SKU 的 B 列,Size、Grade、Location 的 C 列和 12、A、X1 的 D 列。当前输出有 3 列,我需要将其显示在 4 列中。
  • 顺便说一句,如何在不定义范围的情况下开始转换?你看,一些 InData 文件可能只有 2 行和一些,几千行。因此,在执行转换之前不断调整范围有点乏味。
  • 添加了大小输入数组并提示运行时错误“1004”:Range 类的选择方法失败,Range("InData!A2")。选择突出显示。
  • 添加了 test3() 并提示运行时错误 '424: Object required with reOrgV2 Range("InData!A2").Resize(i, 5), [OutData!A1] 突出显示。
【解决方案2】:

您可以将 ADO 与 Excel 一起使用。大致:

Sub ColsToRows()
Dim cn As Object
Dim rs As Object
Dim strFile As String
Dim strCon As String
Dim strSQL As String
Dim s As String
Dim i As Integer, j As Integer

    ''This is not the best way to refer to the workbook
    ''you want, but it is very convenient for notes
    ''It is probably best to use the name of the workbook.

    strFile = ActiveWorkbook.FullName

    ''Note that if HDR=No, F1,F2 etc are used for column names,
    ''if HDR=Yes, the names in the first row of the range
    ''can be used.
    ''This is the Jet 4 connection string, you can get more
    ''here : http://www.connectionstrings.com/excel

     strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Docs\TestBook.xls " _
            & ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"

    ''Late binding, so no reference is needed

    Set cn = CreateObject("ADODB.Connection")
    Set rs = CreateObject("ADODB.Recordset")


    cn.Open strCon

    strSQL = "SELECT [TAG], [SKU], 'SIZE ' & [SIZE] As S, " _
           & "'GRADE ' & [GRADE] As G, 'LOCATION ' & [LOCATION] As L " _
           & "FROM [Sheet1$] a " _
           & "ORDER BY [Tag] "

    rs.Open strSQL, cn, 3, 3


    ''Pick a suitable empty worksheet for the results

    With Worksheets("Sheet3")

        j = 1 '' Row counter

        Do While Not rs.EOF
            For i = 2 To 4
                .Cells(j, 1) = rs!Tag
                .Cells(j, 2) = rs!SKU
                .Cells(j, 3) = rs(i)
                j = j + 1
            Next
            rs.MoveNext
        Loop
    End With

   ''Tidy up
   rs.Close
   Set rs = Nothing
   cn.Close
   Set cn = Nothing

End Sub

【讨论】:

  • 我尝试了上述操作并提示运行时错误'-2147467259 (80004005)': Invalid argument with cn.Open strCon highlight.
  • 再一次,相当标准的 VBA 似乎给您带来了问题,所以我想知道您使用的是哪个版本的 Excel?你的操作系统是什么?
  • 好的,这两个问题的共同点似乎是活动工作表或工作簿。我已编辑 strCon 行以引用工作簿的名称,请将其更改为引用测试工作簿的全名和路径。
  • 我又想到一件事,你的语言环境是什么?
  • 区域设置类似于国家/地区,为应用程序或计算机设置区域设置决定了货币分隔符(或。)以及 VBA 中的一些奇怪的点点滴滴。
【解决方案3】:

这是一个非常简单的解决方案,假设数据集不是很大。它将输入范围转换为数组,将其转换为结果数组,然后将数组移动到指定的目标。目标由左上角的单元格定义。

如果可能,这种方法比直接使用工作表上的单元格要快几个数量级。

底部的测试功能需要您在工作表 InData 上放置一个输入集,并为结果定义一个工作表 OutData,但您的输入和输出范围可以是您想要的任何地方。

Option Explicit

Public Sub reOrgV1(inSource As Range, inTarget As Range)
'' This version uses VBA arrays to do the work.
'' Takes a source range, reorganizes it to the target
''    given as the top-left cell of the result.

    Dim srcArray As Variant
    Dim resArray As Variant
    Dim resNames()
    resNames = Array("SIZE", "GRADE", "LOCATION")

    Dim srcRows As Integer
    Dim resRows As Integer
    Dim i As Integer
    Dim j As Integer
    Dim g As Integer

    '' Move range into source array
    srcArray = inSource.Value
    srcRows = UBound(srcArray, 1)
    resRows = srcRows * 3

    ''Build result array
    ReDim resArray(1 To resRows, 1 To 3)

    '' transfer source to result array
    g = 1
    For i = 1 To srcRows
        For j = 0 To 2
            resArray(g + j, 1) = srcArray(i, 1)
            resArray(g + j, 2) = srcArray(i, 2)
            resArray(g + j, 3) = resNames(j) & " " & srcArray(i, j + 3)
        Next j
        g = g + 3
    Next i

    '' Move the results to the target range
    inTarget.Resize(resRows, 3).Value = resArray
End Sub

Public Sub test1()
    reOrgV1 Range("InData!A2:E4"), Range("OutData!A1")
End Sub

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-27
    • 1970-01-01
    • 1970-01-01
    • 2021-11-17
    • 2021-05-12
    • 1970-01-01
    • 2012-01-28
    相关资源
    最近更新 更多