ADO Parameterized Queries with Subqueries Error - sql-server

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))

Related

Error: Row cannot be located for updating. Some values may have been changed since it was last read. On changing provider from SQLOLEDB to MSOLEDBSQL

On change of provider from SQLOLEDB to MSOLEDBSQL in the ADODB connection string, we get the error:
-2147217864 Row cannot be located for updating. Some values may have been changed since it was last read.
The connection string is:
Provider=MSOLEDBSQL;SERVER=servername;APP=Applicationname;DATABASE=databasename;WSID=id;Trusted_Connection=yes;MARS Connection=True;DataTypeCompatibility=80
And the code looks like:
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open("SELECT * FROM tableName WHERE 1 = 2", Adoconnection, adOpenStatic, adLockBatchOptimistic, CommandTypeEnum.adCmdText)
rs.AddNew
'Add the fields
...
...
rs.UpdateBatch ''this line throws error
Now, when in the connection string of provider is changed to SQLOLEDB, with the same code it works great without any issue.
Try adding a timestamp, or so called "rowversion" column to the table. (use type timestamp - which has ZERO to do with time).
Also, if you have any bit columns in that table, then make sure they are not null, and make sure a default value of (0) is set for that bit column.
And if the application has linked tables, then re-link your tables after you made the above change server side.
I found out the issue, it was in the SQL triggers.
The respective table had some update statements on the trigger. Adding SET NOCOUNT ON just before the update statement in the trigger helped me to avoid this error.
I found my way to this thread because I had a similar error. I am using Access 365 VBA 32-bit on Windows 10. Here's a snippet of my code (some details omitted):
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
cmd.ActiveConnection = GetCurrentConnection()
cmd.CommandText = sql
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open cmd, , adOpenDynamic, adLockOptimistic
With rs
.Fields("FailedImport") = True
.Fields("ImportErrors") = ReadErrorsFromResponse(xmlDoc)
.Update
End With
I also encountered the same error as in the OP. In my case, I didn't get the error until I included the update to the boolean field called FailedImport. I found my way to this forum post from 2008, containing this comment (formatting mine):
Had same error, however had it with recordsets rather than data control.
From another forum, I learnt that if you are using a static cursor for the recordset, using adLockBatchOptimistic instead of adLockOptimistic solved the problem.
So, I changed this line:
rs.Open cmd, , adOpenDynamic, adLockOptimistic
to this:
rs.Open cmd, , adOpenDynamic, adLockBatchOptimistic
And my code worked again! Hope this helps someone else in the future!

SQL Server "Incorrect syntax near" WHERE statement

I've researched and researched... yet to find a solution. I've read people having similar trouble because of the encoding, but I've tried retyping the query and even used convert to UTF-8 inside Notepad++. Any ideas?
Error:
Incorrect syntax near 'NEW'.
Query:
delete from [orgDefaults]
where ([orgcode] = N'NEW')
and ([ctlName] = N'AllowReportables')
This is being executed inside a VB.NET program I've created using this OLEDB driver:
Dim conn As New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & updates_mdb & ";Jet OLEDB:Database Password=" & Settings.Password & ";")
You are using the wrong driver to connect to SQL server.
You are using the MS Access Jet Engine. But this uses another SQL syntax, that's why it does not work.
Just use the SQL Server OLEDB driver, and it will work.
Just for hack of it , try this:
Using conn As New SqlConnection("sqlServer Conn string - connectionstrings.com")
Dim sql As String = "DELETE FROM [orgDefaults] WHERE [orgcode] = #1 AND [ctlName] = #2"
Using cmd As New SqlCommand(sql, conn)
cmd.Parameters.AddWithValue("#1", "NEW")
cmd.Parameters.AddWithValue("#2", "AllowReportables")
cmd.CommandType = CommandType.Text
conn.Open()
Dim retVal As Integer = cmd.ExecuteNonQuery()
If retVal > 0 Then
Debug.WriteLine("Success - deleted 1 or more records")
' remeber, retVal may not match num of rows deleted.
' Rather, it indicates total rows affected
Else
Debug.WriteLine("Still Success - deleted Nothing")
End If
End Using
End Using
This should give you better picture [if you have some "special" issue] because Sql executed this way will match your values and field types better.

Possible for TSQL query to use a value from a Access Forms?

I am working on an Access DB, which have ODBC linked SQL Server table, and I have following script to run TSQL query, as you can see I tried to include a value from Access Forms in the query, but it fails to run. The form is opened and filled with data when I execute the script. I am wondering if this is impossible or there is another way of doing it? I am new to TSQL and SQL server, here is my question. Appreicate if someone can help. Thanks a lot.
Function formtest()
Dim qryd As QueryDef
Set qryd = CurrentDb.CreateQueryDef("")
qryd.Connect = "ODBC;DSN=SQLSERVER;"
qryd.SQL = "UPDATE dbo.table1 SET firstname = [Forms]![testform]![datainput]"
qryd.ReturnsRecords = False
qryd.Execute
End Function
The SQL Server doesn't know anything about your forms. You have to send the data with the query. Something like this:
qryd.SQL = "UPDATE dbo.table1 SET firstname = '" & [Forms]![testform]![datainput] & "'"
One thing you have to be aware of though is that if there are any single quotes in your datainput it could invalidate the SQL. It could also be a security issue. Either test for single quotes and raise an error, or replace each of them with two.
The best way to do it is to use a parameterized query. This will absolutely prevent issues SQL injection and also help with performance in many cases. Unfortunately, I don't believe you can create a paramaterized query for SQL Server using DAO. You would have to convert to ADO, which is best suited for sending queries to a SQL Engine other than Jet.
To use ADO you might have to add a reference to Microsoft ActiveX Data Objects by opening the VBA code window and selecting Tools -> References -> and checking the box next to it. Then your code would look something like this:
Dim Conn1 As ADODB.Connection
Dim Cmd1 As ADODB.Command
Dim Param1 As ADODB.Parameter
Rem Create and Open Connection Object.
Set Conn1 = New ADODB.Connection
Conn1.ConnectionString = "ODBC;DSN=SQLSERVER;"
Conn1.Open
Rem Create Command Object.
Set Cmd1 = New ADODB.Command
Cmd1.ActiveConnection = Conn1
Cmd1.CommandText = "UPDATE dbo.table1 SET firstname = ?"
Rem Create Parameter Object.
Set Param1 = Cmd1.CreateParameter(, adVarChar, adParamInput, 25)
Param1.Value = [Forms]![testform]![datainput]
Cmd1.Parameters.Append Param1
Set Param1 = Nothing
Rem Open Recordset Object.
Call Cmd1.Execute

connection.execute

`Dim con1 As New ADODB.Connection
Dim rs1 As New ADODB.Recordset
Dim sql1 As String
sql1 = "Update Balance set Balance_Amt = (Balance_Amt + " & a & ") where Company = " & Combo3.Text
con1.Execute (sql1)
"Can anyone say why this code does not work? It says No value for one or more required parameters"
I would guess that the immediate problem is that the SQL fragment
where Company = value
is invalid SQL. It should be quoted:
where Company = 'value'
But you really should be using SQL parameters.
I would have avoided this issue since the parameter would have been automatically quoted as necessary.
It would have made the code easier to read.
It would not be susceptible to SQL Injection attacks.
e.g.
Using cmd = new SqlCommand("UPDATE Balance SET Balance_Amt = (Balance_Amt + #a) WHERE Company=#company", con1)
cmd.Parameters.AddWithValue("#a", a)
cmd.Parameters.AddWithValue("#company", company)
cmd.ExecuteNonQuery()
End Using
Print out the sql statement and see if it is ok, copy/paste it to the sql management studio.
I think you are missing apostrophes around the string Combo3.Text.
Also consider what sql it would result in if Combo3.Text contains
'a'; delete from Balance

Issue with parameters assignment in ADODB Command

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

Resources