Expert guide
Import Excel into Microsoft Access with VBA
TransferSpreadsheet into a staging table, validate, then append: the pattern that survives messy files
Call us at +1 (385) 386-3860
Short answer: clear a staging table whose fields are all Short Text, import with DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12Xml, "tblStaging", path, True, add the source file name, run validation queries that flag bad rows, then append only valid, non-duplicate rows to the real table with type conversions. Log each import. For one-time moves of whole workbooks into a proper database, see converting Excel to an Access database.
Excel files arrive from everywhere: supplier price lists, bank exports, timesheets, CRM extracts, other departments' trackers. Importing them straight into live tables works until the file has a blank row, a text value in a number column or a changed header. The staging pattern below turns imports into a dependable routine.
TransferSpreadsheet in One Line
DoCmd.TransferSpreadsheet TransferType:=acImport, _
SpreadsheetType:=acSpreadsheetTypeExcel12Xml, _
TableName:="tblStaging", _
FileName:="C:\Imports\Prices.xlsx", _
HasFieldNames:=True, _
Range:="Prices!A1:F5000"| Argument | Notes |
|---|---|
| acSpreadsheetTypeExcel12Xml | .xlsx files; use acSpreadsheetTypeExcel12 for .xlsb/.xlsm and older types for .xls |
| TableName | Existing table: rows are appended; missing table: Access creates it and guesses types |
| HasFieldNames | True if row 1 holds headers; headers must match field names when appending to an existing table |
| Range | "SheetName!" for a whole sheet, "SheetName!A1:F5000" for a block, or a named range |
Why Import Into Staging
Type guessing
If Access creates the table, it guesses each column's type from the first rows. A column of numbers with one 'n/a' further down produces import errors.
Messy rows
Totals rows, blank lines, notes at the bottom and repeated headers all import as data.
Duplicates
The same file imported twice doubles your data.
Changed layouts
A renamed column fails the append; staging lets you detect and report it.
Create tblStaging once with all fields as Short Text (Long Text for long notes), named exactly as the Excel headers, plus SourceFile, RowStatus and RowError fields.
The Full Import Routine
Public Function ImportPriceFile(ByVal filePath As String) As Long
Dim db As DAO.Database, fileName As String
On Error GoTo Fail
Set db = CurrentDb
fileName = Mid$(filePath, InStrRev(filePath, "\") + 1)
If DCount("*", "tblImportLog", "FileName='" & Replace(fileName, "'", "''") & "'") > 0 Then
MsgBox fileName & " has already been imported.", vbExclamation: Exit Function
End If
db.Execute "DELETE FROM tblStaging", dbFailOnError
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12Xml, "tblStaging", _
filePath, True, "Prices!"
' Tag rows and remove obvious junk
db.Execute "UPDATE tblStaging SET SourceFile='" & Replace(fileName, "'", "''") & _
"', RowStatus='New'", dbFailOnError
db.Execute "DELETE FROM tblStaging WHERE Nz(SKU,'')='' AND Nz(Price,'')=''", dbFailOnError
' Validate
db.Execute "UPDATE tblStaging SET RowStatus='Error', RowError='Price not numeric' " & _
"WHERE RowStatus='New' AND Not IsNumeric(Price)", dbFailOnError
db.Execute "UPDATE tblStaging SET RowStatus='Error', RowError='Unknown SKU' " & _
"WHERE RowStatus='New' AND SKU Not In (SELECT SKU FROM tblProducts)", dbFailOnError
' Append valid rows with type conversion
db.Execute "INSERT INTO tblPrices (SKU, Price, EffectiveDate, SourceFile) " & _
"SELECT SKU, CCur(Price), CDate(EffectiveDate), SourceFile FROM tblStaging " & _
"WHERE RowStatus='New'", dbFailOnError
ImportPriceFile = db.RecordsAffected
db.Execute "INSERT INTO tblImportLog (FileName, ImportedOn, RowsImported, RowsRejected) " & _
"VALUES ('" & Replace(fileName, "'", "''") & "', Now(), " & ImportPriceFile & ", " & _
DCount("*", "tblStaging", "RowStatus='Error'") & ")", dbFailOnError
Exit Function
Fail:
MsgBox "Import failed: " & Err.Description, vbCritical
End FunctionRejected rows stay in tblStaging with a reason until the next import, so a user can open a simple form, see what failed and fix the source file.
Importing Every File in a Folder
Public Sub ImportFolder(ByVal folder As String)
Dim f As String
If Right$(folder, 1) <> "\" Then folder = folder & "\"
f = Dir(folder & "*.xlsx")
Do While Len(f) > 0
If Left$(f, 2) <> "~$" Then ' skip Excel lock files
ImportPriceFile folder & f
Name folder & f As folder & "Processed\" & f
End If
f = Dir()
Loop
End SubMoving files to a Processed subfolder means the folder only ever contains new work. Create the subfolder once, or check for it in code.
Import vs Link vs Power Query
| Approach | Best when | Watch out for |
|---|---|---|
| Import (this guide) | Data must be validated, related and kept | Needs a repeatable routine |
| Link to the workbook | Read-only reference to a live workbook | Slow; read-only; breaks if the file moves or layout changes |
| Clean in Excel with Power Query first | Files need heavy reshaping | Two-step process |
| Import via an API instead of Excel | Source system offers an API | Integration effort, but no files at all |
If Excel exports come from a system with an API, skipping the spreadsheet altogether is often worth it; see parsing JSON in Access VBA. If you are moving a whole team off a workbook into Access, read converting Excel to Access and Excel to database solutions.
FAQ
Importing Excel into Access FAQ
TransferSpreadsheet, types and duplicates