Using ##IDENTITY in a multi user environment - ms-access

The SELECT ##IDENTITY statement in Access VBA, would allow one to view/obtain the AutoNumber that has been generated in a table, for which a record has just been inserted, on the database. This is somewhat easy to track and maintain, if there is just one user and only one INSERT takes place at any moment of time.
My application is multi user, so far only one person used to do data entry. Now we have two people and when one user (Mr Brown) inserts a record (Auto ID : 1234) and within a second (we are dealing with milliseconds) another (Mr Green) INSERTS a record (Auto ID : 1235), the ##IDENTITY seems to return the latest ID 1235 for Mr. Brown and not 1234 which he inserted. As the same Mr. Green gets the same 1235, which is correct.
My Question is, is there a way to make sure that ##IDENTITY return the AutoID of that particular INSERT for that user that performed the INSERT. This is my code, if that helps.
tranDB As DAO.Database
Set tranDB = CurrentDb
tranSQL = "INSERT INTO Transactions (Password....." 'My Insert goes here
tranDB.Execute tranSQL
Dim idRS As DAO.Recordset
Set idRS = tranDB.OpenRecordset("SELECT ##IDENTITY AS LastID;")
TranID = idRS!LastID
idRS.Close
Set idRS = Nothing
EDIT: Not a Duplicate of MS Access TableAdapter Get Identity after insert. As the other thread involves with SQL Server, mine is JET Engine and Not .NET, I am using this code in VBA.

Paul have your tried to encapsulate it within a transaction? try this
Dim tranDB As DAO.Database
Set tranDB = CurrentDb
tranSQL = "INSERT INTO Transactions (Password....." 'My Insert goes here
dbEngine.BeginTrans
on Error goto ERROR_INSERT:
tranDB.Execute tranSQL, dbFailOnError
Dim idRS As DAO.Recordset
Set idRS = tranDB.OpenRecordset("SELECT ##IDENTITY AS LastID;")
'ideally put another error trap here
TranID = nz(idRS("LastID"),0)
DBEngine.CommitTrans
On Error resume Next
idRS.Close
Set idRS = Nothing
SET tranDB = nothing
Exit sub/Function
ERROR_INSERT:
dbengine.rollback
'and other stuffs or resume to exit label

Paul,
I have used this technique for forever and it works for me (I would have lost my job many times over if it didn't work!!).
1) You have tagged this as an Access 10 question. So, are you using this reference in your project?:
Microsoft Office 14.0 Access database engine Object Library
C:\Program Files (x86)\Common Files\Microsoft Shared\OFFICE14\ACEDAO.DLL
Older DAO references might be problematic. This feature is not supported before DAO 4.0.
2) Regarding krish's suggestion: transactions, while a good idea, are not required for this feature to work. The last Identity value is locally stored by the current connection. You just have have to call it before the user does another insert.
3) But, it is a good idea to use the same database reference to do both the Insert and retrieve the Identity. In your example, your use of tranDB should be correct -- but if you are re-assigning it to CurrentDB before calling Identity, this could be the problem.
4) Are you using Timestamps to be sure that Brown is inserting before Green?
5) As a last resort, you can try an all-DAO alternative:
'------------------------------------------------------------------------------------------
' The query insert method is used here because DAO respects record-level locking
' on programmatically opened recordsets
'------------------------------------------------------------------------------------------
Set idRs = tranDB.OpenRecordset("SELECT ID, Password, InsertTime FROM Transactions (Password.....", dbOpenDynaset, dbAppendOnly + dbSeeChanges, dbOptimistic)
With idRs
.AddNew
!Password = sPassword
!InsertTime = Now
.Update
' Move to New Record
.Bookmark = .LastModified
lIdentity = !ID
.Close
End With
See if this yields different results.

Related

MS Access delete then append tabledef breaks querydef

Following this question thread, was able to successfully code the change suggested by "changing the sourcetable of a linked table in access 2007 with C#". However, it appears this customer has queries coded with relationships defined at the query level and the delete/append process breaks the relationships. Anyone have any idea how to preserve the relationships? And why is it that the tabledef.Sourcetable can't be updated?
Code snip:
Option Compare Database
Sub test()
Dim tdf As TableDef
Dim db As Database
Set db = CurrentDb
Open "out.txt" For Output As #1
For Each tdf In db.TableDefs
If tdf.Connect <> vbNullString Then
Print #1, tdf.Name; " -- "; tdf.SourceTableName; " -- "; tdf.Connect
Select Case tdf.SourceTableName
Case "CTITLU.txt"
'tdf.SourceTableName = "dbo.GRANTSADJS"
'tdf.Connect = "ODBC;DRIVER=SQL Server;SERVER=DCFTDBCL01-L01\EDS_DEV;DATABASE=GRANTSDB2;UID=grants_reader;PWD=xxxxx;TABLE=DBO.GRANTSTABL"
tdf.RefreshLink
End Select
End If
Next
End Sub
When I run this with just the tdf.connect syntax uncommented, it errors on the tdf.refreshlink call with "Run-time error '3011': The Microsoft Access database engine could not find the object 'objectname'..." I'm trying to update a text linked table to the equivalent SQL Server based linked table. The objectname does have spaces and hyphens in it, but it is correctly showing the name in the error message. For whatever reason, the previous developer shipped dumps of the tables to a file system instead of linking the tables directly. This is a small DB with very light transactional activity so there is very little chance this will cause any issues. When the tdf.SourceTableName is uncommented, throws the "Run-time error '3268': Cannot set this property once the object is part of a collection."
I followed other threads indicating this issue (noted above), and was successful using the tdf.delete / tdf.append calls to duplicate the tabledef with new source tablename and connection info. However, the dependent query's relationship definitions have disappeared and the query is unusable without redefining all of the links.
C Perkins, thanks, that was it. There was a slight difference in the table definition such that when using delete/append, it 'broke' the relationships (yes joins) in the related query. Using a DB view to fix that, it worked just fine. However it still 'moves' the query from its former place as being related to the original table object. Our customers will at least have their current data and not a weekly snapshot. Thanks again.

Copy Access database query into Excel spreadsheet

I have an Access database and an Excel workbook.
What I need to do is query the database and paste the query into a worksheet.
The issue is Runtime. I have stepped throught the program and everything works, but it works extremely slow, we're talking up to 30 second run times per query, although most of this run time is coming with the CopyFromRecordset call.
The database has over 800k rows in the table I'm querying.
Currently at my company there are people every morning who manually query the tables and copy and paste them into excel. I'm trying to remove this process.
Here is what I have:
Sub new1()
Dim objAdoCon As Object
Dim objRcdSet As Object
' gets query information '
Dim DataArr()
Sheets("Data2").Activate
DataArr = Range("A1:B40")
For i = 1 To UBound(DataArr)
job = DataArr(i, 1)
dest = DataArr(i, 2)
If InStr(dest, "HT") > 0 Then
OpCode = "3863"
ElseIf InStr(dest, "HIP") > 0 Then
OpCode = "35DM"
End If
strQry = "SELECT * from [BATCHNO] WHERE ([BATCHNO].[Job]='" & job & "') AND ([BATCHNO].[OperationCode] = " & "'" & OpCode & "')"
Set objAdoCon = CreateObject("ADODB.Connection")
Set objRcdSet = CreateObject("ADODB.Recordset")
objAdoCon.Open "Provider = Microsoft.Jet.oledb.4.0;Data Source = C:\Users\v-adamsje\Desktop\HTmaster.mdb"
'long run time
objRcdSet.Open strQry, objAdoCon
'very long run time
ThisWorkbook.Worksheets(dest).Range("A2").CopyFromRecordset objRcdSet
Set objAdoCon = Nothing
Set objRcdSet = Nothing
Next i
End Sub
Any help is appreciated. I am new to VBA and Access so this could be an easy fix. Thanks
Excel is very good at getting data for itself, without using VBA.
On the DATA ribbon
create a connection to a table or view of data somewhere (eg mdb or SServer)
then use the "existing connections" button to add data from your connected table to a worksheet table (ListObject).
You can even set the workbook (ie connection) to refresh the data every 12 hours.
Repeat for all the tables /view you need to grab data for. You can even specify SQL as part of the connection.
Let excel look after itself.
I just grabbed a 250,000 row table from a "nearby" disk in 2 secs.
It will look after itself and has no code to maintain!
I don't see how the CopyFromRecordset can be improved. You could copy the recods programmatically (in VB) record-by-record but that will probably be slower than the CopyFromRecordset.
You can move the CreateObject statements out of the loop, With the connection and RecordSet already created, this could be faster:
Set objAdoCon = CreateObject("ADODB.Connection")
Set objRcdSet = CreateObject("ADODB.Recordset")
For i = 1 To UBound(DataArr)
...
next i
Set objRcdSet = Nothing
Set objAdoCon = Nothing
You could also try ADO instead of DAO. ADO seems to perform faster on large record sets.
But also the server could be an issue, for example, are there indexes on Job and OperationCode? If not, then the slowness could be the server selecting the records rather than Excel placing them in the worksheet.
Whelp, never found out why the CopyFromRecordset runtime was obsurd, but solved my problem by pulling the whole table into excel then into an array, looping through that and putting them in respective sheets. From 30min runtime to <1min

Get ID of Last Inserted Record - Access DAO, ODBC, SQL Server 2008 Identity Field

It's a very common question but I'm having trouble getting the ID of the last inserted record. I'm using DAO with ODBC linked tables to duplicate a record and it's child records. My tables are in SQL Server 2008 and have Identity fields for ID fields.
Here's what I've tried so far. My first bit of code here results in error 3167, Record is Deleted. If I do a debug.Print the recordset actually contains 3 records.
Dim r as DAO.Recordset, db as DAO.Database
Set db = CurrentDb
Set r = db.OpenRecordset("SELECT TOP 2 * FROM item ORDER BY DateTimeModified DESC", dbOpenDynaset, dbSeeChanges)
r.AddNew
'Set field values here
r.Update 'Completes without error
r.Bookmark = r.LastModified
Debug.Print r("ItemID") 'Error 3167, Record is deleted
Here's the next thing I tried:
Debug.Print db.OpenRecordset("SELECT ##identity FROM item")(0)
This last one completes without any problem but the value returned is incorrect. Where the actual new ItemID is 321 this returns the value 614. The value it is returning does appear to be incremental (it changes as I keep testing this) but it does not appear to relate at all to my table. There is no field with the value 614. I've double checked to make sure I'm looking up the correct table.
I know I could use something like DLookup or DMax but I don't think that would be considered bullet proof in a multi-user environment.
I suppose I could use a Stored Procedure with ADO to get around this problem. I'm wondering if that is my only option?
Edit1:
I'm now using the following code and it is doing what I need/want it to. I suspect this is basically the same as using DMax.
Dim r as DAO.Recordset, db as DAO.Database
Set db = CurrentDb
Set r = db.OpenRecordset("SELECT TOP 1 * FROM item ORDER BY ItemID DESC", dbOpenDynaset, dbSeeChanges)
r.AddNew
'Set field values here
r.Update
r.Requery
r.MoveFirst
Debug.Print r("ItemID")
As far as I'm aware ##IDENTITY doesn't work for cursor-based inserts. DAO and ADO both use cursors behind the scenes.
After you .Update the record you should be able to get the identity value back simply by reading the value.
The following works fine for me via an ADO Recordset opened with Keyset semantics:
r.Update
Debug.Print r("ItemID")
The following works fine for me via a DAO Recordset opened with Dynaset semantics:
r.Update
r.Bookmark = r.LastModified
Debug.Print r("ItemID")
You should avoid .Requery and .MoveFirst, you're introducing concurrency problems. Consider:
Dim r as DAO.Recordset, db as DAO.Database
Set db = CurrentDb
Set r = db.OpenRecordset("SELECT TOP 1 * FROM item ORDER BY ItemID DESC", dbOpenDynaset, dbSeeChanges)
r.AddNew
''// Set field values here
r.Update
''// At this point another user adds a new record
r.Requery
r.MoveFirst ''// ORDER BY ItemID DESC means that you're going to see the new user's row
Debug.Print r("ItemID")
The following works as expected (using Office 2013 and SQL Server 2014)
Set rsProjects = db.OpenRecordset("JobProjects", dbOpenDynaset, dbSeeChanges Or dbAppendOnly)
rsProjects.AddNew
rsProjects.Name = 'xyz'
rsProjects.Update
rsProjects.Bookmark = rsProjects.LastModified
lNewProjectID = rsProjects!ProjectID.Value
Key point: instead of using 'SELECT TOP 2' or 'SELECT TOP 1', etc. I used 'dbSeeChanges Or dbAppendOnly'. I verified in sql profiler that opening the recordset does not generate any queries to SQL Server.
When you issue the update, access generates an insert statement followed immediately by a SELECT ##IDENTITY to get the id of the new record.
Edited: add missing .AddNew, Remove duplicate .Update.
it dosnt work with sql server backend(in multi user app
). for access table it work
for sql use stored procedure.use this way
CREATE PROCEDURE dbo.AddAsset
#Name VARCHAR(500),
#URL VARCHAR(2000),
#new_identity INT = NULL OUTPUT
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.Assets(Name, URL) SELECT #Name, #URL;
SET #new_identity = SCOPE_IDENTITY();
END
GO
then use this sp in front end
Simply get the value of the key field before you execute the Update statement. As pointed out in the comment below, this process will not work if you are using a different backend than Microsoft Access. But I leave this response here in case that is your use case and you are just searching for an answer to the general question of how you get the ID of the last inserted record.
For your example, you can do this with Microsoft Access:
Dim r as DAO.Recordset, db as DAO.Database
Dim lKey As Long
Set db = CurrentDb
Set r = db.OpenRecordset("SELECT TOP 2 * FROM item ORDER BY DateTimeModified DESC", dbOpenDynaset, dbSeeChanges)
r.AddNew
'Set field values here
'Retrieve the key value before executing the Update
lKey = r!ItemID
r.Update
Debug.Print lKey

DAO.Recordset.Update results in reckord lock

I am trying to run the following code to loop around a recordset and do updates where neccessary.
I have a Microsoft Access database connected to a MySql backend. Whenever I run this code I get the following error:
3197 error: The Microsoft Office Access database engine stopped the process because you and another user are attempting to change the same data at the same time.
The code is below:
Private Sub test()
Dim rs As DAO.Recordset, rsCnt As Long, i As Long
Set rs = CurrentDb.OpenRecordset("qryMyQuery", DB_OPEN_DYNASET)
rs.MoveLast
rsCnt = rs.RecordCount
rs.MoveFirst
For i = 1 To rsCnt
rs.Edit
rs!MyFieldInTable = "test"
rs.Update
Next i
End Sub
I thought the Access database might be corrupt so I pulled an earlier backup but it's doing the same thing which makes me think it's a MySql issue.
We use an identical piece of code on another version of this database linked to a different MySql table and it works fine.
Also, when I open the query the record-set is based on I can edit the data in the query without any issues.
Just to add, on the first loop, rs!MyFieldInTable is updated, then I get the error.
It does not appear that you are moving to another record in the recordset. Simply incrementing i doesn't move to the next record. A more traditional approach would be to iterate over the recordset without the need for your other variables (i and rsCnt).
Dim rs as DAO.Recordset
Set rs = CurrentDb.OpenRecordset("qryMyQuery", DB_OPEN_DYNASET)
rs.moveFirst
Do Until rs.EOF
rs.Edit
rs!FieldNameHere = "test"
rs.Update
rs.MoveNext
Loop
EDIT
After a bit of searching I came across this thread which seems to be similar to your issue. At the bottom of the thread a suggestion is made to modify the ODBC settings for your MySQL DSN by selecting the "Advanced" tab and selecting the option to "Return Matching Rows". The post also says to drop the linked table and then re-link it to your Access database.
I haven't used Access with MySQL in the past, so I have no idea whether this will work or not, so proceed with caution!
You may also try changing your recordset to use the dbOptimistic flag for the recordset locking option to see if that helps at all:
set rs = CurrentDB.OpenRecordSet("qryMyQuery", DB_OPEN_DYNASET, dbOptimistic)
Two things you can try. First, try adding the dbSeeChanges option when opening the recordset:
Dim rs as DAO.Recordset, db As DAO.Database
Set db = Currentdb
Set rs = db.OpenRecordset("qryMyQuery", dbOpenDynaset, dbSeeChanges)
Do Until rs.EOF
rs.Edit
rs!FieldNameHere = "test"
rs.Update
rs.MoveNext
Loop
The other option, as #HansUp suggested, is to use a SQL update statement instead of a dynamic recordset. The key there is to open the recordset as a snapshot, so that changes you make to the records do not affect the recordset itself.
Dim rs as DAO.Recordset, db As DAO.Database
Set db = Currentdb
Set rs = db.OpenRecordset("qryBatchPayments", dbOpenSnapshot)
Do Until rs.EOF
db.Execute "UPDATE Payments " & _
"SET DCReference='test' " & _
"WHERE PaymentID=" & !PaymentID, dbFailOnError
rs.MoveNext
Loop
I was having the same problem and my solution turned out to be the default value for BIT(1) fields. Access does not like these to be null. Make sure you use either 0 or 1 in mysql for these fields.
I don't have MySQL here to try this against, but it looks to me as if your code is not advancing the recordset after the rs.Update method is executed, so that you are trying to udate the same field in the fierst record.
Add this line after the rs.Update:
rs.MoveNext
Hope that helps.
Try calling OpenRecordset from an object variable set to CurrentDb(), rather than directly from CurrentDb().
Dim rs as DAO.Recordset
Dim db As DAO.Database
Set db = Currentdb
Set rs = db.OpenRecordset("qryMyQuery", DB_OPEN_DYNASET)
rs.moveFirst
Do Until rs.EOF
rs.Edit
rs!FieldNameHere = "test"
rs.Update
rs.MoveNext
Loop
The reason for that suggestion is I've found operations on CurrentDb directly can throw an error about "block not set". But I don't get the error when using an object variable instead. And ISTR OpenRecordset was one such operation where this was an issue.
Also, my impression was your approach is a cumbersome way to accomplish the equivalent of:
UPDATE qryMyQuery SET FieldNameHere = "test";
However, I suspect the example is a proxy for a real world situation where the recordset approach is useful. Still that makes me wonder whether you would see the same or a different error when executing the UPDATE statement.
If you continue to have trouble with this, it may help to show us the SQL View for qryMyQuery.
I have discovered that if one tries to save data which are the same as the one already in the MySql record Access will display this kind of error. I've tried some suggestions from this thread but did not help.
The simple solution for this is to save a slightly diffrent data by using a manual time-stamp. Here is an example of heaving a sort order field and setting it to 10, 20, 30...
i = 10
timeStamp = Now()
Do Until Employee.EOF
Employee.Edit
Employee!SortOrderDefault = i
Employee!LastUpdated = timeStamp
Employee.Update
i = i + 10
Employee.MoveNext
Loop
I've tried automatic time-stamp in the MySql table but did not help when the new entry data is the same as the old one.
My little helpful hint is, bits are very, very, very bad data types to use when linking SQL tables to Microsoft Access because only SQL Server understands what a bit is, Microsoft Access has a hard time interpreting what a bit is. Change any bit datatypes to int (integers) and relink your tables that should clear things up. Also, make sure your Booleans always contain a 1 or a 0 (not a yes/no or a true/flase) in your VBA code or your updates will fail to the linked SQL tables because Microsoft Access will try to update them with a True/False or a Yes/No and SQL will not like that.
I also had same problem; i solved them adding those to code using dao.recordset:
**rst.lockedits = true**
rst.edit
rst.fields(...).value = 1 / rst!... = 1
rst.update
**rst.lockedits = false**
this seems fix conflict between just opened data (such as in a form) and updating them with code.
Sorry for my bad english... i read a lot but i never had learn it! I'm just italian.

How to delete a record and assign an existing record in its place

I'm wondering if anyone could help me out here.
Is it possible when deleting a record to assign all instances of the deleted record to a new record?
For example:
For each project in a time planning database, i can assign a worker as team leader for each project. If there was a duplicate entry for a team leader i would need to delete one of the instances of these details. If you did this you would then be left with empty values for the records assigned to this 'team leader'. This would be a problem.
I appreciate that you would want to nip this in the bud, so to speak, and not allow duplicate entries. However, if you would need to delete a record is it possible to assign a separate record in its place?
Hope that makes sense, if you could help me out it would be greatly appreciated.
Cheers
Noel
Cascade update is a possibility, but probably overkill for something that will only happen occasionally and should not happen at all. I suggest that you run a query to update the relevant records before you delete the team leader:
Update Projects Set TeamLeaderID=123
Where TeamLeaderID=456
Yes this is possible. It is a matter of knowing, where your worker is referenced, finding all such locations and replacing the reference through a new reference to another worker. Once all references to the old worker have been removed, you can delete the worker.
Option Compare Database
Private Sub test()
Dim db As Database
Dim rs As Recordset
Dim sql As String
Set db = CurrentDb
sql = "select worker_id from worker_ref_table"
Set rs = db.OpenRecordset(sql, dbOpenDynaset, dbSeeChanges)
While Not rs.EOF
rs.Edit
rs!worker_id = "new value"
rs.Update
rs.MoveNext
Wend
rs.Close
sql = "select worker_id from worker_table"
Set rs = db.OpenRecordset(sql, dbOpenDynaset, dbSeeChanges)
While Not rs.EOF
rs.delete
Wend
rs.Close
End Sub
See also VB Database Programming for further help on connecting and operating on a database. Please note, that VB and VBA are not the same. You should be able to use the above code inside Access VBA to accomplish this task.