I use this function to load CSV, txt and Excel files as part of the ETL Process.
An example of this function call is available within our sample app. The button “Import and Load” will first clear the existing data and then import any files which match the section name “Import Invoices” in the table tblimportGeneric
The tblImportGeneric has 3 different file types which can be explored in the table or the user maintenance form
Function ImportFile_Generic(tmpSectionName, tmpFoldertoUse, tmpCallingForm, Optional tmpRecid, Optional FilePrefix)
'=======================================================================================
' ImportFile_Generic
'Function ImportFile_Generic(tmpProcessName, tmpFoldertoUse, tmpArchiveFolder, tmpFiletoFind, tmpFindAll, tmpExcelorCSV, tmpImportSpec, _
' tmpAppendQuery, tmpImportTable, tmpAllowPause, tmpCallingForm, tmpClearHeader, tmpClearHeaderLine, tmpFieldtoTest, Optional tmpSheetName) ', Optional tmpArrayFields, Optional tmpTabletoUse)
'
'---------------------------------------------------------------------------------------
' Purpose : Generic import function for CSV / Excel files
' Author : Pat Jones
' Date : 15th Mar 2018
' Dependencies : FSO
' Notes :CSV imports to tblImport_tmp
' :Excel imports to tblDynamic (rename for new imports - using legacy queries)
' :G255 - opens csv / txt files greater than 255 columns and parses the contwent based on array of fields in ImportSpec
' Variables
' tmpProcessName - Message to send to user
' tmpFoldertoUse - Folder to get the files from
' tmpArchiveFolder - Archive folder to Use
' tmpFiletoFind - File pattern to match
' tmpFindAll - Find all - true or false for single
' tmpExcelorCSV - Excel CSV or G255
' tmpImportSpec - Import Spec for CSV or Array of field numbers for G255
' tmpImportTable - Table to import into
' tmpAppendQuery - Array of queries to run, if it starts with zzSQL then it uses the custom SQL Function
' tmpAllowPause - Allow the screen to pause to get the user message
' tmpCallingForm - Form making the call - can use me.name
' tmpClearHeader - Clear the imported header
' tmpClearHeaderLine - HEader Line to check for
' tmpFieldtoTest - Field to tes for the header value
'---------------------------------------------------------------------------------------
' Returns :string to define if successful or error
'---------------------------------------------------------------------------------------
' Tested :Nov 2019
' Revision History:1.8 Sept 2019
' Use tblImport_tmp for Excel, tblDynamic for CSV
' 1.2 fix missing declarations
' 1.3 Added array to queries to be run to allow multiple executes
' 1.4 Handle Apostrophe
' 1.5 Clear import temp table at the start to ensure its empty if we have no file
' 1.6 trim to 255 wide to allow for long footers
' 1.7 Change Text from VB to FSO to allow for files with LBlf rather than vbcrlf
' 1.8 File counter to avoid issues with destination file existing when importing small files
'=======================================================================================
On Error GoTo Errorhandler
Dim fso As FileSystemObject, tmpBuf
Dim sFilename, tmpTimeStamp, tmpArray, LineFromFile As String, RowNumber, tmpElement As Variant, i
Dim rst As Recordset, rstSource As Recordset, LineItem, tmpCount, tmpSQLExecError
Dim TS As TextStream, tmpErrorSection, tmpErrorDisplay, tmpFileCounter
'Declare variable for function
Dim tmpProcessName
Dim tmpArchiveFolder
Dim tmpFiletoFind
Dim tmpFindAll
Dim tmpExcelorCSV
Dim tmpImportSpec
Dim tmpAppendQuery
Dim tmpImportTable
Dim tmpAllowPause
Dim tmpClearHeader
Dim tmpClearHeaderLine
Dim tmpFieldtoTest
Dim tmpSheetName
Dim tmpOrgFileName ' stores the orig name for saving in the log
If IsMissing(tmpRecid) Then tmpRecid = 0
If IsMissing(FilePrefix) Then FilePrefix = ""
'check source folder
If Dir(tmpFoldertoUse, vbDirectory) = "" Then
ImportFile_Generic = tmpProcessName & " - Folder not Found " & tmpFoldertoUse
Exit Function
End If
'check file pattern
If Dir(tmpFoldertoUse & tmpFiletoFind) = "" Then
ImportFile_Generic = tmpProcessName & " - No files match pattern " & tmpFiletoFind
Exit Function
End If
'set recordsource to defined section name
Set rstSource = CurrentDb.OpenRecordset("Select * from tblImportGeneric where IG_Active=-1 and IG_SectionName='" & tmpSectionName & "'" & IIf(tmpRecid <> 0, " and IG_ID=" & tmpRecid, "") & " Order by IG_RunningOrder")
If rstSource.RecordCount = 0 Then
ImportFile_Generic = "No files for section " & tmpSectionName
Set rstSource = Nothing
Exit Function
End If
tmpFileCounter = 0
rstSource.MoveFirst
Do While Not rstSource.EOF 'loop for all in order
tmpFileCounter = tmpFileCounter + 1
tmpErrorSection = "Loading Variables"
tmpProcessName = rstSource!IG_ProcessName
tmpArchiveFolder = rstSource!IG_ArchiveFolder
tmpFiletoFind = rstSource!IG_FiletoFind
tmpFindAll = rstSource!IG_FindAll
tmpExcelorCSV = rstSource!IG_ImportType
tmpImportSpec = rstSource!IG_ImportSpec
tmpAppendQuery = rstSource!IG_ActionsAfterImport
tmpImportTable = rstSource!IG_ImportTable
tmpAllowPause = rstSource!IG_AllowPause
tmpClearHeader = rstSource!IG_ClearHeader
tmpClearHeaderLine = rstSource!IG_ClearHeaderLine
tmpFieldtoTest = rstSource!IG_FieldtoTest
tmpSheetName = rstSource!IG_SheetName
tmpErrorSection = "Clearing Tables"
'Clear the import table in case we have no data and other actions use that data
CurrentDb.Execute "delete * from " & tmpImportTable
tmpErrorSection = "Making folders"
If Dir(tmpFoldertoUse & tmpArchiveFolder, vbDirectory) = "" Then
MkDir tmpFoldertoUse & tmpArchiveFolder
End If
tmpErrorSection = "Setting reference to FSO"
'link to MS Scripting object
Set fso = CreateObject("Scripting.FileSystemObject")
'set the file to load
sFilename = Dir(tmpFoldertoUse & tmpFiletoFind)
If sFilename = "" Then
tmpProcessName = "No files to import"
GoTo AfterFileProcessed ' no file skip processing
End If
'set original file for renaming if required
tmpOrgFileName = sFilename
tmpErrorSection = "Start Type Selection"
Select Case tmpExcelorCSV
Case "CSV"
Do While sFilename <> ""
tmpFileCounter = tmpFileCounter + 1
If CheckFileLog(FilePrefix & sFilename) = True Then
Call UpdateStatus("File skipped - already imported " & tmpOrgFileName, tmpCallingForm, "Y")
Else
If tmpAllowPause And Len(tmpCallingForm & "") > 0 Then ' if we are refreshing user interface and a form name is passed
Call UpdateStatus("Importing file " & sFilename, tmpCallingForm)
DoEvents ' Allows a screen update
End If
If Len(sFilename) > 32 Then ' Only files with 32 len can be imported into Access
tmpTimeStamp = Format(Now(), "ddhhnnss")
fso.CopyFile tmpFoldertoUse & sFilename, tmpFoldertoUse & Trim(Left(tmpTimeStamp & "_" & Left(sFilename, Len(sFilename) - 4), 32) & ".csv")
fso.MoveFile tmpFoldertoUse & sFilename, tmpFoldertoUse & tmpArchiveFolder & tmpFileCounter & Format(Now(), "ddhhnnss") & "_" & sFilename
sFilename = Trim(Left(tmpTimeStamp & "_" & Left(sFilename, Len(sFilename) - 4), 32) & ".csv")
End If
If InStr(1, sFilename, "'", vbTextCompare) > 1 Then
tmpErrorSection = "Apostrophe extraction"
'check for apostrophe and advise user
tmpTimeStamp = Format(Now(), "ddhhnnss")
Call UpdateStatus("File has an apostrophe - File renamed " & sFilename, tmpCallingForm)
fso.CopyFile tmpFoldertoUse & sFilename, tmpFoldertoUse & Right(tmpTimeStamp & "_" & "Renamed" & Right(sFilename, 5), 32)
fso.MoveFile tmpFoldertoUse & sFilename, tmpFoldertoUse & tmpArchiveFolder & tmpFileCounter & Format(Now(), "ddhhnnss") & "_" & sFilename
sFilename = Right(tmpTimeStamp & "_" & "Renamed" & Right(sFilename, 5), 32)
End If
tmpErrorSection = "CSV Import"
DoCmd.SetWarnings False
DoCmd.TransferText acImportDelim, tmpImportSpec, tmpImportTable, tmpFoldertoUse & sFilename, False
CurrentDb.Execute "Update " & tmpImportTable & " set F115='" & FilePrefix & StripSpecial(tmpOrgFileName) & "' where Len(f115&'')=0"
tmpErrorSection = "Logging File Import"
Call Import_LogFile(FilePrefix & tmpOrgFileName, tmpProcessName, tmpCallingForm)
DoEvents
DoCmd.SetWarnings True
tmpErrorSection = "Archive File"
End If
fso.MoveFile tmpFoldertoUse & sFilename, tmpFoldertoUse & tmpArchiveFolder & tmpFileCounter & Format(Now(), "ddhhnnss") & "_" & sFilename
tmpErrorSection = "Reset File Selection"
If tmpFindAll Then ' Multiple file import
sFilename = Dir(tmpFoldertoUse & tmpFiletoFind)
tmpOrgFileName = sFilename
Else
GoTo Single_Import
End If
Loop
Case "Excel"
tmpErrorSection = "Excel File Type"
Do While sFilename <> ""
tmpFileCounter = tmpFileCounter + 1
If CheckFileLog(sFilename) = True Then
Call UpdateStatus("File skipped - already imported " & tmpOrgFileName, tmpCallingForm, "Y")
Else
If tmpAllowPause And Len(tmpCallingForm & "") > 0 Then ' if we are refreshing user interface and a form name is passed
Call UpdateStatus("Importing file " & sFilename, tmpCallingForm)
DoEvents ' Allows a screen update
End If
If InStr(1, sFilename, "'", vbTextCompare) > 1 Then
tmpErrorSection = "Apostrophe Extraction for Excel"
'check for apostrophe and advise user
tmpTimeStamp = Format(Now(), "ddhhnnss")
Call UpdateStatus("File has an apostrophe - File renamed " & sFilename, tmpCallingForm)
fso.CopyFile tmpFoldertoUse & sFilename, tmpFoldertoUse & Right(tmpTimeStamp & "_" & "Renamed" & Right(sFilename, 5), 32)
fso.MoveFile tmpFoldertoUse & sFilename, tmpFoldertoUse & tmpArchiveFolder & tmpFileCounter & Format(Now(), "ddhhnnss") & "_" & sFilename
sFilename = Right(tmpTimeStamp & "_" & "Renamed" & Right(sFilename, 5), 32)
End If
tmpErrorSection = "Sheet name definition"
If tmpSheetName <> "" Then
DoCmd.TransferSpreadsheet acImport, IIf(Right(sFilename, 1) = "x", acSpreadsheetTypeExcel12, acSpreadsheetTypeExcel9), tmpImportTable, tmpFoldertoUse & sFilename, False, tmpSheetName
Else
DoCmd.TransferSpreadsheet acImport, IIf(Right(sFilename, 1) = "x", acSpreadsheetTypeExcel12, acSpreadsheetTypeExcel9), tmpImportTable, tmpFoldertoUse & sFilename, False
End If
CurrentDb.Execute "Update " & tmpImportTable & " set F115='" & FilePrefix & StripSpecial(tmpOrgFileName) & "' where Len(f115&'')=0"
tmpErrorSection = "Excel import Logging"
Call Import_LogFile(FilePrefix & tmpOrgFileName, tmpProcessName, tmpCallingForm)
DoEvents
End If
tmpErrorSection = "Archive Excel import"
fso.MoveFile tmpFoldertoUse & sFilename, tmpFoldertoUse & tmpArchiveFolder & tmpFileCounter & Format(Now(), "ddhhnnss") & "_" & sFilename
tmpErrorSection = "Next file definition"
If tmpFindAll Then ' Multiple file import
sFilename = Dir(tmpFoldertoUse & tmpFiletoFind)
tmpOrgFileName = sFilename
Else
GoTo Single_Import
End If
Loop
Case "G255"
tmpErrorSection = "G255 Selection - Array"
tmpArray = Split(tmpImportSpec, ",")
Set rst = CurrentDb.OpenRecordset(tmpImportTable)
Do While sFilename <> ""
tmpFileCounter = tmpFileCounter + 1
tmpOrgFileName = sFilename
If CheckFileLog(sFilename) = True Then
Call UpdateStatus("File skipped - already imported " & tmpOrgFileName, tmpCallingForm, "Y")
Else
If tmpAllowPause And Len(tmpCallingForm & "") > 0 Then ' if we are refreshing user interface and a form name is passed
Call UpdateStatus("Importing file " & sFilename, tmpCallingForm)
DoEvents ' Allows a screen update
End If
tmpErrorSection = "Setting Textstream"
Set TS = fso.OpenTextFile(tmpFoldertoUse & sFilename, ForReading)
If Len(TS.ReadLine) > 3000 Then ' suggest we have an issue with no line return maybe a linux file
TS.Close
Set TS = fso.OpenTextFile(tmpFoldertoUse & sFilename)
tmpBuf = TS.ReadAll
TS.Close
Set TS = fso.OpenTextFile(tmpFoldertoUse & sFilename, ForWriting)
TS.Write Replace(tmpBuf, Chr(13), Chr(10) & Chr(13))
TS.Close
Set TS = fso.OpenTextFile(tmpFoldertoUse & sFilename, ForReading)
End If
RowNumber = 1
Do Until TS.AtEndOfStream 'EOF(1)
tmpErrorSection = "Start of text stream"
LineFromFile = TS.ReadLine
LineItem = splitLine2(LineFromFile)
'Debug.Print UBound(LineItem)
If Len(LineFromFile & "") > 0 Then ' to accommodate an empty final line
i = 1
rst.AddNew
tmpErrorSection = "Reading texts stream elements"
For Each tmpElement In tmpArray
If tmpElement <= UBound(LineItem) Then
rst.Fields("F" & i) = Trim(Left(Replace(LineItem(tmpElement), Chr(34), ""), 255)) ' trim to 255 wide to allow for long footers
End If
i = i + 1
Next tmpElement
rst.Update
End If
RowNumber = RowNumber + 1
Loop
Close #1
tmpErrorSection = "Closing text stream and archive"
Set TS = Nothing ' clear the lock on the file
CurrentDb.Execute "Update tblCSVImport set F115='" & FilePrefix & StripSpecial(tmpOrgFileName) & "' where Len(f115&'')=0"
tmpErrorSection = "Logging G355 File"
Call Import_LogFile(FilePrefix & tmpOrgFileName, tmpProcessName, tmpCallingForm)
DoEvents
End If
fso.MoveFile tmpFoldertoUse & sFilename, tmpFoldertoUse & tmpArchiveFolder & tmpFileCounter & Format(Now(), "ddhhnnss") & "_" & sFilename
tmpErrorSection = "Finding next G255 file"
If tmpFindAll Then ' Multiple file import
sFilename = Dir(tmpFoldertoUse & tmpFiletoFind)
tmpOrgFileName = sFilename
Else
GoTo Single_Import
End If
Loop
Case Else
ImportFile_Generic = tmpProcessName & " No import function found "
End Select
Single_Import:
tmpErrorSection = "Clearing Header line"
'clear the header line from the import table
If tmpClearHeader And Len(tmpClearHeaderLine & "") > 0 Then
CurrentDb.Execute "delete * from " & tmpImportTable & " where " & tmpFieldtoTest & "='" & tmpClearHeaderLine & "'"
End If
tmpErrorSection = "Running post import actions"
'if an append query has been passed then execute that query (must have prefix qry) - can also contain Custom SQL prefaced
If Len(tmpAppendQuery & "") <> 0 Then
DoCmd.SetWarnings False
LineItem = Split(tmpAppendQuery, ",") ' add the queries in the sequence you want them ran
tmpCount = UBound(LineItem)
i = 0
Do While i <= tmpCount
If Left(LineItem(i), 3) <> "qry" Then
tmpErrorSection = "Running Custom SQL"
tmpSQLExecError = Exec_CustomSQL(tmpCallingForm, Replace(LineItem(i), "zzSQL", "")) ' remove marker for custom SQL and execute
If tmpSQLExecError <> "All Completed" Then
Call UpdateStatus(tmpSQLExecError, tmpCallingForm, "Y")
End If
Else ' its already a query
tmpErrorSection = "Running QRY"
DoCmd.SetWarnings False
DoCmd.OpenQuery LineItem(i)
DoCmd.SetWarnings True
End If
i = i + 1
Loop
DoCmd.SetWarnings True
End If
AfterFileProcessed:
tmpErrorSection = "Clearing System Setting"
Set fso = Nothing
Set rst = Nothing
rstSource.MoveNext
Loop
Set fso = Nothing
Set rst = Nothing
Set TS = Nothing
ImportFile_Generic = "Process completed :" & Replace(tmpProcessName, "'", "")
Exit Function
Errorhandler:
If LogError(Err.Number, tmpErrorSection & " | " & Err.Description, GetSetting("Deck", "Login", "Username", "Not Logged in"), "ImportFile_Generic") = -1 Then
ImportFile_Generic = "Error " & Err.Number & " in section " & tmpErrorSection
Close #1
Resume Next
Else
Close #1
tmpErrorDisplay = "Error " & Err.Number & " in section " & tmpErrorSection
If MsgBox("Continue after this error ?", vbYesNo) = vbYes Then
Resume Next
Else
Exit Function
End If
End If
End Function