Relinking database tables: Access, VBA - ms-access

I have a procedure that relinks all the tables in a database baed on whether or not they are a linked table. Currently this is set up to run automatically as it's set inside an AutoExec macro which calls the function.
The code works but only if I close the database and reopen it. I know that this is because this needs to be done for the new links to take effect but is there anyway around this? Or, failing that, would it be better to make the VBA code close the database and reopen it?
Thanks in advance for the feedback
P.S. Here's the code, in case you're curious:
'*******************************************************************
'* This module refreshes the links to any linked tables *
'*******************************************************************
'Procedure to relink tables from the Common Access Database
Public Function RefreshTableLinks() As String
On Error GoTo ErrHandler
Dim strEnvironment As String
strEnvironment = GetEnvironment
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim strCon As String
Dim strBackEnd As String
Dim strMsg As String
Dim intErrorCount As Integer
Set db = CurrentDb
'Loop through the TableDefs Collection.
For Each tdf In db.TableDefs
'Verify the table is a linked table.
If Left$(tdf.Connect, 10) = ";DATABASE=" Then
'Get the existing Connection String.
strCon = Nz(tdf.Connect, "")
'Get the name of the back-end database using String Functions.
strBackEnd = Right$(strCon, (Len(strCon) - (InStrRev(strCon, "\") - 1)))
'Debug.Print strBackEnd
'Verify we have a value for the back-end
If Len(strBackEnd & "") > 0 Then
'Set a reference to the TableDef Object.
Set tdf = db.TableDefs(tdf.Name)
If strBackEnd = "\Common Shares_Data.mdb" Or strBackEnd = "\Adverse Events.mdb" Then
'Build the new Connection Property Value - below needs to be changed to a constant
tdf.Connect = ";DATABASE=" & strEnvironment & strBackEnd
Else
tdf.Connect = ";DATABASE=" & CurrentProject.Path & strBackEnd
End If
'Refresh the table links
tdf.RefreshLink
End If
End If
Next tdf
ErrHandler:
If Err.Number <> 0 Then
'Create a message box with the error number and description
MsgBox ("Error Number: " & Err.Number & vbCrLf & _
"Error Description: " & Err.Description & vbCrLf)
End If
End Function
EDIT
Following on from Gords comments I have added the macro AutoExec method for calling the code below. Anyone see a problem with this?
Action: RunCode
Function Name: RefreshTableLinks()

The most common error in this situation is forgetting to .RefreshLink the TableDef but you are already doing that. I just tested the following VBA code which toggles a linked table named [Products_linked] between two Access backend files: Products_EN.accdb (English) and Products_FR.accdb (French). If I run the VBA code and then immediately open the linked table I see that the change has taken place; I don't have to close and re-open the database.
Function ToggleLinkTest()
Dim cdb As DAO.Database, tbd As DAO.TableDef
Set cdb = CurrentDb
Set tbd = cdb.TableDefs("Products_linked")
If tbd.Connect Like "*_EN*" Then
tbd.Connect = Replace(tbd.Connect, "_EN", "_FR", 1, 1, vbBinaryCompare)
Else
tbd.Connect = Replace(tbd.Connect, "_FR", "_EN", 1, 1, vbBinaryCompare)
End If
tbd.RefreshLink
Set tbd = Nothing
Set cdb = Nothing
End Function
I even tested calling that code from an AutoExec macro and it also seems to work as expected.
One thing you could try would be to call db.TableDefs.Refresh right at the end of your routine to see if that helps.
Edit
The issue here was that the database had a "Display Form" specified in its "Application Options", and that form apparently opens automatically before the AutoExec macro runs. Moving the function call for the re-linking code to the Form_Load event handler for that "startup form" seems a likely fix.

Related

How do I programmatically retrieve the values from a linked table's property sheet?

I am working in MS Access. All the tables and views are linked to a SQL Server database. I want to write a procedure that will retrieve and store all of the formatting information about these objects. A lot of this information is available from the property sheet (I open a table in Design View, and hit F4 for the property sheet). Eg:
Filter
Order By
Filter On Load
Order by On Load
Order by On
How do I retrieve these properties programmatically? I only see them listed for Reports.
Note that I need to retrieve the values, not just set them. I know about the SetFilter method, and that's not what I need.
The linked table exists as a DAO.TableDef in your database's TableDefs collection. So you can check the TableDef.Properties collection for those 5 properties.
However beware that both Filter and OrderBy are user-created instead of default properties, which means they are not included in the Properties collection unless you've assigned them values. Attempting to retrieve one which doesn't exist triggers error 3270, "Property not found". You can trap that error, respond to it as you wish, and continue on for the other properties you're interested in. Or you could first determine whether the property exists and only attempt to retrieve its value when it does exist.
This code sample uses the first approach (trap the error):
Const cstrTable As String = "YourLinkedTableNameHere"
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim strMsg As String
Dim varProp As Variant
Dim varProperties As Variant
On Error GoTo ErrorHandler
varProperties = Array("Filter", "FilterOnLoad", "OrderBy", _
"OrderByOn", "OrderByOnLoad")
Set db = CurrentDb
Set tdf = db.TableDefs(cstrTable)
For Each varProp In varProperties
Debug.Print varProp, tdf.Properties(varProp).Value
Next
ExitHere:
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 3270 ' Property not found.
strMsg = "Property '" & varProp & "' not found."
'MsgBox strMsg
Debug.Print strMsg
Resume Next
Case Else
strMsg = "Error " & Err.Number & " (" & Err.Description & ")"
MsgBox strMsg
Resume ExitHere
End Select
How about something like this? (I've defined "table2" to have two fields, "PropertyName" and "PropertyValue"..."table1" is a placeholder for any of your existing tables)
Dim i As Integer
Dim j As Integer
Dim RS As DAO.Recordset
On Error Resume Next
Set RS = CurrentDb.OpenRecordset("select * from table2")
j = CurrentDb.TableDefs("table1").Properties.Count
For i = 0 To j - 1
RS.AddNew
RS!PropertyName = CurrentDb.TableDefs("table1").Properties(i).Name
RS!PropertyValue = Nz(CurrentDb.TableDefs("table1").Properties(i).Value, "-")
RS.Update
Next i

Access VBA code to pull all files from a folder and insert them into seperate attachment fields in a table

I have code written to pull a specific file from a folder, insert it into an attachment field (local_attachment) and which creates a new record in table TEMP_attachment. I am trying to pull all the files from a folder and have them each be a new record in the table but I keep running into issues where I either pull all the files and they all go into one record, or it won't pull any. Thank you for your help!!!
Here is my code:
Dim x As Long
Dim strFile As String
Dim db As DAO.Database
Dim rs As DAO.Recordset2
Dim rsA As DAO.Recordset2
Dim fld As DAO.Field2
Dim SQL As String
x = 1
strFile = "C:\dev\test_file2.txt"
SQL = "INSERT INTO TEMP_Attachment (ID) "
SQL = SQL & "VALUES (" & x & ")"
DoCmd.RunSQL SQL
Set db = CurrentDb
Set rs = db.OpenRecordset("TEMP_Attachment")
Set fld = rs("local_attachemnt")
'Navigate through the table
Set rsA = fld.Value
rs.Edit
rsA.AddNew
rsA("FileData").LoadFromFile strFile
rsA.Update
rs.Update
The problems with your code can be fixed my taking a more methodical approach. As I see it, you need to find all the files in the folder, add a record for each one, add an attachment record for the file, read the file data into the new record, and generate a unique key for the parent record.
Rather than try to do everything at once, let's break into pieces and take them in reverse order, so we're dealing with the smallest problems first:
First, let's figure out how we are going to generate a key. The easiest way to do this is to use an Autonumber field. Rather than cook up something fancier, I'm going to assume this is the solution you will use. This will make DoCmd.RunSQL unnecessary and simplify the whole operation.
Second, write a routine which adds one file to one record in the database, and make sure this is working. My suggestion would be to create parameters for a recordset to the main table and a path to the file, like so (I have not tested this, that will be your job. I've added error handlers to help you work out any issues):
Private Sub AddFileAttachment(ByRef rs As DAO.Recordset, ByVal sPath As String)
Dim rsAttachments As DAO.Recordset
On Error Goto EH
With rs
'(this will generate a new Autonumber in the main table)
.AddNew
'this will create a new attachment in the field and add it
Set rsAttachments = .Fields("local_attachemnt").Value
With rsAttachments
.AddNew
.Fields("FileData").LoadFromFile sPath
.Update
.Close
End With
'this is what adds the main record
.Update
End With
EH:
With Err
MsgBox .Number & vbcrlf & .Source & vbCrLf & .Description
End With
FINISH:
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
If Not rsAttachments Is Nothing Then
rsAttachments.Close
Set rsAttachments = Nothing
End If
End Sub
And call it like so:
Private Sub cmdTest_Click()
Dim db As DAO.Database
Dim rs As DAO.Recordset
On Error Goto EH
Set db = CurrentDb
Set rs = db.OpenRecordset("TEMP_Attachment")
AddFileAttachment rs, "C:\dev\test_file2.txt"
Goto FINISH
EH:
With Err
MsgBox .Number & vbcrlf & .Source & vbCrLf & .Description
End With
FINISH:
rs.Close
End Sub
Important! Perfect the first routine before moving on. You should test it until you know it works over and over. You should be able to click the button it is attached to 10 times and each time get a new record with the file attached.
Once you know this is working, you are ready to write the main routine that calls it for each file you are attaching. I will not include that here, but would suggest researching the FileSystemObject. You should be able to find a lot of vba examples for how to get all the files in a folder. You would loop through them and call your routine for each file the same way it is called in the test above, passing in the open recordset.

Access query need to bypass "enter parameter value" error with a msg box saying field not found

I have a simple query tied to a command button that shows a summary of the values in a particular field. It's running on a table that changes with each use of the database, so sometimes the table will contain this field and sometimes it won't. When the field (called Language) is not in the file, the user clicks the command button and gets the "Enter Parameter Value" message box. If they hit cancel they then get my message box explaining the field is not present in the file. I would like to bypass the "Enter Parameter Value" and go straight to the message if the field is not found. Here is my code:
Private Sub LangCount_Click()
DoCmd.SetWarnings False
On Error GoTo Err_LangCount_Click
Dim stDocName As String
stDocName = "LanguageCount"
DoCmd.OpenQuery stDocName, acNormal, acEdit
Err_LangCount_Click:
MsgBox "No Language field found in Scrubbed file"
Exit_LangCount_Click:
Exit Sub
DoCmd.SetWarnings True
End Sub
You can attempt to open a recordset based on the query before you run the query:
Set rs = CurrentDb.QueryDefs("query1").OpenRecordset
This will go straight to the error coding if anything is wrong with the query.
Alternatively, if it is always the language field and always in the same table, you can:
sSQL = "select language from table1 where 1=2"
CurrentDb.OpenRecordset sSQL
This will also fail and go to your error coding, but if it does not fail, you will have a much smaller recordset, one with zero records.
You can easily enough get a list of fields in a table with ADO Schemas:
Dim cn As Object ''ADODB.Connection
Dim i As Integer, msg As String
Set cn = CurrentProject.Connection
Set rs = cn.OpenSchema(adSchemaColumns, Array(Null, Null, "Scrubbed"))
While Not rs.EOF
i = i + 1
msg = msg & rs!COLUMN_NAME & vbCrLf
rs.MoveNext
Wend
msg = "Fields: " & i & vbCrLf & msg
MsgBox msg
More info: http://support.microsoft.com/kb/186246
You have a command button named LangCount. It's click event has to deal with the possibility that a field named Language is not present in your Scrubbed table.
So then consider why a user should be able to click that command button when the Language field is not present. When the field is not present, you know the OpenQuery won't work (right?) ... so just disable the command button.
See if the following approach points you to something useful.
Private Sub Form_Load()
Me.LangCount.Enabled = FieldExists("Language", "Scrubbed")
End Sub
That could work if the structure of Scrubbed doesn't change after your form is opened. If the form also includes an option to revise Scrubbed structure, update LangCount.Enabled from that operation.
Here is a quick & dirty (minimally tested, no error handling) FieldExists() function to get you started.
Public Function FieldExists(ByVal pField As String, _
ByVal pTable As String) As Boolean
Dim blnReturn As Boolean
Dim db As DAO.Database
Dim fld As DAO.Field
Dim tdf As DAO.TableDef
Set db = CurrentDb
' next line will throw error #3265 (Item not found in this collection) '
' if table named by pTable does not exist in current database '
Set tdf = db.TableDefs(pTable)
'next line is not actually needed '
blnReturn = False
For Each fld In tdf.Fields
If fld.Name = pField Then
blnReturn = True
Exit For
End If
Next fld
Set fld = Nothing
Set tdf = Nothing
Set db = Nothing
FieldExists = blnReturn
End Function

MS Access Metadata

I'm performing a data cleansing operation on an access database. I have several duplicate records in a table that I want to consolidate down into one single record. In doing this I will need to update all references to the records that I will be consolidating.
If I know the column name that holds the record id is there a way to find all of the tables in access that contain this column?
You can examine the TableDefs collection and determine which tables contain a field with a given name.
Public Sub TablesWithField(ByVal pName As String)
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim strMsg As String
Dim strName As String
On Error GoTo ErrorHandler
Set db = CurrentDb
For Each tdf In db.TableDefs
strName = vbNullString
'ignore system and temporary tables '
If Not (tdf.name Like "MSys*" Or tdf.name Like "~*") Then
strName = tdf.Fields(pName).name
If Len(strName) > 0 Then
Debug.Print tdf.name & ": " & pName
End If
End If
Next tdf
ExitHere:
On Error GoTo 0
Set tdf = Nothing
Set db = Nothing
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 3265 'Item not found in this collection. '
Resume Next
Case Else
strMsg = "Error " & Err.Number & " (" & Err.description _
& ") in procedure TablesWithField"
MsgBox strMsg
GoTo ExitHere
End Select
End Sub
Short answer: Yes. And there are many ways to skin that cat. Two ideas:
(1) Via VBA, make use of: Application.CurrentDb.TableDefs(i).Fields(j).Name
(2) Via Tools==>Analyze==>Documenter, make a report and then search its output (Publish it with MS Word).
Sorry, but Access isn't built like MS SQL Server or DB2 - the MSys* tables really aren't set up for querying table schemas like that. However, others have VBA based solutions that look useful.
You can use Schemas, not exactly a query, but similar:
Function ListTablesContainingField(SelectFieldName) As String
'Tables returned will include linked tables
'I have added a little error coding. I don't normally do that
'for examples, so don't read anything into it :)
Dim cn As New ADODB.Connection
Dim rs As ADODB.Recordset
Dim strTempList As String
On Error GoTo Error_Trap
Set cn = CurrentProject.Connection
'Get names of all tables that have a column called <SelectFieldName>
Set rs = cn.OpenSchema(adSchemaColumns, _
Array(Empty, Empty, Empty, SelectFieldName))
'List the tables that have been selected
While Not rs.EOF
'Exclude MS system tables
If Left(rs!Table_Name, 4) <> "MSys" Then
strTempList = strTempList & "," & rs!Table_Name
End If
rs.MoveNext
Wend
ListTablesContainingField = Mid(strTempList, 2)
Exit_Here:
rs.Close
Set cn = Nothing
Exit Function
Error_Trap:
MsgBox Err.Description
Resume Exit_Here
End Function

How can a relative path specify a linked table in Access 2007?

I have a front end and back end of an Access database. The front end references linked tables and I need to do a relative link instead of an explicit one i.e. "../database" is referenced instead of "address/database"
Is it possible to do this, or must I specify the absolute path?
Tables linked to files (such as mdb, accdb, dbf, etc.) require absolute paths in their connection strings.
However there is a workaround: during the database startup you can use vba to redefine the the links to match the directory of the current database instance.
(The code below has not been tested / debugged)
Private Sub RelinkTables()
Dim oldConnection As String
Dim newConnection As String
Dim currentPath As String
currentPath = CurrentProject.Path
Dim tblDef As TableDef
For Each tblDef In CurrentDb.TableDefs
oldConnection = tblDef.Connect
' Depending on the type of linked table
' some string manipulation which defines
' newConnection = someFunction(oldConnection,currentPath)
tblDef.Connect = newConnection
tblDef.RefreshLink
Next tblDef
End Sub
I have tried some of the answers above, especially the answer of Martin Thompson which I got some errors with, and thus modified it as follows:
Public Function reLinkTables() As Boolean
On Error GoTo ErrorRoutine
Dim sMyConnectString As String
Dim tdf As TableDef
Dim db_name As String
' The Main Answer is by Martin Thompson
' Modified by Dr. Mohammad Elnesr
'We will link all linked tables to an accdb Access file located in the same folder as this file.
'Replace the DATA file name in the following statement with the name of your DATA file:
sMyConnectString = ";DATABASE=" & CurrentProject.Path & "\"
For Each tdf In CurrentDb.TableDefs
If Len(tdf.Connect) > 0 Then
'It's a linked table, so re-link:
'First, get the database name
db_name = GetFileName(tdf.Connect)
' Then link the table to the current path
tdf.Connect = sMyConnectString & db_name
tdf.RefreshLink
End If
Next tdf
ExitRoutine:
MsgBox "All tables were relinked successfully"
Exit Function
ErrorRoutine:
MsgBox "Error in gbLinkTables: " & Err.Number & ": " & Err.Description
Resume ExitRoutine
End Function
Function GetFileName(FullPath As String) As String
Dim splitList As Variant
splitList = VBA.Split(FullPath, "\")
GetFileName = splitList(UBound(splitList, 1))
End Function
After fininshing this, Goto Access Ribon>Create>Macro From the dropdown select "RunCode", then in the function name type "reLinkTables" which we typed here. Then save the macro with the name "AutoExec". Every time you open the database, all the linked tables will be relinked to the original path. This is very useful if you put your databases in a portable media.
As far as I know, your TableDef's Connect property requires an absolute path. If I'm wrong on that point, I hope someone will tell how to create a linked table using a relative path.
Take a look at Armen Stein's free utility to manage your table links: J Street Access Relinker
Here is a simple routine that worked for me:
Public Function gbLinkTables() As Boolean
On Error GoTo ErrorRoutine
Dim sMyConnectString As String
Dim tdf As TableDef
'We will link all linked tables to an accdb Access file located in the same folder as this file.
'Replace the DATA file name in the following statement with the name of your DATA file:
sMyConnectString = ";database=" & CurrentProject.Path & "\Loan-Tracking-Data.accdb"
For Each tdf In CurrentDb.TableDefs
If Len(tdf.Connect) > 0 Then
'It's a linked table, so re-link:
tdf.Connect = sMyConnectString
tdf.RefreshLink
End If
Next tdf
ExitRoutine:
Exit Function
ErrorRoutine:
MsgBox "Error in gbLinkTables: " & Err.Number & ": " & Err.Description
Resume ExitRoutine
End Function
The following code has been tested in the Form_Load event of the form listed in the "Display Form" option for the database; that is the form that loads whenever the database is opened. This code could also be called from the AutoExec macro for the database:
Private Sub Form_Load()
Dim strOldConnect As String
Dim strNewConnect As String
Dim intSlashLoc As Integer
Dim intEqualLoc As Integer
Dim strConnect As String
Dim strFile As String
Dim strCurrentPath As String
strCurrentPath = CurrentProject.path
Dim tblDef As TableDef
Dim tblPrp As Property
For Each tblDef In CurrentDb.TableDefs
Debug.Print tblDef.Name
If tblDef.Connect & "." <> "." Then
strOldConnect = tblDef.Connect
intEqualLoc = InStr(1, strOldConnect, "=", vbTextCompare)
strConnect = Left(strOldConnect, intEqualLoc)
intSlashLoc = InStrRev(strOldConnect, "\", -1, vbTextCompare)
strFile = Right(strOldConnect, Len(strOldConnect) - intSlashLoc)
strNewConnect = strConnect & strCurrentPath & "\" & strFile
tblDef.Connect = strNewConnect
tblDef.RefreshLink
End If
Next tblDef
End Sub
you can make a "calculated" field.. works for me in Office Access 2016
"F:\Komponenten\Datenbank\Bilder\" & [Kategorie] & "\Pinout\" & [Bezeichnung] & ".jpg"
maybe there are better solutions, see images
calculated path
result