Display full query in statement with parameters - vb.net

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

Related

VB.NET - Best practice for handling complex SQL queries with optional parameters

In my application, the user should have the option to browse for orders that have been placed by customers - and it should be possible to apply several filters to the search. That means that I need a dynamic SQL query where a variable amount of parameters can be applied.
In a standard WinForms application, what would be the best way to handle this?
So far I've been working with TableAdapters and stored procedures but as far as I know, I can not use these with optional parameters. So for example if a user wants to see all customer orders, this is no problem. But it should also be possible to say for example "Show all orders that have been placed in the last 2 weeks and where at least one product contains the word 'gift code'". So date and product-name would be optional parameters but if I leave those empty in a stored procedure, I get an error.
To fix this, I started building my own queries in a separate class using SqlCommands and parameters. I dynamically generate the commandText for each command depending on the parameters passed into the function, then I add parameters to the SqlCommand, execute it and loop through the SqlDataReader to build a list of items that I will return to my program.
For example (simplified):
Dim cmd As New SqlCommand With {.Connection = con}
cmd.commandText = "SELECT o.id, o.customer_name, o.date, p.productName FROM orders o JOIN order_positions p ON o.id = p.order_id WHERE o.date >= #pDate"
cmd.Parameters.Add("#pDate", SqlDbType.DateTime).Value = searchDate
Dim reader As SqlDataReader = cmd.ExecuteReader()
Dim lstOrderItems As New List(Of OrderDisplayItem)
while reader.read
dim orderId as Integer = reader.Item(0)
dim customerName as String = reader.Item(1)
dim date as Date = reader.Item(2)
dim productName as String = reader.Item(3)
lstOrderItems.add(New OrderDisplayItem With{.id = orderId, .customerName = customerName, .date = date, .productName = productName})
End While
return lstOrderItems
Now obviously this is just to show how I proceed. In reality, I have to create additional loops because one order might contain one or multiple products etc.
My question would be: is this the right way to handle this? It feels like this whole class will grow really big because I have other queries too like looking up invoices, store sales and so on - and for every query I have to write these reader loops which I would have to modify all over again if a slight thing in my database changes.
Is it really not possible to handle this within Visual Studio tableAdapters?
That means that I need a dynamic SQL query where a variable amount of parameters can be applied.
No it doesn't. You can use a single query with a single set of parameters and simply provide NULL for those parameters you want to ignore if you structure your SQL like this:
SELECT *
FROM MyTable
WHERE (#Column1 IS NULL OR Column1 = #Column1)
AND (#Column2 IS NULL OR Column2 = #Column2)
Your VB code might then look something like this:
Using connection As New SqlConnection("connection string here"),
command As New SqlCommand(query, connection)
command.Parameters.Add("#Column1", SqlDbType.VarChar, 50).Value = If(TextBox1.TextLength = 0, CObj(DBNull.Value), TextBox1.Text)
command.Parameters.Add("#Column2", SqlDbType.VarChar, 50).Value = If(TextBox2.TextLength = 0, CObj(DBNull.Value), TextBox2.Text)
'...
End Using
When you provide NULL for a parameter, that effectively matches every record and that parameter is effectively ignored. You can do that with as many parameters as you like of whatever data type that you like.

Retrieving the Query used for a OleDBCommand

I'm currently using the following VB code to make a query against an Access Database, I would like to know is it possible to obtain what the SELECT statement that is being run and send that output to the console.
Dim QuestionConnectionQuery = New OleDb.OleDbCommand("SELECT Questions.QuestionID FROM Questions WHERE Questions.QuestionDifficulty=[X] AND ( Questions.LastDateRevealed Is Null OR Questions.LastDateRevealed < DateAdd('d',-2,Date() ) AND Questions.LastUsedKey NOT LIKE ""[Y]"" );", QuestionConnection)
QuestionConnectionQuery.Parameters.AddWithValue("X", questionDifficulty.ToString)
QuestionConnectionQuery.Parameters.AddWithValue("Y", strDatabaseKey)
Right now when I try to use: Console.WriteLine("Query: " & QuestionConnectionQuery.ToString)
I only get this:
Loop Question #1
Query: System.Data.OleDb.OleDbCommand
The short version comes down to this:
QuestionConnectionQuery.ToString
The QuestionConnectionQuery object is much more than just the text of your command. It's also the parameters, execution type, a timeout, and a number of other things. If you want the command text, ask for it:
QuestionConnectionQuery.CommandText
But that's only the first issue here.
Right now, your parameters are not defined correctly, so this query will never succeed. OleDb uses ? as the parameter placeholder. Then the order in which you add the parameters to the collection has to match the order in which the placeholder shows in the query. The code in your question just has X and Y directly for parameter placeholders. You want to do this:
Dim QuestionConnectionQuery AS New OleDb.OleDbCommand("SELECT Questions.QuestionID FROM Questions WHERE Questions.QuestionDifficulty= ? AND ( Questions.LastDateRevealed Is Null OR Questions.LastDateRevealed < DateAdd('d',-2, Date() ) AND Questions.LastUsedKey NOT LIKE ? );", QuestionConnection)
QuestionConnectionQuery.Parameters.Add("?", OleDbType.Integer).Value = questionDifficulty
QuestionConnectionQuery.Parameters.Add("?", OleDbType.VarChar, 20).Value = strDatabaseKey
I had to guess at the type and lengths of your parameters. Adjust that to match the actual types and lengths of the columns in your database.
Once you have made these fixes, this next thing to understand is that the completed query never exists. The whole point of parameterized queries is parameter data is never substituted directly into the sql command text, not even by the database engine. This keeps user data separated from the command and prevents any possibility of sql injection attacks.
While I'm here, you may also want to examine the WHERE conditions in your query. The WHERE clause currently looks like this:
WHERE A AND ( B OR C AND D )
Whenever you see an AND next to an OR like that, within the same parenthetical section, I have to stop and ask if that's what is really intended, or whether you should instead close the parentheses before the final AND condition:
WHERE A AND (B OR C) AND D
This will fetch the command text and swap in the parameter values. It isnt necessarily valid SQL, the NET Provider objects haven't escaped things yet, but you can see what the values are and what the order is for debugging:
Function GetFullCommandSQL(cmd As Data.Common.DbCommand) As String
Dim sql = cmd.CommandText
For Each p As Data.Common.DbParameter In cmd.Parameters
If sql.Contains(p.ParameterName) AndAlso p.Value IsNot Nothing Then
If p.Value.GetType Is GetType(String) Then
sql = sql.Replace(p.ParameterName,
String.Format("'{0}'", p.Value.ToString))
Else
sql = sql.Replace(p.ParameterName, p.Value.ToString)
End If
End If
Next
Return sql
End Function
Given the following SQL:
Dim sql = "INSERT INTO Demo (`Name`, StartDate, HP, Active) VALUES (#name, #start, #hp, #act)"
After parameters are supplied, you can get back this:
INSERT INTO Demo (`Name`, StartDate, HP, Active) VALUES ('johnny', 2/11/2010 12:00:00 AM, 6, True)
It would need to be modified to work with OleDB '?' type parameter placeholders. But it will work if the DbCommand object was created by an OleDBCOmmandBuilder, since it uses "#pN" internally.
To get or set the text of the command that will be run, use the CommandText property.
To print the results, you need to actually execute the query. Call its ExecuteReader method to get an OleDbDataReader. You can use that to iterate over the rows.
Dim reader = QuestionConnectionQuery.ExecuteReader()
While reader.Read
Console.WriteLine(reader.GetValue(0))
End While
reader.Close()
If you know the data type of the column(s) ahead of time, you can use the type-specific methods like GetInt32. If you have multiple columns, change the 0 in this example to the zero-based index of the column you want.

VB.NET Oracle SQL "INSERT INTO" with "RETURNING INTO" gives ORA-00933 Command Not Properly Ended

I need to update some code and as part of this I need to insert a row into a table and obtain the id (primary key) of the row just entered.
Have researched this and I believe I should be using RETURNING INTO and Oracle Parameters. I have used parameters in the past successfully to Insert values.
I have an INSERT statement that runs perfectly from VB.NET, but as soon as I add the text "" RETURNING id INTO :myId" I get ORA-00933 Command Not Properly Ended.
Here is a version of the code.
sql = "INSERT ... RETURNING id INTO :myId"
Connect()
Dim intRecsAffected As Integer = 0
Dim comm As OracleCommand = New OracleCommand(sql, _conn)
Dim param As OracleParameter
param = New OracleParameter()
param.ParameterName = ":myId"
param.OracleDbType = OracleDbType.Int32
param.Direction = ParameterDirection.Output ' Tried ReturnValue
comm.Parameters.Add(param)
intRecsAffected = comm.ExecuteNonQuery()
id = comm.Parameters(":myId").Value
Disconnect()
Any ideas?
I believe that your syntax is incorrect:
sql = "INSERT ... RETURNING id INTO myId"
Example below:
https://oracle-base.com/articles/misc/dml-returning-into-clause
Actually, realised what was going on. I cut my full SQL as it's quite long and there's some sensitive stuff in there.
The INSERT was using a SELECT rather than VALUES to get the values for the fields. That won't work - I am guessing because an INSERT with SELECT can add multiple rows even though in this case it won't.
Have re-written the SQL to use VALUES and the VB.Net code works fine.
Thanks to all who replied.

String.Format vs Parameter Values SQL Query

I'm trying to figure out if there is a better way to do this
Dim cmd As New SqlCommand
Dim sel As String
Dim obj As New DataHandler
sel = String.Format("SELECT * FROM Customers WHERE Country LIKE '{0}%'", txt_Input.Text)
cmd.CommandText = sel
Me.dgv_Customers.DataSource = obj.SqlDataRetriever(cmd)
Basically what im trying to do is have a textbox that whenever I type a letter, the grid refreshes itself by sending a Query to my SQL server searching for whatever its in the textbox using the LIKE() from SQL. I've been reading about SQL injection and so far everyone suggests to use parameter values (#value) for user input, but if I try to replace the {0} with that it doesn't work. I just wanna make sure that this is a valid way of doing this.
Thanks
Instead just concatenate the string like below. You should consider using parameterized query to avoid SQL Injection.
sel = "SELECT * FROM Customers WHERE Country LIKE '" + txt_Input.Text + "%'";
Use a parameterized query rather. See This Post
Dim cmd as New SqlCommand("SELECT * FROM Customers WHERE Country LIKE #param")
cmd.Parameters.Add("#param", txt_Input.Text +"%")

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.