MS Access Metadata - ms-access

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

Related

How to check if the table is empty in Access 2003?

I need only empty tables in access database. Additionally, it would be great if I can get empty tables from list of tables that I have (part of all tables). But listing all empty tables would work also.
You can use a small VBA function that checks this. Something like:
Function fIsTableEmpty(strTableName As String) As Boolean
On Error GoTo E_Handle
Dim db As DAO.Database
Dim rsData As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb
strSQL = "SELECT COUNT(*) FROM [" & strTableName & "];"
Set rsData = db.OpenRecordset(strSQL)
fIsTableEmpty = True ' start by assuming that there are records
If Not (rsData.BOF And rsData.EOF) Then
If rsData(0) > 0 Then fIsTableEmpty = False
End If
fExit:
On Error Resume Next
rsData.Close
Set rsData = Nothing
Set db = Nothing
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fIsTableEmpty", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
You can use DCount:
Public Function ListEmptyTables()
Dim Table As DAO.TableDef
For Each Table In CurrentDb.TableDefs
If Table.SourceTableName = "" Then
If DCount("*", Table.Name) = 0 Then
Debug.Print Table.Name
End If
End If
Next
End Function

Access: Missing entries in the TableDef.fields collection

I'm using a VBA procedure to add some fields to an existing table by modifying its TableDef.
As the names of these fields could change between imports, I opted to delete the old entries before adding new ones.
The code below has no problem adding the fields from the library table (P6 Files AC).
Where it goes wrong is in deleting existing entries. The count at the beginning always gives the correct number of fields. But the FOR EACH statement jumps over some of the entries.
Running the code repeatedly, ultimately does delete all of the field that meet the criteria.
Set curdb = CurrentDb()
Set tdf = curdb.TableDefs("TASK")
Debug.Print tdf.Fields.Count
tdf.Fields.Refresh
For Each fld In tdf.Fields
Debug.Print fld.Name
If InStr(1, fld.Name, "AC#", vbTextCompare) > 0 Then tdf.Fields.Delete fld.Name
Next fld
'add the field from the P6 Files AC table
strSQL = "SELECT [P6 Files AC].Field_Name " & _
"FROM [P6 Files AC] " & _
"ORDER BY [P6 Files AC].Field_Name;"
Set newfields = curdb.OpenRecordset(strSQL, dbOpenSnapshot)
With newfields
Do Until .EOF()
tdf.Fields.Append tdf.CreateField(!field_name, dbText, 15)
.MoveNext
Loop
End With
I think it would be much simpler to link the source table, then use it as source in a create-table query:
SELECT *
INTO [TASK]
FROM [P6 Files AC];
It will overwrite an existing TASK table.
When you loop a collection of items, such as fields in a table, to delete them, you need to do so in reverse order, otherwise the current field positions get out of sync to those being considered in your loop. Try something like:
Sub sDeleteFields()
On Error GoTo E_Handle
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim lngCount As Long
Dim lngLoop1 As Long
Set db = CurrentDb
Set tdf = db.TableDefs("tblRatings")
lngCount = tdf.Fields.Count - 1
For lngLoop1 = lngCount To 0 Step -1
If InStr(tdf.Fields(lngLoop1).name, "AC#") > 0 Then
tdf.Fields.Delete tdf.Fields(lngLoop1).Name
End If
Next lngLoop1
tdf.Fields.Refresh
sExit:
On Error Resume Next
Set tdf = Nothing
Set db = Nothing
Exit Sub
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "sDeleteFields", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume sExit
End Sub
Note that fields are 0-indexed, so the first field is at position 0, and the last field is at position count-1.
Regards,

Ignore error 58 when renaming files

I've got a small Access program that looks up files names from a query ("qryImagesToRename"), goes through a loop and renames them. However, if an image already exists with the same name Access wants to rename it to, I receive
error 58 - File Already Exists
How do I ignore this error and continue with the loop? This my code:
Private Sub Command10_Click()
On Error GoTo Command10_Click_Error
Dim rs As DAO.Recordset
Dim db As DAO.Database
Dim strSQL As String
DoCmd.Hourglass True
Set db = CurrentDb
strSQL = "select * from qryImagesToRename"
Set rs = db.OpenRecordset(strSQL)
Do While Not rs.EOF
Name rs.Fields("From").Value As rs.Fields("To").Value
rs.MoveNext
Loop
DoCmd.Hourglass False
MsgBox "All matching files renamed"
On Error GoTo 0
Exit Sub
Command10_Click_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Command10_Click of VBA Document Form_frmRename - Please take a screenshot and email xxxxxx#xxxxxxx.com"
End Sub
If you are certain that you can ignore the error then you could use On Error Resume Next to ignore it and continue processing. Ensure that you add On Error Goto 0 as soon as you can, to reinstate the normal error processing.
On Error Resume Next
Do While Not rs.EOF
Name rs.Fields("From").Value As rs.Fields("To").Value
rs.MoveNext
Loop
On Error GoTo 0
This is most often a poor practice, but can be used if there is certainty about behaviour.
A better practice would be to check if the file already exists using Dir (or FileSystemObject) and skip it. Discussed here
Two particular solutions come to mind. The first, is in-line logic to check for the existing file, and skip that item, and the second is to put a case statement in the error handler. I have outlined the code below to have both options. I hope it helps.
Private Sub Command10_Click()
On Error GoTo Command10_Click_Error
Dim rs As DAO.Recordset
Dim db As DAO.Database
Dim strSQL As String
Dim fso as New FileSystemObject
DoCmd.Hourglass True
Set db = CurrentDb
strSQL = "select * from qryImagesToRename"
Set rs = db.OpenRecordset(strSQL)
Do While Not rs.EOF 'if you want to use the logic inline, use the check below
If fso.fileexists(rs.Fields("To").value) = false Then
Name rs.Fields("From").Value As rs.Fields("To").Value
End If
NextRecord: 'if you want to use the goto statement, use this
rs.MoveNext
Loop
DoCmd.Hourglass False
MsgBox "All matching files renamed"
On Error GoTo 0
Exit Sub
Command10_Click_Error:
Select case Err.number
Case 58
GoTo NextRecord
Case Else
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Command10_Click of VBA Document Form_frmRename - Please take a screenshot and email xxxxxx#xxxxxxx.com"
End select
End Sub

Access-Adding a Calculated Field from a Linked Excel Table

I have two tables, Table: A which is built in Access and Table: Error which is a linked table in Excel. The Error Table has the calculation that is SQL coding that I want to be in the Query within Access. An example of the Error is:
IIf([Table A].[RETURN_DAYS]<>30,"-RETURN POLICY DAYS SHOULD BE 30","")
The Columns in the Errors Table are:
|Audit Name|Error Codes|
|Audit 1 |the code above
|Audit 2 |
Currently I manually Copy and paste the Error Code into the Query within the Field from the Excel table in order for the calculation to take place. This takes an incredibly long time since I have over 150+ Audits and 5 Different Error fields.
I want to be able to select the error from the error column in the linked table that is applicable based on the Audit name and place that error in the field so that when the Query is ran, access completes the calculation. I can not find any information on how to do this or if its possible. Any help is appreciated.
Since I have no idea about what cells you are using, or sheet names, you will need to adjust the following code.
This code will monitor changes to a certain Column (Your SQL) and will change the query in Access. Assumes the query name is in col A, SQL in col B.
Place this code in the sheet where you will make changes.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
'Monitor the range of cells you may be making changes to. I have assumed SQL is in col 2 (B)
If Target.column <> 2 Then Exit Sub ' If change not made in column 'A' then Ignore!
' Assuming you have the query name in Col A and the SQL in Col B
If Target.column = 1 Then
MsgBox "You are changing the query name?"
End If
If Change_SQL(Cells(Target.Row - 1, Target.column), Cells(Target.Row, Target.column)) <> "passed" Then
MsgBox "Update failed."
End If
End Sub
Function Change_SQL(strQueryName As String, strNewSQL As String) As String
'Declare Variables'
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim qdf As DAO.QueryDef
Dim strSQL_Old As String
Dim strSQL_New As String
'Connect to the Database'
On Error GoTo Error_Trap
Set dbs = DBEngine.OpenDatabase("C:\data\Access\xxxxxx.mdb", False, False, _
"MS Access;")
Set qdf = dbs.QueryDefs("Query5")
strSQL_Old = qdf.Sql
Debug.Print "Query Name: " & qdf.Name & vbTab & qdf.Sql
qdf.Sql = strNewSQL
qdf.Close
' You can execute the query if needed....
'Set rst = dbs.OpenRecordset(strNewSQL) ' If it's a select query
'Do While Not rst.EOF
' Do whatever
' rst.MoveNext
'Loop
'rst.Close
'Set rst = Nothing
Set dbs = Nothing
Change_SQL = "passed"
Exit Function
Error_Trap:
Change_SQL = "failed"
Debug.Print Err.Number & vbTab & Err.Description
MsgBox Err.Number & vbTab & Err.Description
Exit Function
Resume
End Function

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