Access: Missing entries in the TableDef.fields collection - ms-access

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,

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

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

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

Error 3218: Could not update; currently locked

I've been looking through the other questions related to mine, but most are about multi-user and this one came close but not quite.
System
MS Access 2013
with Linked Tables to Office 365 Sharepoint
tblQuote - frmQuote
tblQuoteItems - sbfrmQuoteItems
No Record Locks
I'm attempting to setup a select all/deselect all button that when clicked runs db.Execute Update on the tblQuoteItems where equal to Quote ID and Quote Version.
I have a button on the main form that calls the below process.
Public Sub SelectLineItems(strTable As String, strID As String, _
intID As Integer, bln As Boolean, Optional intVersion As Integer)
Dim db As Database
Dim strSQL As String
Dim strVersion As String
Set db = CurrentDb
strSQL = "UPDATE " & strTable & " SET [Selected] = "
If intVersion > 0 Then
strVersion = " AND [QuoteVersion] = " & intVersion
Else
strVersion = ""
End If
If bln Then
strSQL = strSQL + "False WHERE " & strID & " = " & intID & strVersion & ";"
Else
strSQL = strSQL + "True WHERE " & strID & " = " & intID & strVersion & ";"
End If
db.Execute strSQL, dbFailOnError
db.Close
Set db = Nothing
Exit Sub
It's pretty simple, nothing to crazy. The problem occurs when I try to run this after a record has been modified by the form and it still has focus. Because of the dbFailOnError I get the error message, If I remove dbFailOnError it will update every record except the one that has been modified through the form.
If I modify the record then select a different record manually by clicking with the mouse, the record is no longer locked and the Update works with no errors.
I have tried to replicate the process of clicking on a new record and have put the below code
If Me.Dirty Then Me.Dirty = False
In every Event I could think of like:
The subform_Current, subform_Dirty, subform.Control.Dirty/Lost_focus/subform_Before and After Update, The exit event of the subform control on the main form...etc
Placing the code in different areas doesn't make any difference, the first time the code is called it updates the record and I can confirm this in the table, the new value is present.
I've attempted to requery the subform
I've tried
DoCmd.GoToRecord , , acFirst
Then setting focus to the first control of the record.
I've even tried changing the update from the db.Execute to using a recordset object
Dim db As Dao.Database
Dim rs As Dao.Recordset
Dim strSQL As String
Dim strVersion As String
If intVersion > 0 Then
strVersion = " AND [QuoteVersion] = " & intVersion
Else
strVersion = ""
End If
strSQL = "SELECT * FROM " & strTable & " WHERE " & strID & "= " & intID & strVersion
Set db = CurrentDb
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
With rs
Do Until .EOF
.Edit
!Selected = bln
.Update
.MoveNext
Loop
End With
rs.Close
db.Close
Set rs = Nothing
Set db = Nothing
But again it will cycle through every unlocked record and update, until it gets to the one modified by the form which then throws the same error.
I've tried opening the recordset then closing it then reopening it. But it doesn't seem to matter it's the form that's holding onto the locked record.
The only solution that has worked for me was to Set the subform recordsource to nothing, then run the update, then reset the recordsource to what it was.
The Selected column is within the QuoteItems table itself, and not in it's own table with reference to the QuoteItems ID
My question is how do I get the form to release the record through code that mimics the action of manually clicking on a new record without resetting the subform's recordsource.
Your approach with using Dirty=False is the right thing to do. But you have to apply it to the subform, as this is where the recordlock occurs. If your Code is in the Main form, you need to add this before your code to update the records.
Me.sbfrmQuoteItems.Form.Dirty = False
In that line sbfrmQuoteItems is supposed to be the name of the SubForm-Control in your main form!

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