将两个Excel表格合并成一个,追加数据?

11

我在一个MS Excel 2007工作簿的两个单独的工作表上有2个表格,如下所示:

===========================
no.   f_name     l_name  
===========================
13   Little     Timmy
1   John       Doe
17   Baby       Jessica
---------------------------


===========================
no.   f_name     l_name  
===========================
1   john       Tim
16   kyle       joe
14   Baby       katy
22   qbcd       wsde
---------------------------

这两个表具有相同的列,但它们可能包含不同的数据。

我想垂直地合并这两个表的数据,即在第三个单独的工作表中获得所有数据的单个表格。如果可能的话,我还想添加另一列,显示数据所在的工作表名称。

===================================
SheetName   no.   f_name     l_name  
===================================
Sheet1      13   Little     Timmy
Sheet1      1   John       Doe
Sheet1      17   Baby       Jessica
Sheet2      1   john       Tim
Sheet2      16   kyle       joe
Sheet2      14   Baby       katy
Sheet2      22   qbcd       wsde
-----------------------------------

不使用宏能否完成这个任务?

5个回答

11
This answer deals with Structured Tables as interpreted by Excel. While the methods could easily be transcribed to raw data matrixes without assigned table structure, the formulas and VBA coding for this solution will be targeted at true structured tables.
Preamble A third table can maintain the combined data of two tables with some native worksheet formulas but keeping the third table sized correctly as rows are added or deleted to/from the dependent tables will require either manual resizing operations or some VBA that tracks these changes and conforms the third table to suit. I've included options to add both the source table's worksheet name as well as some table maintenance VBA code at the end of this answer.
If all you want is an operational example workbook without all the explanation, skip to the end of this answer for a link to the workbook used to create this procedure.
Sample data tables

    Table Collection Sample Data

我使用了原帖中的样例数据,在工作表Sheet1和Sheet2上分别构建了两个名为默认名称为Table1和Table2的表格。为了演示结构化表格能够像独立实体一样在公式中引用自身或其他结构化表格,而不受其在父级工作表上位置的影响,我故意让它们从各自工作表的A1单元格偏移了不同程度。第三个表也将以类似的方式构建。这些偏移仅供演示;并非必需。
步骤1:构建第三个表
构建第三个表的表头,并选择该未来的表头行以及至少一行以下内容,以便基于插入►表格►表格命令进行操作。

        Combining Tables New Table

你的Sheet3工作表上的新空第三个表格应该如下所示。

    Collecting Table Data Build New Table

步骤2:填充第三个表格

从填充第三个表格的DataBodyRange的第一个单元格开始。在本例中,这将是Sheet3!C6。键入或粘贴以下公式到C6中,记住它基于默认表名。如果您已更改表名,请相应地进行调整。

=IFERROR(INDEX(Table1, ROW([@[no.]])-ROW(Table3[#Headers]),COLUMN(A:A)), INDEX(Table2, ROW([@[no.]])-ROW(Table3[#Headers])-ROWS(Table1),COLUMN(A:A)))

INDEX函数首先从Table1中检索每一行可用数据。实际行数是通过ROW函数引用结构化表格的定义部分并进行简单计算得出的。当Table1没有更多行时,检索将通过IFERROR函数传递到引用Table2的第二个INDEX函数,并使用ROW和ROWS函数以及更多的数学方法来检索其连续的行。COLUMN函数用作COLUMN(A:A),它将检索引用表格的第一列,无论它在工作表上的位置如何。随着公式向右填充,这将进展到第二列、第三列等。

说到正确填充,将公式正确地填充到E6。你应该会得到以下近似结果。

    Aggregating table data, third table

步骤2.5:[可选] 添加源表的父工作表名称

在下图所示的样本图像中,抓取Table3的大小调整手柄(由橙色箭头指示),向右拖动一个列以添加新列到表中。将标题标签重命名为比默认值更合适的内容。我使用了Sheet作为列标签。

        Collating table data - source worksheet name

虽然您无法直接检索源表的工作表名称,但CELL函数可以检索保存的工作簿¹中任何单元格的完全限定路径、文件名和工作表作为其可选info_types之一。
将以下公式放入Table3的新列的第一行空单元格中。
=TRIM(RIGHT(SUBSTITUTE(CELL("filename", IF((ROW([@[no.]])-ROW(Table3[#Headers]))>ROWS(Table1), Table2, Table1)), CHAR(93), REPT(CHAR(32), 999)), 255))

完成Table3的填充

如果您不打算使用一些VBA来维护Table3的尺寸,以便在两个源表中添加或删除行时,只需抓取Table3的调整大小手柄并向下拖动,直到累积了来自两个表的所有数据。请参阅本答案底部的示例图像以查看预期结果。

如果您计划添加一些VBA,则跳过对Table3的完全填充,并继续进行下一步。

步骤3:添加一些VBA以维护第三个表格

通过工作表的Worksheet_Change事件宏启动由工作表数据更改触发的完全自动化进程是最好的方法。由于涉及三个表格,每个表格都在其自己的工作表上,因此Workbook_SheetChange事件宏是处理来自多个工作表的更改事件的更好方法。

使用 Alt+F11 打开 VBE。打开后,在左上角寻找 项目资源管理器 。如果看不见,请按 Ctrl+R 打开。找到 ThisWorkbook ,右键点击然后选择查看代码(或者双击 ThisWorkbook)。

        Collect data from multiple tables

将下面的内容粘贴到类似于 Book1 - ThisWorkbook (Code) 的新窗格中。
Option Explicit

Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
    Select Case Sh.Name
        Case Sheet1.Name
            If Not Intersect(Target, Sheet1.ListObjects("Table1").Range.Offset(1, 0)) Is Nothing Then
                On Error GoTo bm_Safe_Exit
                Application.EnableEvents = False
                Call update_Table3
            End If
        Case Sheet2.Name
            If Not Intersect(Target, Sheet2.ListObjects("Table2").Range.Offset(1, 0)) Is Nothing Then
                On Error GoTo bm_Safe_Exit
                Application.EnableEvents = False
                Call update_Table3
            End If
    End Select

bm_Safe_Exit:
    Application.EnableEvents = True
End Sub

Private Sub update_Table3()
    Dim iTBL3rws As Long, rng As Range, rngOLDBDY As Range
    iTBL3rws = Sheet1.ListObjects("Table1").DataBodyRange.Rows.Count
    iTBL3rws = iTBL3rws + Sheet2.ListObjects("Table2").DataBodyRange.Rows.Count
    iTBL3rws = iTBL3rws + Sheet3.ListObjects("Table3").DataBodyRange.Cells(1, 1).Row - _
                          Sheet3.ListObjects("Table3").Range.Cells(1, 1).Row
    With Sheet3.ListObjects("Table3")
        Set rngOLDBDY = .DataBodyRange
        .Resize .Range.Cells(1, 1).Resize(iTBL3rws, .DataBodyRange.Columns.Count)
        If rngOLDBDY.Rows.Count > .DataBodyRange.Rows.Count Then
            For Each rng In rngOLDBDY
                If Intersect(rng, .DataBodyRange) Is Nothing Then
                    rng.Clear
                End If
            Next rng
        End If
    End With
End Sub

这两个例程广泛使用工作表的 .CodeName属性。工作表的 CodeNameSheet1、Sheet2、Sheet3等,并且在工作表重命名时不会更改。实际上,即使是高级用户也很少更改它们。它们被用来使您能够重命名工作表而无需修改代码。但是,它们现在应该指向正确的工作表。如果您的表格和工作表与给定的不同,请修改代码。您可以在上面显示 VBE 的项目资源管理器中的工作表 .Name属性 旁边的括号中看到各个工作表的代码名称。

点击 Alt+Q 返回到您的工作表。接下来要做的就是通过选择 Table1Table2 中的任意单元格,并模拟修改它们,然后按下 F2 键再按下 Enter↵ 键来完成填充 Table3。您的结果应该如下所示。

    Combine two tables into one automatically

如果你一路跟随到这里,那么你应该拥有一个相当全面的集合表,它可以积极地将来自两个源“子”表的数据结合起来。如果你也添加了VBA,那么第三个集合表的维护几乎是不存在的。

重命名表格

如果你选择重命名任何或所有三个表格,则工作表公式将立即自动反映更改。如果你选择包含Workbook_SheetChange和相应的帮助程序子过程,你需要回到ThisWorkbook代码表并使用查找和替换来进行适当的更改。 示例工作簿 我已经从我的公共DropBox中提供了完全操作的示例工作簿。

Table_Collection_w_Sheetname.xlsb


¹ CELL函数只能检索已保存工作簿的工作表名称。如果工作簿尚未保存,则没有文件名,当要求文件名时,CELL函数将返回空字符串。


这是一篇很好的文章,而且它有效。但是我该如何添加第三个表格以进行追加呢? - user2745896
很棒的写作。针对常见问题的简单解决方案。 - Alex B
很棒的答案!解决方案的工作原理也写得非常全面。 - Pim Jager
@Jeeped:你的答案非常棒,详细说明了许多。样本文件似乎不再可用了,你能再分享一次吗? - Antoine Driard
1
@AntoineDriard,我复制了这个工作簿并将其放在Google Drive上。我已经更新了上面回复中的链接。 - user4039065

4
你可以激活“Office剪贴板”(位于“功能区主页”选项卡的剪贴板部分右下方)。复制两个范围,然后使用如下所示的“全部粘贴”命令。不过,你仍需要先在一个额外的列中填写工作表名称,这可以通过双击填充手柄完成。
更新
要使用公式获得相同的结果,请尝试将以下内容填充到工作表名称中:
=IF(ROW()<=COUNTA(Sheet1!A:A),"Sheet1",IF(ROW()<COUNTA(Sheet1:Sheet2!A:A),"Sheet2",""))

然后在表格中填充此公式的值,沿着行和列进行:

=IF(ROW()<=COUNTA(Sheet1!A:A),Sheet1!A2,IF(ROW()<COUNTA(Sheet1:Sheet2!A:A),INDEX(Sheet2!A:A,ROW()-COUNTA(Sheet1!A:A)+1),""))

谢谢!是的,那是一个选项,但问题是我还希望最终表在我更改任何输入表中的内容时自动更新... 我不知道我是否可以在这里使用任何函数。我已经搜索了一段时间,但没有找到任何东西。也许宏是这里唯一的选择 :( - anuj
您可以使用“粘贴链接”命令的“特殊粘贴”选项将合并后的表格链接到源表格。只要列表中的项目数量不变,这将自动更新为原始表格中的更改。 - lori_m
我改进了@lori_m的答案,使其更容易在源表中插入行:=IF(ROW()<(COUNTA(Sheet1!A:A)+1),INDEX(Sheet1!A:A,ROW()),IF(ROW()<COUNTA(Sheet1:Sheet2!A:A),INDEX(Sheet2!A:A,ROW()-COUNTA(Sheet1!A:A)+1),"") - Steven Stangle

2

lori_m做出了非常好的贡献,我通过使用Microsoft Excel表和结构引用进行了改进。

首先,在输出表中创建一个名为RowID的列,其中包含表格内的行号,然后使用它来填充数据值。

=IF( INDIRECT("Table3[RowId]")<=ROWS(Table1)
    ,INDEX(Table1[column1],INDIRECT("Table3[RowId]"))
    ,INDEX(Table2[Column1],INDIRECT("Table3[RowId]")-ROWS(Table1)))

我在博客中提供了详细的解释,因为这里的篇幅有限无法涉及所有内容。


1
Jeeped的代码稍作修改。
如果您使用类似的方法,但有多个表格(例如超过10个),那么手动添加每个表格的名称将会非常麻烦。如果更改表格的名称,则名称在VBA中是硬编码的,这也是一个问题。为避免额外的工作,请考虑以下内容:
因此,假设以下情况:
- 每个工作表上都有一个或多个表格,但它们具有相似的结构。 - 工作表上只有表格 - 没有其他ListObjects集合成员存在。 - 每次我们编辑工作表上的表格时,这将触发主表格(表格3)的更新。
然后在上面的示例中,Workbook_SheetChange子程序可能如下所示:
     Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
     Dim tbl As ListObject
     For Each tbl In ActiveSheet.ListObjects
        If Not Intersect(Target, tbl.Range.Offset(1, 0)) Is Nothing Then
            On Error GoTo bm_Safe_Exit
            Application.EnableEvents = False
            Call update_Table
        End If
        Next tbl
        bm_Safe_Exit:
        Application.EnableEvents = True
     End Sub

编辑。第二个例程将如下所示:
   Private Sub update_Table()
    Dim iTBL3rws As Long, rng As Range, rngOLDBDY As Range
    Dim tbl As ListObject
    Dim sht As Worksheet
    iTBL3rws = 0

    ' consider all tables, excluding master table
    For Each sht In ThisWorkbook.Worksheets
        For Each tbl In sht.ListObjects
            If tbl.Name <> "Table3" Then
                iTBL3rws = iTBL3rws + tbl.DataBodyRange.Rows.Count
            End If
        Next tbl
    Next sht

    iTBL3rws = iTBL3rws + Sheet3.ListObjects("Table3").DataBodyRange.Cells(1, 1).Row - Sheet3.ListObjects("Table3").Range.Cells(1, 1).Row
        With Sheet3.ListObjects("Table3")

            Set rngOLDBDY = .DataBodyRange

            .Resize .Range.Cells(1, 1).Resize(iTBL3rws, .DataBodyRange.Columns.Count)

            If rngOLDBDY.Rows.Count > .DataBodyRange.Rows.Count Then
                For Each rng In rngOLDBDY
                    If Intersect(rng, .DataBodyRange) Is Nothing Then
                        rng.Clear
                    End If
                Next rng
            End If
        End With

End Sub

这个例程与之前的不同之处在于消除了预设情况。当活动工作表上发生变化时,即将被更改的任何表格都会触发update_Table过程。

0

我正在使用这个代码/公式。对我的需求很好,唯一想知道的是如何制作更好的单元格公式,以便我可以使用3个以上的表格作为参考。目前我只是在iferror语句中嵌套了一堆iferror语句。

=IFERROR(INDEX(Table1, ROW([@Date])-ROW(Table3[#Headers]),COLUMN(A:A)),IFERROR( INDEX(Table2, ROW([@Date])-ROW(Table3[#Headers])-ROWS(Table1),COLUMN(A:A)), IFERROR(INDEX(Table4, ROW([@Date])-ROW(Table3[#Headers])-ROWS(Table2)-ROWS(Table1),COLUMN(A:A)),INDEX(Table5, ROW([@Date])-ROW(Table3[#Headers])-ROWS(Table2)-ROWS(Table1)-ROWS(Table4),COLUMN(A:A)))))

我也在使用


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接