Create an Access Database File and Import a Text File to a Table Within Using VBA

Jimbo's picture

Access' 2GB file size limit has been a thorn in the side of developers for years, but it is possible to circumvent. One option is to create link tables to external text files instead of importing them, but this snippet of code explains how to create a disposable Access database to hold the data from just one table.

This doesn't increase the speed at which Access handles data and it takes up more space on the hard drive, but it does add some functionality like the ability to update records. The code below assumes that the Access file is disposable and designed to hold just one table, so it deletes the file if it already exists.

So much of this code is shamelessly recycled from the VBA snippet that imports a text file into an Access table which is included in Jimbo's VBA Automation Toolbox. It is doctored just enough to get the data into a disposable Access file.

The text files can be extracts from another team, clipboard copies from SE16 or a query from a SQL server saved directly to text. The last parameter of the function is the delimiter that defaults to Tab.

Sub TestA()
    Dim y
    y = ImportTextToExternalAccessDB("c:\stuff\PlantMagic\MARC.txt", "c:\stuff\PlantMagic\TestMARC.accdb", "tblMARC")
End Sub


Function ImportTextToExternalAccessDB(strFileName As String, strAccessFileName As String, strTableName As String, Optional ByVal strDelim As String = vbTab)

    'This code assumes each Access file will hold just one table.
    If Len(Dir(strAccessFileName)) > 0 Then
        Kill strAccessFileName
    End If
    Dim accessApp As Access.Application
    Set accessApp = New Access.Application
    accessApp.DBEngine.CreateDatabase strAccessFileName, DB_LANG_GENERAL
    accessApp.Quit
    Set accessApp = Nothing
    
    Dim db As Database
    Set db = OpenDatabase(strAccessFileName)
   
    Dim rs As DAO.Recordset
    Dim nCurrent As Long, nFieldCount As Long, nRecordCount As Long
    Dim RetVal As Variant, nCurRec As Long, dnow As Date, nCurSec As Long
    Dim nTotalSeconds As Long, nSecondsLeft As Long
    Dim nTotalbytes As Long, nFileLen As Long
    Dim strTest As Variant
    Dim strTemp As String
    Dim strHeadersIn() As String
    Dim strHeaders(999) As String
    Const nReadAhead As Long = 300000
    Dim nSizes(999) As Long, strRecords(nReadAhead) As String, nRecords As Long, nLoaded As Long
    Dim strFields() As String
    
    Dim nHeaders As Long
    Dim isSAP As Boolean
    
    nFileLen = FileLen(strFileName)
    RetVal = SysCmd(acSysCmdSetStatus, "Preparing to import " & strTableName & " from " & strFileName & "...")
    RetVal = DoEvents()
    
    Open strFileName For Input As #1
    Line Input #1, strTest
    If Left(strTest, 6) = "Table:" Then 'This is an SAP extract!
        isSAP = True
        Line Input #1, strTest
        Line Input #1, strTest
        Line Input #1, strTest  'Fourth line has the headers!
    Else
        isSAP = False
    End If
    
    If InStr(1, strTest, "|", vbTextCompare) Then
        strDelim = "|"
    End If
    
    nTotalbytes = nTotalbytes + Len(strTest) + 2 ' +2 for vbCrLf--This line prevents div by zero later...
    strTest = Trim(strTest)
    If Right(strTest, 1) = strDelim Then
        strTest = Left(strTest, Len(strTest) - 1)
    End If
    strHeadersIn = Split(Trim(strTest), strDelim)
    nHeaders = 0
    
    
    For Each strTest In strHeadersIn
        nHeaders = nHeaders + 1
        strTest = Replace(Replace(strTest, " ", ""), ".", "")
        strTest = Replace(Replace(strTest, " ", ""), ".", "")
        If Len(Trim(strTest)) = 0 Then
            strHeaders(nHeaders) = "HEADER" & Right("000" & nHeaders, 3)
        Else
            strHeaders(nHeaders) = Trim(strTest)
        End If
        For nCurrent = 1 To nHeaders - 1
            If strHeaders(nHeaders) = strHeaders(nCurrent) Then
                strHeaders(nHeaders) = strHeaders(nHeaders) & nHeaders
            End If
        Next
    Next
    strHeaders(0) = nHeaders
    RetVal = SysCmd(acSysCmdClearStatus)
    RetVal = SysCmd(acSysCmdInitMeter, "Preparing to import " & strTableName & " from " & strFileName & "...", nReadAhead)
    RetVal = DoEvents()
    
    Do While Not EOF(1) And nRecords < nReadAhead 'Read through the file and get the maximum sizes for fields in advance.
        Line Input #1, strTest
        strTest = Trim(strTest)
        If Right(strTest, 1) = strDelim Then
            strTest = Left(strTest, Len(strTest) - 1)
        End If
        If isSAP And Left(strTest, 20) = "--------------------" Then
            strTest = ""  'Skip this line!
        End If
        If Len(strTest) > 0 Then
            nRecords = nRecords + 1
            strRecords(nRecords) = strTest
            strFields = Split(strTest, strDelim)
            nCurrent = 0
            For Each strTest In strFields
                nCurrent = nCurrent + 1
                If Len(strTest) > nSizes(nCurrent) Then
                    nSizes(nCurrent) = Len(strTest)
                End If
            Next
            If Second(Now) <> nCurSec Then
                nCurSec = Second(Now)
                RetVal = SysCmd(acSysCmdUpdateMeter, nRecords)
                RetVal = DoEvents()
            End If
        End If
    Loop
    
    
    If CreateExternalAccessTable(strAccessFileName, strTableName, strHeaders, nSizes) Then
        If isSAP Then
            For nCurrent = 1 To nHeaders
                If Left(strHeaders(nCurrent), 8) = "HEADER00" Then
                    strHeaders(nCurrent) = ""  'Don't bother importing this field.
                End If
            Next
        End If
        Set rs = db.OpenRecordset(strTableName)
        nLoaded = 0
        nTotalSeconds = 0
        Do While Not EOF(1) Or nLoaded < nRecords
            nCurRec = nCurRec + 1
            If Second(Now()) <> nCurSec Then
                nCurSec = Second(Now())
                nTotalSeconds = nTotalSeconds + 1
                'RetVal = DoEvents()
                If nTotalSeconds > 3 Then
                    'nSecondsLeft = Int(((nTotalSeconds / nCurRec) * rs.RecordCount) * ((rs.RecordCount - nCurRec) / rs.RecordCount))
                    nSecondsLeft = Int(((nTotalSeconds / nTotalbytes) * nFileLen) * ((nFileLen - nTotalbytes) / nFileLen))
                    RetVal = SysCmd(acSysCmdRemoveMeter)
                    RetVal = SysCmd(acSysCmdInitMeter, "Importing " & strTableName & " from " & strFileName & "... " & nSecondsLeft & " seconds remaining.", nFileLen)
                    RetVal = SysCmd(acSysCmdUpdateMeter, nTotalbytes)
                    RetVal = DoEvents()
                End If
            End If
            If nLoaded < nRecords Then
                nLoaded = nLoaded + 1
                strTest = strRecords(nLoaded)
            Else
                Line Input #1, strTest
            End If
            nTotalbytes = nTotalbytes + Len(strTest) + 2 'vbCrLf
            strTest = Trim(strTest)
            If Right(strTest, 1) = strDelim Then
                strTest = Left(strTest, Len(strTest) - 1)
            End If
            If isSAP And Left(strTest, 20) = "--------------------" Then
                strTest = ""  'Skip this line!
            End If
            If Len(strTest) > 0 Then
                strFields = Split(strTest, strDelim)
                nCurrent = 0
                rs.AddNew
                For Each strTest In strFields
                    nCurrent = nCurrent + 1
                    If Len(Trim(strHeaders(nCurrent))) > 0 Then
                        'rs.FIELDS(strHeaders(nCurrent)).Value = Trim(strFields(nCurrent - 1))
                        rs.FIELDS(strHeaders(nCurrent)).Value = Left(Trim(strFields(nCurrent - 1)), rs.FIELDS(strHeaders(nCurrent)).Size)
                    End If
                Next
                rs.Update
            End If
        Loop
        rs.Close
    End If
    Close #1
    RetVal = SysCmd(acSysCmdRemoveMeter)

   
End Function

Function CreateExternalAccessTable(strAccessFileName As String, strTableName As String, strFields() As String, nSizes() As Long) As Boolean
    Dim nCounter As Long
    Dim dbs As DAO.Database
    'Now create the database.  Rename the old database if necessary.
    'Set dbs = CurrentDb
    Set dbs = OpenDatabase(strAccessFileName)
    Dim tdf As DAO.TableDef
    Dim fld1 As DAO.Field
    Dim fld2 As DAO.Field
    Dim fName As String
    Dim fType As Integer
    Dim fSize As Integer
    
    On Error GoTo ErrorHandler
    'Check for existence of TargetTable
    nCounter = 0
    Do While nCounter < dbs.TableDefs.Count
        If dbs.TableDefs(nCounter).Name = strTableName Then
            'Delete TargetTable--must start from scratch
            dbs.TableDefs.Delete (strTableName)
        End If
        nCounter = nCounter + 1
    Loop
    
    Set tdf = dbs.CreateTableDef(strTableName)
    For nCounter = 1 To Val(strFields(0))
        fName = strFields(nCounter)
        fType = dbText
        fSize = nSizes(nCounter) 'fSize = 255
        Set fld1 = tdf.CreateField(fName, fType, fSize)
        fld1.AllowZeroLength = True
        fld1.Required = False
        tdf.FIELDS.Append fld1
    Next
    'Create the table in the database
    dbs.TableDefs.Append tdf
    dbs.TableDefs.Refresh
    CreateExternalAccessTable = True
    Exit Function
ErrorHandler:
    MsgBox "Error number " & Err.Number & ": " & Err.Description
    CreateExternalAccessTable = False
    Exit Function
End Function

Note: This snippet of code relies on several referenced DLLs. Be sure to add these in the Module to ensure that this code functions.

Programming Language: 
ABAP