I am new to using VBA and would like to add coding to a button that will identify the size of a table called "QA_Activity" and clear all but the header row on clicking the button. I was hoping that this would be quite simple but am struggling as to how to identify a table that could be a different size each month. Many thanks in advance.
Tables are called ListObjects in VBA. A ListObject has a property called a .DataBodyRange that contains everything under the header. That has a .Clear method.
I generally use this syntax to clear the body of a ListObject:
Range("Table1").ListObject.DataBodyRange.Clear
The Range("Table1") bit allows me to find the ListObject even if I don't know what sheet it's on. This works because Tables are also Named Ranges that you can address by name. So you just say "Go to the Named Range called Table1, and get me the ListObject that lives there". Otherwise you would need to know in advance what sheet the ListObject is on, and use the following:
Worksheets("SomeSheet").Listobjects("Table1").DataBodyRange.Clear
...which works just fine until one day you move the Table to a different sheet, and forget to update your code.
Note that a table is not guaranteed to actually have a .DataBodyRange, because someone may have deleted all the rows under the header. For instance, take this Table:
How many rows does it have in the DataBodyRange?
? Range("Table1").ListObject.DataBodyRange.Rows.Count
3
Okay, now I'm going to delete those rows:
...leaving this:
How many rows in that DataBodyRange now?
? Range("Table1").ListObject.DataBodyRange.Rows.Count
Whoops...you can't reference a .DataBodyRange if it don't exist.
So to be safe, stick an On Error Resume Next before you try to reference a .DataBodyRange, and an On Error Goto 0 afterwards. Or something fancier.
First, create a named range. If required, you can make this dynamic. In this example the named range is Name "Data".
Then Trigger the Sub "Test" from the following VBA code.
Option Explicit
Sub ClearRange(ByVal rngCell As Range)
Dim rngRange As Range
Set rngRange = rngCell.CurrentRegion
rngRange.Range(rngRange.Parent.Cells(2, 1), rngRange.Parent.Cells(rngRange.Rows.Count, rngRange.Columns.Count)).ClearContents
End Sub
Sub test()
Dim rngCell As Range
Set rngCell = ActiveSheet.Range("Data").Cells(1, 1)
ClearRange rngCell
End Sub
This should clear the range except for the first row (headers).
Important: The header row and the first column of your range must be filled completely (no empty cells) for the above to work smoothly.
Related
The following excel sub is a filter that is filtering out rows based on the rows in the criteria row.
The code works well when the ranges are set with absolute data. I want to change the code to take the range from references stored as cell values (an indirect reference) but I cannot find a way to adapt other code snippets I see to work and I wonder if anyone can help me. I am not a programmer.
The problem is that as new data is inserted from time to time the range reference will move and the start of the data an the associated filter is in cell (using RC notation 14,14) and the data in cell 13,12. While I know I can’t use the indirect function in vba I wondered if there is a way to dynamically assign a range to be able to use the Advance filter function.
I have the code to find the last column and row of the data block.
I have tried the following code (2 attempts) but it won’t let me use the object in this way
I have tried to crate the cell reference as a string then assign it using the range function. I then read an answer where someone had put the value of the cells directly into the range function and it has worked for them ( they were copying cells). The 2 attempt are broadly the same but in the second I am trying to be more specific.
The issue seems to be as soon as I change from an absolute reference "A50" in the range statement the range no longer works. I am unsure how to resolve this and perhaps it can't be
It may be helpful to know the that data being filtered is rows of name and telephone data along with a tally system to show attendance (one column per week for a year)
The cells with the dynamic data hold them in the form A1 not RC format
Sub UseAdvancedFilterInPlace()
'This version of the sub has absolute references and works perfectly
Dim rdData As Range
Dim rgcriteria As Range
Call TurnOffStuff
Set rgData = Sheet9.Range(“A50”).CurrentRegion
Set rgcriteria = Sheet9.Range(“A46”).CurrentRegion
rgData.AdvancedFilter xlFilterInPlace, rgcriteria
Call TurnOnStuff
End Sub
Sub UseAdvancedFilterInPlace()
'This version of the sub has dynamic references and fails
Dim rdData As Range
Dim rgcriteria As Range
Call TurnOffStuff
Dim Top_of_data As String
Dim Top_of_Criteria As String
Dim My_range As Range
‘Attempt 1
'Set rgData = Range(Sheet9.Cells(13, 12).Value).CurrentRegion
'Set rgcriteria = Range(Sheet9.Cells(14, 14).Value).CurrentRegion
'Attempt 2
Set rgData = Sheet9.Range(Sheet9.Range(Cells(13, 12)).Value).CurrentRegion
Set rgcriteria = Sheet9.Range(Sheet9.Range(Cells(14, 14)).Value).CurrentRegion
rgData.AdvancedFilter xlFilterInPlace, rgcriteria
Call TurnOnStuff
End Sub
The actual error message I get is an application-defined or object-defined error
This worked for me.
Set rdData = Sheet9.Range(Sheet9.Range("L13").Value).CurrentRegion
Set rgcriteria = Sheet9.Range(Sheet9.Range("N15").Value).CurrentRegion
given that Range("L13").Value is A50 and Range("N15").Value is A46.
extra: Use the statement Option Explicit in the first line of every module, out of every sub or function. This option throws an error on undeclared variables, and will help you avoid renameing mistakes on variables.
I have a sheet named "Staffdb" with two named tables "PermTBL" and "StaffTBL", same headers "Girls" "ID" "Hire_date" and "Status". All of the current and historic staff are in PermTBL. I would like to filter PermTBL on the Status field for "A" meaning active and then copy these to the StaffTBL which is empty. After manually filtering the PermTBL with the Status down arrow and select only "A" I go in to test the code and get an apparent partial copy. My code is
Option Explicit
Sub PermTBLtoStaffTBL()
Dim rgnsrc As Range
Dim rgndest As Range
Set rgnsrc = Worksheets("Staffdb").Range("PermTBL")
Set rgndest = Worksheets("Staffdb").Range("StaffTBL")
rgnsrc.SpecialCells(xlCellTypeVisible).Copy rgndest
End Sub
Finally as an additional piece of information the StaffTBL appears to have hidden rows, 3-7 are not visible which appears to correspond with my missing data. I have tried to unhide to no avail. Suggestions as to where to go next? Must I loop through the table or have I made an error in my destination? New at this, and 3rd world internet speed, along with inability to have books delivered makes this a tedious process. Please bear with the NewBee.
New piece of information, I have found that if I unhide the entire sheet the correct data appears in the StaffTBL, of course the filter of the PermTBL also disappears, so apparently I was on the right track. Would still like comments and suggestions on programmatically (as opposed to manually) filtering PermTBL. I will continue to search sites for that, but any suggestions are appreciated.
Sub CopyData()
Dim t As ListObject
Dim t2 As ListObject
Set t = ActiveSheet.ListObjects("PermTBL")
Set t2 = ActiveSheet.ListObjects("StaffTBL")
' Remove all rows from StaffTBL table
If Not t2.DataBodyRange Is Nothing Then
t2.DataBodyRange.Rows.Delete
End If
' Filter Status by "A"
t.DataBodyRange.AutoFilter Field:=4, Criteria1:="A"
' Copy to first cell right below the table's header
t.DataBodyRange.Copy t2.Range(1).Offset(1)
' Remove filter from PermTBL table
t.DataBodyRange.AutoFilter
End Sub
UPDATE
Example workbook
I have 2 tables on 2 separate sheets of an MS Excel 2007 workbook, like below:
===========================
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
---------------------------
Both have the same columns, but they can have different data.
I want to combine the data of both tables vertically i.e. a single table with all the data in a 3rd separate sheet.
If possible, I want to add another column with the sheet name from where the row came.
===================================
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
-----------------------------------
Can it be done without using macros?
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
I've used the OP's sample data to construct two tables named (by default) Table1 and Table2 on worksheets Sheet1 and Sheet2 respectively. I've intentionally offset them by varying degrees from each worksheet's A1 cell in order to demonstrate a structured table's ability to address either itself or another structured table in a formula as a separate entity regardless of its position on the parent worksheet. The third table will be constructed in a similar manner. These offsets are for demonstration purposes only; they are not required.
Step 1: Build the third table
Build the headers for the third table and select that future header row and at least one row below it to base the Insert ► Tables ► Table command upon.
Your new empty third table on the Sheet3 worksheet should resemble the following.
Step 2: Populate the third table
Start by populating the first cell in the third table's DataBodyRange. In this example, that would be Sheet3!C6. Type or paste the following formula in C6 keeping in mind that it is based on the default table names. If you have changed your tables names, adjust accordingly.
=IFERROR(INDEX(Table1, ROW([#[no.]])-ROW(Table3[#Headers]),COLUMN(A:A)), INDEX(Table2, ROW([#[no.]])-ROW(Table3[#Headers])-ROWS(Table1),COLUMN(A:A)))
The INDEX function first retrieves each available row from Table1. The actual row numbers are derived with the ROW function referencing defined pieces of the structured table together with a little maths. When Table1 runs out of rows, retrieval is passed to a second INDEX function referencing Table2 by the IFERROR function and its sequential rows are retrieved with the ROW and ROWS functions using a bit more maths. The COLUMN function is used as COLUMN(A:A) which is going to retrieve the first column of the referenced table regardless of where it is on the worksheet. This will progress to the second, third, etc. column as the formula is filled right.
Speaking of filling right, fill the formula right to E6. You should have something that approximates the following.
Step 2.5: [optional] Add the source table's parent worksheet name
Grab Table3's sizing handle (indicated by the orange arrow in the sample image below) in the lower right hand corner and drag it right one column to add a new column to the table. Rename the header label to something more appropriate than the default. I've used Sheet as a column label.
While you cannot retrieve the worksheet name of the source table directly, the CELL function can retrieve the fully qualified path, filename and worksheet of any cell in a saved workbook¹ as one of its optional info_types.
Put the following formula into Table3's empty cell in the first row of the new column you have just created.
=TRIM(RIGHT(SUBSTITUTE(CELL("filename", IF((ROW([#[no.]])-ROW(Table3[#Headers]))>ROWS(Table1), Table2, Table1)), CHAR(93), REPT(CHAR(32), 999)), 255))
Complate populating Table3
If you are not planning on finishing this small project with some VBA to maintain Table3's dimensions when rows are added or deleted from either of the two source tables then simply grab Table3's resizing handle and drag down until you have accumulated all of the data from both tables. See the bottom of this answer for a sample image of the expected results.
If you are planning to add some VBA, then skip the full population of Table3 and move on to the next step.
Step 3: Add some VBA to maintain the third table
Full automation of a process that is triggered by changes to a worksheet's data is best handled by the worksheet's Worksheet_Change event macro. Since there are three tables involved, each on their own worksheet, the Workbook_SheetChange event macro is a better method of handling the change events from multiple worksheets.
Open the VBE with Alt+F11. Once you have it open, look for the Project Explorer in the upper left. If it is not visible, then tap Ctrl+R to open it. Locate ThisWorkbook and right-click then choose View Code (or just double-click ThisWorkbook).
Paste the following into the new pane titled something like 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
These two routines make extensive use of the Worksheet .CodeName property. A worksheet's CodeName is Sheet1, Sheet2, Sheet3, etc and does not change when a worksheet is renamed. In fact, they are rarely changed by even the more advanced user. They have been used so that you can rename your worksheets without having to modify the code. However, they should be pointing to the correct worksheets now. Modify the code if your tables and worksheets are not the same as given. You can see the individual worksheet codenames in brackets beside their worksheet .Name property in the above image showing the VBE's Project Explorer.
Tap Alt+Q to return to your worksheets. All that is left would be to finish populating Table3 by selecting any cell in Table1 or Table2 and pretending to modify it by tapping F2 then Enter↵. Your results should resemble the following.
If you have followed along all the way to here then you should have a pretty comprehensive collection table that actively combines the data from two source 'child' tables. If you added the VBA as well then maintenance of the third collection table is virtually non-existent.
Renaming the tables
If you choose to rename any or all of the three tables, the worksheet formulas will instantly and automatically reflect the changes. If you have opted to include the Workbook_SheetChange and accompanying helper sub procedure, you will have to go back into the ThisWorkbook code sheet and use Find & Replace to make the appropriate changes.
Sample Workbook
I've made the fully operational example workbook available from my public DropBox.
Table_Collection_w_Sheetname.xlsb
¹ The CELL function can only retrieve the worksheet name of a saved workbook. If a workbook has not been saved then it has no filename and the CELL function will return an empty string when asked for the filename.
You can activate the Office Clipboard (arrow at bottom right of clipboard section on Ribbon Home Tab). Copy both ranges then use the Paste All command as shown below.
You would still need to fill down the sheet name in an extra column first though which can be done by double-clicking the fill handle.
Update
To get the same results with formulas try filling down this for the sheet name:
=IF(ROW()<=COUNTA(Sheet1!A:A),"Sheet1",IF(ROW()<COUNTA(Sheet1:Sheet2!A:A),"Sheet2",""))
and then fill down and across this formula for the values in the tables:
=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),""))
lori_m made a really good contribution that I built upon by using Microsoft Excel Tables and structured references.
First make a column in your output table called RowID which contains the row number within the table and then use this to fill the data values.
=IF( INDIRECT("Table3[RowId]")<=ROWS(Table1)
,INDEX(Table1[column1],INDIRECT("Table3[RowId]"))
,INDEX(Table2[Column1],INDIRECT("Table3[RowId]")-ROWS(Table1)))
There is a detailed explanation of how this works on my blog as it was too long to include here.
A slight modification to Jeeped's code.
If you happen to use a similar approach, but with several tables (e.g. more than 10), then it will be rather cumbersome to attempt to manually add every name of every table. This is also a problem if you change names of the tables, since the names are hard-wired in VBA. To avoid additional work, consider this:
So, assume the following:
On each worksheet there is one or several tables, but they have similar structure.
There are only tables on worksheets - no other members of ListObjects collection are being present.
Every time we edit a table on a sheet, this will trigger an update in master table (table 3).
Then the Workbook_SheetChange Sub in the example above could look like the following:
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
Edit. The second routine will then look like:
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
This routine differs from previous one by eliminating preprogrammed cases. When there is a change registered on active worksheet, then any table in this worksheet which is about to be changed will trigger update_Table procedure.
Im using this code/formula. works well for my needs only thing i would like to know is how do i make a better cell formula so i can use 3+ tables as a reference. currently im just nesting a bunch of iferror statements within the 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)))))
Im also using the
I have a monster of a workbook that I'm trying to make more manageable for those that use it after me. I have a ton of code that is ran when buttons are pressed to make it more user friendly to those that know little to nothing of Excel. So here is where I need help.
I have several sheets with similar tables. My first sheet contains a Master List of customer information and pressing a button, copies this information to each other sheet and sorts it to categorize these customers on their respective sheets. This allows me to enter new information only on the first sheet and have it auto-populate the sheets correctly to minimize human error.
To cut down on a lot of the errors, I utilized structured referencing in tables. I didn't originally have it this way, but I've been trying to improve this workbook over time.
Anyway, so I have a column "Charge Type" in each table, and the total column references it as
[#[Charge Type]]
which is great, considering customers will be added and removed pretty regularly and this cuts down on errors.
However, when this formula gets copied to one of the other sheets, it's converted over to
All_List[#[Charge Type]]
which adds the name of the table on sheet1, which is "All_List". Now I want it to refer to the column "Charge Type" specifically in the new table on the new sheet, and I cannot for the life of me figure out how.
This solution uses a variable to hold the ListObject "Field" formula then loops trough all other ListObjects in the same workbook with the same "Field" and applies the formula to that "Field".
ListObjects before
Sub ListObjects_Formula_Copy()
Dim wsh As Worksheet
Dim lob As ListObject
Dim rTrg As Range
Dim sFld As String
Dim sFmlR1C1 As String
Rem Get Formula from Primary ListObject
sFld = "Price" 'Change as required
Set lob = ThisWorkbook.Sheets("Sht(0)").ListObjects(1) 'Change as required
sFmlR1C1 = lob.ListColumns(sFld).DataBodyRange.Cells(1).FormulaR1C1
Rem Apply Formula to Other ListObjects
For Each wsh In ThisWorkbook.Worksheets
If wsh.Name <> "Sht(0)" Then
For Each lob In wsh.ListObjects
Rem Validate Field
Set rTrg = Nothing
On Error Resume Next
Set rTrg = lob.ListColumns(sFld).DataBodyRange
On Error GoTo 0
Rem Applies Formula
If Not (rTrg Is Nothing) Then rTrg.FormulaR1C1 = sFmlR1C1
Next: End If: Next
End Sub
ListObjects after
I have 2 tables on 2 separate sheets of an MS Excel 2007 workbook, like below:
===========================
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
---------------------------
Both have the same columns, but they can have different data.
I want to combine the data of both tables vertically i.e. a single table with all the data in a 3rd separate sheet.
If possible, I want to add another column with the sheet name from where the row came.
===================================
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
-----------------------------------
Can it be done without using macros?
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
I've used the OP's sample data to construct two tables named (by default) Table1 and Table2 on worksheets Sheet1 and Sheet2 respectively. I've intentionally offset them by varying degrees from each worksheet's A1 cell in order to demonstrate a structured table's ability to address either itself or another structured table in a formula as a separate entity regardless of its position on the parent worksheet. The third table will be constructed in a similar manner. These offsets are for demonstration purposes only; they are not required.
Step 1: Build the third table
Build the headers for the third table and select that future header row and at least one row below it to base the Insert ► Tables ► Table command upon.
Your new empty third table on the Sheet3 worksheet should resemble the following.
Step 2: Populate the third table
Start by populating the first cell in the third table's DataBodyRange. In this example, that would be Sheet3!C6. Type or paste the following formula in C6 keeping in mind that it is based on the default table names. If you have changed your tables names, adjust accordingly.
=IFERROR(INDEX(Table1, ROW([#[no.]])-ROW(Table3[#Headers]),COLUMN(A:A)), INDEX(Table2, ROW([#[no.]])-ROW(Table3[#Headers])-ROWS(Table1),COLUMN(A:A)))
The INDEX function first retrieves each available row from Table1. The actual row numbers are derived with the ROW function referencing defined pieces of the structured table together with a little maths. When Table1 runs out of rows, retrieval is passed to a second INDEX function referencing Table2 by the IFERROR function and its sequential rows are retrieved with the ROW and ROWS functions using a bit more maths. The COLUMN function is used as COLUMN(A:A) which is going to retrieve the first column of the referenced table regardless of where it is on the worksheet. This will progress to the second, third, etc. column as the formula is filled right.
Speaking of filling right, fill the formula right to E6. You should have something that approximates the following.
Step 2.5: [optional] Add the source table's parent worksheet name
Grab Table3's sizing handle (indicated by the orange arrow in the sample image below) in the lower right hand corner and drag it right one column to add a new column to the table. Rename the header label to something more appropriate than the default. I've used Sheet as a column label.
While you cannot retrieve the worksheet name of the source table directly, the CELL function can retrieve the fully qualified path, filename and worksheet of any cell in a saved workbook¹ as one of its optional info_types.
Put the following formula into Table3's empty cell in the first row of the new column you have just created.
=TRIM(RIGHT(SUBSTITUTE(CELL("filename", IF((ROW([#[no.]])-ROW(Table3[#Headers]))>ROWS(Table1), Table2, Table1)), CHAR(93), REPT(CHAR(32), 999)), 255))
Complate populating Table3
If you are not planning on finishing this small project with some VBA to maintain Table3's dimensions when rows are added or deleted from either of the two source tables then simply grab Table3's resizing handle and drag down until you have accumulated all of the data from both tables. See the bottom of this answer for a sample image of the expected results.
If you are planning to add some VBA, then skip the full population of Table3 and move on to the next step.
Step 3: Add some VBA to maintain the third table
Full automation of a process that is triggered by changes to a worksheet's data is best handled by the worksheet's Worksheet_Change event macro. Since there are three tables involved, each on their own worksheet, the Workbook_SheetChange event macro is a better method of handling the change events from multiple worksheets.
Open the VBE with Alt+F11. Once you have it open, look for the Project Explorer in the upper left. If it is not visible, then tap Ctrl+R to open it. Locate ThisWorkbook and right-click then choose View Code (or just double-click ThisWorkbook).
Paste the following into the new pane titled something like 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
These two routines make extensive use of the Worksheet .CodeName property. A worksheet's CodeName is Sheet1, Sheet2, Sheet3, etc and does not change when a worksheet is renamed. In fact, they are rarely changed by even the more advanced user. They have been used so that you can rename your worksheets without having to modify the code. However, they should be pointing to the correct worksheets now. Modify the code if your tables and worksheets are not the same as given. You can see the individual worksheet codenames in brackets beside their worksheet .Name property in the above image showing the VBE's Project Explorer.
Tap Alt+Q to return to your worksheets. All that is left would be to finish populating Table3 by selecting any cell in Table1 or Table2 and pretending to modify it by tapping F2 then Enter↵. Your results should resemble the following.
If you have followed along all the way to here then you should have a pretty comprehensive collection table that actively combines the data from two source 'child' tables. If you added the VBA as well then maintenance of the third collection table is virtually non-existent.
Renaming the tables
If you choose to rename any or all of the three tables, the worksheet formulas will instantly and automatically reflect the changes. If you have opted to include the Workbook_SheetChange and accompanying helper sub procedure, you will have to go back into the ThisWorkbook code sheet and use Find & Replace to make the appropriate changes.
Sample Workbook
I've made the fully operational example workbook available from my public DropBox.
Table_Collection_w_Sheetname.xlsb
¹ The CELL function can only retrieve the worksheet name of a saved workbook. If a workbook has not been saved then it has no filename and the CELL function will return an empty string when asked for the filename.
You can activate the Office Clipboard (arrow at bottom right of clipboard section on Ribbon Home Tab). Copy both ranges then use the Paste All command as shown below.
You would still need to fill down the sheet name in an extra column first though which can be done by double-clicking the fill handle.
Update
To get the same results with formulas try filling down this for the sheet name:
=IF(ROW()<=COUNTA(Sheet1!A:A),"Sheet1",IF(ROW()<COUNTA(Sheet1:Sheet2!A:A),"Sheet2",""))
and then fill down and across this formula for the values in the tables:
=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),""))
lori_m made a really good contribution that I built upon by using Microsoft Excel Tables and structured references.
First make a column in your output table called RowID which contains the row number within the table and then use this to fill the data values.
=IF( INDIRECT("Table3[RowId]")<=ROWS(Table1)
,INDEX(Table1[column1],INDIRECT("Table3[RowId]"))
,INDEX(Table2[Column1],INDIRECT("Table3[RowId]")-ROWS(Table1)))
There is a detailed explanation of how this works on my blog as it was too long to include here.
A slight modification to Jeeped's code.
If you happen to use a similar approach, but with several tables (e.g. more than 10), then it will be rather cumbersome to attempt to manually add every name of every table. This is also a problem if you change names of the tables, since the names are hard-wired in VBA. To avoid additional work, consider this:
So, assume the following:
On each worksheet there is one or several tables, but they have similar structure.
There are only tables on worksheets - no other members of ListObjects collection are being present.
Every time we edit a table on a sheet, this will trigger an update in master table (table 3).
Then the Workbook_SheetChange Sub in the example above could look like the following:
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
Edit. The second routine will then look like:
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
This routine differs from previous one by eliminating preprogrammed cases. When there is a change registered on active worksheet, then any table in this worksheet which is about to be changed will trigger update_Table procedure.
Im using this code/formula. works well for my needs only thing i would like to know is how do i make a better cell formula so i can use 3+ tables as a reference. currently im just nesting a bunch of iferror statements within the 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)))))
Im also using the