I want to simply retrieve a single record from a database in a classic ASP page. The code below basically works, but there are a couple problems I need help solving:
1) I want to see if a record was returned or not. result is not Nothing, so the redirect at the bottom is never performed. contact.RecordCount always returns -1, so I apparently can't use that either. Oddly, trying to access RecordCount outside the function throws an "Object doesn't support this property or method: 'RecordCount'" error.
2) I've read about disconnected queries and have seen examples where the connection and command are closed and/or set to Nothing at the end of the function. Is there a definitive best practice on what I should do?
3) Will using a parameterized query fully protect me from SQL injection, or do I need to manually remove dangerous words and characters?
function GetContactByUsername(username)
Dim conn, command, param, contact
set conn = server.CreateObject("adodb.connection")
conn.Open Application("DatabaseConnectionString")
Set command = Server.CreateObject("ADODB.COMMAND")
set command.ActiveConnection = conn
command.CommandType = adCmdText
command.CommandText = "Select * from MY_DATABASE.dbo.Contact where Username = ?"
Set param = command.CreateParameter ("Username", adVarWChar, adParamInput, 50)
param.value = username
command.Parameters.Append param
Set contact = Server.CreateObject("ADODB.RECORDSET")
contact.Open command
Response.Write contact.RecordCount '' always -1
set GetContactByPurlCode = contact
end function
dim result
result = GetContactByUsername(Request.QueryString("username"))
if result is Nothing then '' never true
Response.Redirect "/notfound.asp"
end if
FirstName = Trim(result("FirstName"))
LastName = Trim(result("LastName "))
1) To check for a lack of records, use rs.EOF, not "Is Nothing." The RecordSet object is always an object. It's just that sometimes it doesn't have any rows.
If you want to use RecordCount but are getting -1, then switch to a client-side cursor (adUseClient).
2) No definitive best-practice here, but I've personally always closed the Connection and Command, and have not had much in the way of performance problems. Connection objects are particularly precious, so close them as early as possible on high volume pages.
3) Yes, parameterizing your variable is perfect, unless you are calling a stored procedure that constructs a dynamic query.
By the way, you should avoid "SELECT *" as that will cause you to return more data than needed and is a maintenance problem waiting to happen.
Related
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
This problem has driven me mad for over a day now. I can create a connection to the database, I can execute sql and return results from that but I can't seem to call a stored Procedure. Here is the code
Dim myCMD As New OracleCommand
Dim TheDataReader as New OracleDataReader
myConnection1.Open()
myCMD.Connection = myConnection1
myCMD.CommandType = CommandType.StoredProcedure
myCMD.CommandText = "WS_DATA_LAYER.select_user_groups"
myCMD.Parameters.Add(New OracleParameter("id_user", OracleDbType.VarChar2)).Value = "TXA"
myCMD.Parameters.Add(New OracleParameter("ws_rs", OracleDbType.RefCursor)).Direction = ParameterDirection.Output
' Tried every single execute function here and none have worked
' Either error is thrown or empty refcursor
myCMD.ExecuteScalar()
TheDataReader = myCMD.Parameters(1).Value().GetDataReader()
The Problem lies in ExecuteScalar at the moment. It's throwing an exception called "Input string was not in a correct format". I've tried passing the string with Oracle single quotes and get the same thing. If I use
TheDataReader = myCMD.ExecuteQuery()
it works ok but no results are returned. I've verified that the procedure returns results for the user I'm logged in as. When the query was executing I could see a refcursor in there but it was empty. I must be going mad.
Any help is appreciated
Anyone else that may have this problem, I was passing the OracleDBType.Varchar2 as a parameter to the above VB method. But I had it declared as an integer, it needs to be explicitly passed as an OracleDBType
I've created an XML file using the .Save() method of an ADODB recordset in the following manner.
dim res
dim objXML: Set objXML = Server.CreateObject("MSXML2.DOMDocument")
'This returns an ADODB recordset
set res = ExecuteReader("SELECT * from some_table)
With res
Call .Save(objXML, 1)
Call .Close()
End With
Set res = nothing
Let's assume that the XML generated above then gets saved to a file.
I'm able to read the XML back into a recordset like this:
dim res : set res = Server.CreateObject("ADODB.recordset")
res.open server.mappath("/admin/tbl_some_table.xml")
And I can loop over the records without any problem.
However what I really want to do is save all of the data in res to a table in a completely different database. We can assume that some_table already exists in this other database and has the exact same structure as the table I originally queried to make the XML.
I started by creating a new recordset and using AddNew to add all of the rows from res to the new recordset
dim outRes : set outRes = Server.CreateObject("ADODB.recordset")
dim outConn : set outConn = Server.CreateObject("ADODB.Connection")
dim testConnStr : testConnStr = "DRIVER={SQL Server};SERVER=dev-windows\sql2000;UID=myuser;PWD=mypass;DATABASE=Testing"
outConn.open testConnStr
outRes.activeconnection = outConn
outRes.cursortype = adOpenDynamic
outRes.locktype = adLockOptimistic
outRes.source = "product_accessories"
outRes.open
while not res.eof
outRes.addnew
for i=0 to res.fields.count-1
outRes(res.fields(i).name) = res(res.fields(i).name)
next
outRes.movefirst
res.movenext
wend
outRes.updatebatch
But this bombs the first time I try to assign the value from res to outRes.
Microsoft OLE DB Provider for ODBC Drivers error '80040e21'
Multiple-step OLE DB operation generated errors. Check each OLE DB status value, if available. No work was done.
Can someone tell me what I'm doing wrong or suggest a better way for me to copy the data loaded from XML to a different database?
Update, partly solved
So it turns out that my error is caused by my attempting to set the value of an Identity field. If I temporarily change that field to not be an Identity, all of the data gets inserted perfectly.
Now a followup question
How can I temporarily turn off the Identity property of that field, then turn it back on when I'm done with my updates?
I was never able to get Recordset.AddNew to work because of the above problem.
As a workaround, I'm doing a SET IDENTITY_INSERT table ON, executing the INSERT sql, and SET IDENTITY_INSERT table OFF.
After reading the XML back, set the connection property of the recordset to the new database connection and then invoke UpdateBatch.
When ever I try to access the RecordCount property, I always get a return value of -1. Below is my sample code.
Set oConn = Server.CreateObject ("ADODB.Connection")
oConn.Open Application("strConnectstring")
Set rs = Server.CreateObject ("ADODB.Recordset")
rs.ActiveConnection = oConn
SQL = "Publications_PicoSearchListing"
set rs = oConn.execute(SQL)
I'm not sure if I'm doing forwardCursor or dynamic cursors, or if the provider even supports the RecordCount property. How do I check if the provider supports RecordCount property or if I'm using either forwardCursor or dynamic cursors.
Any help would be appreciated.
Thank You
Recordcount is not supported with the default forward-only cursor.
you must add extra parameters to the open command
rs.open sql,conn,1,1
That should let you have access to rs.recordcount.
But paging is best done by using the Recordset.GetRows() + Recordset.Move() method.
http://databases.aspfaq.com/database/how-do-i-page-through-a-recordset.html
(scroll down to the bold "Recordset.GetRows() + Recordset.Move()" this is fastest way without using stored procedures)
Please note: unless you move to the end of the recordset there is no guarantee that the RecordCount will have been populated. The standard pattern to to iterate over each row in the recordset using While Not rs.EOF. In all the VBA code I've ever written, I have never relied on checking rs.RecordCount
Rather than checking the cursor type, you can set it. For example:
Set conn=Server.CreateObject("ADODB.Connection")
conn.Provider="Microsoft.Jet.OLEDB.4.0"
conn.Open(Server.Mappath("northwind.mdb"))
set rs = Server.CreateObject("ADODB.recordset")
sql="SELECT * FROM Customers"
rs.CursorLocation = adUseClient
rs.CursorType = adOpenStatic
rs.LockType = adLockBatchOptimistic
rs.Open sql, conn
If all you want is the count, why not emit a "SELECT Count(*) From Publications_PicoSearchListing"
Of Interest?: Understanding ADO's Default Cursor Type
Another alternative to get the RecordCount is to execute:
rs.MoveLast
rs.MoveFirst
and then check the RecordCount, and even then I seem to remember some cursor types aren't guaranteed (but memory hazy on this).
Also note: Don't use the MoveLast/MoveFirst unless you really need to: this will be slow with a large recordset or a recordset drawn across a network. Instead use the Count(*) technique.
For paging you can use the recordset.PageSize and recordset.AbsolutePage like this
Set rs = Server.CreateObject("ADODB.Recordset")
' make recordset use adUSEclient ( client side cursor)'
rs.CursorLocation = 3
' make recordset use the adOpenStatic cursor ( scrollable )'
rs.CursorType = 3
rs.PageSize = RecordsPerPage
rs.Open sql, conn
' go to selected page'
if not rs.EOF and not rs.BOF then
rs.AbsolutePage = page_you_want_to_go
end if
you then have access to recordset.PageCount to know the number of pages returned..
I am doing some calculation with the data set I take from my database. Null values give errors so I tried replacing null values with zeros(0). Here is the error I get,
ADODB.Recordset error '800a0cb3'
Current Recordset does not support
updating. This may be a limitation of
the provider, or of the selected
locktype.
Never seen it before. Here is my code.
If IsNull(objRevenueToday("REVENUE")) Then
objRevenueToday("REVENUE") = 0
End If
Your recordset appears to be read-only. There could be a number of reasons for this; you're reading a view that contains a Group By clause, you don't have permissions, etc.
Using the syntax Set Recordset = Command.Execute always opens a read only cursor. What you need to do is open the cursor using the Recordset object. The Source parameter of the Open method is your Command object. This allows you to set the desired location and locktype.
Dim cmdProc as ADODB.Command
Dim rsData as ADODB.Recordset
Set cmdProc = New ADODB.Command
With cmdProc
Set .ActiveConnection = SomeConnection
.CommandType = adCmdStoredProc
.CommandText = "selCustomer"
' ... Create parameters
End With
Set rsData as New ADODB.Recordset
rsData.Open cmdProc,, adOpenStatic,adLockBatchOptimistic
'...Process recordset data.
Here is the solution:
If IsNull(objRevenueToday("REVENUE")) Then
RevenueToday = "0"
Else
RevenueToday = objRevenueToday("REVENUE")
End If
Not very ideal but fixed my error.
Assuming SQL Server (although similar techniques available in other DBs.
Change the query so that is will not return nulls in records. For example in the T-SQL
SELECT ISNULL(REVENUE, 0), .... FROM ....
Change the settings as below. It force the client side cursor...It worked for me
set pagedlistrs=CreateObject("adodb.recordset")
pagedlistrs.cursorlocation = 3 ' adUseClientpagedlistrs
pagedlistrs.Open SQL, objConn, 3,3,1