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.
Related
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.
I have a macro that opens an ADODB connection to a MySQL library. There are two queries that are generated in the macro. The first, which works, is a select statement to check for duplicates. The second, which is not working, is to insert records onto the same table the select statement referenced. I am receiving no errors from VBA, and when I copy/paste directly into MySQL the query works fine.
At the top of my macro I set up the connection as follows:
TimesheetConn = "DRIVER={MySQL ODBC 5.3 ANSI Driver}; SERVER=*server number*;PORT=*port number*;database=my_db;UID=User;PWD=password;Option=2"
'Connection Info
Dim cnn As ADODB.Connection
On Error GoTo AdoError
Set cnn = New ADODB.Connection
With cnn
.ConnectionString = TimesheetConn
.Open
.CommandTimeout = 0
End With
Set FIT_Data = New ADODB.Recordset
Set Task_Data = New ADODB.Recordset
I then develop the Select query (fitidquery) and run it as:
FIT_Data.Open fitidquery, cnn, CursorType = 2
I do not close the connection, but then move on to generate my next query, an insert query (addtasks3), by looping through rows and assigning variables.
Then I try to call the connection again with:
Task_Data.Open addtasks3, cnn, adOpenForwardOnly, adLockReadOnly
cnn.Close
And it doesn't work, nor does it give me any errors either for VBA or SQL. As said before, copying the result of debug.print(addtasks3) into MySQL the query runs correctly and inserts the records.
I've tried opening a 2nd connection with the same parameters. That didn't work as well. I moved the On Error language down to in front of the second query call and it moves on to the AdoError message which seems to indicate that the connection is lost there.
I appreciate any help!
If you issue an Insert-statement, there is no need to involve a recordset. You can simply execute
cnn.Execute addtasks3
Or, if you want to get the number of rows you inserted, use a ADODB.Command:
Dim cmd As New ADODB.Command
cmd.ActiveConnection = cnn
cmd.CommandText = addtasks3
Dim rowsInserted As Long
Call cmd.Execute(rowsInserted)
Debug.Print rowsInserted & " rows inserted."
So I have to say thank you to all of you that mentioned On Errors. Your descriptions after rereading finally made it click with me what I did. I had thought that On Error Resume Next only worked for the line it was in front of in the code (I am still very much a VBA newbie learning as I go) and didn't realize that it would follow through the rest of the code. I had one before my first query because if its a new row it wouldn't return anything to check a duplicate against and cause an error. Hence why I wasn't getting error messages.
I added "On Error GoTo 0" after that section and now I'm getting error messages again. Turns out my user is denied use of the Insert Command for that table (even though I modeled it after a table where it does have use), so it looks like I just need to figure that out. Thank you everyone!
I have a little problem with downloading data from Oracle DB and moving it to the MySQL table.
On Oracle i have only a read privilege. The only task I could execute is Select.
On MySQL i have select/insert/update privilege. Also, there's no problem to change anything in mysql structure - i also have access to mysql root.
Each week I have to download huge amount of data from Oracle and move it to MySQL (for further processing). The best (and the only) solution in my case is to use MS Access.
My vba code snippet looks like (code is usually put into a loop, where I perform some inserts, each for different business site. Executing it in one query sometimes meet no end).
Public Sub DownloadData()
On Error GoTo ErrorTrans
DoCmd.SetWarnings False
Dim strWhere1 As String, strWhere2 As String, strQueryName As String
Dim qdDaneObrRabE As QueryDef, qdDaneObrRabTmp As QueryDef
Dim cnADO As ADODB.Connection
DoCmd.OpenQuery ("qryDaneObrRabCzysc") 'Calling truncate data procedure on mysql table
Set cnADO = CurrentProject.Connection
cnADO.CommandTimeout = 300 '5 minut
Set qdDaneObrRabE = CurrentDb.QueryDefs("qryDaneObrRabE")
strQueryName = "tmpObroty_" & GenerateHash(8)
Set qdDaneObrRabTmp = CurrentDb.CreateQueryDef(strQueryName, "select * from table2;") 'this select is just dummy statement, replaced some lines below
qdDaneObrRabTmp.Connect = qdDaneObrRabE.Connect
qdDaneObrRabTmp.SQL = Replace(qdDaneObrRabE.SQL, "{WHERE1}", strWhere1)
qdDaneObrRabTmp.SQL = Replace(qdDaneObrRabTmp.SQL, "{WHERE2}", strWhere2)
cnADO.Execute "INSERT INTO mysqltable SELECT * FROM " & strQueryName & ";"
Call QueryDefsCleanUp("tmpObroty_")
Call MsgBox("Success")
Set cnADO = Nothing
Exit Sub
ErrorTrans:
Call ActivityLog(Environ("USERNAME"), Now, "DownloadData", True, Err.Number, Err.Description)
End Sub
Please let me know if executing these types of inserts via ADODB is good.
I also tested DAO but i don't see difference.
All of used tables/querydefs are linked tables or pass-through querydefs.
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.
I have the following code to access a recordset from SQL Server 2008.
Dim cnn As Object
Dim rs As Object
Dim strSQL As String
Set cnn = CreateObject("ADODB.Connection") 'ADO Connection
cnn.ConnectionString = "DRIVER=SQL Server;SERVER=" & dbServer & ";" & _
"Trusted_Connection=Yes;DATABASE=" & dbDatabase
cnn.Open
strSQL = "Select * from mytable"
Set rs = CreateObject("ADODB.Recordset")
rs.Open strSQL, cnn, adOpenKeyset, adLockOptimistic
On rs.Open it takes from one second to three seconds. Is there a faster way to access the recordset?
The variables dbServer and dbDatabase are defined in a module as a constant string.
Assuming mytable is a base table rather than a VIEW, the SQL is as trivial as is possible i.e. there is no scope for optimization.
If you are bringing the entire table contents over to do further RBAR ("row by agonizing row" = pejorative) processing in VBA code then consider rewriting your procedural logic as a "set-based" paradigm using more complex SQL e.g. adding a WHERE clause would be a good start.
If you do need to bring the entire table contents over and the table is relatively large then it will take a relatively long time. Again, there is no scope in your code for optimization.
You are currently performing a synchronous fetch, meaning execution of your VBA code will wait at the rs.Open line until the fetch is complete. The effect for your users will be that the application appears frozen, possibly crashed.
An alternative approach is to use an asynchronous fetch. Relocate your code class module, declare the recordset as WIthEvents (needs to be relocated to module level) and specify the adAsyncFetch option on the recordset before opening it (you may need to alter your approach slightly to accommodate this). This will cause execution to continue past the rs.Open as soon as the request has been sent (so you will need to relocate any code that closes the connection/recordset to a separate sub procedure). You can then use the FetchProgress and FetchComplete events to give feedback to your end users in the application. For more details, see this MSDN article.
I've never been able to get any meaningful 'percent completed' style progress out of the Access engine/provider. But you can at least show a marquee style progress bar to users to reassure them that data is being fetched. They will be able to still interact with the UI (e.g. click on controls) and be reassured that the application has not crashed.
You have 2 operations:
open connection
open recordset using that connection
Where is the delay?
You could add line for Debug.Print Now() immediately before the two open statements, and another after the recordset open.
If opening the connection is the bottleneck, see whether using the native client provider speeds things up. www.connectionstrings.com: SQL Server Native Client 10.0 OLE DB Provider
If opening the recordset is the bottleneck, you have several options. If you don't actually need a dynamic/editable recordset, use adOpenStatic instead of adOpenKeyset. You could try CommandTypeEnum adCmdTable to open the table instead of using a SELECT statement which returns everything. Or with the SELECT statement, include an explicit field list instead of "*" ... because "*" requires an extra trip to the server to figure out which fields it represents. (Although that's probably not a big bottleneck.) And you could also try a WHERE clause to select a single record based on a primary key value.