DAO.Recordset.Update results in reckord lock - mysql

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.

Related

Test to see if ODBC Linked Table is Available

I have an Access database that links to six tables in other SQL databases using ODBC connections. I then have a couple of queries running checks on these tables generating some reports. However, before I run the queries, I want to ensure that all six tables are reachable. All the DB's are offsite and I connect to them over a VPN connection, so it does happen that a table might be offline for a couple of minutes due to connectivity constraints.
I have done so many searches but I am not able to find something that even remotely answers my question.
Ideally I would like to have a list of the tables with a little green / red dot next to each showing whether it is online or not. But I guess one needs to be realistic.
Any assistance will be greatly appreciated.
You could simply “try” and open the table(s) in question. However, if your connection is gone, then you find some VERY nasty effects. First you get a huge delay (bad). And if you trigger an ODBC error, in most cases you have to exit the application.
To avoid the above?
It turns out there is a different pathway to get access to check if you have use of the server. It not only eliminates the dreaded and all evil ODBC error, but ALSO errors out MUCH faster to boot! You find the “error out” is VERY fast!
The way this works is you use a queryDef to check if you have a connection. This causes access to use a “different” path and test then opening a reocrdset.
So this approach avoids the long delay, and also ODBC errors.
You can use this routine for the test:
Function TestLogon(strCon As String) As Boolean
On Error GoTo TestError
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Set dbs = CurrentDb()
Set qdf = dbs.CreateQueryDef("")
qdf.Connect = strCon
qdf.ReturnsRecords = False
'Any VALID SQL statement that runs on server will work below.
qdf.SQL = "SELECT 1"
qdf.Execute
TestLogon = True
Exit Function
TestError:
TestLogon = False
Exit Function
End Function
Now, say in our code, we had a linked table. So, we can use/grab the connection of that linked table, and test like this:
Sub MyCoolUpdate()
Dim strCon As String
Dim rstHotels As DAO.Recordset
strCon = CurrentDb.TableDefs("dbo_tblHotels").Connect
If TestLogon(strCon) = True Then
' it is safe to open the linked table,
'eg:
Set rstHotels = CurrentDb.OpenRecordset("dbo_tblHotels", dbOpenDynaset, dbSeeChanges)
' we are conneced - do your stuff
' walk the dog - do updates
rstHotels.Close
End If
End Sub
So I much suggest you don't try and touch a linked table to the server until such time you test using the above trick.

Classic ASP VBScript need dropin replacement for MyConn.Execute( query )

I have a site running on ASP VBScript, and the original code never closes a DB connection. It opens connections as part of "startup" for any given page, then does whatever it does and stops -- but never explicitly closes connections. This is now causing problems where things are crashing at the web server level -- presumably from the lack of garbage collection.
So I want to make a function that acts as a drop-in replacement for all the MyConn.Execute( sqlQuery ) commands throughout the site. I've found some good candidates, but none of them seem to quite work. The most promising appears to be the code below, but when I try to actually use the recordset returned I get an error.
Function GetRS(strSQL)
'this function returns a disconnected RS
'Set some constants
Const adOpenStatic = 3
Const adUseClient = 3
Const adLockBatchOptimistic = 4
'Declare our variables
Dim oConn
Dim strSQL
Dim oRS
'Open a connection
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.ConnectionString = "Driver={MySQL ODBC 5.3 Unicode Driver};Server=localhost;User=foo;Password=bar;Database=baz"
oConn.Open
'Create the Recordset object
Set oRS = Server.CreateObject("ADODB.Recordset")
oRS.CursorLocation = adUseClient
'Populate the Recordset object with a SQL query
oRS.Open strSQL, oConn, adOpenStatic, adLockBatchOptimistic
'Disconnect the Recordset
Set oRS.ActiveConnection = Nothing
'Return the Recordset
Set GetRS = oRS
'Clean up...
oConn.Close
oRS.Close
Set oConn = Nothing
Set oRS = Nothing
End Function
'call the function
strSQL = "SELECT * FROM Authors"
set RS = GetRS(strSQL)
(source: https://web.archive.org/web/20211020134116/https://www.4guysfromrolla.com/webtech/080101-1.shtml)
Here's my test code:
Set rs = GetRS( "SELECT `first_name` FROM `users` WHERE `id`=123" )
x = rs( "first_name" )
response.write x
I get the error:
ADODB.Recordset error '800a0cc1'
Item cannot be found in the collection corresponding to the requested name or ordinal.
/test.asp, line 25
Using an ordinal -- rs(0) -- returns the same error.
Looks like an empty recordset to me, but it's a legit Query that does return a record.
Does anyone know why this isn't working, or can point me to other code that will do the job? (Especially with practical usage examples, which the 4guys article lacks.)
My understanding is that a a Recordset is tied to the datasource. When you execute a query, by default, the client (your program) doesn't get the entire contents of the query, but will wait until you actually request the data. That way, you can choose a specific page size, page offset etc. to efficiently select rows from the database without transferring potentially millions of rows over the wire.
As a side-effect of this, if you close the database connection, you will no longer be able to use the Recordset. You must leave the connection open until you are done with it. In the same way, closing the Recordset itself will stop you from being able to interact with it further.
You have two options: copy the data out of the Recordset into your own variables/arrays before closing, or use a different technique to manage your connection. I'll talk about the latter option here.
There is technique which will allow you to open the DB connection once, and ensure it is closed properly by VBScript when it terminates.
Class DbConnectionManager
Private Sub Class_Initialize()
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.ConnectionString = "Driver={MySQL ODBC 5.3 Unicode Driver};Server=localhost;User=foo;Password=bar;Database=baz"
oConn.Open
End Sub
Private Sub Class_Terminate()
oConn.Close
End Sub
End Class
Dim connMgr : Set connMgr = New DbConnectionManager
This code snippet is untested, but the general principle is that you start your program by defining a class and creating an instance of it. When a class instance is created, Class_Initialize is called, and when your program ends (or the instance gets removed and garbage-collected), then Class_Terminate will be called. That means oConn.Close should always be called before your program ends, even in the event of an error.
This is a very basic example of how classes work, but you could actually extend the class further and insert your Execute functions into the class itself to encapsulate the database connection details for easier maintenance. If you haven't used classes in VBScript yet but you have a basic understanding of how Object-Oriented programming works, I would highly recommend you try that.
Bonus extra: It looks like you're passing in raw SQL strings. To avoid SQL injection vulnerabilities, don't build your SQL queries dynamically. Instead, use ADO and parameters so any user-created content can be safely passed into the query without security risks. How do I run a parameterized SQL query in classic ASP? And is it secure? W3Schools also has a section on how to use ADO.

VBA Variables inside DoCmd.RunSQL

Just a question. Situation is as follows:
I have 15 make table queries that pulls data for a different submission clarification code that was used on a claim; i.e. Pull all claims where submission clarification code 5. As of right now I have a macro that will run all 15 queries, but each time I am required to type in the region I am filtering for due to the [What Region?] prompt I had put in the criteria field.
My question is:
Is it possible to use VBA to run all 15 queries using the DoCmd.RunSQL where I only have to type in the region number once and it will apply it to all queries?
My initial thoughts were I would have VBA prompt me for what region I'm filtering for, store that in a variable, and then use that variable in the SQL statement. But I'm not even sure if you can do that? Thanks in advance for any advice that may be given!
Update: So after reading a few threads, I created a mock database to try out some of the concepts and I think I might be on the right track?
Private Sub btnTest_Click()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim SQLString As String
SQLString = "SELECT tblEmployees.ID, tblEmployees.Last_Name, tblEmployees.First_Name, tblEmployees.SS_ID INTO Test" _
& "FROM tblEmployees" _
& "WHERE (((tblEmployees.ID)=[What number]));"
Set qdf = CurrentDb.QueryDefs("Query1")
qdf.Parameters(0).Value = InputBox("What is the ID Number")
qdf.Execute
qdf.Close
Set db = Nothing
Set qdf = Nothing
End Sub
So to apply this to the 15 queries I would just Dim other variables as DAO.QueryDef right? I'm note really sure i need the SQLString part either? Also, I noticed that when running this mock code it took quite a while for it to create the new table. Is this normal? Also also, the code will not run if the table it is creating already exists. Is there a way to just have the code replace the existing table with the new one? Kind of new to this VBA so thanks for your patience.
Short answer is yes, this is possible. Some keywords you want to familiarize yourself with are "parameters" which are the variables with the prompt and "Querydef" or query definition.
There are quite a few articles detailing how to pass parameter values to a query programmatically. Check out this page for a solid overview of how to accomplish this. Most notably, the last example uses an inputbox to prompt the user to provide the parameter value, which gets you close to what you need. (ie. cmd.Parameters(0).Value = InputBox("Enter a country name"))
Modified to your design, it might be best to create a string variable and ask for the parameter first, then use the variable in declaring the parameters individually, which would permit a single parameter submission that gets applied to all queries.
EDIT
I have adjusted your code to show you how to go about it. You will have to repeat the block for each query.
Private Sub btnTest_Click()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim strParamter As String
Set db = CurrentDb
strParamter = InputBox("What is the ID Number")
Set qdf = db.QueryDefs("Query1")
qdf.Parameters(0).Value = strParameter
qdf.Execute
qdf.Close
'Now move to next query
Set qdf = db.QueryDefs("Query2")
qdf.Parameters(0).Value = strParameter
qdf.Execute
qdf.Close
'...etc.
Set qdf = Nothing
Set db = Nothing
End Sub

Converting from Access to SQL Back End (Multi-Value Fields)

So I'm converting an access back-end to SQL. I've tried a few different tools (SSMA, Upsizing Wizard, and a simple import). I've found so far that the SSMA tool and importing seem to work the best, eliminating most of the work necessary for me. However, I'm running into one issue I can't figure out how to overcome.
Two fields allow multiple values (dropdown with check boxes). In converting these, it errors in a way that it not only doesn't carry all of the information over, but also grabs information from another field (and doesn't carry that information over).
I've tried forcing access to only accept the first value (and get rid of multi-values all together), but it won't let me.
Any ideas?
This should get you started. It will turn all those values which are selected in the multi select field into their own table. You will need to establish the relationships between the three tables to create a true many to many relationship after the fact.
Sub ExtractMultiValueFields()
Dim JoinTable As New DAO.TableDef
JoinTable.Name = "JoinTable"
With JoinTable
.Fields.Append .CreateField("MainTableId", dbInteger)
.Fields.Append .CreateField("JoinToValue", dbText)
End With
Dim joinRs As DAO.Recordset
CurrentDb.TableDefs.Append JoinTable
Set joinRs = CurrentDb.OpenRecordset("JoinTable")
Dim rs As DAO.Recordset
Dim childrs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("select * from table1")
Do While Not rs.EOF
Debug.Print rs("ID")
Set childrs = rs("col1").Value
Do While Not childrs.EOF
Debug.Print childrs("value") 'always "value"
joinRs.AddNew
joinRs("MainTableId") = rs("ID")
joinRs("JoinToValue") = childrs("value")
joinRs.Update
childrs.MoveNext
Loop
rs.MoveNext
Loop
End Sub

Creating and writing data to a table is MS Access

EDIT: Changed as I have a different issue with the same code
2nd Edit: Adding additional code that seems to be casuing the issue
I have created a vba program in access that aggregates data from a number of external sources and write the results into a new table. Ideally when I run the program I want to wipe out all of the data that is currently in the table and replace it with my new data. I am currently currently deleting all of the data in the table... then writing my new data
Here is the code for reference
Function getTestFixtures(FixtureName As String) As Recordset
Dim db As Database
Set db = OpenDatabase(GetDBPath & "TestFixtures.xlsx", False, False, "Excel 12.0;HDR=Yes;")
If db Is Nothing Then
MsgBox "Can't find the file!", vbExclamation, ThisWorkbook.Name
Exit Function
End If
Set getTestFixtures = db.OpenRecordset("Select * from [" & FixtureName & "$]")
End Function
The recordset created above is modified and the output data is placed in a dictionary and passed to this function.
Sub Write_OTDC_Data(POlist As Dictionary)
Dim Rst As Recordset
DoCmd.SetWarnings False
DoCmd.runsql "Delete * from [OTDC Results]"
DoCmd.SetWarnings True
Set Rst = CurrentDb.OpenRecordset("OTDC Results")
With Rst
For Each key In POlist.Keys
.AddNew
For i = 0 To 9
.Fields(i).value = POlist(key)(i)
Next
.Update
Next
.Close
End With
End Sub
My Problem is that I get the following error if I try to change anything after running both of the above procuedures.
Running either in isolation does not generate the error.
I'm unsure whether this question is still unresolved. In case it's not, I have some suggestions for you to try, but not a lot of confidence they will cure the problem.
Try DoCmd.TransferSpreadsheet to import the sheet's data into a scratch table instead of using OpenDatabase with the workbook.
In your MsgBox, I wonder whether ThisWorkbook.Name means anything to an Access application. Aside from that, I would check whether the workbook exists, then open it (or import the sheet from it) only if the file is found.
If Len(Dir(GetDBPath & "TestFixtures.xlsx")) = 0 Then
'not found
Else
'use it
End If
Actually I'm unclear why you don't get an error from OpenDatabase if the workbook file doesn't exist. And that makes me suspicious of DoCmd.SetWarnings False Never, ever turn SetWarnings off. Doing so suppresses important information. And it is completely unnecessary. Set a DAO.Database object variable to CurrentDB(), then use this instead:
dbObjectVariable.Execute "Delete from [OTDC Results]", dbFailOnError
Add an error handler to deal with any problems dbFailOnError exposes.
Finally, this bears repeating because it's so important. NEVER turn SetWarnings off.