Getting identity auto-number from SQL in VBA - sql

I've been messing with an Access unbound form with SQL back-end. I add a new record using VBA but want to retrieve the Identity value created. At first I simply tried "tbField0 = rs.fields(0).value" but after the rs.update is shows record is deleted. I tried adding rs.bookmark = rs.lastmodified but same result.
Found this article
Access: Get newly created auto number in DAO and a comment by Rick about BIGINT & Access which I knew about. I don't use BIGINT because of it but NUMERIC instead. So, since no suggestions would work I tried to change the field type to INT and test. Works as required.
Therefore my question is how can I retrieve an incremented identity field from SQL which is not a datatype if INT using VBA in Access. I saw some mention of an OUTPUT command but not sure how to use or if applicable.
I have created two tables, one called tbl1_Int and the other tbl2_Num. Both contain two fields IndexID and Field1. Both Field1 are nchar and as you might surmise IndexID is a Int in tbl1_Int and a Numeric in tbl2_Num.
Created a blank form with two unbound fields txtIndex and txtField1 as well as two buttons btnInt and btnNumeric with the following code.
Private Sub btnInt_Click()
Dim DB As Database
Dim RS As Recordset
Dim strSQL As String
strSQL = "INSERT INTO dbo_tbl1_Int (Field1) VALUES ('" & txtField1 & "')"
Set DB = CurrentDb
DB.Execute strSQL
txtIndex = DB.OpenRecordset("SELECT ##IDENTITY")(0)
DB.Close
End Sub
Private Sub btnNumeric_Click()
Dim DB As Database
Dim RS As Recordset
Dim strSQL As String
strSQL = "INSERT INTO dbo_tbl2_Num (Field1) VALUES ('" & txtField1 & "')"
Set DB = CurrentDb
DB.Execute strSQL
txtIndex = DB.OpenRecordset("SELECT ##IDENTITY")(0)
DB.Close
End Sub
The records get created in the tables however it will only capture (thus populate) the index from tbl1 which is when the identity is a field type of Int. Which leads back to my original question of how do I capture the index when it is not an Integer?
I've gotten it to work with the following.
Private Sub btnNumeric_Click()
Dim DB As Database
Dim RS As Recordset
Dim strSQL As String
strSQL = "select * from dbo_tbl2_Num where false"
Set DB = CurrentDb
Set RS = DB.OpenRecordset(strSQL, dbOpenDynaset, dbSeeChanges)
RS.AddNew
RS.Fields(1) = txtField1
RS.Update
RS.Move 0, RS.LastModified
txtIndex = Null
txtIndex = RS.Fields(0).Value
RS.Close
DB.Close
End Sub

Related

Need button on form to insert field names of a table into another table in Access

There are similar questions and answers on here, but I am such a VBA beginner that I don't have the ability to make changes to a code to make it work for me. Ideally this is what I need to create in an Access database.
On a form there is a text field that has a table name in it.
A pressed button inserts that table and its field names into a separate table (call it TableFields for ease, with columns "TableName" and "FieldName").
Is this possible? If this is answered elsewhere, can someone point me in the right direction? Thank you in advance!
It seems like this post is similar, but not specific/dumbed down enough for me:
How to List Field's Name in table in Access Using SQL
Use a combobox or listbox that lists tables.
Code in button Click or combobox AfterUpdate event like:
Dim rst As DAO.Recordset, db As DAO.Database, x As Integer
If Not IsNull(Me.cbxTable) Then
Set db = CurrentDb
Set rst = db.OpenRecordset("SELECT * FROM [" & Me.cbxTable & "] WHERE ID=0")
For x = 0 to rst.Fields.Count - 1
db.Execute "INSERT INTO TableFields(TableName, FieldName) " & _
"VALUES('" & Me.cbxTable & "','" & rst(x).Name & "')"
Next
End If
Alternative code.
Dim rst1 As DAO.Recordset, rst2 As DAO.Recordset, x As Integer
If Not IsNull(Me.cbxTable) Then
Set rst1 = CurrentDb.OpenRecordset("SELECT * FROM [" & Me.cbxTable & "] WHERE ID=0")
Set rst2 = CurrentDb.OpenRecordset("SELECT * FROM TableFields WHERE ID=0")
For x = 0 to rst1.Fields.Count - 1
rst2.AddNew
rst2!TableName = Me.cbxTable
rst2!FieldName = rst1(x).Name
rst2.Update
Next
End If

MS Access 2016 SQL in VBA

I'm sure I'm making a simple mistake. I have a SQL Server with tables that I have linked in MS Access. I am trying to run a SQL query in Form OnLoad to populate a text box based on a value in my form.
Below is my code. The field I want returned TE is varchar in MSSQL and short text in Access. I am getting a 'Type Mismatch' on the textbox. When I try outputting to a MsgBox I get:
'rst!TE not in collection'.
TN and TN_1 are smallint, in Access they are Number.
Dim rst As DAO.Database
Set rst = CurrentDb
rst.OpenRecordset "SELECT dbo_STCH.TE FROM dbo_STCH RIGHT JOIN dbo_SCVR ON dbo_STCH.TN = dbo_SCVR.TN_1 WHERE dbo_SCVR.TN_1=99;"
Me.Text22.Text = rst!TE
rst.Close
Set rst = Nothing
You are conflating DAO recordsets and databases and hence the message is correct as the item is not found in collection. Simply initialize rst as a recordset and assign it to OpenRecordset call.
Dim rst As DAO.Recordset
Set rst = CurrentDb.OpenRecordset("SELECT dbo_STCH.TE FROM dbo_STCH" _
& " RIGHT JOIN dbo_SCVR ON dbo_STCH.TN = dbo_SCVR.TN_1" _
& " WHERE dbo_SCVR.TN_1=99;")
Me.Text22.Text = rst!TE
rst.Close
Set rst = Nothing
You have to use rst.MoveFirst to move to the first record of the opened recordset before reading any values from it.
Also as the answer above you have to Set rst = CurrentDb.OpenRecordset

insert full ADO Recordset into existing ACCESS table WITHOUT LOOP

I have a filled ADO recordset in my VBA module. I also have a table in ACCESS that has exactly the same structure as the recordset.
Now I fill the table using a loop (which is fine) going through each dataset record.
What I am wondering: is there a way to insert an entire recordset into the access table? (and more importantly: would this be significantly faster)
Here's a basic example (run from excel in this case) which illustrates using a disconnected recordset to add records.
Sub Tester()
Dim con As ADODB.Connection, rs As ADODB.Recordset
Dim i As Long
Set con = getConn()
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient '<<<< important!
'get an empty recordset to add new records to
rs.Open "select * from Table1 where false", con, _
adOpenDynamic, adLockBatchOptimistic
'disconnect the recordset and close the connection
Set rs.ActiveConnection = Nothing
con.Close
Set con = Nothing
'add some new records to our test recordset
For i = 1 To 100
rs.AddNew
rs("UserName") = "Newuser_" & i
Next i
'reconnect to update
Set con = getConn()
Set rs.ActiveConnection = con
rs.UpdateBatch '<<< transfer to DB happens here: no loop!
rs.Close
'requery to demonstrate insert was successful
rs.Open "select * from Table1", con, _
adOpenDynamic, adLockBatchOptimistic
Do While Not rs.EOF
Debug.Print rs("ID").Value, rs("UserName").Value
rs.MoveNext
Loop
rs.Close
con.Close
End Sub
Function getConn() As ADODB.Connection
Dim rv As New ADODB.Connection
Dim strConn As String
strConn = "Provider=Microsoft.ACE.OLEDB.12.0;" _
& "Data Source = " & ThisWorkbook.Path & "\Test.accdb"
rv.Open strConn
Set getConn = rv
End Function
VBA Recordsets exist virtually in memory called during runtime until they are contained into an actual physical format (i.e., csv, txt, xlsx, xml, database temp table) saved to hard disk. This is akin to data frames in R or Python pandas, SAS datasets, PHP arrays, and other data structures.
Consider exporting your ADO in such a format using CopyFromRecordset methods into an Excel spreadsheet to be saved as csv, txt, xlsx, or xml. Alternatively, you can use the Save method to save recordset in a persistent format type like xml.
Then, append resultant file to MS Access table with its automated data migration features:
For spreadsheets: DoCmd.TransferSpreadsheet
For txt, csv, or other delimited files: DoCmd.TransferText
For xml files: Application.ImportXML
For local or ODBC/OLEDB linked database tables: INSERT INTO append SQL query
To accomplish this with a SQL statement you use the SELECT/INSERT... IN [Designate DB A; record posted to] or FROM... IN [Designate DB B; record original source]
You can only use the IN statement once in a single query. Therefore you create the other connection using the ADODB connection to determine the other source connection.
Function example()
Dim dB_External As String
Dim db_Local As String
Dim cnLocal As ADODB.Connection
Dim cnExternal As ADODB.Connection
Set cnLocal = CurrentProject.Connection
Set cnExternal = New ADODB.Connection
cnExternal .Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\\...accdb;Persist Security Info=False;"
dB_External = "C:\Users\\...accdb"
db_LOCAL = "C:\Users\\...accdb"
Example A:
strSQL = "INSERT INTO *Local table to receive records* (Column Designations)"
strSQL = strSQL & " SELECT ( *Corresponding records from external table* )"
strSQL = strSQL & " FROM *External table name* IN '" & dB_External & "'"
cnLocal.Execute (strSQL)
I use the above code, with the local ADODB connections if I select from a single external table.
Example B:
strSQL = "INSERT INTO *Local table to receive records* (Column Designations) IN '" & dblocal & "'"
strSQL = strSQL & " ( *Corresponding records from external table* )"
strSQL = strSQL & " FROM *External table name*
cnExternal.Execute (strSQL)
I use the above code using the external ADODB connection, if I select involves joining multiple tables in the external db.
No. There is no reverse equivalent - could be SetRows - to the method GetRows.

Errors with linked tables and Ms Access ( Run-time error '3622' : dbSeeChanges/Identity column )

I am trying to output the name of all linked tables, including their fields which are Date/Time, and that fields values.
The following code can output the first table, field name and their first value, not all values, although when it gets to the next linked table, I get this error
Run-time Error '3622'
You must use the dbSeeChanges option with OpenRecordSet when accessing a SQL Server table that has an IDENTITY column.
Here is my code
Private Sub btnGetFields_Click()
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim f As Field
Dim rst As DAO.Recordset
Dim numField As Integer
Set db = CurrentDb
For Each tdf In db.TableDefs
If Left$(tdf.Connect, 9) = "ODBC;DSN=" Then
Set rst = CurrentDb.OpenRecordset(tdf.Name)
numField = rst.Fields.Count
Debug.Print "Table: " & tdf.Name
For index = 0 To numField - 1
If rst.Fields(index).Type = dbDate Then
Debug.Print "Field: " & rst.Fields(index).Name; " Value : "; rst.Fields(index).Value
End If
Next
End If
Next
Set tdf = Nothing
Set db = Nothing
End Sub
I read something that if I'm using sql tables I should use ADO?
Any ideas?
You can continue to use your existing DAO code, just change
Set rst = CurrentDb.OpenRecordset(tdf.Name)
to
Set rst = CurrentDb.OpenRecordset(tdf.Name, dbOpenSnapshot)
That opens a static read-only Recordset, so dbSeeChanges is not required.

MS Access 2007, checking current user against a table

We have a simple access database, and would like a button on a form to only be available to select members of staff. (The button has an event tied to it). I'd like to store the usernames of the staff allowed to click the button in a separate table.
What I'd like to do, is perform a simple query to see if the username exists in the table, and set the enabled state of the button depending upon the outcome.
My background is C# and SQL Server, but VBA and access are new to me, and I think I'm struggling with the quirks of this environment.
I've got the username of the logged on user in a string fOSUserName via a call to GetUserNameA in advapi32.dll, but I'm struggling with the simplest of queries to determine if the username exists in the table.
Dim strSQL As String
Dim intResult As Integer
Dim db As DAO.Database
Dim rs As Recordset
Set db = CurrentDb
strSQL = "SELECT COUNT(*) FROM [USERS] WHERE [USERS].[NAME] = '" & _
fOSUsername & "'"
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
If Not rs.EOF Then
intResult = rs.Fields(0)
Else
intResult = 0
End If
rs.Close
db.Close
This fails on db.OpenRecordset giving me the error
Run-time error '3061':
Too few parameters. Expected 1.
Can anyone offer some pointers?
When you continue a line in VBA, you need a space between before the line continuation character ("_"). So instead of this:
strSQL = "SELECT COUNT(*) FROM [USERS] WHERE [USERS].[NAME] = '" &_
fOSUsername & "'"
Use this:
strSQL = "SELECT COUNT(*) FROM [USERS] WHERE [USERS].[NAME] = '" & _
fOSUsername & "'"
However, as #Igor Turman pointed out, the lack of a space before the underline character should trigger a compile error. So I'm unsure what's going on but suggest you fix it anyway to avoid confusion.
I'll suggest that rather than opening a recordset, and then reading a value from that recordset, this could be handled simply with the DCount() function.
Dim strCriteria As String
strCriteria = "[USERS].[NAME] = '" & fOSUsername & "'"
Debug.Print "strCriteria: '" & strCriteria & "'"
If DCount("*", "USERS", strCriteria) = 0 Then
Debug.Print "not found"
Else
Debug.Print "found"
End IF
If your missing parameter error is because USERS is a query rather than a table, you can ask DCount() to use a table instead. Or fix the query.
Sounds like your [USERS] object is not a table but Query (with parameter). Also, if you had a syntax error like '&_'(invalid) as opposed to '& _'(valid), your database would not compile. So, if table vs query is your case, please use the following:
...
Dim rs As Recordset
Dim qdf As QueryDef
Set qdf = CurrentDb.QueryDefs("Users")
qdf.Parameters("UserNameParameter") = fOSUsername
Set rs = qdf.OpenRecordset
...
I'm not totally familiar with the way you are using it, but I've always done it this way:
Dim sSQL As String
Dim rs As ADODB.Recordset
Dim cn As ADODB.Connection
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=[your access db file path and name];Persist Security Info=False"
sSQL = "SELECT COUNT(*) FROM [USERS] WHERE [USERS].[NAME] = '" &_
fOSUsername & "'"
Set rs = New ADODB.Recordset
rs.Open sSQL, cn
If Not rs.EOF Then
intResult = rs.Fields(0)
Else
intResult = 0
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing