Filter taking 2 seconds on small adbodb recordset - asp-classic

I have a small adodb recordset I am trying to filter. This one is 6 records for our test customer.
For some reason the filter is taking 2 seconds to complete, and I am doing this around 30 times on my asp page. Thus, making my page really slow to load. The other recordset filters on this page are running fast.
I have tried setting different CursorLocations and CursorTypes..
Can anyone help me determine why this filter is so slow?
rsAvgPrice.Filter = "CommodityID = 13 AND CropYear = '12'"

Probably the whole query is executed again and only then the filter is being applied.
I would have one single loop over all the items, store the required data in local variables then have my own filter. Best efficiency, much better control.
For example, if you want the data filtered by those two fields, I would use Dictionary like this:
Dim oCommodity_CropYear_Data, curKey
Dim curCommodityID, curCropYear, curData
Set oCommodity_CropYear_Data = Server.CreateObject("Scripting.Dictionary")
Do Until rsAvgPrice.EOF
curCommodityID = rsAvgPrice("CommodityID")
curCropYear = rsAvgPrice("CropYear")
curKey = curCommodityID & "_" & curCropYear
curData = "field1: " & rsAvgPrice("somefield") & ", field 2: " & rsAvgPrice("other_field") & "<br />"
oCommodity_CropYear_Data(curKey) = oCommodity_CropYear_Data(curKey) & curData
rsAvgPrice.MoveNext
Loop
rsAvgPrice.Close
Then to extract the data in a loop:
For x=1 To 30
For y=1 To 12
curKey = x & "_" y
If oCommodity_CropYear_Data.Exists(curKey) Then
Response.Write("Data for Commodity " & x & " and CropYear " & y & ":<br />" & oCommodity_CropYear_Data(curKey)
End If
Next
Next
This is the general idea, hope you can use it for your actual needs.

I have resolved this issue.
The issue was when I declare a record set the following way, the cursor type gets set as adOpenForwardOnly and the cursor location to adUseServer. These settings cannot be changed if you fill your recordset using command.Execute.
Set cmd = Server.CreateObject("ADODB.Command")
cmd.CommandType = adCmdText
cmd.CommandText = mySQL
cmd.CommandTimeout = 3000
cmd.ActiveConnection = cn
Set rs = Server.CreateObject("ADODB.Recordset")
Set rs = cmd.Execute
Set cmd = Nothing
The way I resolved this was manually declaring a permanent recordset with its fields. Then I filled a temporary recordset using the command.execute. I then manually populated my declared recordset with the temporary recordset record by record. This allowed me to set the cursorlocation to adUseClient.
Thus speeding up the filter by leaps and bounds.

Related

ms_access Run time error 3078 in VBA although query runs as saved query [duplicate]

I have a query called qryAlloc_Source that has two paramaters under one criteria:
>=[forms]![frmReportingMain]![txtAllocStart] And <=[forms]![frmReportingMain]![txtAllocEnd])
A have a separate query that ultimately references qryAlloc_Source (there are a couple queries in between), and that query runs fine when I double click it in the UI, but if I try to open it in VBA, I get an error. My code is:
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("qryAlloc_Debits")
I am getting run-time error 3061, Too few parameters. Expected 2. I've read that I may need to build out the SQL in VBA using the form parameters, but it would be pretty complex SQL given that there are a few queries in the chain.
Any suggestions as to a workaround? I considered using VBA to create a table from the query and then just referencing that table--I hate to make extra steps though.
The reason you get the error when you just try to open the recordset is that your form is not open and when you try to access [forms]![frmReportingMain] it's null then you try to get a property on that null reference and things blow up. The OpenRecordset function has no way of poping up a dialog box to prompt for user inputs like the UI does if it gets this error.
You can change your query to use parameters that are not bound to a form
yourTableAllocStart >= pAllocStart
and yourTableAllocEnd <= pAllocEnd
Then you can use this function to get the recordset of that query.
Function GetQryAllocDebits(pAllocStart As String, pAllocEnd As String) As DAO.Recordset
Dim db As DAO.Database
Dim qdef As DAO.QueryDef
Set db = CurrentDb
Set qdef = db.QueryDefs("qryAlloc_Debits")
qdef.Parameters.Refresh
qdef.Parameters("pAllocStart").Value = pAllocStart
qdef.Parameters("pAllocEnd").Value = pAllocEnd
Set GetQryAllocDebits = qdef.OpenRecordset
End Function
The disadvantage to this is that when you call this now on a form that is bound to it it doesn't dynamically 'fill in the blanks' for you.
In that case you can bind forms qryAlloc_debts and have no where clause on the saved query, then use the forms Filter to make your where clause. In that instance you can use your where clause exactly how you have it written.
Then if you want to still open a recordset you can do it like this
Function GetQryAllocDebits(pAllocStart As String, pAllocEnd As String) As DAO.Recordset
Dim qdef As DAO.QueryDef
Set qdef = New DAO.QueryDef
qdef.SQL = "Select * from qryAlloc_Debits where AllocStart >= pAllocStart and pAllocEnd <= pAllocEnd"
qdef.Parameters.Refresh
qdef.Parameters("pAllocStart").Value = pAllocStart
qdef.Parameters("pAllocEnd").Value = pAllocEnd
Set GetQryAllocDebits = qdef.OpenRecordset
End Function
While a [Forms]!... reference does default to a form reference when a QueryDef is run from the GUI, it is actually just another Parameter in the query in VBA. The upshot is you don't have to recode your query/create a new one at all. Also, as #Brad mentioned, whether a parameter is in the final query of a chain of queries or not, you are able to refer to the parameter as if it is in the collection of the final query. That being the case, you should be able to use code similar to this:
Sub GetQryAllocDebits(dteAllocStart As Date, dteAllocEnd as Date)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim rst As DAO.Recordset
Set db = CurrentDb()
Set qdf = db.QueryDefs("qryAlloc_Debit")
If CurrentProject.AllForms("frmReportingMain").IsLoaded Then
qdf.Parameters("[forms]![frmReportingMain]![txtAllocStart]") = [forms]![frmReportingMain]![txtAllocStart]
qdf.Parameters("[forms]![frmReportingMain]![txtAllocEnd]") = [forms]![frmReportingMain]![txtAllocEnd]
Else
qdf.Parameters("[forms]![frmReportingMain]![txtAllocStart]") = CStr(dteAllocStart)
qdf.Parameters("[forms]![frmReportingMain]![txtAllocEnd]") = CStr(dteAllocEnd)
End If
Set rst = qdf.OpenRecordset
Do Until rst.EOF
'...do stuff here.
Loop
Set rst = Nothing
Set qdf = Nothing
Set db = Nothing
End Function
If the referenced form is open, the code is smart enough to use the referenced controls on the form. If not, it will use the dates supplied to the subroutine as parameters. A gotcha here is that the parameters did not like when I set them as date types (#xx/xx/xx#), even if the field were dates. It only seemed to work properly if I set the params as strings. It didn't seem to be an issue when pulling the values straight out of the controls on the forms, though.
I know it's been a while since this was posted, but I'd like to throw in my tuppence worth as I'm always searching this problem:
A stored query can be resolved:
Set db = CurrentDb
Set qdf = db.QueryDefs(sQueryName)
For Each prm In qdf.Parameters
prm.Value = Eval(prm.Name)
Next prm
Set rst = qdf.OpenRecordset
For SQL:
Set db = CurrentDb
Set qdf = db.CreateQueryDef("", "SELECT * FROM MyTable " & _
"WHERE ID = " & Me.lstID & _
" AND dWeekCommencing = " & CDbl(Me.frm_SomeForm.Controls("txtWkCommencing")) & _
" AND DB_Status = 'Used'")
For Each prm In qdf.Parameters
prm.Value = Eval(prm.Name)
Next prm
Set rst = qdf.OpenRecordset
This assumes that all parameter values are accessible - i.e. forms are open and controls have values.
'I have two parameters in my recordset and I was getting the "Too few parameters. Expected 2" 'error when using an OpenRecordset in MS Access vba, and this is how I got around it and IT WORKS! see the below sub routine:
'Private Sub DisplayID_Click()
'1. I created variables for my two parameter fields xEventID and xExID as seen below:
Dim db As Database
Dim rst As Recordset
Dim xEventID As Integer
Dim xExId As Integer
'2. Sets the variables to the parameter fields as seen below:
Set db = CurrentDb
xEventID = Forms!frmExhibitorEntry!txtEventID
xExId = Forms!frmExhibitorEntry!subExhibitors!ExID
'3. Set the rst to OpenRecordSet and assign the Set the variables to the WHERE clause. Be sure to include all quotations, ampersand, and spaces exactly the way it is displayed. Otherwise the code will break!exactly as it is seen below:
Set rst = db.OpenRecordset("SELECT tblInfo_Exhibitor.EventID,tblInfo_Display.ExID, tblMstr_DisplayItems.Display " _
& "FROM tblInfo_Exhibitor INNER JOIN (tblMstr_DisplayItems INNER JOIN tblInfo_Display ON tblMstr_DisplayItems.DisplayID = tblInfo_Display.DisplayID) ON tblInfo_Exhibitor.ExID = tblInfo_Display.ExID " _
& "WHERE (((tblInfo_Exhibitor.EventID) =" & xEventID & " ) and ((tblInfo_Exhibitor.ExID) =" & xExId & " ));")
rst.Close
Set rst = Nothing
db.Close
'End Sub

Creating dynamic ADODB connections in Classic ASP

I have a list of Database connection strings, Database Name. These databases have the same table structure. What I am trying to do is dynamically create a connection to each one, add/delete/modify a table, however, if an error pops up anywhere, then RollbackTrans, else, CommitTrans.
My basic question to get my on the correct path is this:
Is this code possible in Classic ASP to make Dynamically named connections?
'create the dynamic object
execute("Set Con" & index & " = Server.CreateObject(""ADODB.connection"")")
'connect to the dynamic object
execute("Con" & index & ".Open " & DBString(index))
The error I get is 'Expected end of statement' on the .open line (the last one)
This might do the trick: Just use an array of connection strings. From this you create an array of connections. Then you can iterate over this array and send your commands to the separate databases.
dim connectionStrings(1)
dim connections(1)
dim curConn
connectionStrings(0) = "Provider=sqloledb;Server=.\EXPRESS2012;Database=master;uid=youruser;pwd=yourpwd"
connectionStrings(1) = "Provider=sqloledb;Server=.\EXPRESS2012;Database=model;uid=youruser;pwd=yourpwd"
for curConn = 0 to ubound( connectionStrings)
set connections(curConn) = Server.CreateObject("ADODB.Connection")
connections(curConn).Open connectionStrings(curConn)
next
dim cmd : cmd = "select ##servername, db_name()"
for curConn = 0 to ubound( connectionStrings)
dim rs
set rs = connections(curConn).Execute( cmd)
Response.write( rs( 0) & ":" & rs(1) & "<br />")
rs.close
set rs = nothing
next
for curConn = 0 to ubound( connectionStrings)
call connections(curConn).Close
set connections(curConn) = nothing
next
Mysql Dynamic Connection String , sample for t=1 to 4 , four different database connection conns(t)
dim conns(4)
Set Conns(1)=Server.Createobject("ADODB.Connection")
Conns(1).Open "DRIVER={MySQL ODBC 3.51 Driver}; SERVER=localhost;port=3306;DATABASE=dbname;UID=root;PASSWORD=pass;OPTION=3"
Conns(1).Execute "SET NAMES 'latin5'"
Conns(1).Execute "SET CHARACTER SET latin5"
Conns(1).Execute "SET COLLATION_CONNECTION = 'latin5_turkish_ci'"

Insert long string into Access DB using parametrised query in classic ASP

I'm trying to update a classic ASP application and as part of the update I've tried to replace dynamic SQL using string concatenation with a parametrised query.
The problem is that the parameters won't accept a value which is longer than 210 characters.
I get the following error...
ADODB.Parameter error '800a0d5d'
Application uses a value of the wrong type for the current operation.
/admin/Save_product_subcategories.asp, line 30
My first attempt looks like this...
SQLString = "UPDATE Product_SubCategories
SET SubCategory=?, Description=?
WHERE SubCategoryID=?"
Set courseCommand = Server.CreateObject("ADODB.Command")
courseCommand.ActiveConnection = objConn
courseCommand.CommandText = SQLString
courseCommand.Parameters(0).value = cleanCategory
courseCommand.Parameters(1).Value = cleanDescription
courseCommand.Parameters(2).value = cleanSubCategoryId
I've tried manually setting the parameter type and increasing the size of the parameter...
courseCommand.Parameters(1).Type = 203
courseCommand.Parameters(1).Size = 300
courseCommand.Parameters(1).Type = adLongVarWChar
I've also tried creating a parameter with the command.CreateParameter method but that gives the same error.
param = courseCommand.CreateParameter(,,,,cleanDescription)
'or
param = courseCommand.CreateParameter(,adLongVarWChar,,,cleanDescription)
'or
param = courseCommand.CreateParameter(,adLongVarWChar,,300,cleanDescription)
courseCommand.Parameters(1) = param
I'm beginning to think that my only option is to go back to dynamic sql.
Edit:
I tried to Append the parameter instead of adding it to the collection using the array index but none of the parameters worked after that.
Provider error '80020005'
Type mismatch.
/admin/Save_product_subcategories.asp, line 31
For anyone else looking for this the answer is to use a Recordset.
SQLString = "select * from Product_SubCategories where 1=0"
Set rs= Server.CreateObject("ADODB.Recordset")
rs.open SQLString , objConn, 1,3 'open as keyset ,lock optimistic that will create empty recordset for you
' Add new record
rs.AddNew
'assign values
rs("SubCategoryID")=cleanSubCategoryId
rs("Description")=cleanDescription
rs("SubCategory")=cleanCategory
' send new record with values to database
rs.Update
'close recordset
rs.close
'destroy recordset object
se rs=nothing

Access 2010: Display contents of multiple records to unbound controls in datasheet

I'm using a dynamic pass-through query in Access 2010 to retrieve one or more records from a back-end database. After much trial and error, I plagiarized enough of the right code to retrieve the appropriate records and assign them to unbound text-boxes on my datasheet form during an OnLoad event. The only problem remaining is in displaying multiple records. I've verified that I AM retrieving multiple records, but the contents of each record's fields overwrite the previous values stored to the form's textbox controls, so I always end up with just a single record displayed in my datasheet when I expect to see anywhere from one to 10.
I'm sure it's a simple solution. Can someone please point it out to me?
Private Sub Form_Load()
Dim sqlString As String
sqlString = "SELECT Transmitter_ID, Receiver_ID, UTC_Date, Local_Date from Detections"
If Not IsNull(Me.OpenArgs) Then
sqlString = sqlString & " where " & OpenArgs
End If
Dim cnn As New ADODB.Connection
Dim cmd As New ADODB.Command
Dim rst As ADODB.Recordset
'Define and open connection
cnn.ConnectionString = "DRIVER={SQLite3 ODBC Driver};Database=z:\EWAMP\EWAMP_be_dev.sqlite"
cnn.Open
'Define ADO command
cmd.ActiveConnection = cnn
cmd.CommandText = sqlString
'Populate and enumerate through recordset
Set rst = cmd.Execute
If rst.EOF Then
MsgBox "Nothing found...", vbInformation + vbOKOnly
Exit Sub
Else
Do While Not rst.EOF
'// I'm guessing the problem is with my control assignments, here.
Me.cntl_Receiver_ID.Value = rst("Receiver_ID")
Me.cntl_Transmitter_ID.Value = rst("Transmitter_ID")
Me.cntl_UTC_Date.Value = rst("UTC_Date")
Me.cntl_Local_Date.Value = rst("Local_Date")
Debug.Print {Show me the four control values}
rst.MoveNext
Loop
End If
End Sub
Cheers!
DUHdley
I don't believe a form in Datasheet view can be used as an unbound form. But you can use the ADO recordset as the forms recordset.
Set Me.Recordset = rst
Then just be careful not to close your variable named rst until the form closes.
Another alternative solution is to use an in-memory, fabricated, disconnected ADO recordset. Basically, you'd end up creating a new recordset, append fields to it to match your existing recordset, and then move all the data into your new recordset. But I really don't see the point in doing this if you already have a valid, filled ADO recordset.
If you really need/want to display multiple records in an unbound form, I think you would have to use ActiveX controls such as the GridView, ListView, TreeView, or MSFlexGrid. I've noticed that most skilled, professional Access developers stay away from ActiveX controls as much as possible. If and when they do use them, they usually limit it to only the TreeView and the ListView, I think because they are about the only ActiveX controls that add enough value to be worth putting up with whatever problems they might introduce.
I suggest you take a look at this article concerning the differences between DAO and ADO.
http://www.utteraccess.com/wiki/index.php/Choosing_between_DAO_and_ADO
A reader in another forum pointed me to a solution similar to that posed by HK1, namely Set Me.Recordset = rst. That fixed my original problem, but created another.
First I re-bound my four textbox controls on the unbound form, and then modified the code significantly, using the sample from http://msdn.microsoft.com/en-us/library/ff835419.aspx. The revised code looks like this:
Private Sub Form_Load()
Dim sqlString As String
sqlString = "SELECT Transmitter_ID, Receiver_ID, UTC_Date, Local_Date from Detections"
If Not IsNull(Me.OpenArgs) Then
sqlString = sqlString & " where " & OpenArgs
End If
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
'Define and open connection
Set cn = New ADODB.Connection
cn.ConnectionString = "DRIVER={SQLite3 ODBC Driver};Database=z:\EWAMP\EWAMP_be_dev.sqlite;"
cn.Open
'Create an instance of the ADO Recordset class,
'and set its properties
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Source = sqlString
'// .LockType = adLockOptimistic
.LockType = adLockReadOnly
.CursorType = adOpenKeyset
'// .CursorType = adOpenStatic
.Open
End With
'Set the form's Recordset property to the ADO recordset
Set Me.Recordset = rs
Set cn = Nothing
Set rs = Nothing
End Sub
The form now display four rows for four returned records, 20 rows for twenty records, and on, up to at least 256k rows (as specified by my parameter set). The only remaining teeny tiny problem is that for four or more records, if I press the "last row" navigation button (>|), the local cursor sets focus to one or more of the intermediate rows, and the control's properties sheet refreshes vigorously (multiple times per second). If I have more form rows than can be displayed on the screen, I can not navigate or cursor to the last row. It's as though the record set is constantly being updated.
As you can see, I've played with the RecordSet LockType and CursorType properties (including adOpenDynamic and adOpenForwardOnly, both of which caused a run-time error with the Set Me.Recordset statement). Toggling the LockType between adLockOptimistic and AdLockReadOnly, and the CursorType between adOpenKeyset and adOpenStatic makes no difference in the retrieval performance (which is fantastically fast now!) or the apparent refresh rate (which is even faster, unfortunately).
Perhaps it's worth mentioning that the "Detections" table the sqlString "selects" from contains ~4M records. I was frustrated in my previous attempts to use a form with a data source bound to a passthrough query of this table, because the query always returned the entire 4M records to the client regardless of the filter/WhereClause/OpenArgs parameter I passed to the form. The solution shown above would be perfect if only I could close the connection (I've tried) or otherwise quiesce the RecordSet after I've invoked it once.

How to get the insert ID from this ADODB.Recordset?

I'm trying to avoid using straight SQL queries in my web app. I looked around and have come to the conclusion that ADO Recordsets would be the best or at least safest tool for the job. I need to insert records into a database table. Unfortunately I'm at a loss as to how to get the identity value for the record which was just inserted. Here's a reduction of what I've got now:
<%
dim insertID, rs
set rs = Server.CreateObject("ADODB.Recordset")
rs.Open "my_table_name", conn, adOpenForwardOnly, adLockOptimistic
rs.AddNew()
Call m_map_values_to_rs(rs)
rs.Update()
insertID = rs("id")
rs.Close()
rs = Nothing
%>
The code I have is successfully inserting the record, but I can't for the life of me figure out how to get the id field of the Recordset to update after the insert. How can I get the identity column value back from this Recordset?
UPDATE - Here's the solution with regard to the code above.
I had to change the cursor type to adOpenKeyset instead of adOpenForwardOnly. After I did this the record is automatically updated with the "auto number" field's new value after the insert. However it is not what you think it is. The value of rs("id") doesn't become an integer or even a variant. It becomes some sort of Automation type and cannot be evaluated as a number. Nor can CInt() be used directly on that type for some reason. So what you must do is to convert the value to a string and then convert it to an Int. Here's how I managed that:
insertID = CInt( rs("id") & "" )
Thanks to Dee for their answer. It helped immensely.
This article explains the means of getting identity value with example code.
The relevant code snippet is:
<%
fakeValue = 5
set conn = CreateObject("ADODB.Connection")
conn.open "<conn string>"
sql = "INSERT someTable(IntColumn) values(" & fakeValue & ")" & _
VBCrLf & " SELECT ##IDENTITY"
set rs = conn.execute(sql)
response.write "New ID was " & rs(0)
rs.close: set rs = nothing
conn.close: set conn = nothing
%>

Resources