如何将数据从行格式化为列

时间:2017-10-06 16:47:03

标签: excel vba

我可以在R中完成此任务,但我的工作讨厌除excel之外的所有其他程序。我知道excel有VBA但老实说我不明白。是否有代码可以格式化这些数据而无需手动移动它?

编辑:我添加到我的数据中,表明基本上每个姓名都被要求列出他们拥有的每个帐户并回答7个问题。答案可能因文本框而异。

当前数据

Name Acct Question Answer
ABC   1      1       A
ABC   1      2       A
ABC   1      3       A
ABC   1      4       A
ABC   1      5       A
ABC   1      6       A
ABC   1      7       A
ABC   2      1       A
ABC   2      2       A
ABC   2      3       A
ABC   2      4       A

我需要它看起来像这样。

Name Type 1 2 3 4 5 6 7
ABC   1   A A A A A A A 
ABC   2   A A A A A A A

1 个答案:

答案 0 :(得分:0)

我很好奇,所以我试图模仿Transpose - 函数:


Option Explicit
Option Base 1

' Q at https://stackoverflow.com/questions/46610421/how-to-format-data-from-rows-to-columns

'
' How I understand the needed functionality :
'
' 1. search for all seperate names in 'Name'
'    -> create rows with  for different found names
'    -> and for each found name seperate rows for each 'Acct/Type'
' 2. search in 'Acct' for the highest number
'    -> number of rows for each seperate 'Name'
' 3. search in 'Question' for the highest number
'    -> create column headers as many as the highest number
' 4. search for 'Answer' for the combination 'Name/Acct/Question'
'    -> put result in 'Name/Type/column-number'
' 5. by NOT using the function 'Transpose', this functionality allows
'    -> to have different number of answers to the questions
'    ( see example on the bottom of the code in 'vba_window_direct_v01' )
'


Public Sub f11()
Const initValue As String = "-init-"
Dim Cell
Dim SourceRange As Range
Dim TargetRange As Range
Dim SourceNames() As String

Dim CurrentValue As String
Dim PreviousValue As String
Dim ArrayIndex As Long

Dim RowCount As Long
Dim MaxAcct As Long
Dim MaxQuestionNumber As Long
Dim AcctOrTypeCounter As Long
Dim QuestionCounter As Long

Dim SourceTable_FirstCell_Address As String
Dim TargetTable_FirstCell_Address As String

    Sheets("Page03").Select         ' select the worksheet whit the data
    Sheets("Page03").Activate       ' so ActiveSheet is where I perform the functionality

    SourceTable_FirstCell_Address = "B3"    ' my location for 'Name' of the source-table
    TargetTable_FirstCell_Address = "G3"    ' my location for 'Name' of the target-table
    '
    ' select first column with the different names
    '
    Set SourceRange = Range(SourceTable_FirstCell_Address)
    ' to avoid processing too much empty rows, only select the rows from 'CurrentRegion'
    RowCount = SourceRange.CurrentRegion.Rows.Count
    Set SourceRange = Range(SourceRange, SourceRange.Offset(RowCount, 0))
    ' if too much rows, warn the user
    If RowCount > 100 Then
        If MsgBox("are you sure to process " & RowCount & " rows ?" & vbCrLf & _
                    "It could take a while ;-)", vbYesNo + vbDefaultButton2)  vbYes Then
            End
        End If
    End If
    '
    ' loop thru the first column
    '
    CurrentValue = ""
    PreviousValue = ""
    ReDim Preserve SourceNames(1)   ' need to initialise to 1, otherwise UBound will return an error
    SourceNames(1) = initValue      ' put a value in this, in order to be able to test if array is empty or so
    '
    For Each Cell In SourceRange
        CurrentValue = Cell.Value
        If CurrentValue  PreviousValue Then
            If CurrentValue  "" And CurrentValue  "Name" Then
                SourceNames(UBound(SourceNames)) = CurrentValue
                ReDim Preserve SourceNames(UBound(SourceNames) + 1)
                SourceNames(UBound(SourceNames)) = initValue
                PreviousValue = CurrentValue
            End If
        End If
    Next
    '
    ' print out array with found names
    '
    For ArrayIndex = LBound(SourceNames) To UBound(SourceNames)
        Debug.Print "'" & ArrayIndex & " : " & SourceNames(ArrayIndex)
    Next
    '
    ' second column // 'Acct' or 'Type'
    '
    Set SourceRange = Range(SourceTable_FirstCell_Address)
    Set SourceRange = SourceRange.Offset(0, 1)   ' go to next column
    Set SourceRange = SourceRange.Offset(1, 0)   ' this column starts with 'Acct', so go to next row
    ' this RowCount will have a too high value, but, the essence is, we are not processing 10.000+ rows ;-)
    RowCount = SourceRange.CurrentRegion.Rows.Count
    Set SourceRange = Range(SourceRange, SourceRange.Offset(RowCount, 0))
    '
    MaxAcct = 0
    For Each Cell In SourceRange
        CurrentValue = Cell.Value
        If Val(CurrentValue) > MaxAcct Then
            MaxAcct = Val(CurrentValue)
        End If
    Next
    '
    Debug.Print "' Max number of Acct or Type : " & MaxAcct

    '
    ' thirth column // 'Question'
    '
    Set SourceRange = Range(SourceTable_FirstCell_Address)
    Set SourceRange = SourceRange.Offset(0, 2)   ' go to thirth column
    Set SourceRange = SourceRange.Offset(1, 0)   ' this column starts with 'Question', so go to next row
    ' this RowCount will have a too high value, but, the essence is, we are not processing 10.000+ rows ;-)
    RowCount = SourceRange.CurrentRegion.Rows.Count
    Set SourceRange = Range(SourceRange, SourceRange.Offset(RowCount, 0))
    '
    MaxQuestionNumber = 0
    For Each Cell In SourceRange
        CurrentValue = Cell.Value
        If Val(CurrentValue) > MaxQuestionNumber Then
            MaxQuestionNumber = Val(CurrentValue)
        End If
    Next

    Debug.Print "' Max number of Question : " & MaxQuestionNumber

    '
    ' first, clear out old results
    '
    Set TargetRange = Range(TargetTable_FirstCell_Address)
    Set TargetRange = TargetRange.CurrentRegion
    Application.CutCopyMode = False
    TargetRange.Delete Shift:=xlToLeft
    '
    ' create a TargetTable like 'Name/Type/1..MaxQuestionNumber'
    '
    Set TargetRange = Range(TargetTable_FirstCell_Address)
    TargetRange.FormulaR1C1 = "Name"
    Set TargetRange = TargetRange.Offset(0, 1)
    TargetRange.FormulaR1C1 = "Type"
    For ArrayIndex = 1 To MaxQuestionNumber
        Set TargetRange = TargetRange.Offset(0, 1)
        TargetRange.FormulaR1C1 = ArrayIndex
    Next
    '
    ' create the rows with the 'Name' and 'Type' in the TargetTable
    '
    Set TargetRange = Range(TargetTable_FirstCell_Address)
    Set TargetRange = TargetRange.Offset(1, 0)              ' skip title 'Name', go to next row
    For ArrayIndex = LBound(SourceNames) To UBound(SourceNames)
        Debug.Print "'" & ArrayIndex & " : " & SourceNames(ArrayIndex)
        If SourceNames(ArrayIndex) = initValue Then
            ' skip / exit
            Exit For
        Else
            For AcctOrTypeCounter = 1 To MaxAcct
                TargetRange.Value = SourceNames(ArrayIndex)
                TargetRange.Offset(0, 1).Value = AcctOrTypeCounter
                Set TargetRange = TargetRange.Offset(1, 0) ' go to next row
            Next
        End If
    Next

    '
    ' Now copying the values of the answers from the sourcetable to the targettable
    '
    Set SourceRange = Range(SourceTable_FirstCell_Address)
    Set SourceRange = SourceRange.Offset(1, 0)
    Do While SourceRange.Offset(0, 3).Value  ""
        'Debug.Print "'Source 0,3 := " & SourceRange.Offset(0, 3).Value

        ' go to the right name
        Set TargetRange = Range(TargetTable_FirstCell_Address)
        Do While TargetRange.Value  SourceRange.Offset(0, 0).Value
            Set TargetRange = TargetRange.Offset(1, 0)
        Loop

        ' go to the right Acct/Type
        If Val(SourceRange.Offset(0, 1)) > 1 Then
            For AcctOrTypeCounter = 2 To Val(SourceRange.Offset(0, 1))
                Set TargetRange = TargetRange.Offset(1, 0)
            Next
        End If

        ' go to the wright column with the question-number
        Set TargetRange = TargetRange.Offset(0, 1) ' first go from column with 'Name' to 'Type'
        For AcctOrTypeCounter = 1 To Val(SourceRange.Offset(0, 2))
            Set TargetRange = TargetRange.Offset(0, 1)
        Next

        'TargetRange.Select
        TargetRange.Value = SourceRange.Offset(0, 3).Value

        ' select next row / select next answer
        Set SourceRange = SourceRange.Offset(1, 0)
    Loop

    ' set font to 'courier new' and align horizontally to the center
    Set TargetRange = Range(TargetTable_FirstCell_Address)
    Set TargetRange = TargetRange.CurrentRegion
    TargetRange.Font.Name = "Courier New"
    TargetRange.HorizontalAlignment = xlCenter

End Sub

Public Sub vba_window_direct_v01()

' Sample Table, added some lines with 'DEF' and 'GHI'
' Numbers behind each answers are only to keep track of the working of the function

'Name    Acct    Question    Answer
'ABC        1       1           A1
'ABC        1       2           A2
'ABC        1       3           A3
'ABC        1       4           A4
'ABC        1       5           A5
'ABC        1       6           A6
'ABC        1       7           A7
'ABC        2       1           A8
'ABC        2       2           A9
'ABC        2       3           A10
'ABC        2       4           A11
'DEF        1       6           B12
'DEF        1       7           B13
'DEF        1       8           B14
'DEF        2       1           B15
'DEF        2       2           B16
'GHI        1       1           C17
'GHI        2       1           C18
'GHI        3       1           C19
'GHI        3       2           C20


' Sample of table with transposed results

'Name    Type    1   2   3   4   5   6   7   8
'ABC        1   A1  A2  A3  A4  A5  A6  A7
'ABC        2   A8  A9  A10 A11
'ABC        3
'DEF        1                       B12 B13 B14
'DEF        2   B15 B16
'DEF        3
'GHI        1   C17
'GHI        2   C18
'GHI        3   C19 C20

End Sub