`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
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 trying to create an SQL statement using user-supplied data. I use code similar to this in C#:
var sql = "INSERT INTO myTable (myField1, myField2) " +
"VALUES ('" + someVariable + "', '" + someTextBox.Text + "');";
var cmd = new SqlCommand(sql, myDbConnection);
cmd.ExecuteNonQuery();
and this in VB.NET:
Dim sql = "INSERT INTO myTable (myField1, myField2) " &
"VALUES ('" & someVariable & "', '" & someTextBox.Text & "');"
Dim cmd As New SqlCommand(sql, myDbConnection)
cmd.ExecuteNonQuery()
However,
this fails when the user input contains single quotes (e.g. O'Brien),
I cannot seem to get the format right when inserting DateTime values and
people keep telling me that I should not do this because of "SQL injection".
How do I do it "the right way"?
Use parameterized SQL.
Examples
(These examples are in C#, see below for the VB.NET version.)
Replace your string concatenations with #... placeholders and, afterwards, add the values to your SqlCommand. You can choose the name of the placeholders freely, just make sure that they start with the # sign. Your example would look like this:
var sql = "INSERT INTO myTable (myField1, myField2) " +
"VALUES (#someValue, #someOtherValue);";
using (var cmd = new SqlCommand(sql, myDbConnection))
{
cmd.Parameters.AddWithValue("#someValue", someVariable);
cmd.Parameters.AddWithValue("#someOtherValue", someTextBox.Text);
cmd.ExecuteNonQuery();
}
The same pattern is used for other kinds of SQL statements:
var sql = "UPDATE myTable SET myField1 = #newValue WHERE myField2 = #someValue;";
// see above, same as INSERT
or
var sql = "SELECT myField1, myField2 FROM myTable WHERE myField3 = #someValue;";
using (var cmd = new SqlCommand(sql, myDbConnection))
{
cmd.Parameters.AddWithValue("#someValue", someVariable);
using (var reader = cmd.ExecuteReader())
{
...
}
// Alternatively: object result = cmd.ExecuteScalar();
// if you are only interested in one value of one row.
}
A word of caution: AddWithValue is a good starting point and works fine in most cases. However, the value you pass in needs to exactly match the data type of the corresponding database field. Otherwise, you might end up in a situation where the conversion prevents your query from using an index. Note that some SQL Server data types, such as char/varchar (without preceding "n") or date do not have a corresponding .NET data type. In those cases, Add with the correct data type should be used instead.
Why should I do that?
It's more secure: It stops SQL injection. (Bobby Tables won't delete your student records.)
It's easier: No need to fiddle around with single and double quotes or to look up the correct string representation of date literals.
It's more stable: O'Brien won't crash your application just because he insists on keeping his strange name.
Other database access libraries
If you use an OleDbCommand instead of an SqlCommand (e.g., if you are using an MS Access database), use ? instead of #... as the placeholder in the SQL. In that case, the first parameter of AddWithValue is irrelevant; instead, you need to add the parameters in the correct order. The same is true for OdbcCommand.
Entity Framework also supports parameterized queries.
VB.NET Example Code
This is the example code for the wiki answer in vb.net, assuming Option Strict On and Option Infer On.
INSERT
Dim sql = "INSERT INTO myTable (myField1, myField2) " &
"VALUES (#someValue, #someOtherValue);"
Using cmd As New SqlCommand(sql, myDbConnection)
cmd.Parameters.AddWithValue("#someValue", someVariable)
cmd.Parameters.AddWithValue("#someOtherValue", someTextBox.Text)
cmd.ExecuteNonQuery()
End Using
UPDATE
Dim sql = "UPDATE myTable SET myField1 = #newValue WHERE myField2 = #someValue;"
' see above, same as INSERT
SELECT
Dim sql = "SELECT myField1, myField2 FROM myTable WHERE myField3 = #someValue;"
Using cmd As New SqlCommand(sql, myDbConnection)
cmd.Parameters.AddWithValue("#someValue", someVariable)
Using reader = cmd.ExecuteReader()
' ...
End Using
' Alternatively: Dim result = cmd.ExecuteScalar()
' if you are only interested in one value of one row.
End Using
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.
So my question actually has more than 2 parts but all linked with protecting my application against SQL injection.
Lately I am rebuilding my application to make it SQL injection proof. My application is written in VB.NET and I am using Parameters.Add to protect the queries.
UPDATE first question:
Found out that the NVARCHAR datatype is equal to the WChar datatype in OLEDB. When hovering over WChar it says in the description it is WSTR.
I am having a few questions though of which I cant find the answer on the web.
The first one, and I guess the easiest one is what the datatype in OleDB is of the NVARCHAR. When doing some research I found this link https://msdn.microsoft.com/en-us/library/ms130984.aspx which states that in OleDB the DBTYPE_WSTR datatype is the same as the NVARCHAR (which I am using in my t-SQL server). However when adding parameters in my VB application there is no such datatype available (WSTR). Some options are the VarChar, VarWChar, WChar, LongVarChar, LongVarWChar.
The question is, which one is the right one? I assume it is VarChar.
-------UPDATED UNTIL HERE-----
The second question is about how to handle parameters when a IF condition is involved in the query (see the example below).
sSQL1 = "SELECT [Omschrijving], [Nummer], [OrderType], [Orderdatum] FROM [Orders] WHERE [OrderDatum]>dateadd(""ww"",-4,GetDate()) "
If sOrderType <> "" Then
sSQL1 &= "AND ([OrderType]=""" & sOrderType & """)"
End If
sSQL1 &= "ORDER BY [Orders].[Nummer] DESC;"
lstOrder.Items.Clear()
Try
OpenConn()
cmd = New OleDbCommand(sSQL1, cn)
dr = cmd.ExecuteReader
While dr.Read()
lstOrder.Items.Add(dr("nummer") & " - " & dr("Omschrijving"))
End While
Catch
Debug.Print(sSQL1)
MsgBox("Error: " & sSQL1)
End Try
dr.Close()
CloseConn()
How should I rewrite this with using parameters? Do I need to instantiate the cmd = New OleDbCommand(sSQL1, cn) in the if condition together with the Parameters.Add ? But I guess this would also mean I have to open the connection earlier?
I thought about rewriting in something like this:
OpenConn()
sSQL1 = "SELECT [Omschrijving], [Nummer], [OrderType], [Orderdatum] FROM [Orders] WHERE [OrderDatum]>dateadd(""ww"",-4,GetDate()) "
If sOrderType <> "" Then
sSQL1 &= "AND ([OrderType]=?)"
cmd = New OleDbCommand(sSQL1, cn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add("#OrderType", OleDbType.VarChar).Value = sOrderType
Else
cmd = New OleDbCommand(sSQL1, cn)
End If
sSQL1 &= "ORDER BY [Orders].[Nummer] DESC;"
lstOrder.Items.Clear()
Try
dr = cmd.ExecuteReader
While dr.Read()
lstOrder.Items.Add(dr("nummer") & " - " & dr("Omschrijving"))
End While
Catch
Debug.Print(sSQL1)
MsgBox("Error: " & sSQL1)
End Try
No idea if this works though (and for now I sadly can't test it).
Another question is: Do I need to use parameters for the dateadd (see query above) in the query as well? And if I do, how?
The last question is, when I am using the Parameters.Add, is it best to give a size value as well or is this not necessary?
Thank you in advance!
My answer to your first question that
I think this is the list of equal data-types you want:
SQL Server | OLEDB (ADO => ad+...)
-----------+---------------
char | Char
nchar | WChar
varchar | VarChar
nvarchar | VarWChar
text | LongVarChar
ntext | LongWVarChar
I had confusion with my code:
Dim sqladapter As SqlDataAdapter = New SqlDataAdapter()
Dim sqlcmd As SqlCommand = New SqlCommand()
sqlcmd = New SqlCommand("SELECT login, pass from Table1 where login=" & login.Text & "and pass='" & password.Text.ToString() & "';", connect)
Dim dr As SqlDataReader = sqlcmd.ExecuteReader()
Dim dt As DataTable = New DataTable()
dt.Load(dr)
If (dt.Rows.Count = 1) Then
'Display welcome page or do some action here.
Now, my question is, is there any other way of doing Rows.Count==1 . I'm feeling that it is very wrong and makes no sense at.
How do you verify from database that a user has only one valid record in table other than counting rows.
Thanks in Advance :)
(Please ask me before reporting question)
You have two problems, one is called Sql Injection and you have already numerous links that explain why is really bad. Another one is the plain text password stored in your database. This is a big security concern because everyone that has the possibility to look at your database could see the passwords of your users. (The gravity of this, of course, is linked to the nature of your application but cannot be downplayed) See this link for an answer on how to hash a string (a password) and get its encrypted version to store in the database instead of the plain text.
Finally the code you use could be changed to avoid both the SqlDataAdapter and the DataTable.
Just use an ExecuteScalar against an IF EXIST query that return just 1 if the user/password exists or zero if not
Dim cmdText = "IF EXISTS(SELECT 1 FROM Table1 WHERE login = #log AND pass = #pwd) " & _
"SELECT 1 ELSE SELECT 0"
using connect = new SqlConnection(connectionstring)
using sqlcmd = New SqlCommand(cmdText, connect)
connect.Open()
sqlcmd.Parameters.AddWithValue("#log", login.Text)
sqlcmd.Parameters.AddWithValue("#pwd", password.Text) ' <- Subst with a call to an hash function
Dim exists = Convert.ToInt32(sqlcmd.ExecuteScalar())
if exists = 1 Then
'Display welcome page or do some action
else
end if
End Using
End Using
There is only one way to answer to the question and its to count rows. The different solution would be to count them in database. For example you could write stored procedure that takes username and password and returns boolean this way you would drag less data.
As a side note there is potential sql injection in your code. You should not store clear password in database. You should return the whole row and match hash of the password from database to the hash of the paasword that you get from UI.