Run Access Queries from Excel VBA - vba

I am trying to write a macro in excel vba that simply opens an access database and runs 2 queries in access. It seems to work and run the queries every 2 clicks of the macro button. What I mean is I click it, it works, the 2nd click I get a 'runtime error 462' on the second click, the third click it works, the fourth click I get the error again and so on. I can't seem to figure out why this is. Here is the code below.
Sub QueryAccess1()
Dim db As Access.Application
Set db = New Access.Application
'set variables
db.Visible = True
db.OpenCurrentDatabase ("DatabaseFileName")
'open database
'--------------------------------------------------------------
On Error Resume Next
db.DoCmd.DeleteObject acTable, "TableName"
'if the table does not exist it skips this line
'--------------------------------------------------------------
On Error GoTo 0
'sets the error back to normal
'--------------------------------------------------------------
CurrentDb.Openrecordset ("QUERY1")
CurrentDb.Execute ("QUERY2")
'Calls the queries
'--------------------------------------------------------------
'--------------------------------------------------------------
db.CloseCurrentDatabase
db.Quit
'Closes Access
'--------------------------------------------------------------
Set db = Nothing
End Sub
When I get the error I am getting it on the line
CurrentDb.Openrecordset ("QUERY1")

I managed to get it to work with Parfait's method. Here is what I have got.
Sub QueryAccess1()
Dim conn As Object, rst As Object
Dim path As String
Set conn = CreateObject("ADODB.Connection")
Set rst = CreateObject("ADODB.Recordset")
path = Sheets("SheetName").Range("A1")
'OPEN CONNECTION
conn.Open ConnectionString:="Provider = Microsoft.ACE.OLEDB.12.0; data source=" & path
'DELETES TABLE CONTENTS
conn.Execute "DELETE FROM [Table1]"
'RUN UNION QUERY AND INSERT INTO TABLE
rst.Open "SELECT * FROM [Query1]", conn
conn.Execute "INSERT INTO [Table1] select * from [QUERY1] "
Set rst = Nothing: Set conn = Nothing
End Sub

Likely the every two click error is due to opening the table that was just deleted where every other time it exists. Consider iterating through MS Access's TableDefs collection to conditionally delete the object if it exists. Then, re-order your action query to run before OpenRecordset call.
Public Sub RunQueries()
On Error Goto ErrHandle:
' DAO REQUIRES REFERENCE TO Microsoft Office X.X Access Database Engine Object Library
Dim tbl As DAO.TableDef
Dim rs As DAO.Recordset
Dim db As New Access.Application
db.Visible = False ' KEEP DATABASE RUNNING IN BACKGROUND
For Each tbl in db.CurrentDb.TableDefs
If tbl.Name = "TableName" Then
db.DoCmd.DeleteObject acTable, "TableName"
End If
Next tbl
' ASSUMED AN ACTION QUERY
db.CurrentDb.Execute "QUERY2", dbFailOnError
' ASSUMED A SELECT QUERY BUT CALL BELOW IS REDUNDANT AS IT IS NEVER USED
Set rs = db.CurrentDb.OpenRecordset("QUERY1")
ExitHandle:
' FREE RESOURCES
Set rst = Nothing: Set conn = Nothing
db.CloseCurrentDatabase
db.Quit
Set db = Nothing
Exit Sub
ErrHandle:
MsgBox Err.Number & " - " & Err.Description, vbCritical, "RUNTIME ERROR"
Resume ExitHandle
End Sub
Aside - Avoid ever using On Error Resume Next in VBA. Always proactively anticipate and handle exceptions.
Alternatively, instead of using the make-table command SELECT * INTO and then having to worry about deleting the table programmatically, simple create your table once and then use DELETE and INSERT which can be run each time. Of course this assumes the table's structure (fields/types) remain the same.
DELETE FROM myTable;
INSERT INTO myTable (Col1, Col2, Col3)
SELECT Col1, Col2, Col3 FROM myOtherTable;
SELECT * FROM myTable;
Finally, there is no reason to even use the MS Access object library to open/close the .GUI just to run queries. Since Access is a database, connect to it like any other backend (i.e., SQLite, Postgres, Oracle) and run your queries from there. Below is an ODBC connection example which the driver can easily be swapped for drivers of other RBDMS's.
Dim conn As Object, rst As Object
Set conn = CreateObject("ADODB.Connection")
Set rst = CreateObject("ADODB.Recordset")
' OPEN CONNECTION
conn.Open "DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};" _
& "DBQ=C:\Path\To\Access\DB.accdb;"
' RUN ACTION QUERIES
conn.Execute "DELETE FROM myTable"
conn.Execute "INSERT INTO myTable (Col1, Col2, Col3)" _
& " SELECT Col1, Col2, Col3 FROM myOtherTable"
' OPEN RECORDSET
rst.Open "SELECT * FROM myQuery", conn
' OUTPUT TO WORKSHEET
Worksheets("DATA").Range("A1").CopyFromRecordset rst
rst.Close
In fact, the above approach does not even require MS Access GUI .exe installed! Also, be sure to save the SELECT query (even one inside INSERT) inside Access and not run as a VBA SQL string since the Access engine will save the best execution plan for stored queries.

Related

How do I import data from a Teradata table to MS access in an automated way?

I am new to MS Access. I have been trying to import data from a Teradata table to MS Access database. I could establish the connection between the two using VBA. However, I am not being able to write the contents to the access database.
For Excel, we generally use objects like sheets and range to populate the values. What are Access counterparts of these objects?
Given below is the code that I have been using:
Sub TBEN_PR_DSM_SEAS()
Dim cn As ADODB.Connection
Set cn = New ADODB.Connection
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
Dim cmdSQLData As ADODB.Command
Set cmdSQLData = New ADODB.Command
Dim query As String
cn.Open "DRIVER={Teradata}; DBCNAME=ABC2; Persist Security Info=True; User ID= ******; Password=******; Session Mode=ANSI;"
Set cmdSQLData.ActiveConnection = cn
query = "SELECT * FROM PRODBBYCIADHOCWRK.TBEN_PR_DSM_SEAS;"
cmdSQLData.CommandText = query
cmdSQLData.CommandType = adCmdText
cmdSQLData.CommandTimeout = 0
Set rs = cmdSQLData.Execute()
End Sub
Can anyone please help me out with the rest of the part? I am using Access 2007-2010.
Thanks and regards,
Nirvik
MS Access is an interesting piece of software as it can serve as both a RDMS database and GUI console to a database. By default, it connects to the Jet/ACE SQL Engine (Windows .dll files) which would compare to SQLite another file-level RDMS. However, with MSAccess.exe Office program, this default can be switched or supplemented with any other ODBC/OLEDB compliant database including the server-level RDMS (Oracle, SQL Server, MySQL, Sybase, even Teradata) using linked tables. And in connecting to external backends it would compare to MySQL's phpmyadmin, SQL Server's Management Studio, PostgreSQL's pgAdmin, and other consoles.
Therefore, consider creating a linked table to Teradata using DoCmd.TransferDatabase where changes reflect on both ends without constant import and export of table data:
DoCmd.TransferDatabase acLink, "ODBC Database", _
"ODBC;DRIVER={Teradata}; DBCNAME=ABC2; Persist Security Info=True; User ID= ******;" _
& "Password=******; Session Mode=ANSI;", acTable, "TBEN_PR_DSM_SEAS", "NewAccessTable"
And for a static, local copy (which would add redundancy to your application needs) you can run an append or make-table query in Query Window or VBA's DoCmd.RunSQL or CurrentDb.Execute to a local Access table using above linked table.
INSERT INTO NewAccessTable SELECT * FROM [TBEN_PR_DSM_SEAS]
SELECT * INTO NewAccessTable FROM [TBEN_PR_DSM_SEAS]
Insert the data in the recordset into the Access table, using new recordset, Connection and Command objects.
Sub TBEN_PR_DSM_SEAS()
Dim cn As ADODB.Connection
Set cn = New ADODB.Connection
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
Dim cmdSQLData As ADODB.Command
Set cmdSQLData = New ADODB.Command
Dim query As String
cn.Open "DRIVER={Teradata}; DBCNAME=ABC2; Persist Security Info=True; User ID= ******; Password=******; Session Mode=ANSI;"
Set cmdSQLData.ActiveConnection = cn
query = "SELECT * FROM PRODBBYCIADHOCWRK.TBEN_PR_DSM_SEAS;"
cmdSQLData.CommandText = query
cmdSQLData.CommandType = adCmdText
cmdSQLData.CommandTimeout = 0
Set rs = cmdSQLData.Execute()
'Up to here is your code.
'Asuming you have a table in Access with identical number of fields, and field names:
dim dRst as dao.Recordset, fld as Variant
set dRst = CurrentDb.("AccessTable")
Do While Not Rs.EOF
dRst.AddNew
For Each fld in dRst.Fields
dRst.Fields(fld.Name) = rs.Fields(fld.Name)
Next
'Update an entire record:
dRst.Update
Rs.MoveNext: Loop
End Sub
Tables are Sheets and Queries select Ranges.
Fields are Columns and Records are Rows.
'Loop through Records
Do Until rs.EOF
'rs.Fields(0) is the first field returned from the Teradata query
'executed in your initial question.
Debug.Print rs.Fields(0)
rs.MoveNext
Loop
'Append to Table
'Have to create Table1 in Access database.
'Table1
'Field Name: Column1
'Data Type: Text
If rs.BOF = False Then rs.MoveFirst 'BOF = Beginning of file
DoCmd.SetWarnings False
Do Until rs.EOF 'EOF = End of file
DoCmd.RunSQL ("INSERT INTO Table1 (Column1) SELECT '" & rs.Fields(0) & "'")
rs.MoveNext
Loop
DoCmd.SetWarnings True
cn.Close
Set cn = Nothing

Adding a new record with VBA

I have a form in which one of the ComboBoxes lists all the documents of a given project. The user should select one and after pressing a button, and if present in Table Dessinsit opens a second form showing that record. If it is not present in that table, I want to add it in.
One of my collegues told me all I had to do was to execute an SQL query with VBA. What I have so far is this:
Dim rsDessin As DAO.Recordset
Dim strContrat As String
Dim strProjet As String
Dim strDessin As String
Dim sqlquery As String
'I think these next 3 lines are unimportant. I set a first query to get information I need from another table
strDessin = Me.Combo_Dessin
strProjet = Me.Combo_Projet
sqlquery = "SELECT [Projet HNA] FROM [Projets] WHERE [Projet AHNS] = '" & strProjet & "'"
Set rsDessin = CurrentDb.OpenRecordset(sqlquery)
If Not rsDessin.RecordCount > 0 Then 'If not present I want to add it
strContrat = rsDessin![Projet HNA]
sqlquery = "INSERT INTO Feuilles ([AHNS], [Contrat], [No Projet]) VALUES (strDessin, strContrat, strDessin)"
'Not sure what to do with this query or how to make sure it worked.
End If
'Checking my variables
Debug.Print strProjet
Debug.Print strContrat
Debug.Print strDessin
'By here I'd like to have inserted my new record.
rsDessin.Close
Set rsDessin = Nothing
I also read online that i could achieve a similar result with something like this:
Set R = CurrentDb.OpenRecordset("SELECT * FROM [Dessins]")
R.AddNew
R![Contrat] = strContrat
R![Projet] = strProjet
R![AHNS] = strDessin
R.Update
R.Close
Set R = Nothing
DoCmd.Close
Is one way better than the other? In the case where my INSERT INTO query is better, what should I do to execute it?
You're asking which is preferable when inserting a record: to use an SQL statement issued to the Database object, or to use the methods of the Recordset object.
For a single record, it doesn't matter. However, you could issue the INSERT statement like this:
CurrentDb.Execute "INSERT INTO Feuilles ([AHNS], [Contrat], [No Projet]) VALUES (" & strDessin & ", " & strContrat & ", " & strDessin & ")", dbFailOnError
(You should use the dbFailOnError option to catch certain errors, as HansUp points out in this answer.)
For inserting multiple records from another table or query, it is generally faster and more efficient to issue an SQL statement like this:
Dim sql = _
"INSERT INTO DestinationTable (Field1, Field2, Field3) " & _
"SELECT Field1, Field2, Field3 " & _
"FROM SourceTable"
CurrentDb.Execute sql
than the equivalent using the Recordset object:
Dim rsSource As DAO.Recordset, rsDestination As DAO.Recordset
Set rsSource = CurrentDb.OpenRecordset("SourceTable")
Set rsDestination = CurrentDb.OpenRecordset("DestinationTable")
Do Until rs.EOF
rsDestination.AddNew
rsDestination!Field1 = rsSource!Field1
rsDestination!Field2 = rsSource!Field2
rsDestination!Field3 = rsSource!Field3
rsDestination.Update
rs.MoveNext
Loop
That said, using an SQL statement has its limitations:
You are limited to SQL syntax and functions.
This is partially mitigated in Access, because SQL statements can use many VBA built-in functions or functions that you define.
SQL statements are designed to work on blocks of rows. Per-row logic is harder to express using only the Iif, Choose, or Switch functions; and logic that depends on the current state (e.g. insert every other record) is harder or impossible using pure SQL. This can be easily done using the Recordset methods approach.
This too can be enabled using a combination of VBA and SQL, if you have functions that persist state in module-level variables. One caveat: you'll need to reset the state each time before issuing the SQL statement. See here for an example.
One part* of your question asked about INSERT vs. Recordset.AddNew to add one row. I suggest this recordset approach:
Dim db As DAO.Database
Dim R As DAO.Recordset
Set db = CurrentDb
Set R = db.OpenRecordset("Dessins", dbOpenTable, dbAppendOnly)
With R
.AddNew
!Contrat = rsDessin![Projet HNA].Value
!Projet = Me.Combo_Projet.Value
!AHNS = Me.Combo_Dessin.Value
.Update
.Close
End With
* You also asked how to execute an INSERT. Use the DAO.Database.Execute method which Zev recommended and include the dbFailOnError option. That will add clarity about certain insert failures. For example, a key violation error could otherwise make your INSERT fail silently. But including dbFailOnError ensures you get notified about the problem immediately. So always include that option ... except in cases where you actually want to allow an INSERT to fail silently. (For me, that's never.)

Edit records in ADODB recordset

What I am trying to do is to get some data from an online server through an SQL Query and then loop through the recordset modifying the records.
I get an error when trying to modify the recordset:
"Multiple-Step operation generated errors. Check each status value."
My question is: Is there a way of modifying a record from a recordset that I got from a Query?
In this case I am modifying field 2 if field 1 meets a certain criteria. (In this case Field 2 is a string)
Here is the simplified code:
Dim adoConn As ADODB.Connection
Dim locRS As New ADODB.Recordset, proRS As ADODB.Recordset
Dim strConnection As String
Set getSQL = New ADODB.Recordset
'Set Objects
Set adoConn = New ADODB.Connection
'Specify connection string
strConnection = "User ID=xxx; Password=xxx;Data Source=xxx;Provider=OraOLEDB.Oracle"
'Open the connection
adoConn.Open (strConnection)
'Set up recordset properties
getSQL.CursorType = adOpenStatic
getSQL.CursorLocation = adUseClient
getSQL.LockType = adLockBatchOptimistic
'Import the data
getSQL.Open "SELECT FIELD1, FIELD2 FROM TABLE", adoConn, adOpenStatic, adLockOptimistic
Set getSQL.ActiveConnection = Nothing
getSql.Update
'Loop through data
getSQL.MoveFirst
Do While Not stockRS.EOF
'If cetrain condition is met then modify the null column
if getSQL!FIELD1=CRITERIA then
'Error here
getSQL!FIELD2="SOME STRING"
End If
getSQL.MoveNext
Loop
'Close
adoConn.Close
Set adoConn = Nothing
Your SQL is not doing what you think:
SELECT ... NULL OUTCOME ... is going to return the value NULL in a field called OUTCOME but will not link to a field in the table called OUTCOME (which is what I think you are looking for) as your current syntax is setting up an ALIAS not selecting the field. I am assuming the field OUTCOME exists on the table. If not you need to create it up front or do an alter table to add the field before you can write anything to it.
I recommend creating field up front (which I think you have already done). But make sure that the default value is NULL so you don't need to do your NULL trick in the select ALSO make sure that the field is allowed to take a NULL value or you will see errors. Select becomes:
getSQL.Open "SELECT FIELD1, FIELD2, OUTCOME FROM TABLE", adoConn, adOpenStatic, adLockOptimistic
And then manage the NULL value in the function as follows:
if getSQL!FIELD1=CRITERIA then
'Error here
getSQL!OUTCOME="SOME STRING"
ELSE
getSQL!OUTCOME=NULL
End If
This ensure that you always write something to OUTCOME field so processing and OUTCOME don't get out of sync.
Also I still think that you have divorced the recordset data from the server when you:
Set getSQL.ActiveConnection = Nothing
Do this to release resources after your are done.
You may also need a
getSql.Update
After making changes to commit them back to database.

Using Access to do a Make Table query to create a SQL Server table using ODBC connection

I am upsizing an Access 2010 database to SQL server. There is an updatable "Make Table" query that is run to create a table and fill it. Currently, it fills a backend table in another ACCDB file. I have moved all the backend tables to SQL Server and trying to change the query to do make a table on SQL Server.
I removed the path to the ACCDB file from the Destination DB property, and put in an ODBC connection string in the Dest Connect Str property.
When I run the query, I get the error
ODBC call failed There is already an object name 'MyTableName' in the database (#2714)
I delete the table on the SQL server first and then run the query it works. If using a local table, it will properly delete the table, re-create it, and then fill it. Using ODBC connection it appears that it unable to delete it first, thus making the Make Table useless.
I have the remote table configured as a Linked table in Access, was hoping there was a way to use it directly without having to re-specify the connection string once again. This didn't seem possible either.
Looking for a solution or any possible alternatives to this problem. I have almost 20 queries that are of this type.
A make-table query targeting an ODBC external database will have a .SQL property similar to this
SELECT localTable.ID, localTable.textCol
INTO (ODBC;DSN=myDb;Trusted_Connection=Yes;DATABASE=myDb;AutoTranslate=No;) externalTable
FROM localTable;
Therefore we can use a bit of VBA code to identify that type of make-table query, drop the table on the SQL Server, and then execute the make-table query. So, instead of doing
DoCmd.OpenQuery "YourMakeTableQueryName"
(as I suspect the code does now) you could use
RunMakeTableQuery "YourMakeTableQueryName"
where RunMakeTableQuery is defined in a standard VBA module as
Option Compare Database
Option Explicit
Public Sub RunMakeTableQuery(MakeTableQueryName As String)
Dim cdb As DAO.Database, qdf As DAO.QueryDef, qdf2 As DAO.QueryDef
Dim i As Long, j As Long, ConnectionString As String, TableName As String
Const ExternalIntoTag = "INTO (ODBC;"
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs(MakeTableQueryName)
i = InStr(1, qdf.SQL, ExternalIntoTag, vbBinaryCompare)
If i > 0 Then
' target table is external (SQL Server)
i = i + Len(ExternalIntoTag)
j = InStr(i, qdf.SQL, ")", vbBinaryCompare)
ConnectionString = Trim(Mid(qdf.SQL, i, j - i))
i = InStr(j + 1, qdf.SQL, "FROM", vbBinaryCompare)
TableName = Trim(Mid(qdf.SQL, j + 1, i - j - 3))
Set qdf2 = cdb.CreateQueryDef("")
qdf2.Connect = "ODBC;" + ConnectionString
qdf2.ReturnsRecords = False
qdf2.SQL = "IF OBJECT_ID('" & TableName & "','U') IS NOT NULL DROP TABLE [" & TableName & "]"
qdf2.Execute dbFailOnError
Set qdf2 = Nothing
qdf.Execute dbFailOnError
Set qdf = Nothing
Else
' target table is an Access table
Set qdf = Nothing
' this will overwrite an existing target table with no prompts
DoCmd.SetWarnings False
DoCmd.OpenQuery MakeTableQueryName
DoCmd.SetWarnings True
End If
Set cdb = Nothing
End Sub

How to get id of newly inserted record using Excel VBA?

Seems a common enough problem this, but most solutions refer to concatenating multiple SQL commands, something which I believe can't be done with ADO/VBA (I'll be glad to be shown wrong in this regard however).
I currently insert my new record then run a select query using (I hope) enough fields to guarantee that only the newly inserted record can be returned. My databases are rarely accessed by more than one person at a time (negligible risk of another insert happening between queries) and due to the structure of the tables, identifying the new record is normally pretty easy.
I'm now trying to update a table that does not have much scope for uniqueness, other than in the artificial primary key. This means there is a risk that the new record may not be unique, and I'm loathe to add a field just to force uniqueness.
What's the best way to insert a record into an Access table then query the new primary key from Excel in this situation?
Thanks for the replies. I have tried to get ##IDENTITY working, but this always returns 0 using the code below.
Private Sub getIdentityTest()
Dim myRecordset As New ADODB.Recordset
Dim SQL As String, SQL2 As String
SQL = "INSERT INTO tblTasks (discipline,task,owner,unit,minutes) VALUES (""testDisc3-3"",""testTask"",""testOwner"",""testUnit"",1);"
SQL2 = "SELECT ##identity AS NewID FROM tblTasks;"
If databaseConnection Is Nothing Then
createDBConnection
End If
With databaseConnection
.Open dbConnectionString
.Execute (SQL)
.Close
End With
myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly
Debug.Print myRecordset.Fields("NewID")
myRecordset.Close
Set myRecordset = Nothing
End Sub
Anything stand out being responsible?
However, given the caveats helpfully supplied by Renaud (below) there seems nearly as much risk with using ##IDENTITY as with any other method, so I've resorted to using SELECT MAX for now. For future reference though I would be interested to see what is wrong with my attempt above.
About your question:
I'm now trying to update a table that
does not have much scope for
uniqueness, other than in the
artificial primary key. This means
there is a risk that the new record
may not be unique, and I'm loathe to
add a field just to force uniqueness.
If you are using an AutoIncrement for your primary key, then you have uniqueness and you could use SELECT ##Identity; to get the value of the last autogenerated ID (see caveats below).
If you are not using autoincrement, and you are inserting the records from Access but you want to retrieve the last one from Excel:
make sure your primary key is sortable, so you can get the last one using a query like either of these:
SELECT MAX(MyPrimaryField) FROM MyTable;
SELECT TOP 1 MyPrimaryField FROM MyTable ORDER BY MyPrimaryField DESC;
or, if sorting your primary field wouldn't give you the last one, you would need to add a DateTime field (say InsertedDate) and save the current date and time every time you create a new record in that table so you could get the last one like this:
SELECT TOP 1 MyPrimaryField FROM MyTable ORDER BY InsertedDate DESC;
In either of these cases, I think you would find adding an AutoIncrement primary key as being a lot easier to deal with:
It's not going to cost you much
It's going to guarantee you uniqueness of your records without having to think about it
It's going to make it easier for you to pick the most recent record, either using ##Identity or through sorting by the primary key or getting the Max().
From Excel
To get the data into Excel, you have a couple of choices:
create a data link using a query, so you can use the result directly in a Cell or a range.
query from VBA:
Sub GetLastPrimaryKey(PrimaryField as string, Table as string) as variant
Dim con As String
Dim rs As ADODB.Recordset
Dim sql As String
con = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source= ; C:\myDatabase.accdb"
sql = "SELECT MAX([" & PrimaryField & "]) FROM [" & MyTable & "];"
Set rs = New ADODB.Recordset
rs.Open sql, con, adOpenStatic, adLockReadOnly
GetLastPrimaryKey = rs.Fields(0).Value
rs.Close
Set rs = Nothing
End Sub
Note about ##Identity
You have to be careful of the caveats when using ##Identity in standard Access databases(*):
It only works with AutoIncrement Identity fields.
It's only available if you use ADO and run SELECT ##IDENTITY;
It returns the latest used counter, but that's for all tables. You can't use it to return the counter for a specific table in MS Access (as far as I know, if you specify a table using FROM mytable, it just gets ignored).
In short, the value returned may not be at all the one you expect.
You must query it straight after an INSERT to minimize the risk of getting a wrong answer.
That means that if you are inserting your data at one time and need to get the last ID at another time (or another place), it won't work.
Last but not least, the variable is set only when records are inserted through programming code.
This means that is the record was added through the user interface, ##IDENTITY will not be set.
(*): just to be clear, ##IDENTITY behaves differently, and in a more predictive way, if you use ANSI-92 SQL mode for your database.
The issue though is that ANSI 92 has a slightly different syntax than
the ANSI 89 flavour supported by Access and is meant to increase compatibility with SQL Server when Access is used as a front end.
If the artificial key is an autonumber, you can use ##identity.
Note that with both these examples, the transaction is isolated from other events, so the identity returned is the one just inserted. You can test this by pausing the code at Debug.Print db.RecordsAffected or Debug.Print lngRecs and inserting a record manually into Table1, continue the code and note that the identity returned is not that of the record inserted manually, but of the previous record inserted by code.
DAO Example
'Reference: Microsoft DAO 3.6 Object Library '
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
db.Execute ("INSERT INTO table1 (field1, Crdate ) " _
& "VALUES ( 46, #" & Format(Date, "yyyy/mm/dd") & "#)")
Debug.Print db.RecordsAffected
Set rs = db.OpenRecordset("SELECT ##identity AS NewID FROM table1")
Debug.Print rs.Fields("NewID")
ADO Example
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Set cn = CurrentProject.Connection
cn.Execute ("INSERT INTO table1 (field1, Crdate ) " _
& "VALUES ( 46, #" & Format(Date, "yyyy/mm/dd") & "#)"), lngRecs
Debug.Print lngRecs
rs.Open "SELECT ##identity AS NewID FROM table1", cn
Debug.Print rs.Fields("NewID")
Re: "I have tried to get ##IDENTITY working, but this always returns 0 using the code below."
Your code sends SQL and SQL2 through different connection objects. I don't think ##identity will return anything other than zero unless you ask from the same connection where you executed your INSERT statement.
Try changing this:
myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly
to:
myRecordset.Open SQL2, databaseConnection, adOpenStatic, adLockReadOnly
Here's my solution that does not use ##index or MAX.
Const connectionString = "Provider=SQLOLEDB; Data Source=SomeSource; Initial Catalog=SomeDB; User Id=YouIDHere; Password=YourPassword"
Const RecordsSQL = "SELECT * FROM ThatOneTable"
Private Sub InsertRecordAndGetID()
Set connection = New ADODB.connection
connection.connectionString = connectionString
connection.Open
Set recordset = New ADODB.recordset
recordset.Open SQL, connection, adOpenKeyset, adLockOptimistic
With recordset
.AddNew
!Field1 = Value1
!Field2 = Value2
End With
recordset.MoveLast
ID = recordset.Fields("id")
End Sub
Enjoy!
Try following macro code.First add a command button to the sheet from the control box and paste following codes in the code window
Private Sub CommandButton1_Click()
MsgBox GetLastPrimaryKey
End Sub
Private Function GetLastPrimaryKey() As String
Dim con As String
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim sql As String
con = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\myaccess.mdb;Persist Security Info=False"
sql = "SELECT MAX(id) FROM tblMyTable"
Set cn = New ADODB.Connection
Set rs = New ADODB.Recordset
cn.Open con
rs.Open sql, cn, 3, 3, 1
If rs.RecordCount <> 0 Then
GetLastPrimaryKey = rs.Fields(0).Value
End If
rs.Close
cn.Close
Set rs = Nothing
Set cn = Nothing
End Function
8 years late to the party... The problem you are having is that you are using dbConnectionString to create a new connection. ##identity is specific to the connection you are using.
First, don't close the original connection
'.Close
replace
myRecordset.Open SQL2, dbConnectionString, adOpenStatic, adLockReadOnly
with the connection you previously used for the insert
myRecordset.Open SQL2, databaseConnection, adOpenStatic, adLockReadOnly
and you'd have been all set. In fact, you don't even need to specify the table:
SQL2 = "SELECT ##identity AS NewID"