Insert multiple rows using one insert statement in Access 2010 - ms-access

I want to insert multiple values into an Access 2010 table, but I can't seem to find a way.
MySQL had a nice way:
INSERT INTO Production.UnitMeasure
VALUES
(N'FT2', N'Square Feet ', '20080923'),
(N'Y', N'Yards', '20080923'),
(N'Y3', N'Cubic Yards', '20080923');
Can something like this be done in SQL Server too?

As marc_s has pointed out, for SQL Server 2008 and later, you can just use table value constructors. For previous versions, you can use insert and select...union all, e.g.:
INSERT INTO Production.UnitMeasure
SELECT N'FT2',N'Square Feet ','20080923' union all
SELECT N'Y', N'Yards', '20080923' union all
SELECT N'Y3', N'Cubic Yards', '20080923'
(Specific documentation on Table Value Constructors in SQL Server. I can't find specific separate documentation on row value constructors, but that's what they are)

Use this confirm working query:
INSERT INTO Product (Code,Name,IsActive,CreatedById,CreatedDate )
SELECT * FROM
(
SELECT '10001000' AS Code,
'Blackburn sunglasses' AS Name,
1 AS IsActive,
1 AS CreatedById,
'2/20/2015 12:23:00 AM' AS CreatedDate
FROM Product
UNION
SELECT '10005200' AS Code,
'30 panel football' AS Name,
1 AS IsActive,
1 AS CreatedById,
'2/20/2015 12:23:09 AM' AS CreatedDate
FROM Product
) ;

For SQL-Server: Yes, and it can exactly like you write. Just be certain that the column values are in the same order as they appear in the table. Also: you must supply a value for each existing column.
For Access 2010: No. At least not by hardcoded values in the sql, but only by selecting multiple records from a table (in the same or in another database). See also the link in the answer of Khepri.

SQL Server definitely allows this: EDIT: [As of SQL Server 2008, thank you Marc_s]
INSERT INTO [Table]
([COL1], [COL2])
VALUES
('1#1.com', 1),
('2#2.com', 2)
As for the Access requirement, I'm no access guru but I found this MSDN documentation that shows how to do multiple inserts at once.
INSERT INTO target [(field1[, field2[, …]])] [IN externaldatabase]
SELECT [source.]field1[, field2[, …] FROM tableexpression
Doing some cursory reading beyond this, you can use a "dummy" from table if all of your values are known ahead of time as in your example.

Create a table called OneRow with a single integer column. Insert one row.
Then:
INSERT INTO Production.UnitMeasure
SELECT 'FT2', 'Square Feet ', '20080923' FROM OneRow
UNION ALL SELECT 'Y', 'Yards', '20080923' FROM OneRow
UNION ALL SELECT 'Y3', 'Cubic Yards', '20080923' FROM OneRow
Your exact syntax works on SQL Server 2008. For earlier use my above query without the FROM clauses and no helper table.

I know its late to answer, but there are a couple of methods which are still useful today, (not mentioned here).
There are two general methods.
Loop through using a VBA script with the 'Docmd.RunSQL' statement. - This is generally quite slow especially as the number of rows increases but easy to understand.
Copy your 'data array' into an excel worksheet and then use a database query link to the excel file (my preferred method) - Benefits of this is it is almost as fast as if the table was already in the database and not necessarily slowed down by the number of records as the previous method - slightly slower than the above when you have a small number of records however
The Docmd Method:
Option Compare Database
Option Base 1
'------- method created by Syed Noshahi --------
'https://www.linkedin.com/in/syed-n-928b2490/
Sub DoCmdRoutine()
Dim arr() As Variant ' create an unsized array ready for data
'--------------For the purposes of the Example, put some data in the array----------
ReDim arr(5, 5)
For i = LBound(arr) To UBound(arr)
For j = LBound(arr) To UBound(arr)
arr(i, j) = i * j
If i = LBound(arr) Then arr(i, j) = "col_" & arr(i, j) 'Append "col_" before the column names
Next
Next
'-------------Importing the data to a table in MS ACCESS----------------------------
sSQL = "INSERT INTO [#TableTemp] " ' change table to actual table name
DoCmd.SetWarnings False 'turn warnings off - no popups!
For i = 2 To UBound(arr) 'start at 2 assuming that the first columns are headers
vals = "" 'go through each column and copy the data to SQL string format
'replace any single quote with double quotes so it does not error importing into SQL
For j = 1 To UBound(arr, 2)
If IsDate(arr(i, j)) Then 'if a date, convert to a number and let access re-covert to date (best chance at success)
vals = vals & " cdate('" & CDbl(arr(i, j)) & "'),"
ElseIf IsNumeric(arr(i, j)) Then 'if a number put through as a number
vals = vals & arr(i, j) & ","
Else 'otherwise treat as a text value
vals = vals & Replace(arr(i, j), "'", "''", , , 1) & "',"
End If
Next
vals = " VALUES(" & Left(vals, Len(vals) - 1) & ")" 'put in correct sql format
DoCmd.RunSQL sSQL & vals 'Run the SQL statement and import into the database
Next
DoCmd.SetWarnings True 'turn warnings on
End Sub
The Excel Link Method:
Option Compare Database
Option Base 1
'------- method created by Syed Noshahi --------
'https://www.linkedin.com/in/syed-n-928b2490/
Sub ExcelLinkRoutine()
Dim arr() As Variant ' create an unsized array ready for data
Dim oExcel As Object ' Excel instance - late binding
' works with access 2007+, access 2003 has a different SQL syntax
'--------------For the purposes of the Example, put some data in the array----------
ReDim arr(5, 5)
For i = LBound(arr) To UBound(arr)
For j = LBound(arr) To UBound(arr)
arr(i, j) = i * j
If i = LBound(arr) Then arr(i, j) = "col_" & arr(i, j) 'Append "col_" before the column names
Next
Next
'----------------------------output the array to an excel file ---------------------
Set oExcel = CreateObject("Excel.Application")
oExcel.Workbooks.Add 1
Set wb = oExcel.ActiveWorkbook
'network file path & normal path examples below
'fileNameWithExtention = "\\networkpath\location\example999.xlsb" ' note that xlsb file format must be used
' other formats can be used by changing 'xlExcel12'
' ONLY change the path not the FILE NAME
fileNameWithExtention = "C:\Users\public\documents\example999.xlsb" ' same as above
checkFileExists = Dir(fileNameWithExtention)
If Len(checkFileExists) > 0 Then
'only delete the file if its called example999!
If checkFileExists = "example999.xlsb" Then
Kill fileNameWithExtention
End If
End If
With wb
.Sheets(1).Cells(1, 1).Resize(UBound(arr), UBound(arr, 2)).Value2 = arr()
.SaveAs fileNameWithExtention, 50 ' 50 means xlExcel12
.Close False
End With
Set wb = Nothing
Set oExcel = Nothing
'------------ Importing the data to a table in MS ACCESS-----------------------------
'NOTE1: The saved down excelfile MUST be named Sheet1
'NOTE2: if the file path contains special characters such as ,-'
' you may need find the correct way to input (or remove the special chars)
sSQL = "SELECT T1.* INTO [#TableTemp] " ' change table to actual table name
sSQL = sSQL & " FROM [Excel 12.0;HDR=YES;IMEX=2;ACCDB=YES;DATABASE=" & fileNameWithExtention & "].[Sheet1$] as T1;" ' linked table format
DoCmd.SetWarnings False 'turn warnings off - no popups!
DoCmd.RunSQL sSQL 'Run the SQL statement and import into the database
DoCmd.SetWarnings True 'turn warnings on
End Sub
OUTPUT:
Col_1
Col_2
Col_3
Col_4
Col_5
2
4
6
8
10
3
6
9
12
15
4
8
12
16
20
5
10
15
20
25

MS Access does not allow multiple insert from same sql window. If you want to insert, say 10 rows in table, say movie (mid, mname, mdirector,....), you would need to
open the sql windows,
type the 1st stmt, execute 1st stmt, delete 1st stmt
type the 2nd stmt, execute 2nd stmt, delete 2nd stmt
type the 3rd stmt, execute 3rd stmt, delete 3rd stmt ......
Very boring.
Instead you could import the lines from excel by doing:
Right-click on the table name that you have already created
Import from Excel (Import dialog box is opened)
Browse to the excel file containing the records to be imported in the table
Click on "Append a copy of the records to the table:"
Select the required table (in this example movie)
Click on "OK"
Select the worksheet that contains the data in the spreadsheet
Click on Finish
The whole dataset in the excel has been loaded in the table "MOVIE"

I know I'm a bit late to the game, but I was wanting to do the exact same thing you guys mentioned in your example. I was trying to insert a new list of default rows into a table/list using Access because I've had a lot of SQL experience, I was trying to do it the same way, however as you posters have noted, it's not possible to do the Unions and such.
However I just wanted to post a reply up here because in the case where you're manually typing in the values (string default values in this case) you can simply open Access in datasheet view, copy your data from Excel and just paste it into your Access table (or in my case, SharePoint list). You'll need to make sure you're columns are lined up exactly, but if you were going to manually type in your "insert" sql statements, just putting that info into an Excel spreadsheet shouldn't be a big deal.
In my case, my table/list only had a single column as a lookup, so I just copied the column from notepad++ and pasted it into the datasheet view.
Good luck everyone!

Check following,
INSERT INTO [Customer] ([Id],[FirstName],[LastName],[City],[Country],[Phone])VALUES(1,'Maria','Anders','Berlin','Germany','030-0074321')
INSERT INTO [Customer] ([Id],[FirstName],[LastName],[City],[Country],[Phone])VALUES(2,'Ana','Trujillo','México D.F.','Mexico','(5) 555-4729')

Related

how to get attachment files using union query in ms access

I have two independent tables in ms access now i want to get all data from both tables and show as one table using union query.
all is work but the problem is the attachment fields doesn't not loaded and its Empty.
SELECT *
FROM Table1
UNION ALL select * from Table2;
--This is my query which gets all records from both table but the attachment type field is empty
This example may be usefull
Set rsChild = rsDocs.Fields("DocText").Value
rsChild.Fields("FileData").SaveToFile newFileName
rsChild.Close
where rsDocs - ADO (or DAO) recordset as query result
DocText - attachment field name
rsChild.Fields("FileData") - stream object
SaveToFile - method of attachment field
rsDocs.Fields("DocText").Value.Fields("FileData").SaveToFile
The below example uses an attachment field named AttachmentField. You can pass the name of the file you're looking through the parameter.
If you run only the SQL, the parameter window will pop-up asking for the parameter value. To search, find the file and open it, you will need to use VBA.
See an example:
PARAMETERS [FileNameParam] Text (255);
SELECT T.ID, T.FileName
FROM (
SELECT ID, AttachmentField.FileName AS FileName
FROM Table1
UNION ALL
SELECT ID, AttachmentField.FileName AS FileName
FROM Table2
) AS T
WHERE T.FileName Like '*' + [FileNameParam] + '*'
Dim q As DAO.QueryDef
Set q = CurrentDb().QueryDefs("Query1")
q.Parameters("[FileNameParam]").Value = "Blank.pdf"
Dim r As DAO.Recordset
Set r = q.OpenRecordset(dbOpenSnapshot)
'nothing found
If r.EOF Then
MsgBox "File not found."
Exit Sub
End If
Dim filepath As String
filepath = r![FileName]
'open
Application.FollowHyperlink filepath
'clean up
If Not r Is Nothing Then r.Close
If Not q Is Nothing Then q.Close

Filtering Data from a specific column

Hello Everyone I am at a dilemma I am trying to find out a way to filter out certain data in a column field I have an idea on how to do this but I do not know the correct syntax to use. First here is the table and here is the code structure I would like to write.
for i=1 to length of column First Match
for j=1 to length of column Second Match
If ((value of the data in column First Match = 15) OR (value of the data in column FirstMatch = 1)) AND
((value of the data in column Second Match = 15) OR (value of the data in column Second Match = 1))
Then
Filter the data and append so the filtered datas are saved for both First Match and Second Match
end if
next
next
I am trying to filter out the data that is a 15 and 1 so that only data that have the values 0,2,3,4,5,6...14 will be shown for instance the information of john and steve will not be shown because both the first and second match fields have a 1 or 15 but the rest of data will be shown my form is a split form setup.
Is my method correct?
First Name Last Name First Match Second Match
James Matheson 0 2
Monroe Labonson 4 3
Barack Obama 2 5
Frederick Douglas 3 4
Steve MCGowan 1 1
John Seals 15 15
Mike Omalley 14 15
Set rs = CurrentDb.OpenRecordset("Table1")
Do While Not rs.EOF
If rs!Fields("First Match") > 1 And rs!Fields("First Match") < 15 And rs!Fields("Second Match") > 1 And rs!Fields("Second Match") < 15 Then
End If
Loop
With a better understanding (I hope) I've reproduced your data in Access and built a query that does what you seem to ask. The best way to see this is to create a new query, not add a table, and go directly to SQL view. Paste the following SQL statement (the one in quotes) in and replace matchFilter with your table name.
In your event code you can create a recordset based on the SQL:
Dim sSQL as string, rs as Recordset
sSQL = "SELECT matchFilter.[First Name], matchFilter.[Last Name], matchFilter.[First Match], matchFilter.[Second Match] " & _
"FROM matchFilter " & _
"WHERE ((([First Match]<>1 And [First Match]<>15 And [Second Match]<>1 And [Second Match]<>15)=True))"
Set rs = CurrentDB.OpenRecordset(sSQL)
' now do what you want
Wrong answer below!
Dim rs as Recordset
Set rs = CurrentDB.OpenRecordset("yourTableName")
Do While Not rs.EOF
If rs!Fields("col1") > 1 AND rs!Fields("col1") < 15 AND rs!Fields("col2") > 1 AND rs!Fields("col2") Then
'do what you have to do with filtered out records
End If
Loop
I have found the solution apparently it was just a misunderstanding because the first time I tried this code I thought it was wrong but it was because my field names did not have spaces in between the first time I wrote them.
Me.Filter = ""
Me.Filter = "[First Nam]<>'Jamie' AND [Last Nam]<>'Cartman'"
Me.FilterOn = True

Returned Recordset is closed (mysql DB accessed via ODBC in VBA)

Long time viewer first time poster. I'm working on a database application with a front-end in Excel. Hence I am using VBA to access a MySQL database. The driver I'm using is the ODBC driver (Windows) and I have a valid connection to the database as lots of other queries function well.
What I'm trying to do is return the results from a series of SQL statements rather than trying to combine it all into one massive statement (which would probably work but be difficult to maintain/understand). I have added the flag FLAG_MULTI_STATEMENTS = 67108864 to the connection string which suppressed driver syntax errors.
But now when I run the following:
queryDB.Open SQLquery, conn
The recordset (queryDB) remains closed with no apparent error. The sql statement can be found here.
I may be generating errors that aren't being returned to VBA so any help here would be much appreciated.
NOTE: The sql statements work as I can paste that statement into phpMyAdmin and it returns the correct (non-empty) results. I don't know if the statements specifically are the problem but perhaps the use of CREATE TEMPORARY TABLE ... or the use of multiple statements in general.
Also I guess that the driver may be trying to return a result for each sql statement and VBA is only getting the first or something...
EDIT: the sql statement for future reference.
CREATE TEMPORARY TABLE tmpOrders AS
SELECT
o.customerName,
SUM(o.Sales) AS Sales,
SUM(TotalFobCost + TotalLandedCost + TotalLocalCost + TotalCmtCost) AS TotalCost,
YEAR(o.deliveryDate) AS YEAR,
MONTH(o.deliveryDate) AS MONTH
FROM devere_costing.orders_fixed_extras AS o
WHERE o.orderApproved = TRUE
AND o.orderCanceled = FALSE
AND o.deliveryDate BETWEEN '2014-01-01' AND '2014-03-31'
GROUP BY customerName, YEAR, MONTH
ORDER BY YEAR ASC, MONTH ASC, customerName ASC;
CREATE TEMPORARY TABLE tmpProj AS
SELECT p.customerName,
IF(p.MONTH > 9, p.YEAR, p.YEAR - 1) AS TrueYear,
1 + ((p.MONTH + 2) MOD 12) AS TrueMonth,
SUM(p.actualSalesInvoiced) AS salesInvoiced,
SUM(p.budget) AS budget
FROM devere_costing.sales_projection_data AS p
GROUP BY p.customerName, p.YEAR, p.MONTH
HAVING TrueYear BETWEEN YEAR('2014-01-01') AND YEAR('2014-03-31')
AND TrueMonth BETWEEN MONTH('2014-01-01') AND MONTH('2014-03-31');
CREATE TEMPORARY TABLE tmpLeft AS
SELECT
IF(o.customerName IS NULL, p.customerName, o.customerName) AS customerName,
p.budget AS TotalBudget,
o.Sales AS Sales,
p.salesInvoiced,
0 AS varianceToBudget,
o.TotalCost,
0 AS directMargin,
0 AS directMarginPercent,
IF(o.YEAR IS NULL, p.TrueYear, o.YEAR) AS YEAR,
IF(o.MONTH IS NULL, p.TrueMonth, o.MONTH) AS MONTH
FROM tmpOrders AS o
LEFT JOIN tmpProj AS p
ON (o.customerName = p.customerName
AND o.YEAR = p.TrueYear
AND o.MONTH = p.TrueMonth);
CREATE TEMPORARY TABLE tmpRight AS
SELECT
IF(o.customerName IS NULL, p.customerName, o.customerName) AS customerName,
p.budget AS TotalBudget,
o.Sales AS Sales,
p.salesInvoiced,
0 AS varianceToBudget,
o.TotalCost,
0 AS directMargin,
0 AS directMarginPercent,
IF(o.YEAR IS NULL, p.TrueYear, o.YEAR) AS YEAR,
IF(o.MONTH IS NULL, p.TrueMonth, o.MONTH) AS MONTH
FROM tmpOrders AS o
RIGHT JOIN tmpProj AS p
ON (o.customerName = p.customerName
AND o.YEAR = p.TrueYear
AND o.MONTH = p.TrueMonth);
(SELECT * FROM tmpLeft) UNION DISTINCT (SELECT * FROM tmpRight);
I have answered my own question!
The secret lies here:
So I was right in that there was more than one recordset returned. I just had to iterate through them to find the data that I want. The collection isn't indexed so you have to search through each one. In my case every sql statement does not return a recordset (that's why my recordset remained closed when I tried to open it). The only exception is the last sql statement which returns records. My loop looks like:
Dim rs As ADODB.Recordset
Set rs = queryDB(Sql)
' Loop through returned recordsets to find the data
Do
If Not rs Is Nothing Then
If rs.State = adStateOpen Then
' we have an open recordset. This means that the final select statement
' has returned this data.
Exit Do
Else
' Otherwise iterate through to the next recordset
Set rs = rs.NextRecordset
End If
Else
MsgBox "No recordset returned by sql statement"
GoTo ExitCode
End If
Loop
Answer copied from the question body:
I have answered my own question!
The secret lies here:
So I was right in that there was more than one recordset returned. I just had to iterate through them to find the data that I want. The collection isn't indexed so you have to search through each one. In my case every sql statement does not return a recordset (that's why my recordset remained closed when I tried to open it). The only exception is the last sql statement which returns records. My loop looks like:
Dim rs As ADODB.Recordset
Set rs = queryDB(Sql)
' Loop through returned recordsets to find the data
Do
If Not rs Is Nothing Then
If rs.State = adStateOpen Then
' we have an open recordset. This means that the final select statement
' has returned this data.
Exit Do
Else
' Otherwise iterate through to the next recordset
Set rs = rs.NextRecordset
End If
Else
MsgBox "No recordset returned by sql statement"
GoTo ExitCode
End If
Loop

Excel Macro to concatenate multiple rows from Column B per ID rows in Column A with newlines

This problem is in an Excel .xls file.
Simplest Use Case:
Column A has one row.
Column B has 5 rows.
The 5 rows in Column B need to be merged into one row, delimited by newlines.
I have a huge .xls document where there are a ton of IDs in column A.
There are on average anywhere from 3 to 10 rows that belong to each column A row.
How to know which Column B rows belong to which Column A?
By the positioning of the cells.
One Column A row may have 5 Column B rows to the right of it.
I don't have any VBA experience.
I have looked around for macros and functions but haven't had any luck finding anything that matches this problem.
Edit:
I am now trying to figure out how to get the script to ignore rows that have a one-to-one mapping between column A and column B.
Edit again - 06-20-2012:
Now that I can attach images, here is a screenshot of an image for what I'm trying to get.
The rows for Brian and Mark should be ignored, while Scott and Tim get their values copied over.
Edit:
Unmerging column A, using the code that Andy supplied, and then using this VB script afterwards does the trick:
Sub mergeA()
For i = 2 To Cells(65535, 1).End(xlUp).Row
If IsEmpty(Cells(i, 1)) Then Range(Cells(i - 1, 1), Cells(i, 1)).Merge
Next
End Sub
That VB script puts the cells in column A back together
I didn't make the script, it came from this web page:
http://www.vbforums.com/showthread.php?t=601304
This will transform the data shown on the left to the output on the right:
Option Explicit
Sub Make_Severely_Denormalized()
Const HEADER_ROWS As Long = 1
Const OUTPUT_TO_COLUMN As Long = 3
Const DELIMITER As String = vbNewLine
Dim A_Range As Range
Dim B_Range As Range
Dim A_temp As Range
Dim B_temp As Range
Dim B_Cell As Range
Dim Concat As String
On Error GoTo Whoops
Set A_Range = Range("A1").Offset(HEADER_ROWS)
Do While Not A_Range Is Nothing
Set B_Range = A_Range.Offset(0, 1)
' some helper ranges
If A_Range.Offset(1, 0).Value = "" Then
Set A_temp = Range(A_Range, A_Range.End(xlDown).Offset(-1, 0))
Else
Set A_temp = A_Range.Offset(1, 0)
End If
Set B_temp = Range(B_Range, B_Range.End(xlDown)).Offset(0, -1)
' determine how high "B" is WRT no change in "A"
Set B_Range = Range(B_Range, B_Range.Resize( _
Application.Intersect(A_temp, B_temp, ActiveSheet.UsedRange).Count))
' loop through "B" and build up the string
Concat = ""
For Each B_Cell In B_Range
Concat = Concat & B_Cell.Value & DELIMITER
Next
Concat = Left(Concat, Len(Concat) - Len(DELIMITER))
' do the needful
A_Range.Offset(0, OUTPUT_TO_COLUMN - 1).Value = Concat
' find the next change in "A"
If A_Range.Offset(1, 0).Value = "" Then
Set A_Range = Application.Intersect(A_Range.End(xlDown), ActiveSheet.UsedRange)
Else
Set A_Range = A_Range.Offset(1, 0)
End If
Loop
Exit Sub
Whoops:
MsgBox (Err & " " & Error)
Stop
Resume Next
End Sub

MySQL or VBA Iteration Function To Find Inter-Related Records

I'm using MS Access with a MySQL database. One table has records which represent 'documents':
Table: doc
Primary Key: doc_id
These records can be linked together via a link table:
Table: link
Primary Key: link_id
Foreign Key: doc_id_A
Foreign Key: doc_id_B
Thus the records may be linked in a chain, eg Doc A linked to Doc B, which is linked to Doc C, etc, and also, Doc A may be linked to any number of other documents.
In reality, the 'family' of inter-related documents wouldn't exceed 20 records.
I'm looking for an efficient MySQL proc, or VBA function - or a query - to find all the members of a 'family' for one specified record.
Any suggestions would be most welcome!
So the link table gives a self join to doc, you absolutely need a doc to be able to:
Link to itself
... as many times as it likes
Link to another doc
... multiple times
Be separately linked to by the same other doc
... multiple times
Store information about the link
So your link table could have 10 separate links 1-1, 1-1, 1-1, 1-2, 1-2, 1-2, 2-1, 2-1, 2-1, 2-2 etc. with just 2 docs in the 'family'.
I expect when you look at this list you will probably think that you don't need most of them, a lot of the inefficiency in your solution might be coming from this unnecessary flexibility. My favoured suggestion would be to start with a strict hierarchy and build minimally from there.
But here is my answer anyway, it's tested and working in Access-2010 and local tables. ADODB should work just as well with linked tables.
Option Compare Database
Option Explicit
Const MaxInFamily = 30
'Requires a reference to "Microsoft ActiveX Data Objects 2.x Library" (VBA Menu: Tools, references)
Function GetFamily(id As Long) As Long()
Dim Found(MaxInFamily) As Long
Dim MaxFound As Integer
Dim CurrentSearch As Integer
Dim Sql As String
Dim rs As New ADODB.Recordset
Found(1) = id
MaxFound = 1
For CurrentSearch = 1 To MaxInFamily
If CurrentSearch > MaxFound Then Exit For
Sql = "SELECT doc_id_2 as NewID FROM link WHERE doc_id_1 = " & Found(CurrentSearch) _
& " AND doc_id_2 NOT IN (" & ArrayToCsv(Found, MaxFound) & ")" _
& " UNION " _
& " SELECT doc_id_1 FROM link WHERE doc_id_2 = " & Found(CurrentSearch) _
& " AND doc_id_1 NOT IN (" & ArrayToCsv(Found, MaxFound) & ")"
rs.Open Sql, CurrentProject.Connection
Do While Not rs.EOF
MaxFound = MaxFound + 1
Found(MaxFound) = rs("NewID").Value
rs.MoveNext
Loop
rs.Close
Next CurrentSearch
GetFamily = Found
End Function
Function ArrayToCsv(SourceArray() As Long, ItemCount As Integer) As String
Dim Csv As String
Dim ArrayIndex As Integer
For ArrayIndex = 1 To ItemCount
Csv = Csv & SourceArray(ArrayIndex)
If ArrayIndex < ItemCount Then Csv = Csv & ", "
Next ArrayIndex
ArrayToCsv = Csv
End Function
Duplicated results and queries are avoided by excluding items already found at the server, and as the link is unidirectional I've used a UNION query to look both ways at once. At most it'll do MaxInFamily round trips to the server.
Unfortunately, it cannot be done in MySQL given such a table structure as MySQL does not support recursive queries. I suggest to explore the answers in Mysql recursion? - there is some advice as to how to store the data in MySQL to be able to write such queries.
You also have three other options:
In case you know the max. depth of such families, and it's not huge, you can still achieve it with mySQL. For a depth of 3 levels, it would look like this:
SELECT A.doc_id_a as a, B.doc_id_a a_child, C.doc_id_a as a_sub_child
FROM links as A, links as B, links as C
WHERE A.doc_id_a = your_doc_id AND
A.doc_id_b = B.doc_id_a AND
B.doc_id_b = C.doc_id_a
Following the same logic, you can add as many layers as you need. The only thing is - you'll need to get result from all columns and find unique values (and there can be many rows, if the relationship is not 1-1 all the time).
A second option is to do this in VBA. I do not know enough to provide the code, but essentially it could look like this (a recursive approach):
family = array();
family = getFamily('your_doc_id', family);
function getFamily(id) {
children = DB->getColumn('SELECT doc_id_b FROM links WHERE doc_id_a = ?', id);
if (empty(children)) return family;
else {
foreach (children as child) {
family[] = getFamily(child);
}
}
}
Lastly, you can switch to PostgreSQL, which supports recursive queries :)
(http://www.postgresql.org/docs/8.4/static/queries-with.html).
I had a similar problem some time ago. Instead of creating links between the documents I used a separate table containing the families:
Table: LinkTable
PK: LinkId, DocumentID
FK: DocumentID
Instead of having pairs of document IDs, each time you create a new family, you introduce a new LinkId.
LinkId | DocumentID
1 | 7
1 | 9
1 | 13
2 | 4
2 | 22
2 | 23
2 | 30
3 | 6
3 | 80
Here you have three families with the document IDs:
{ 7, 9, 13 },
{ 4, 22, 23, 30 },
{ 6, 80 }
It is easier to retrieve families, however it requires a somewhat more complicated logic for inserting and removing documents to and from families.