Tag Archives: MS Access Tables

MS Access Working with Attachment Data Types

Although I highly recommend completely avoiding the use of Attachment Data Types, it is still a common question in discussion forums so I thought I’d put together a simple demonstration database.  In the following sample I demonstrate a few ways to work with attachment fields such as, how to:

  • Create subform to display all the attachments
  • Save an individual attachment to a folder of your choosing
  • Save all the attachments to a folder of your choosing
  • Open/Preview an attachment
  • Delete an attachment

Continue reading

Great Access Tools – Calendar Controls

Those of you that follow this blog or have seen my forum posts regarding the built-in date picker know my thoughts on the matter.  To put it simply, it is a disgrace that this is what Microsoft supplied with Access for selecting dates.  If you want more reasons as to why I think this, refer to my earlier post

 

So, since I highly recommend completely disabling the built-in date picker and replacing it, I thought I should show a couple of the excellent replacements.

Also, another reason to implement a date picker and stop manual entry by your users is to avoid formatting issues depending on what format the user chose to use when inputting the date.  06/11/11, what’s the day, month and year?  No guarantee Access will save it as was intended by the users.  However, by using a date picker, you have 100% certainty that the date entered is the date saved!

Continue reading

MS Access – List All Lookup Fields

We all know about the evils of Lookup fields and why they are to be avoided at all costs! If you aren’t already familiar with why they are to never be used, then take a few moments to review The Evils of Lookup Fields in Tables (pay special attention regarding Upsizing should you ever think you will need to eventually port your db to SQL Server, or another RDMS one day!) and Did I Mention I Have a Thing Against Lookup Fields in Tables?.

The problem is that many novice developers are not aware of the above and use them, some abundantly. So when I take over their projects I need to easily find these fields so I can remove them and set things up properly. As such, here is a simple little procedure I put together that will list all the table lookup field in a database.

Continue reading

Why MS Access isn’t a Secure Database – Part 2

It is one things to explain how Access stores back-end passwords in plain English text in a hidden system table as I did in my article entitled Why MS Access isn’t a Secure Database, but I didn’t just want you to take my word for it. So I quickly put together what I have dubbed my BE Password Extractor.

Do note, this utility only works for Access database using Access back-ends. It will not retrieve SQL Server passwords …

Simply pick the Front-End to use to extract Back-End information about, specify the Password for the Front-End if applicable and then click on the Retrieve BE Info.  In a few split seconds, you should see a list of Back-End file(s) and their respective Password and Connection String.

Continue reading

Upgrading to Windows 10 Results in “Your network access was interrupted. To continue, close the database and then open it again.”

I’ve been seeing a series of threads in the past few months regarding people experiencing the vague error

Your network access was interrupted. To continue, close the database and then open it again.

after upgrading their system to Windows 10.

Now, I won’t bore you with the details, but the gist of it is as follows.  If you use mapped drives (possibly when done through network Group Policies) to link your tables, it would seem the upgrade process, or Windows 10, screws things up.  There are 2 solutions to the problem:

  1. Remap the drives manually
  2. Use UNC paths when linking tables (this is the preferred solution as it eliminates any possible problem coming from drive mapping and this problem would never have occurred in the first place)

A few useful links on the subject with more information:

Your network access was interrupted. To continue, close the database and then open it again.
Access 2013 – Your network access was interrupted w/Windows 10
Your network access was interrupted error since Windows 10 upgrade

 

MS Access – VBA – Split Data into Related Table

The following was created to help a user in a forum.

The issue was a denormalized set of data that he wanted to cleanup, but not manually as there were far too many records to process.

Thake a simple example, a table

TblClients
ClientID
FirstName
LastName
OrderNumbers

with data like

1 | Joe | Doyle | 112,267,298,3009,1200
2 | Jane | Smith | 23,445,446,449
3 | Lois | Carleton | 1111

Now proper normalization dictates that we have an orders table to in which each order is entered instead of a single text field (OrderNumbers).

So we create another table

TblOrder
OrderID
ClientID
OrderNumber

so the task becomes how do I take the existing data and split each OrderNumber entry and append it to the TblOder table when I have 300K+ records?

Well, that’s where my procedure become useful.

'---------------------------------------------------------------------------------------
' Procedure : SplitFieldIntoChildTable
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Take a list from one parent table and split it up and insert them into a
'             child table related by the PK/FK
' Copyright : The following may be altered and reused as you wish so long as the
'             copyright notice is left unchanged (including Author, Website and
'             Copyright).  It may not be sold/resold or reposted on other sites (links
'             back to this site are allowed).
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
'sInputTable        Name of the Parent table containing the string to be split
'sDataFieldName     Name of the field in the parent table containing the string to be split
'sDelim             Delimiting character(s) that will be used to split the string
'sPKField           Name of the Primary Key field in the parent table
'sChildTable        Name of the Child table to which the split string values are to be inserted
'sChildFieldName    Name of the field in the child table to insert the values into
'sFKField           Name of the Foreign Key field in the child table
'
' Usage:
' ~~~~~~
'
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2016-01-10              Initial Release
'---------------------------------------------------------------------------------------
Function SplitFieldIntoChildTable(sInputTable As String, sDataFieldName As String, sDelim As String, sPKField As String, _
                                  sChildTable As String, sChildFieldName As String, sFKField As String)
    Dim db                    As DAO.Database
    Dim rs                    As DAO.Recordset
    Dim sSQL                  As String
    Dim lPK                   As Long
    Dim sFieldValue           As String
    Dim aFieldValues          As Variant
    Dim i                     As Long

    Set db = CurrentDb

    sSQL = "SELECT [" & sPKField & "], [" & sDataFieldName & "] FROM [" & sInputTable & "];"
    Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot)
    With rs
        If .RecordCount <> 0 Then
            Do While Not .EOF
                lPK = rs(sPKField)
                sFieldValue = rs(sDataFieldName)
                aFieldValues = Split(sFieldValue, sDelim)
                For i = 0 To UBound(aFieldValues)
                    db.Execute "INSERT INTO [" & sChildTable & "] ([" & sFKField & "], [" & sChildFieldName & "])" & _
                               " VALUES (" & lPK & ", " & aFieldValues(i) & ");", dbFailOnError
'The following would be for textual data types
'                    db.Execute "INSERT INTO [" & sChildTable & "] ([" & sFKField & "], [" & sChildFieldName & "])" & _
'                               " VALUES (" & lPK & ", " & aFieldValues(i) & ");", dbFailOnError
                Next i
                .MoveNext
            Loop
        End If
    End With
    rs.Close

    Set rs = Nothing
    Set db = Nothing
End Function

With a single line of code I can quickly process all of the entries and be done with this aspect of the data normalization.

SplitFieldIntoChildTable "TblClients", "OrderNumbers", ",", "ClientID", "TblOrder", "OrderNumber", "ClientID"

MS Access – VBA – Determine the Number of Fields in a Table

Sometimes it can be useful to quickly evaluate the databases tables structures.  One aspect that can tell an experienced developer a whole lot is the number of fields in a table.  Unlike Excel, a properly normalized database is one that does not have tables with large numbers of fields.  I personally start to question database design when I start seeing tables with 20+ fields in them.  That isn’t to say they’re aren’t legitimate instances for tables with more fields, but in general terms this is a good indicator that there could be basic data normalization problems.

As such, I created 2 (just to illustrate that is more than one way to get the information) simple functions to get this information.

'---------------------------------------------------------------------------------------
' Procedure : GetTblFldCount
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Determine the number of fields in the specified table
' Copyright : The following may be altered and reused as you wish so long as the
'             copyright notice is left unchanged (including Author, Website and
'             Copyright).  It may not be sold/resold or reposted on other sites (links
'             back to this site are allowed).
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sTblName  : Name of the table you which to get the field count of
'
' Usage:
' ~~~~~~
' GetTblFldCount("Contact")
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2011-02-07              Initial Release
'---------------------------------------------------------------------------------------
Function GetTblFldCount(sTblName As String)
    On Error GoTo Error_Handler

    GetTblFldCount = CurrentDb.TableDefs(sTblName).Fields.Count

Error_Handler_Exit:
    On Error Resume Next
    Exit Function

Error_Handler:
    If Err.Number = 3265 Then
        MsgBox "The following error has occurred." & vbCrLf & vbCrLf & _
               "Error Number: 3265" & vbCrLf & _
               "Error Source: GetTblFldCount" & vbCrLf & _
               "Error Description: The table '" & sTblName & "' could not be found in the current database.", _
               vbCritical, "An Error has Occurred!"
    Else
        MsgBox "The following error has occurred." & vbCrLf & vbCrLf & _
               "Error Number: " & Err.Number & vbCrLf & _
               "Error Source: GetTblFldCount" & vbCrLf & _
               "Error Description: " & Err.Description, _
               vbCritical, "An Error has Occurred!"
    End If
    Resume Error_Handler_Exit
End Function
'---------------------------------------------------------------------------------------
' Procedure : GetTblFldCount2
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Determine the number of fields in the specified table
' Copyright : The following may be altered and reused as you wish so long as the
'             copyright notice is left unchanged (including Author, Website and
'             Copyright).  It may not be sold/resold or reposted on other sites (links
'             back to this site are allowed).
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sTblName  : Name of the table you which to get the field count of
'
' Usage:
' ~~~~~~
' GetTblFldCount("Contact")
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2011-02-07              Initial Release
'---------------------------------------------------------------------------------------
Function GetTblFldCount2(sTblName As String)
    On Error GoTo Error_Handler
    Dim rs As DAO.Recordset
    
    Set rs = CurrentDb.OpenRecordset("SELECT * FROM [" & sTblName & "] WHERE (FALSE);")
    GetTblFldCount2 = rs.Fields.Count

Error_Handler_Exit:
    On Error Resume Next
    rs.Close
    Set rs = Nothing
    Exit Function

Error_Handler:
    If Err.Number = 3265 Then
        MsgBox "The following error has occurred." & vbCrLf & vbCrLf & _
               "Error Number: 3265" & vbCrLf & _
               "Error Source: GetTblFldCount2" & vbCrLf & _
               "Error Description: The table '" & sTblName & "' could not be found in the current database.", _
               vbCritical, "An Error has Occurred!"
    Else
        MsgBox "The following error has occurred." & vbCrLf & vbCrLf & _
               "Error Number: " & Err.Number & vbCrLf & _
               "Error Source: GetTblFldCount2" & vbCrLf & _
               "Error Description: " & Err.Description, _
               vbCritical, "An Error has Occurred!"
    End If
    Resume Error_Handler_Exit
End Function

and then to simplify the process of manually inputting each table name one by one, we could create a simple procedure to quick check every table within the current database, and it would look something like:

Public Sub CheckNoTblFlds()
    On Error GoTo Error_Handler
    Dim db As DAO.Database
    Dim sTblName As String
 
    Set db = CurrentDb()
    For i = 0 To db.TableDefs().Count - 1
        sTblName = db.TableDefs(i).Name
        If Not (sTblName Like "MSys*") And Not (sTblName Like "~*") Then 'Let ignore system tables
            Debug.Print sTblName & vbTab & GetTblFldCount(sTblName)
            'Debug.Print sTblName & vbTab & GetTblFldCount2(sTblName)
        End If
    Next i
    
Error_Handler_Exit:
    On Error Resume Next
    Set db = Nothing
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred." & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: CheckNoTblFlds" & vbCrLf & _
           "Error Description: " & Err.Description, _
           vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

So now, by entering one command into the immediate window I can get a report on every single (non-system) table in the database.

MS Access – VBA – Find Tables with a Specific Field Type

Part of the process of trying to optimize client databases always involves analyzing tables to try and find potential problems. For instance, I like to quickly identify tables containing Attachment and Memo fields. Now if you’re dealing with a simple database it may be conceivable to open each table one by one and look over the fields manually, but this isn’t efficient, can lead to human error, and certainly is not an option for large databases. As such I put together a simply procedure to perform the check for me and I thought I’d share with the community at large.

'---------------------------------------------------------------------------------------
' Procedure : FindTblWFieldsType
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Identify which table contains the specified Field Type
' Copyright : The following may be altered and reused as you wish so long as the
'             copyright notice is left unchanged (including Author, Website and
'             Copyright).  It may not be sold/resold or reposted on other sites (links
'             back to this site are allowed).
'
' Usage:
' ~~~~~~
' Call FindTblWFieldsType(dbAttachment)
' Call FindTblWFieldsType(dbMemo)
' Call FindTblWFieldsType(dbBoolean)
'
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2015-Oct-03                 Initial Release
'---------------------------------------------------------------------------------------
Public Sub FindTblWFieldsType(sFieldType As DataTypeEnum)
    On Error GoTo Error_Handler
    Dim db              As DAO.Database
    Dim td              As DAO.TableDefs
    Dim t               As DAO.TableDef
    Dim tdfld           As DAO.TableDef
    Dim fld             As Field

    Set db = CurrentDb()
    Set td = db.TableDefs
    For Each t In td    'loop through all the fields of the tables
        If Left(t.Name, 4) = "MSys" Then 'ignore system tables
            GoTo Continue
        End If
        
        For Each fld In t.Fields    'loop through all the fields of the tables
            If fld.Type = sFieldType Then  'Find attachment fields
                Debug.Print "Processing Table: " & t.Name
                Debug.Print vbTab & "Field: " & fld.Name & " is a(n) " & FieldTypeName(fld) & " field."
                Debug.Print ""
            End If
        Next
        
Continue:
    Next

Error_Handler_Exit:
    On Error Resume Next
    Set tdfld = Nothing
    Set db = Nothing
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred." & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: FindTblWFieldsType" & vbCrLf & _
           "Error Description: " & Err.Description, _
           vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

Function FieldTypeName(fld As DAO.Field) As String
    'Purpose: Converts the numeric results of DAO Field.Type to text.
    'Source/Copyright: Allen Browne
    'URL: http://allenbrowne.com/func-06.html
    Dim strReturn As String    'Name to return

    Select Case CLng(fld.Type) 'fld.Type is Integer, but constants are Long.
        Case dbBoolean: strReturn = "Yes/No"            ' 1
        Case dbByte: strReturn = "Byte"                 ' 2
        Case dbInteger: strReturn = "Integer"           ' 3
        Case dbLong                                     ' 4
            If (fld.Attributes And dbAutoIncrField) = 0& Then
                strReturn = "Long Integer"
            Else
                strReturn = "AutoNumber"
            End If
        Case dbCurrency: strReturn = "Currency"         ' 5
        Case dbSingle: strReturn = "Single"             ' 6
        Case dbDouble: strReturn = "Double"             ' 7
        Case dbDate: strReturn = "Date/Time"            ' 8
        Case dbBinary: strReturn = "Binary"             ' 9 (no interface)
        Case dbText                                     '10
            If (fld.Attributes And dbFixedField) = 0& Then
                strReturn = "Text"
            Else
                strReturn = "Text (fixed width)"        '(no interface)
            End If
        Case dbLongBinary: strReturn = "OLE Object"     '11
        Case dbMemo                                     '12
            If (fld.Attributes And dbHyperlinkField) = 0& Then
                strReturn = "Memo"
            Else
                strReturn = "Hyperlink"
            End If
        Case dbGUID: strReturn = "GUID"                 '15

        'Attached tables only: cannot create these in JET.
        Case dbBigInt: strReturn = "Big Integer"        '16
        Case dbVarBinary: strReturn = "VarBinary"       '17
        Case dbChar: strReturn = "Char"                 '18
        Case dbNumeric: strReturn = "Numeric"           '19
        Case dbDecimal: strReturn = "Decimal"           '20
        Case dbFloat: strReturn = "Float"               '21
        Case dbTime: strReturn = "Time"                 '22
        Case dbTimeStamp: strReturn = "Time Stamp"      '23

        'Constants for complex types don't work prior to Access 2007 and later.
        Case 101&: strReturn = "Attachment"         'dbAttachment
        Case 102&: strReturn = "Complex Byte"       'dbComplexByte
        Case 103&: strReturn = "Complex Integer"    'dbComplexInteger
        Case 104&: strReturn = "Complex Long"       'dbComplexLong
        Case 105&: strReturn = "Complex Single"     'dbComplexSingle
        Case 106&: strReturn = "Complex Double"     'dbComplexDouble
        Case 107&: strReturn = "Complex GUID"       'dbComplexGUID
        Case 108&: strReturn = "Complex Decimal"    'dbComplexDecimal
        Case 109&: strReturn = "Complex Text"       'dbComplexText
        Case Else: strReturn = "Field type " & fld.Type & " unknown"
    End Select

    FieldTypeName = strReturn
End Function

As indicated in the comments of the main procedure, you simply call the sub by:

Call FindTblWFieldsType(dbAttachment)

and it will return (in the VBE immediate window) a result like:

Processing Table: tblTransaction
    Field: Receipt is a(n) Attachment field.
    Field: RefDoc is a(n) Attachment field.

Processing Table: tblTransactionConfirmation
    Field: ConfirmationNotice is a(n) Attachment field.

You might ask what is the point of Allen Browne’s FieldTypeName function. It is used to convert the returned field type (which is numeric) into a comprehensible text. For example, it converts 101 into the word ‘Attachment’ which I use in the reporting. That said since you perform a search for a single field type that you specify, it could be stripped out of my procedure, but I left it to make it as complete and as ‘idiot proof’ as possible to avoid any possible problems.

I hope this help you quickly take control of your database analysis.

MS Access List Table Indexes

Once again, trying to help someone in a forum and quickly put together the following to enumerate a list of indexes for a given table:

'---------------------------------------------------------------------------------------
' Procedure : Table_ListIndexes
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : List the names of the indexes in the specified table
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
'             (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: Microsoft Office XX.0 Access database engine Object Library
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sTbl      : Name of the table to list the names of the Indexes from
'
' Usage:
' ~~~~~~
' Call Table_ListIndexes("tbl_Appointments")
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2014-11-20                  Initial Release
' 2         2022-03-25                  Header Usage Correction, per blog comment
' 3         2022-03-25                  Added field enumeration per Brent Spaulding and
'                                       Tom Wickerath's recommendations
'                                       Renamed function, updated header and error
'                                       handler
'---------------------------------------------------------------------------------------
Function Table_ListIndexes(sTbl As String)
On Error GoTo Error_Handler
    Dim db              As DAO.Database
    Dim tdf             As DAO.TableDef
    Dim tIndex          As DAO.Index
    Dim fld             As DAO.Field

    Set db = CurrentDb
    Set tdf = db.TableDefs(sTbl)

    For Each tIndex In tdf.Indexes
        Debug.Print tIndex.Name
        For Each fld In tIndex.Fields
           Debug.Print , fld.Name
        Next
    Next

Error_Handler_Exit:
    On Error Resume Next
    If Not fld Is Nothing Then Set fld = Nothing
    If Not tIndex Is Nothing Then Set tIndex = Nothing
    If Not tdf Is Nothing Then Set tdf = Nothing
    If Not db Is Nothing Then Set db = Nothing
    Exit Function

Error_Handler:
    MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
               "Error Number: " & Err.Number & vbCrLf & _
               "Error Source: Table_ListIndexes" & vbCrLf & _
               "Error Description: " & Err.Description & _
               Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
               , vbOKOnly + vbCritical, "An Error has Occured!"
    Resume Error_Handler_Exit
End Function

Count of the objects in your MS Access Database

Have you ever needed to get a simple count of the number of :

  • Tables
  • Queries
  • Forms
  • Macros
  • Reports
  • Modules

I recently needed to get some summary data regarding a database I was working on.  The following single line VBA commands will get you the count(s) you are after.

'Number of Tables
CurrentDb.TableDefs.Count 'includes system tables
'Number of Queries
CurrentDb.QueryDefs.Count
'Number of Forms
Currentproject.AllForms.Count
'Number of Macros
Currentproject.AllMacros.Count
'Number of Reports
Currentproject.AllReports.Count
'Number of Modules
Currentproject.AllModules.Count 'does not include object modules

Easy as can be!