VB .NET Paramaterized SQL call not correctly parsing IN clause arguments [duplicate] - sql

This question already has answers here:
Parameterize an SQL IN clause
(41 answers)
Closed 6 years ago.
So I am currently working on a project where I am looking to retrieve all data from a table matching a modifyable list on the screen. Here is a sample of the code that I am using to make the call
Public Sub GetAnimals(ByReg selectedTypes As String)
Dim strSql As String
Dim pselectedTypes As SqlClient.SqlParameter = New SqlClient.SqlParameter("#pselectedTypes", selectedTypes)
Dim strDBConnection As String = DBConnection.GetConnection.ConnectionString
Dim rs As DataSet = new DataSet
strSql = "SELECT * FROM PetInformation WHERE AnimalType IN (#pselectedTypes)"
Using sqlConn As SqlClient.SqlConnection = New SqlClient.SqlConnection(strDBConnection)
sqlConn.Open()
Using sqlcmd As SqlClient.SqlCommand = New SqlClient.SqlCommand
With sqlcmd
.Connection = sqlConn
.CommandTimeout = DBConnection.DLLTimeout
.CommandType = CommandType.Text
.CommandText = strSql
.Parameters.Add(pselectedTypes)
Using sqlda As SqlClient.SqlDataAdapter = New SqlClient.SqlDataAdapter(sqlcmd)
sqlda.Fill(rs)
End Using
End with
End Using
End Using
' Data Calculations
End Sub
This then makes the SQL query look something like this. For this example I'm using my list of animals as just cats and dogs.
exec sp_executesql N'SELECT * FROM PetInformation WHERE AnimalType IN (#pselectedTypes)',N'#pselectedTypes nvarchar(22),#pselectedTypes='''cat'',''dog'''
So everything looks all fine and dandy but when I go to run it and execute it I don't get any results back. If I change the sql to look like either one of these I get data back, but since this SQL is generated that's not really an option w/o switching away from parameterized sql, which I don't want to do.
exec sp_executesql N'SELECT * FROM PetInformation WHERE AnimalType IN (''cat'',''dog'')',N'#pselectedTypes nvarchar(22),#pselectedTypes='''cat'',''dog'''
or
SELECT * FROM PetInformation WHERE AnimalType IN ('cat','dog')
Any suggestions or ideas?

As far as I know, no db libraries allow parameterizing lists like you are trying. In those situations, you need to build your parameter list and query dynamically based on the number of elements in your list.

Related

How do I retrieve a value from an SQL query and store it in a variable in VB.NET?

I am trying to find the max product ID and store the value in a local variable "MaxID" and return this value. I am trying to convert the result of the query into an Integer type but I am not able to do it. Below is the code:
Public Function GetMaxID(ByVal TableName As String, ByVal ID As String) As Integer
Dim MaxID As Integer
Dim sqlquery As SqlCommand
Dim field_name As String = ID
Dim con As SqlConnection
con = New SqlConnection()
con.ConnectionString = "Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename='D:\Docs Dump\Work\Srinath\SrinathDB.mdf';Integrated Security=True;Connect Timeout=30"
con.Open()
Try
sqlquery = New SqlCommand("SELECT MAX( #field ) FROM #table ", con)
sqlquery.Parameters.AddWithValue("#field", field_name)
sqlquery.Parameters.AddWithValue("#table", TableName)
MaxID = CInt(sqlquery.ToString)
con.Close()
Return MaxID
Catch ex As Exception
Return 0
Exit Function
con.Close()
End Try
End Function
End Class
MaxID = CInt(sqlquery.ExecuteScalar())
You also should know about SqlCommand.ExecuteReader(), SqlCommand.ExecuteNonQuery() (for inserts/updates/deletes), and SqlDataAdapter.Fill().
Where you'll still have a problem is you can't use a parameter value for the table name or column name. The Sql Server engine has a "compile" step, where it has to be able to work out an execution plan, including permissions/security, at the beginning of the query, but variable names like #table and #field aren't resolved until later. It's not what actually happens, but think of it as if you had string literals in those places; imagine trying to run this:
SELECT MAX('ID') FROM 'MyTable'
MAX('ID') will always return the string value ID, and not anything from an ID column in any rows. But the MyTable part is not the correct place for a string literal, and such a query wouldn't even compile.
I also see people here from time to time try to create functions like GetMaxId(), and it's almost always misguided in the first place. If the intended use for this function is the same as what I usually see, you're setting up a major race condition issue in your application (one that probably won't show up in any testing, too). Sql Server gives you features like identity columns, sequences, and the scope_identity() function. You should be using those in such a way that new IDs are resolved on the server as they are created, and only (and immediately) then returned to your application code.
But that issue aside, here's a better way to structure this function:
Public Class DB
Private conString As String = "Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename='D:\Docs Dump\Work\Srinath\SrinathDB.mdf';Integrated Security=True;Connect Timeout=30"
'You want a separate method per-table that already knows the table and column names
Public Function GetMyTableMaxID() As Integer
Dim sql As String = "SELECT MAX(ID) FROM MyTable"
Using con As New SqlConnection(conString), _
sqlQuery As New SqlCommand(sql, con)
'Parameters would go here.
'Do NOT use AddWithValue()! It creates performance issues.
' Instead, use an Add() overload where you provide specific type information.
'No exception handling at this level. The UI or business layers are more equipped to deal with them
con.Open()
Return CInt(sqlQuery.ExecuteScalar())
End Using
'No need to call con.Close()
'It was completely missed in the old code, but handled by the Using block here
End Function
End Class

Reading from hidden Oracle Tables with VBA ADODB

I'm trying to read from different Tables on a Oracle Database using VBA and Excel.
Usually when read the tables I run something like:
Dim rs As Object
Set rs = CreateObject("ADODB.Recordset")
Dim query As String: query = "SELECT * FROM OBJ_NAME"
rs.Open query, con
However, this does not work for all the tables. Using SQL Developer i usually run at the beginning:
exec session#.open_session();
After which I can read any table. Is there a way to run this command with an ADODB.Recordset in the beginning too? Just replacing the SQL Query with the command did not work.
Or is there different way to read this 'hidden' Tables?
Try something like that:
Dim rs As Object
Set rs = CreateObject("ADODB.Recordset")
Dim query As String: query = "begin session#.open_session; end;"
With rs
.ActiveConnection = con
.Open query, con
End With
Set connectToDB = con
This should work using VBA. Let me if it worked :)

Display full query in statement with parameters

I have some trouble to debugging my query in vb.net.
I just wanna get full query with value inside it. I use parameters to add value in my query.
This is my code:
'Select query
Dim stm As String = "SELECT *, FORMAT(NOW(),'DD-MM-YYYY HH:NN:SS') as waktu FROM [user] WHERE [username]=? AND [password]=? AND active=TRUE"
Dim cmd As OleDbCommand = New OleDbCommand(stm, db)
'Parameters
Using md5Hash As MD5 = MD5.Create()
Dim pwd As String = GetMd5Hash(md5Hash, Me.tx_password.Text)
cmd.Parameters.Add("p1", OleDbType.VarChar, 25).Value = Me.tx_username.Text
cmd.Parameters.Add("p2", OleDbType.VarChar, 32).Value = pwd
End Using
'Execute Query
MsgBox(stm)
Dim reader As OleDbDataReader = cmd.ExecuteReader(CommandBehavior.SingleRow)
With this code, I just get result like this:
SELECT *, FORMAT(NOW(),'DD-MM-YYYY HH:NN:SS') as waktu FROM [user]
WHERE [username]=? AND [password]=? AND active=TRUE
How to get result like this:
SELECT *, FORMAT(NOW(),'DD-MM-YYYY HH:NN:SS') as waktu FROM [user]
WHERE [username]='adminUser' AND [password]='adminPassword' AND active=TRUE
Parameters are not concatenated into the command, they are sent separately to the database. Otherwise there will be no difference between using a parameterized query and using a concatenated one. (see the answer to a similar question here.)
This means that in order to debug your queries you will have to work a little harder then if your sql was concatenated by the vb.net code.
If your database supports stored procedure I recommend you start using them instead of parameterized queries. You will probably gain performance, and it will be easier to debug.
If not, you can copy the query as is to the sql editor, and use one of the debugger options to get the values of the parameters and copy them one by one to the sql editor.
Place this code below you have added the parameters and you'll have in debugSQL the SQL statement which will be executed
Dim debugSQL As String = cmd.CommandText
For Each param As SqlParameter In cmd.Parameters
debugSQL = debugSQL.Replace(debugSQL.ParameterName, debugSQL.Value.ToString())
Next

Pass parameter to a query from another query in Access

I have a parameterized query GET_CUSTOMER:
SELECT * FROM Customer WHERE id = [customer_id]
I want to call this query from another query and pass it a parameter:
SELECT * FROM GET_CUSTOMER(123)
Note the above code is not valid, it is here to give you an idea of what I'm trying to do. Is it possible to do this in MS Access?
UPDATE 1:
The queries I posted are for example. The actual queries are much more complex. I know I can use table joins, but in my specific case it would be much easier if I could run parameterized queries inside other queries (that are parameterized as well). I can't use access forms because I'm using access with my .NET application.
This is how I end up solving this with help of https://stackoverflow.com/a/24677391/303463 . It turned out that Access shares parameters among all queries so there is no need to specifically pass parameters from one query to another.
Query1:
SELECT * FROM Customer WHERE ID > [param1] AND ID < [param2]
Query2:
SELECT * FROM Query1
VB.NET code:
Dim ConnString As String = "Provider=Microsoft.Jet.OleDb.4.0;Data Source=Database.mdb"
Dim SqlString As String = "Query2"
Using Conn As New OleDbConnection(ConnString)
Using Cmd As New OleDbCommand(SqlString, Conn)
Cmd.CommandType = CommandType.StoredProcedure
Cmd.Parameters.AddWithValue("param1", "1")
Cmd.Parameters.AddWithValue("param2", "3")
Conn.Open()
Using reader As OleDbDataReader = Cmd.ExecuteReader()
While reader.Read()
Console.WriteLine(reader("ID"))
End While
End Using
End Using
End Using
You can build the SQL on the fly.
MyID = prompt or get from user some ID
strSQl = "Select * from tblCustomer where ID in " & _
"(select * from tblTestCustomers where id = " & MyID
So you can nest, or use the source of one query to feed a list of ID to the second query.

Is it possible to insert an entire VB.NET DataTable into a SQL Server at once

I have a SQLClient.DataSet in VB.NET, and I want to insert the entire thing into a SQL Server table without having to do the following:
For Each dr as Datarow in MyDataset
Dim sc As New SqlCommand("INSERT INTO MyNewTable " & _
"VALUES (#column1, #column2)", MyDBConnection)
sc.Parameters.AddWithValue("#column1", dr.Item(0))
sc.Parameters.AddWithValue("#column2", dr.Item(1))
sc.ExecuteNonQuery()
Next
Since I've got close to a million rows (all pretty skinny, so it's not much space), I obviously don't want to run this loop and generate a million INSERT statements.
I know that one option is to use a linked server when I initially fetch the data, since it's coming from another SQL Server, and just have it to the INSERT from there. However, if I already have the data in my application, is there a more efficient way to bulk insert it? Can I somehow pass the DataTable as a parameter to SQL Server and have it sort it out and insert the rows?
try with SqlBulkCopy
With SQL Server 2008 you can use Table-Valued Parameters:
Dim sc As New SqlCommand(
"INSERT INTO MyNewTable (field1, field2,...)"&
"SELECT field1, field2,... FROM #MyTable;", MyDBConnection)
sc.Parameters.AddWithValue("#MyTable", MyDataset)
sc.ExecuteNonQuery()
Use the SqlDataAdapter's InsertCommand to define your Insert query. Then call the DataAdapter's Update Method with your dataset as a parameter to have it push the data.
Something like:
Dim DA As SqlDataAdapter = New SqlDataAdapter
Dim Parm As New SqlParameter
DA.InsertCommand = New SqlCommand("Insert Into tbl1(fld0, fld1, fld2) Values(#fld0, #fld1, #fld2)", conn)
Parm = DA.InsertCommand.Parameters.Add(New SqlParameter ("#fld0", NVarChar, 50, "fld0"))
Parm = sqlDA.InsertCommand.Parameters.Add(New SqlParameter ("#fld1", SqlDbType.NVarChar, 50, "fld1"))
Parm = sqlDA.InsertCommand.Parameters.Add(New SqlParameter ("#fld2", SqlDbType.NVarChar, 50, "fld2"))
DA.Update(dataset1, "tbl1")
You could call .WriteXML() on the DataSet and dump that into the database in one insert.
A way simplier way is to use a table adapter. Then you can use the Fill method to give a datatable as argument :
Dim oStronglyTypedTable As StronglyTypedDataTable = GetTable() 'A custom function that creates your table from wherever you want)
If Not oStronglyTypedTable Is Nothing Then
Using oAdapter As New StronglyTypedTableAdapter
Dim res As Integer = oAdapter.Update(oStronglyTypedTable)
MsgBox(res & " rows have been updated.")
End Using
End If
Do not forget to change your Database "Copy to Output Directory" property to "Do net copy" and set your connection string properly...