How do implement dynamic data validation e.g. as Excel VBA-function? - excel

Data validation in Excel is a helpful way to verify user input in Excel. The standard way is to (1) define somewhere (e.g. on an auxillary sheet) a list with possible input values and (2) then choose that range in the Source field. Alternatively, one can also directly type in the different options in that field, e.g:
My question: How do we make the data-validation list dynamic?
What I tried so far is to enter a (possibly user-defined VBA) formula returning a list of strings in into the field Source of the data-validation dialog, for example
=INDEX({"New","Mint","Very Good","Good","Acceptable","Poor"},{1,RANDBETWEEN(1,6)})
However, this approach does not work as it leads to an error message
You may not use reference operators (such as unions, intersections,
and ranges) or array constants for Data Validation criteria.
What do I miss? Which (possibly more elegant) way of making the Source of the data validation dynamic do you suggest?
Edit: My concrete problem:
On all (but the first) tab sheet, I have a cell called myTest and the list of allowed values is for a cell on the first tab, where I want be able from the list composed of all possible values of myTest.
References:
Debra Dalgleish: "Create Dependent Drop Down Lists" at contextures.com
Dynamic Data Validation in Excel (Non-VBA!)

You can use something like this in the first sheet's code module:
Private Sub Worksheet_Activate()
Const LIST_COL As Long = 26
Dim sht As Worksheet, i As Long
i = 1
Me.Columns(LIST_COL).ClearContents '<< clear current list
For Each sht In ThisWorkbook.Worksheets '<< collect all the values
If sht.Name <> Me.Name Then
Me.Cells(i, LIST_COL).Value = sht.Range("myTest").Value
i = i + 1
End If
Next sht
End Sub
It will collect the values from the other sheets into a range you can reference for the validation list.
Note: if the other sheet's values are updated via code or formulas (ie. do not require you to go to each sheet to change values) then you'd need to do a bit more work.
EDIT - this is more convoluted but works more reliably, since it gets run whenever you click on the validation drop-down
1. Put this in a regular module (edit as required):
'A function to return a range containing the
' various values which need to appear in the validation list
Public Function ListCompile() As Range
Const LIST_COL As Long = 26 '<< create the list in Col Z
Dim sht As Worksheet, i As Long
i = 1
Sheet1.Columns(LIST_COL).ClearContents
For Each sht In ThisWorkbook.Worksheets
If sht.Name <> Sheet1.Name Then
Sheet1.Cells(i, LIST_COL).Value = sht.Range("myTest").Value
i = i + 1
End If
Next sht
'return the list we just created
Set ListCompile = Sheet1.Cells(1, LIST_COL).Resize(i, 1)
End Function
Note: I'm using the codename for Sheet1 (which might be different from the tab name). You can see the code name in the VB editor Project Explorer.
2. Define a named range "tester" with "RefersTo" equal to ListCompile()
3. Finally set your data validation list range to: =tester

Assuming:
Your list is in column A
There is no header
You could make data-validation with a dynamic list using this formula as source:
=OFFSET(A1,0,0,COUNTA(A:A),1)
Edit: below is an image of an example using the formula above in data-validation applied to cell B1:
If you can somehow join your values in a single column, this is a possibility.

Related

Evaluating use of INDIRECT function taken from Cell Validation

I have a number of cells which have validation lists that are based on the use of the INDIRECT function. In this case there are two variables - one is the currently selected State (as reference through the named range dtState) and another based on a separate selection already made by the user in a different cell. The function in the Validation List for the cell is:
=INDIRECT("dtHdrPlates"&dtSize&dtState)
dtsize : could equal "70" or "90"
dtState : couldequal "VIC" or "QLD"
Naturally there is a named range for all combination of "dtHdrPlates" and the possible options of dtSize and dtState. The INDIRECT function in the Cell Validation list gives me a list that changes based on the selections in other cells.
dtHdrPlates70VIC
dtHdrPlates90VIC
dtHrdPlates70QLD
dthrdPlates90QLD
This works well thanks to advice previously obtained in this forum - thanks!
I am now trying to implement the use of a Combo Box over top of this cell, positioned, populated, made visible, activated and if relevant, dropped down by VBA Code; and removed once the use leaves the control.
Where I'm currently stuck is populating the ListFillRange of the ComboBox in the sheet - based on the INDIRECT function in the Validation.Formula1 of the cell the combo box is intended to supplant.
Sub BuildComboBox(rngCell as Range)
' This procedure checks to see if the Cell concerned is a list, and if so creates a combobox over top of the cell with the appropriate contents.
Dim rngCell as Range ' the cell concerned
Dim ws as Worksheet
Dim Dim cbo As OLEObject
If rngCell.Validation.Type = xlValidateList Then
Set ws = Excel.ActiveSheet
Set cbo = ActiveSheet.OLEObjects("ComboBox")
cbo.Left = rngCell.Left
cbo.Top = rngCell.Top
cbo.Width = rngCell.Width
cbo.Height = rngCell.Height
CBO.LISTFILLRANGE = RNGCELL.VALIDATION.FORMULA1
cbo.LinkedCell = rngCell.Address
If rngCell.Validation.ShowError = True Then
CBO.MATCHREQUIRED = TRUE
Else
CBO.MATCHREQUIRED = FALSE
End If
cbo.Visible = True
cbo.Activate
If rngCell.Value2 = "" Then
CBO.DROPDOWN
End If
End If
End Sub
I am stuck in three places in the above code (where the code is in CAPITALS).
What I would like to solve at the moment is how to get the indirect function taken from the cell validation list (rngCell.Validation.Formula1) and have it converted to the named range acceptable to cbo.ListFillRange - at the moment it comes across as =INDIRECT("dtHdrPlates"&sglLBWWallThickness&dtState) which does not work.
I've been playing with the EVALUATE function but just can't get it to parse the text coming from the Validation.formula1
If while you're looking at this you could also steer me towards how to change the status of an ActiveX ComboBox's MatchRequire property - and how to force it to DropDown, that would be lovely too.
Thanks, Regards, Ken

How to populate filter results in a different worksheet using VBA?

I set up a data to filter by different categories in a drop down list.
The data is on one sheet, and when filters are selected, the results are generated on that same sheet (exactly how a filter would typically work in Excel).
I want that the database is held on a different worksheet, while the filters/drop down menus and results are in another. This is to hide the database with everyone's information, and only populate the results for the filters that were selected.
The autofilter code:
Sub AdvFilt()
Dim rng As Range
Set rng = Range("B13", Range("U") & Rows.Count).End(xlUp))
rng.AdvancedFilter 1, [X1:AE2], 0
End Sub
Is it possible to designate a range from a different worksheet?
If that code is written in the code-behind of, say, your Sheet1 module, then Range("B13") is implicitly Me.Range("B13").
If that code is written in some standard module, say, Module1, then Range("B13") is implicitly referring to whatever worksheet happens to be currently active. Rubberduck (free, open-source VBIDE add-in project that I manage) can locate every instance of such implicit ActiveSheet reference for you.
What you want, is to never use Range, Columns, Rows, Names, or Cells, without a proper, explicit Worksheet qualifier object.
So you declare and assign a Worksheet object:
Dim sourceSheet As Worksheet
Set sourceSheet = ActiveWorkbook.Worksheets("Some Sheet")
Dim destinationSheet As Workshet
Set destinationSheet = ActiveWorkbook.Worksheets("Some Other Sheet")
And now you can get a Range from either sheet:
sourceSheet.Range("B13").Value = 42
destinationSheet.Range("A1").Value = sourceShet.Range("A1").Value
If both sheets exist at compile-time in ThisWorkbook (the host document that contains your VBA project), then you don't need to declare any variables, because VBA already declared them for you - simply locate your sheets in the VBE's Project Explorer, and set their (Name) property to any valid VBA identifier (no spaces, must start with a letter). ...and then you can just use these identifiers in your code as-is.
Watch out here:
Range("U") & Rows.Count
That will throw error 1004, because "U" is not a valid cell reference (unless you have a named range that you conveniently named U, of course), and then the & Rows.Count is being concatenated to whatever Range.[_Default] happens to return for this "U" range (assuming it doesn't just blow up).
You likely meant to concatenate the number of rows with the column heading U, to get a Range off the concatenated cell address:
Range("U" & Rows.Count)

Combine 5 tables from seperate worksheets excel [duplicate]

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

How to use one column in excel as a search parameter in another column to output only certain strings

This question stems off another post I had. (see Search through column in excel for specific strings where the string is random in each cell)
Using the above image as reference, I am trying to search through column B (actually over 1000 lines) using column E as the "lookup values." The end goal would be for "just" the names to be displayed in column C. The trick is all the randomly generated characters the encompass the names. Below is what I would want the datasheet to look like. A formula or module should work, but the vlookup and other lookup function I can't get to work.
For a worksheet function approach, you could enter in C3 and fill down this formula:
=LOOKUP(8^5,SEARCH(E$3:E$7,B3),E$3:E$7)
The constant 8^5=32768 is chosen to be larger than the maximum possible string length so that LOOKUP returns the last matching value. The formula returns #N/A if no string is found.
Another possibility, which may be easier to understand then assylias post initially, but also may be a bit more time consumptive (although with 1,000 rows, I don't think it will matter much) is below.
This requires that you name the range in column E as myNames (or whatever name you wish, just update the code - alternatively, you cuold just write Range("E1:E6")). Also, if you move the random values from column B, update that in the code as well.
Sub findString()
Dim celString As Range, rngString As Range, celSearch As Range, rngSearch As Range
Dim wks As Worksheet
Set wks = Sheets("Sheet1") 'change sheet reference to whatever your sheet name is
Set rngString = wks.Range("myNames")
Set rngSearch = Intersect(wks.UsedRange, wks.Range("B1").EntireColumn)
For Each celString In rngString
For Each celSearch In rngSearch
If InStr(1, celSearch.Text, celString.Value) > 0 Then
celSearch.Offset(, 1) = celString.Value
End If
Next
Next
End Sub
Since, I worked on your original question as well, I would suggest getting the counts through Siddharth's answer and then running this, or assylias's code above to get the names next to the columns. You could put a button the sheet, or just use the Macro dialog box to run the macro.

Combine 2 Excel tables into one appending the data?

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

Resources