【问题标题】:Transpose Data from every other Row to Columns将每隔一行的数据转置到列
【发布时间】:2015-11-29 09:39:15
【问题描述】:

我在 MS Excel 中有大量导入的电信数据,现有数据字段位于 A1:L1 行,部分数据已存在于 A:H 行;我需要转置的剩余数据位于 A 列,每个记录的第 2 到第 5 个单元格中,每个唯一记录之间有一个空格(空白单元格)。 (见图1)

数据表字段标题

NPA-NXX |状态 |公司 | OCN |价格中心 | CLLI |分配日期 |前缀类型 |开关名称 |开关类型 |拉丁美洲 |串联

单元格:A2:H2

318-704 |洛杉矶 | CEBRIDGE TELECOM LA, LLC D/B/A | 260小时 |亚历山大 | ALXNLAMAXKX | 2013 年 11 月 15 日 |前缀类型:CLEC

单元格:A3:A6

交换机名称:不适用
开关类型:不适用
拉丁美洲:洛杉矶什里夫波特 (486)
串联:不适用

TelcoDataImage1 - Rows and Columns separated by a Space and Existing Data Fields

我想使用 MS Excel 公式(最好)或 VBA 代码,将每个数据记录的第 2 列转置到第 5 列,并将数据转置到相邻的 I:L 行。 (见图2)

TelcoData2 - Transpose 2nd - 5th column data to adjacent rows

数据表字段标题

NPA-NXX |状态 |公司 | OCN |价格中心 | CLLI |分配日期 |前缀类型 |开关名称 |开关类型 |拉丁美洲 |串联

Cell: A2:L2(新输出的转置数据)

318-704 |洛杉矶 | CEBRIDGE TELECOM LA, LLC D/B/A | 260小时 |亚历山大 | ALXNLAMAXKX | 2013 年 11 月 15 日 |前缀类型:CLEC |开关名称:不适用 |开关类型:不适用 |拉丁美洲:什里夫波特 LA (486) |串联:不适用

我是一名 Excel 人员,没有大量的 VBA(宏)经验,但现在会认真考虑...谢谢。

【问题讨论】:

  • 听起来像是 PowerQuery 的经典任务。关于VBA的问题,看看this answer
  • 在示例数据中,您总是有:“交换机名称”、“交换机类型”、“Lata”、“Tandem”和按顺序排列的空白行。整个数据集都是这样吗?您在标题行中输入了“开关名称”、“开关类型”等。您真的想将它们保留在数据中吗?这是 VBA 的一个小问题,您应该学习它,因为根据我的经验,此类问题经常发生。如果您对 VBA 解决方案感兴趣,我会给您一个开始。
  • 是的,位于每个单元格第 2 到第 5 行的数据集序列(即 Switch Name、Switch Type、LATA 和 Tandem)始终以特定方式排列。我想将提到的数据集放置并转置在已完成记录列 H 的相邻右侧。关于保留或删除与每条记录相关联的数据字段名称,无论如何都无关紧要,因为转置时的数据将放在预定义的 Header Field Names... 谢谢

标签: excel worksheet-function transpose vba


【解决方案1】:

我建议你学习 VBA。在我的整个职业生涯中,我发现自己正在积累与手头任务相关的数据。使用 VBA 处理这些数据的能力不止一次是救命稻草。 VBA 作为一门语言并不是特别难学。对我来说最困难的部分是 Excel 对象模型:Excel 管理多个工作簿,每个工作簿都有多个工作表,每个工作表都有行、列、范围和单元格,每个都有属性。如果您是一位经验丰富的 Excel 用户,您可能会发现您已经熟悉 Excel 对象模型的大部分内容,尽管您可能不知道该名称。

搜索“Excel VBA 教程”。有很多可供选择,因此请选择与您的学习风格相匹配的一种。我更喜欢书,所以我参观了一个不错的图书馆,查看了他们的 Excel VBA Primers,并借了我最喜欢的书在家里尝试。最后我买了一个我喜欢的作为永久参考,我仍然不时查看它。我找到了学习 VBA 和 Excel 对象模型的时间,很快就得到了回报。

您说“是的,位于每个单元格的第 2 到第 5 行的数据集序列(即 Switch Name、Switch Type、LATA 和 Tandem)始终以特定方式排列。”我相信你相信,但我不相信。

在我的工作生涯中,有一次我参与了转换和合并工作簿的工作。我们将每周或每月从各种来源获取工作簿,并创建合并的工作簿,其中包含我们感兴趣的数据,格式便于我们的数据分析师处理。每个新的源工作簿都应该与其前身具有相同的格式,但我们会一次又一次地发现一个额外的列或一个额外的行类型或一个过时的列或行类型被删除。如果合并宏只是假设数据是正确的并盲目地合并它,它可能会起作用,但会创建一个损坏的工作簿。幸运的是不在我的手表上,有一个案例是几个月后才注意到一个小的变化。找到最后一个未损坏的、合并的工作簿和所有源工作簿,然后构建一个新工作簿是一项巨大的工作。我可能是偏执狂,但我检查了不是我创建的每个数据集。

我的宏检查输入工作表的每一行是否为指定格式之一,如果某行不符合预期,它将停止。宏不知道如何解决问题,但至少警告用户存在问题。我推荐这种被微软称为“防御性编程”的方法。

我想我想说的其他一切都在宏中。

为您的工作簿创建一个启用宏的新版本,添加下面的代码并尝试一下。准备好报告任何问题。

如有必要,请回来提出问题。

Option Explicit
Sub MoveSubLinesToMain()

  ' I do not know from your question if this is a one-off tranformation or if you will need
  ' to use the macro repeatedly as new worksheets in the initial format are created by some
  ' other process. For a one-off macro, brief documentation may be acceptable. But any macro
  ' that is used repeatedly will also most certainly need updating. Trying to decipher an
  ' inadequately documented macro that you wrote six months ago or which some one else wrote
  ' is a nightmare.

  ' Do not run this macro against the master copy of the data since it transforms the data in
  ' situ. The macro is designed to carry on following an error but you must have a master copy
  ' so you can start again if the macro cannot carry on after an error.

  ' This macro updates worksheet "Data". If you worksheet has a different name, change
  ' the statement:
  '   With Worksheets("Data").

  ' Ths macro expects to find:
  '  * Row 1: Header row which is ignored
  '  * Row 2: First data row. If there are more header rows change the statement:
  '             Const ColRowDataFirst As Long = 2
  '  * The first data row must be what is named here as a main row.  That is a row starting with
  '    an NPA-NXX number.  A main row is recognised by the first character of the NPA-NXX column
  '    being numeric.
  '  * A main row may be followed by several row which are named here as a sub rows. The macro
  '    allows for there being no sub rows so the macro can be restarted on a partially processed
  '    worksheet.
  '  * The sub rows are recognised by their leading characters:
  '      "Switch Name: "
  '      "Switch Type: "
  '      "LATA: "
  '      "Tandem: "
  '  * There may also be blank lines which are ignored.
  '  * If a sub row is encountered that does not match one of those listed above, the macro will
  '    stop to allow an examination of the error situation and, when restarted, will terminate
  '    itself. You will have to decide how to update the macro to handle the error situation.
  '    Once the macro has been updated, it should be possible to restart the macro which will
  '    step over the already processed rows and continue with the unprocessed row. If this fails
  '    you will have to overwrite the partially processed worksheet with the master copy of the
  '    original data.
  '  * The block, main row and zero or more sub rows, may be repeated an indefinite number of
  '    times.
  '  * For each block, the macro copies the values from the sub rows to specified columns within
  '    their main row and then the sub rows.


  ' The statements to access a cell need a row and column number. You can use literals but with
  ' larger number of columns or special rows it can all become very confusing.  A const (constant)
  ' statement allows you to define a name to replace the literal which makes your code more
  ' readable. More importantly, what happens if a new sub row is introduced and the Lata and
  ' Tandem columns are to be moved. This is a tiny macro and finding all the 11s and 12s which are
  ' column numbers and replacing them will not be be too difficult.  This is not true of a large
  ' macro.  But updating the const statements defining ColLata and ColTandem updates every
  ' reference to these columns through the module.
  Const ColNpa As Long = 1
  Const ColName As Long = 9
  Const ColType As Long = 10
  Const ColLata As Long = 11
  Const ColTandem As Long = 12
  Const RowDataFirst As Long = 2

  Dim NpaValue As String
  Dim NumRowsToDelete As Long
  Dim RowCrnt As Long
  Dim RowCrntMain As Long
  Dim RowLast As Long

  ' Without this statement, the screen is repainted for every change.  Since I am deleting
  ' rows this will substantially increase the run time for no advantage.
  Application.ScreenUpdating = False

  ' As stated above replace "Data" with the name of your worksheet.
  With Worksheets("Data")

    ' This is the easiest way of locating the last row with data if you know that column ColNpa
    ' will have a value on every row.
    RowLast = .Cells(Rows.Count, ColNpa).End(xlUp).Row

    RowCrnt = RowDataFirst
    RowCrntMain = 0             ' No current main row

    ' I would normally use a For Loop: For RowCrnt = RowDataFirst To RowLast
    ' But I am deleting rows which will require RowCrnt and RowLast to be
    ' changed within the loop.  This is not permitted for a For Loop
    Do While RowCrnt <= RowLast
      NpaValue = .Cells(RowCrnt, ColNpa).Value
      ' This If..IfElse...IfElse statements tests for each known row type
      ' ans actions them as appropiate.  The final Else allows for an
      ' unknown row type.
      If NpaValue = "" Then
        ' Blank line
      ElseIf IsNumeric(Left$(NpaValue, 1)) Then
        ' Main row
        If RowCrntMain <> 0 Then
          ' There is a previous main row whose sub rows must be deleted
          NumRowsToDelete = RowCrnt - RowCrntMain - 1
          If NumRowsToDelete > 0 Then
            .Rows(RowCrntMain + 1 & ":" & RowCrnt - 1).Delete
            RowCrnt = RowCrnt - NumRowsToDelete
            RowLast = RowLast - NumRowsToDelete
          End If
        End If
        RowCrntMain = RowCrnt
      ElseIf Left$(NpaValue, 13) = "Switch Name: " Then
        ' Copy the value of the Switch Name row to column ColName on the main row.
        ' Do the same for all the other sub rows.
        .Cells(RowCrntMain, ColName).Value = Trim(Mid$(NpaValue, 14))
      ElseIf Left$(NpaValue, 13) = "Switch Type: " Then
        .Cells(RowCrntMain, ColType).Value = Trim(Mid$(NpaValue, 14))
      ElseIf Left$(NpaValue, 6) = "LATA: " Then
        .Cells(RowCrntMain, ColLata).Value = Trim(Mid$(NpaValue, 7))
      ElseIf Left$(NpaValue, 8) = "Tandem: " Then
        .Cells(RowCrntMain, ColTandem).Value = Trim(Mid$(NpaValue, 9))
      Else
        ' Row not recognised
        ' If code stops here try to identify why. Terminate the macro
        ' or press F5 and it will terminate itself.
        Debug.Assert False
        Exit Sub
      End If
      RowCrnt = RowCrnt + 1
    Loop

    ' Delete final block of sub-lines, if any
    If RowCrntMain <> 0 Then
      ' There is a previous main row whose sub rows must be deleted
      NumRowsToDelete = RowCrnt - RowCrntMain - 1
      If NumRowsToDelete > 0 Then
        .Rows(RowCrntMain + 1 & ":" & RowCrnt - 1).Delete
      End If
    End If

  End With

End Sub

【讨论】:

  • 托尼,托尼和托尼,非常感谢你们,它就像一个魅力,无需进一步解释。非常感谢...谢谢
  • 托尼,我刚刚意识到/注意到数据集,而我指出开关名称、开关类型、LATA 和串联在每个单元格中都表示这是不正确的,因此,当宏运行时,如果名称字段不存在它返回错误消息“Debug.Alert False”。我想知道您是否能够执行相同的宏,它只是复制每个字段中关联的整个数据,而忽略数据字段名称是 TRUE 还是 FALSE。
  • @Miaka3。在我看到你的评论之前,我已经写了额外的解释。希望对您有所帮助。
  • @Miaka3。你一个小时前的评论刚刚出现。我不明白你的评论。即使缺少一些标准行,我也已经测试了代码是否可以正常工作。如果发现一行不是预期的行,它会命中Debug.Alert False。当您点击 Debug.Alert False 时,将鼠标悬停在 RowCrnt 上。将显示问题行的编号。尝试确定为什么该行不符合预期。案子会不会错?我测试“开关名称:”。小写 N 或缺少冒号,将无法识别该行。
  • 托尼,感谢您的回复。是的,您的宏工作得非常完美......但是我确实注意到,当要转置的字段名称中不存在诸如“Tandem”之类的数据集字段名称时,它会显示消息“Debug.Alert False”。我只是想知道您是否能够复制宏,而是允许保留数据集字段名称并将整个单元格转置到相邻的列中。如果没有,我感谢您的大力帮助。
猜你喜欢
  • 2017-02-07
  • 2015-12-20
  • 1970-01-01
  • 2018-07-12
  • 1970-01-01
  • 2012-11-15
  • 1970-01-01
  • 1970-01-01
  • 2019-06-05
相关资源
最近更新 更多