Make a column primary key when importing from excel - ms-access

I am importing access documents from Excel to Access, I need the first column to become primary key but unsure the method used to do this.
My Code:
Public Sub ImportProtected(strFile As String, _
strPassword As String)
Dim oExcel As Object, oWb As Object
Set oExcel = CreateObject("Excel.Application")
Set oWb = oExcel.Workbooks.Open(FileName:=strFile, _
Password:=strPassword)
DoCmd.TransferSpreadsheet acImport, _
acSpreadsheetTypeExcel9, "_tmp_table", strFile, -1
oWb.Close SaveChanges:=False
oExcel.Quit
Set oExcel = Nothing
End Sub
I call the code using:
ImportProtected "C:\Daves Work\Projects\ID.xlsm", "1234"
This all imports fine but I need the first column to become a primary key and have the numbers 1,2,3,4 etc in the rows that show data.
Help

Just import the data into existing table with primary key, or better - import into temporary table, then verify imported data, then copy valid data from temporary table to working one, with primary key.
You can also create primary key using VBA after import
UPDATE
Missed password protection requirement, removed part of answer.
If your header changes or missing, import into temporary table with HasFieldNames parameter =false, then analyze first row for extracting key name (if needed), then copy the data into working table using query starting second row.

If the data you are importing is well defined - that is, you know the column names, column order and data types - then it is preferable that you import into an existing table that already has the primary key defined. You could combine this with a step that first removes any existing (previous) data from the table.
Otherwise, you will need to use Access VBA to CreateIndex (and set its Primary property) for the table definition (TableDefs). Example.
As the column headings are not available, or cannot be relied upon, and as you need to use Excel Automation to open the file anyway, then I would first use automation to insert and create a header row; save the file, then import into a predefined Access table. For example:
Range("A1:D1").Value = Array("ID", "SecondField", "ThirdField", "Fourth")
This presumes that at least the column order and data types can be counted upon?! Otherwise, more work will be required anyway to manipulate the data.

Also You can add primary key by following code
DoCMD.RunSQL "ALTER TABLE " & tablename & " ADD Column ID COUNTER PRIMARY KEY"

Related

Updating an Access Table with a CSV File Automatically

Problem Background:
I have a Powershell script that I can execute from my Microsoft Access Form that scans through file folders that contain information on different facilities, and produces a CSV that looks something like:
SiteCode FacilityNumber DocumentType HyperlinkPath
DKFZ 10 DD1400 C:\FACILITIES DATABASE\path
DKFZ 10 FLRPLN C:\FACILITIES DATABASE\path
SMQL 17 P1 C:\FACILITIES DATABASE\path
SMQL 17 P2 C:\FACILITIES DATABASE\path
So that way every time new files are added to those folders, I can just run this script and produce an updated list of everything I have:
C:\...\Output\scanResults.csv
All I need now is to take that CSV file and update (or even overwrite) a Table that I have in an Access database, which has relationships to other tables and is used by various Queries and Forms in the database. The CSV columns are already named and formatted in the same way as the Access Table.
I've looked at and tried to replicate the following threads:
VBA procedure to import csv file into access
Access Data Project Importing CSV File In VBA
VBA Import CSV file
The closest answer I found is:
Sub Import()
Dim conn as new ADODB.Connection
Dim rs as new ADODB.Recordset
Dim f as ADODB.field
conn.Open "DRIVER={Microsoft Text Driver (*.txt; *.csv)};DBQ=c:\temp;"
rs.Open "SELECT * FROM [test.txt]", conn, adOpenStatic, adLockReadOnly, adCmdText
While Not rs.EOF
For Each f In rs.Fields
Debug.Print f.name & "=" & f.Value
Next
Wend
End Sub
But this obviously won't write the data into the table, and I could not understand what the author was trying to say with respect to changing Select to Insert.
I've also found:
DoCmd.TransferText acImportDelim, "YourCustomSpecificationName", _
"tblImport", "C:\SomeFolder\DataFile.csv", False
Since both of these are from 2010, I wonder if there isn't a better way to accomplish this in Access 2013. And while I can do this all manually, I would like to incorporate it into the VBA code I use to tell Powershell to produce the CSV, that way I can make it and then upload it immediately.
Any help or suggestions are greatly appreciated. I'm still very green to Access, VBA, and SQL statements in general, so this has been very much a "learning as I go" process.
I prefer to use SQL clauses and queries to import such data. The details depend on your exact configuration, but it tends to look something like this:
SELECT *
INTO MyTable
FROM [Text;FMT=CSVDelimited;HDR=No;DATABASE=C:\...\Output].[scanResults#csv]
Or append the information to the table instead:
INSERT INTO MyTable
(SiteCode, FacilityNumber, DocumentType, HyperlinkPath)
SELECT *
FROM [Text;FMT=CSVDelimited;HDR=No;DATABASE=C:\...\Output].[scanResults#csv]
This allows you to do checks before importing (using a WHERE clause), import only specific values, and allows you to customize a lot without using external files.
DATABASE= is followed by your folder name (use {} if there are characters that need escaping in there), and then followed by your file name with . replaced with #.
You can execute it by either saving it as a query, or using it as a string in either VBA or a macro. Note that I rarely recommend macro's, but you can execute them using a scheduled task and close Access after importing.
To backup and restore a relation before and after updating, you can use the following functions:
Public Function DeleteRelationsGiveBackup(strTablename As String) As Collection
Dim ReturnCollection As Collection
Set ReturnCollection = New Collection
Dim i As Integer
Dim o As Integer
Do While i <= (CurrentDb.Relations.Count - 1)
Select Case strTablename
Case Is = CurrentDb.Relations(i).Table
ReturnCollection.Add DuplicateRelation(CurrentDb.Relations(i))
o = o + 1
CurrentDb.Relations.Delete CurrentDb.Relations(i).NAME
Case Is = CurrentDb.Relations(i).ForeignTable
ReturnCollection.Add DuplicateRelation(CurrentDb.Relations(i))
o = o + 1
CurrentDb.Relations.Delete CurrentDb.Relations(i).NAME
Case Else
i = i + 1
End Select
Loop
Set DeleteRelationsGiveBackup = ReturnCollection
End Function
Public Sub RestoreRelationBackup(collRelationBackup As Collection)
Dim relBackup As Variant
If collRelationBackup.Count = 0 Then Exit Sub
For Each relBackup In collRelationBackup
CurrentDb.Relations.Append relBackup
Next relBackup
End Sub
Public Function DuplicateRelation(SourceRelation As Relation) As Relation
Set DuplicateRelation = CurrentDb.CreateRelation(SourceRelation.NAME, SourceRelation.Table, SourceRelation.ForeignTable)
DuplicateRelation.Attributes = SourceRelation.Attributes
Dim i As Integer
Dim fldLoop As Field
Do While i < SourceRelation.Fields.Count
Set fldLoop = DuplicateRelation.CreateField(SourceRelation.Fields(i).NAME)
fldLoop.ForeignName = SourceRelation.Fields(i).ForeignName
DuplicateRelation.Fields.Append fldLoop
i = i + 1
Loop
End Function
And then, when importing:
Dim colRelBackup As Collection
Set colRelBackup = DeleteRelationsGiveBackup("MyTable")
'Delete MyTable
'Import new version
RestoreRelationBackup colRelBackup
(Note that the code is quite long, developed for a project several years ago, and not extensively tested. If a field name/type is not exactly like how it was before the import, the restore of the backup might fail and the relations will be permanently lost).
So some high level architect advice: replacing data versus replacing table
It is easier replacing data - - the new incoming data must be the exact same structure as the existing table (i.e. same field names and no new fields).
just fire a Delete Query to the existing table that clears out all records
then fire an Append Query to the linked CSV file that writes all those records into the existing table
very simple really.
You can replace the tables if you must - and you are already down this path. You can delete those table relationships entirely. That table relationship feature is useful - but not mandatory. You can create relationships at the query level as an alternative. Essentially the table relationships just auto create the query level relationships. If you delete the table relationships then one must simply create the table relationships at the query level manually - they don't automatically appear. Note however that if one is relying on cascade deletes or referential integrity, then removing table relationships will undo that - so you should check these points.
Deleting Table Relationships will not break any existing queries. Their table relationship join lines will remain intact.

Run-time error '3326' This Recordset is not updateable

i have an application that was created using Access 2007, its working perfectly with DSN, i update the application to use DSN-less connection to SQL-Server, i didn't change any code in the application, but when i run the application i am getting this error
Run-time error '3326' This Recordset is not updateable
Sub DnsLessLinkTable()
Dim td As TableDef
Dim stConnect As String
Dim strTablesName(0 To 7) As String
strTablesName(0) = "dbo_Directorate"
strTablesName(1) = "dbo_Nationality"
strTablesName(2) = "dbo_personal"
strTablesName(3) = "dbo_Qualification"
strTablesName(4) = "dbo_Qualimain"
strTablesName(5) = "dbo_Qualisec"
strTablesName(6) = "dbo_Section"
strTablesName(7) = "dbo_Trips"
For Each td In CurrentDb.TableDefs
For Each TableName In strTablesName
If td.Name = TableName Then
CurrentDb.TableDefs.Delete TableName
End If
Next
Next
stConnect = "ODBC;Driver={SQL Server};Server=ServerNametest;Database=DBName;Uid=user;Pwd=password;"
For Each TableName In strTablesName
Dim splitTarget As Variant
splitTarget = Split(TableName, "_")
Set td = CurrentDb.CreateTableDef(TableName, dbAttachSavePWD, splitTarget(1), stConnect)
CurrentDb.TableDefs.Append td
AttachDSNLessTable = True
Next
Err.Description
End Sub
the only table that dosnt have a primary key is [Personal] in SQL server and i linked it to [dbo_personal]
the DSN-less module was run successfully and updated all the linked table, i am really struggling with this application,
note that before DSN-less connection i was using the same connection string on building the DSN
any suggestion that will help
Access is able to update linked tables only when they have a primary key.
When they don't have one, you'll get the "This Recordset is not updateable" error.
If the underlying SQL Server table has a primary key, Access will usually detect it and use it in the linked table as well.
If the table does not have a primary key on the server, or if it's a view and not a table, Access is not able to set a primary key by itself.
If you link a view manually per DSN, a window will pop up and ask you to select the columns which Access should consider as the primary key:
When you link the table DSN-less (i.e. per code) and you don't specify a primary key, Access will link the table without primary key.
So I suppose that the table you're talking about is either a view or it has no primary key.
When it was originally linked with DSN, someone selected the primary key in the popup window shown above.
When you re-linked it with your DSN-less code, you probably didn't set the primary key, and that's why you're getting the "This Recordset is not updateable" error now.
Solution: After linking the table with your DSN-less code, explicitly set the primary key via VBA code.

Preventing MSAccess linked tables from having a dbo_ prefix?

Is there a way to prevent MSAccess from concatenating 'dbo_' as the prefix to tables linked from a SQL Server db?
No. Microsoft Access' native naming convention process will force the name <schema>_<ObjectName> as your default table name. There are no controls or settings which will allow you to change that, except in code. It's rather complex and goes beyond the scope of this question, but if you do the linking in code (which I do) then you can store the TableAlias and create the linked table name that way.
You can write your table definition in the code to attach the table as below where
strTableAlias is your display name in Access
strTableName is your table name with schema <schema>.<ObjectName>.
Dim td As DAO.TableDef
set td = CurrentDb.CreateTableDef(strTableAlias, dbAttachSavePassword, _
strTableName, strConnectionStr)
CurrentDb.TableDefs.Append td
If you don't do the linking in code as mentioned by Johnny you can rename the links with this VBA code:
Public Sub Remove_DBO_Prefix()
Dim obj As AccessObject
Dim dbs As Object
Set dbs = Application.CurrentData
'Search for open AccessObject objects in AllTables collection.
For Each obj In dbs.AllTables
'If found, remove prefix
If Left(obj.Name, 4) = "dbo_" Then
DoCmd.Rename Mid(obj.Name, 5), acTable, obj.Name
End If
Next obj
End Sub
Courtesy https://www.microsoftaccessexpert.com/Microsoft-Access-Code-RemoveDBOPrefix.aspx
If you're doing this with the idea of using queries that you generate in Access outside of Access remember that Access creates it's own sql dialect so you may need to convert double quotes to single and # to ' among other things.

Programmaticaly rename a column in Access table after import from Excel

Is there a way to programmatically rename the 1st column of a table in Access? Situation: after importing an Excel file into Access, I always need to rename the 1st column which always bears a different column name and manually rename it into F1, it would be much easier if this could be done programmaticaly. Is there an easy way to accomplish that using VBA?
Basically open an access application object then you're essentially renaming it as you would within Access vba.
Dim appAccess As Access.Application
Set appAccess = New Access.Application
With appAccess
.OpenCurrentDatabase "C:\...\DatabaseName.accdb"
.CurrentDb.TableDefs("Table1").Fields(1).Name = "F1"
.CloseCurrentDatabase
End With
Replace "Table1" with the name of your table and .Fields(1) refers to the first field, .Fields(2) refers to the second etc.
Note: Make sure you set your references to Access Object Library (found in Tools > References). It'll have a name similar to "Microsoft Access 12.0 Object Library"

VBA DoCmd.TransferText. Add one more field at access table

I am transferring from text files to access DB table using DoCmd.TransferText. Just that for each record created in my access DB table, apart from the text file lines, i would like to add one more field to each record, but for DoCmd it seems to be a constraint. Is there any way i can do that?
You can import the text file into a staging table then
run a make table query or Append Table Query into your finished table. In the query add the needed field(s) via calculation in the Query Design:
Myfield:Null or MyField:IIF(SomeOtherField="M", ...) etc. Then save the query.
In your VBA (Assuming that "StagingTable is where you import the file and
AppendStagingTableToWorkTable is your appendquery:
DoCmd.TransferText acImportFixed, "My Import Spec", "StagingTable", _
"C:\TextFile.txt", False
DoCmd.OpenQuery "AppendStagingTableToWorkTable"