We use an ADODB command to perform queries (call stored procedures) on our SQL Server 2000 database, using SQLOLEDB driver.
On one of our server we have issues when we assign parameter values. Here's how it goes :
Set conn = Server.CreateObject("ADODB.Connection")
conn.CommandTimeout = 0
conn.ConnectionTimeout = 15
conn.Mode = 1 ' adModeRead
conn.ConnectionString = connectionString ' SQLOLEDB
conn.CursorLocation = 3 ' adUseClient
conn.Open
Set cmd = Server.CreateObject("ADODB.Command")
cmd.ActiveConnection = conn ' previously created connection
cmd.CommandType = 4 ' adCmdStoredProc
cmd.CommandText = "dbo.StoredProcedureName"
cmd.CommandTimeout = 0
cmd.Parameters.Refresh ' Get the parameters info from the database
' Pseudo code here :
Foreach cmd.parameters
cmd.parameters(index).value = somevalue
Next
This code actually works on our production server, but for some odd reasons it does not work on our dev server and generates this error : Application uses a value of the wrong type for the current operation when we assign a value (which contain a decimal part, let's say a string like "12.75") to a datatype money parameter.
The code is exactly the same on dev and on prod. Do this could have something to do with regional settings, language of ADODB, language of thr OS or some other Windows component ? Because it is classic ASP code we already looked at Session.LCID but they are the same on both servers, so we are clueless right now.
Have any idea ?
Is it possible the dev server is set up with a different base language or other regional settings? Just for kicks try assigning the value 12,75 instead of 12.75.
From the MS KB
Parameters.refresh will fail in some situations or return
information that is not entirely correct. Parameters.refresh is
particularly vulnerable when used on ASP pages.
There is a known issue regarding parameter direction when using Parameters.refresh from Classic ASP. MS guidance is to manually generate the parameters.
http://support.microsoft.com/kb/183008
http://support.microsoft.com/kb/174223
Related
I have a legacy classic ASP application running with SQL Server 2012 (also tested with 2016) that I am trying to switch over to using parameterized queries. All the site's queries run through a function which expects a sql statement as a string with parameters represented by question marks as well as an array of those parameters. The function currently filters the parameters to make them sql safe and puts them into the sql string before executing the statement.
Given this, I thought it would be pretty straightforward to switch this to parameterized queries. Initial testing looked good, and everything appeared to be working properly until I hit a sql statement with parameters in subqueries.
Here's a test sample of what works:
Const connectionString = "Provider=SQLNCLI11; DataTypeCompatibility=80; Server=********; Database=********; UID=*******; PWD=*******"
Dim sql, productId, parameters
sql = "SELECT SKU FROM Products WHERE ProductId = ?"
productId = 3
parameters = Array(productId)
Dim conn
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open connectionString
Dim cmd
Set cmd = Server.CreateObject("ADODB.Command")
cmd.ActiveConnection = conn
cmd.CommandText = sql
cmd.Parameters.Refresh
Dim rs
Set rs = cmd.Execute(, parameters)
Response.Write("SKU: " & rs("SKU"))
No problem, this returns the SKU as expected. However, if I use a subquery:
Const connectionString = "Provider=SQLNCLI11; DataTypeCompatibility=80; Server=********; Database=********; UID=*******; PWD=*******"
Dim sql, productId, parameters
sql = "SELECT SKU FROM ( SELECT SKU FROM Products WHERE ProductId = ? ) AS P"
productId = 3
parameters = Array(productId)
Dim conn
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open connectionString
Dim cmd
Set cmd = Server.CreateObject("ADODB.Command")
cmd.ActiveConnection = conn
cmd.CommandText = sql
cmd.Parameters.Refresh
Dim rs
Set rs = cmd.Execute(, parameters)
Response.Write("SKU: " & rs("SKU"))
It throws an error on the cmd.Parameters.Refresh line:
Microsoft VBScript runtime error '0x80004005'
Microsoft SQL Server Native Client 11.0
Syntax error, permission violation, or other nonspecific error
If I check cmd.Parameters.Count in the first sample, I correctly get 1. In the bad sample it throws the same error.
Is there any explanation as to why putting the parameter into a subquery causes problems with the parameter collection? I did try manually adding the parameter to the Parameters collection, and that works fine, but it means modifying hundreds of existing sql calls, so for the moment the cmd.Parameters.Refresh round-trip was worth the expense.
For anyone who might stumble across this, I finally figured out the issue thanks to a co-worker. It turns out there is nothing wrong with the code, but rather with the connection string. I somehow left it out of the sample code, but my connection strings included "DataTypeCompatability=80". If that is present, the code throws the error. However, if I remove it, the error no longer occurs and I get the results back as suspected.
My understanding from this KB article on using ADO with the native client is that DataTypeCompatability should be included to ensure newer data types work properly, but so far I have not found any issues with removing it.
You can give cmd.execute what you want, but I haven't used it in a long time.
cmd.execute("SELECT SKU FROM ( SELECT SKU FROM Products WHERE ProductId = ? ) AS P", Array(productId))
I am working in Classic ASP. I know there is a record that matches my simple SQL select query. It has the ' character ' in it. The code is as follows:
Fieldname = Replace(trim(Request.form("Fieldname")),"'","'", 1, 10)
'replace the "'" up to 10 times with the ' code to avoid SQL issues, LOL.
SQL = "select id,fieldname from table where fieldname='"&Trim(Fieldname)&"'"
set rs = server.createobject("adodb.recordset")
rs.open SQL, Application("conn"), 1, 1
If not rs.eof then
response.redirect "somepage.asp?QS=Fieldname_Exists_in_DB"
Else
'Sample.hold the value in a hidden input field and pass it to the next page
End If
The problem is, I know for a fact the fieldname and fieldname value is in the MS-SQL 2016 server table. I pull data from it all the time. The value in the database field contains the ' value as does the Replaced FORM Fieldname when it is compared to the SQL database field, so it should NOT pass the IF NOT RS.EOF question. Yet it passes every time.
What am I missing? I'm doing the exact same query in other places on this exact same app and it behaves as one would expect.
Tried to explain in the comments but as the point is being missed, I'll try to give you an example here.
Do not trust user input
Classic ASP server-side code that interacts with the ADODB Library doesn't have any notion of sanitised data. This means that any input that comes from the user via the Request object (like Request.Form("Fieldname")) should not be trusted.
Fieldname = Replace(trim(Request.form("Fieldname")),"'","'", 1, 10)
SQL = "select id,fieldname from table where fieldname='"&Trim(Fieldname)&"'"
This example is open to SQL Injection attacks and is generally bad practise and leads to security flaws that can be easily exploited with script tools readily available on the internet.
Manually sanitising data
Apart from the security flaws introduced, it also makes it harder to query data due to how SQL calls for strings and other data types need to be constructed (which varies from provider to provider). Having to account for the various combinations of characters that could be deemed dangerous or likely to break the query can be a cumbersome task and one seen far too often in the wild when ADODB already has a solution.
Parameterised Queries
The ADODB Library has an in-built object called ADODB.Command which takes all these hassles away.
Using the example in the question the same query can be written without the failings of manually sanitising data or executing SQL directly against user input.
Const adCmdText = 1
Const adVarWChar = 202
Const adParamInput = 1
Dim Fieldname, SQL, cmd, rs,
Fieldname = Trim(Request.Form("Fieldname") & "")
SQL = "SELECT id, fieldname FROM table WHERE fieldname = ?"
Set cmd = Server.CreateObject("ADODB.Command")
With cmd
.ActiveConnection = Application("conn")
.CommandType = adCmdText 'Also can use 1
.CommandText = SQL
Call .Append(.CreateParameter("#fieldName", adVarWChar, adParamInput, 255))
Set rs = .Execute(, Array(Fieldname))
End With
Set cmd = Nothing
If Not rs.EOF then
response.redirect "somepage.asp?QS=Fieldname_Exists_in_DB"
Else
'Sample.hold the value in a hidden input field and pass it to the next page
End If
Useful Links
A: Using METADATA to Import DLL Constants (shows an approach to using Named Constants that doesn't require adding your own Const declarations to the code).
I'm struggling with an old and massive system developed on MS-Access 2007, which I'm in charge of its maintenance (including the implementation of changes).
The system interacts with a SQL-Server (2012).
Currently, I need to implement a change in which a new stored procedure needs to be invoked which returns a simple records set, each record being a string.
I tried to use for the invocation some code that already exists within the application (using ADO and copied into the form I'm working on) but nothing I tried works.
Here is the code I'm trying to use:
glblsqlstrToLabels = "EXEC p_Labels_Print 1 , 2878954 , 'OC9991' , '89029' , 4 , 1 , 'dummy'"
Though I'm using exactly the same connection string as it is being used all over the application, executing the above statement returns with an error message (something like ...not open...).
I'm starting to suspect that there is something wrong in the way I'm invoking the function (e.g. not defining any parameters for it and expecting a behavior similar to a select statement).
Any help will be highly appreciated.
EDIT:
Following are the pieces of code describing what I need to have working:
Dim RS As Recordset
' Connection string is: Provider=Microsoft.Access.OLEDB.10.0;Persist Security Info=True;Data Source=****;User ID=****;Password=****;Initial Catalog=***;Data Provider=SQLOLEDB.1
MyCommand = "EXEC p_Labels_Print 1 , 2878954 , 'OC9991' , '89029' , 4 , 1 , 'asdasd'"
RS.Open MyCommand, CurrentProject.Connection
Do Until RS.EOF
Print <record retrieved>
Loop
RS.Close
Set RS = Nothing
The error I get is: Error: Operation is not allowed when the object is closed.
You need to properly use ADO and commands, you can't just rs.Open a stored procedure.
Use the following Microsoft boilerplate code:
Set Cmd1 = New ADODB.Command
Cmd1.ActiveConnection = Conn1
Cmd1.CommandText = "sp_AdoTest"
Cmd1.CommandType = adCmdStoredProc
Cmd1.Parameters.Refresh
Cmd1.Parameters(1).Value = 10
Set Rs1 = Cmd1.Execute()
Where Conn1 is your ADODB connection, and fill in your parameters and stored procedure name.
The weirdest thing. I have a simple procedure that I developed in Microsoft Access 2010 with a SQL Server 2012 backend. I am now trying to deploy this into production which is Access 2016 and a SQL Server 2014 backend.
I've compiled, compact and repaired in the new environment... but I can not get Access to execute this simple stored procedure. Even worse it still executes several other stored procedures fine... but a couple of them it times out and refuses to execute?
Here is my VBA and stored procedure:
Private Sub GenerateUnitKey(UnitColumns As String)
Dim Msg, Style, Title, Response As Variant
Dim lngProcessID As Long
Dim Conn As ADODB.Connection
Dim Cmd As ADODB.Command
Dim CurrentConnection As String
CurrentConnection = LinkMasterConnection()
Msg = "Are you sure you want to update the UnitKey with the selected columns?"
Style = vbYesNo + vbCritical + vbDefaultButton2
Title = "Save Campaign?"
Response = MsgBox(Msg, Style, Title)
If Response = vbYes Then
Call OpenSixHatLoader("Generating Unit Key Across Campaign Records", 1, "")
Set Conn = New ADODB.Connection
Conn.Open CurrentConnection
Set Cmd = New ADODB.Command
With Cmd
.ActiveConnection = CurrentConnection
.CommandText = "usp_GenerateUnitKey"
.CommandType = adCmdStoredProc
.CommandTimeout = 30
.Parameters.Append .CreateParameter("#UnitColumns", adVarChar, adParamInput, 4000, UnitColumns)
.Execute
End With
End If
End Sub
And stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[usp_GenerateUnitKey]
#UnitColumns AS VARCHAR(4000)
AS
SET NOCOUNT ON
DECLARE #SQL AS VARCHAR(MAX)
UPDATE tblStagingTable SET UnitKey =''
SET #SQL = 'UPDATE tblStagingTable SET UnitKey = ' + #UnitColumns + ' FROM tblStagingTable st'
EXEC(#SQL)
-- UPDATE Interests to match Staging Table
UPDATE tblInterests SET UnitKey = st.[UnitKey] FROM tblInterests i
INNER JOIN tblStagingTable st ON i.StagingTableID = st.StagingTableID
I am fairly confident there is nothing wrong with the code... as I said it worked fine in my development environment... even more I am manually able to execute the stored procedure within SQL Server. My SQL Server Native Client 11.0 connection works in executing other stored procedures... but for a couple of them it does not work. I am thinking I need to configure something within SQL Server itself or maybe within the Native Client 11.0 driver?
Unfortunately it gives no exception. I've set the CommandTimeout property to 0 and let it chug for a few hours hoping it would throw and exception to give me a clue but nothing... it just was frozen trying to execute. Any suggestions or ideas would be greatly appreciated because this one has me really stumped because it should be fine!
I would first launch SSMS, and from the SQL studio type in
Exec xxxxx ''
And ensure it runs (and use the SAME logon and connection to SSMS that you currently have for Access.
I would also consider creating a pass-though query, and saving that query in access. (set returns records = false if the sp does not return records). Then in code to run any proc, you can go:
With CurrentDb.QueryDefs("qryPass")
.SQL = "exec usp_GenerateUnitKey '" & UnitColumns & "'"
.Execute
End With
You note how simple the above code is - so if sp works from SSMS, then try the above code.
This was a difficult one that took me about 3 solid days of troubleshooting to get a solution to. Although I am not satisfied with the end solution as it should have just worked... but in the end my theory of the server being an Virtual Machine proved correct. When I deployed this exact same setup to Microsoft Access 2016 32 bit and SQL Server 2014 32 bit on a dedicated server it worked exactly as it was supposed to compared to the Azure VM and 1&1 Cloud Servers I had attempted to deploy to.
SQL Server integration with VM's is getting better from what all I have read, but apparently there is a ways to go. Maybe SQL Server needs to release a special VM version. Thank you to all those who took the time to look into this.
I have a stored procedure which when run from SQL Server Management Studio consistently takes 5 seconds to run when called like this.
exec dbo.MyStoredProc '2009-04-30 00:00:00', '2009-04-30 20:00:00'
When called from an excel spreadsheet via VBA it takes 6 minutes plus (not including the time taken to copy the recordset to a sheet. The VBA is nothing fancy simply using an ADO connection to return a recordset. Unfortunately the Excel approach is a client requirement that I can't get rid of yet.
Public Function GenerateSQL(strQueryName As String) As Recordset
Dim rs As Recordset, cm As Command, dbsConn As Connection
Set dbsConn = New ADODB.Connection
dbsConn.Open Configuration.Range("ConnectionString")
Set cm = New ADODB.Command
With cm
.CommandText = strQueryName
.CommandType = adCmdStoredProc
.CommandTimeout = 300
.ActiveConnection = dbsConn
Set rs = .Execute()
End With
Set GenerateSQL = rs
End Function
Does anyone have any idea why this would happen or how I could begin to trace what is happening?
Thanks,
Steve
Everything you need to know about this topic: Slow in the Application, Fast in SSMS? Understanding Performance Mysteries
I believe I have the same problem as Steve Homer.
In addition to this SO question I also found this thread on eggheadcafe.com Very slow SP execution when using .net - very fast in Management Studio - totico
The answers say it's about parameter sniffing and how that affects which execution plan is used. The answers there specifically mentions the arithabort set option and how that affects the selection of plan.
Now I just need to find out how to change the set options from VBA...
Finally thanks to this forum entry on social.msdn.com i managed to get it right. First, set multiple connections to false:
connectionObject.Properties("Multiple Connections") = False
and then use the following function on your connection to set arithabort on ...
Private Sub OptionSet(ByRef cnn As adodb.Connection)
Dim cmd As adodb.Command
Set cmd = New adodb.Command
With cmd
Set .ActiveConnection = cnn
.CommandType = adodb.CommandTypeEnum.adCmdText
.CommandText = "set arithabort on"
Call .Execute
End With
Set cmd = Nothing
End Sub
Use SQL Server Profiler
Set up a trace on your database.
Limit the trace only to the stored procedure object in question
Limit to the username used by the VBA code
An introduction to SQL Server Profiler
In particular, check the SET options used by the connection and compare these with the defaults used when running the stored procedure in SSMS.
I have come across scenarios before where the SET options were different between calling code and within SSMS and the difference in performance was HUGE.
Thanks I'll take a look at the trace tools.
In reply to the comments on the original question
Are you using the exact same parameter values?
Yes exactly the same.
How much data is being returned (roughly) - number of rows and columns (and are any of them particularly big)?
Under 200 rows, perhaps 15 fields mostly ints with a couple of 20 character varchars.
Can you run SQL profiler and confirm if the sql is the issue or the remains of the macro in excel ?
The SQL is pretty ugly, as is the underlying database schema, and unfortunately is under NDA so I can't post it. If the query were the issue then wouldn't it be slow in management studio too though?