![]() |
Click here to advertise with us
|
|
|||||||
| Database General Discussion General discussion on database related topics |
![]() |
|
|
LinkBack | Thread Tools | Display Modes |
|
||||
|
OK, so I'm frequently found, on the forums, bollocking people for writing dinosaur SQL queries that look like this:
Code:
Dim oc as New OracleCommand( _
"SELECT a || '" & TextBox1.Text & "' as Thing FROM table1 " & _
"WHERE col1 = '" & TextBox2.Text.Replace("'"c, "''") & "' OR c" & _
"ol3 LIKE '" & TextBox3.Text & "%' AND col4date = #" & _
dateTimePicker1.Value.ToString("MM/dd/yyyy HH:mm:ss") & "#" & _
" AND someListCol5 NOT IN('" & TextBox6.Text.Split(","c)(0) & "'," & _
"'" & s & "','" & CInt(iText).ToString("000") &"')")
And it's hardly surprising, because though it might work, after you spend ages debugging which comma is in the wrong place.. It looks truly awful, doesnt it? If another developer wrote that (and thats a relatively simple SELECT statement; you should see some of the SQLs I have to write; when printed they go on for tens of pages..) you would, should, go "eugh!" Heck, you should probably go "eugh!" even if it's your baby.. because it's virually unmaintainable and being the proper, forward looking coders we are, we should be seeking to write nice code that's readable and easy to pick up again in 3 weeks time when we've forgotten we ever wrote it. There are other good reasons for avoiding writing garbage SQLs like this:
It's something similar to a computer program itself. Youre a VB coder, you can see the advantages of having variables: MessageBox.Show(My.Settings.WelcomeMessage) It saves you having to recompile the whole program every time you want to use a different string. It would really be a drag to recompile the whole thing for something as trivial, eh? So, the same is true of SQLs.. every time you send a non-parameterized SQL to the server, it needs to be re-compiled. What a drag. "SELECT * FROM table WHERE lastName = " & Textbox1.Text Every time you change Textbox1.Text, you make a whole new query which must be recompiled all over again by the server. People dont really care because doing so is relatively quick, but it's a bit lame, no? Introduce the parameter. Just like a variable in your code, it sa way of saying to the server "Hey.. this part might change!" Here are some examples of parameterized queries in the Big 3 databases in use today: Access (parameters must be created in order they appear in SQL): "INSERT INTO table1 VALUES(?, ?, ?)" "UPDATE table1 SET col1 = ? WHERE col2 = ?" "SELECT * FROM table1 WHERE col1 = ? OR col2 LIKE ?" "DELETE FROM table1 WHERE col1 = ?" SQLServer (supports named parameters): "INSERT INTO table1 VALUES(@param1, @param2, @param3)" "UPDATE table1 SET col1 = @param1 WHERE col2 = @paramTwo" "SELECT * FROM table1 WHERE col1 = @someCol1 OR col2 LIKE @theColour" "DELETE FROM table1 WHERE col1 = @idToDelete" Oracle (like SQLS, but uses : instead of @): "INSERT INTO table1 VALUES(:param1, :p2, :paraThree)" "UPDATE table1 SET col1 = :aColumn WHERE col2 = :paramTwo" "SELECT * FROM table1 WHERE col1 = :someCol1 OR col2 LIKE :theColour" "DELETE FROM table1 WHERE col1 = :idToDelete" The database command object (whichever you are using) has a .CommandText property, and that is where your query text goes. Youre already faimilar with that, I'm sure. If you like to add the SQL on creation, that too, is fine: Dim oc as New OracleCommand("SELECT * FROM table WHERE lastName = :pLastName", theConnection) or oc.CommandText = "SELECT * FROM table WHERE lastName = :pLastName" The new bit is, of course, in bold. I've chosen a name of pLastName for this oracle parameter though it can be anything I like. I use this because p=parameter, and LastName is what i'm parameterizing. I could have called it :LastName, but then it might have looked a bit too much like the column name LastName and might be confusing in a big, long SQL. If i'd been using SQLServer, i'd have used @pLastName If I'd been using Acces, I personally would have used ? but Access does support named parameters in thsi style too. However, you MUST add the parameters in the order they appear in the SQL, whether you use ? or @Named Adding parameters? Huh? Well.. what I mean by this is, that now we have told our database command what the parameters names are and where they are to be used in code, you need to fill in the rest of the blanks (type? size? value?) We do this through the command's parameters Collection: Dim oc as New OracleCommand("SELECT * FROM table WHERE lastName = :pLastName", theConnection) oc.Parameters.Add("pLastName", OracleType.VarChar) In the bold line, we tell the command that the parameter named pLastName is of string type. Why? because LastName is a string column. Here are some other types: Dim oc as New OracleCommand("SELECT * FROM table WHERE birthDayDate >= :pBirthday", theConnection) oc.Parameters.Add("pBirthday", OracleType.DateTime) Dim oc as New OracleCommand("SELECT * FROM table WHERE age >= :pAge", theConnection) oc.Parameters.Add("pAge", OracleType.Number) Great! No more messing about converting a date to a string, back to a date to a whatever.. It can stay as a date all the way through, no timezone fuss, no confusion over whether 03/01/2007 is march or january. Brilliant. You may have noticed I didnt put a : at the start of the name when i was calling Parameters.Add. In my experience, with oracle, it doesnt matter. I doubt it does with any other drivers either, but if you hit problems, try it with/without. Note you can use the same parameter more than once in the SQL and you DONT add it twice: Dim oc as New OracleCommand("SELECT * FROM table WHERE lastName = :pLastName OR maidenName = :pLastName", theConnection) oc.Parameters.Add("pLastName", OracleType.VarChar) We arent there 100% yet. So far, I've told you how to set up your command.. its ready to go, but we havent done anything else. A very important point i want to note is that YOU ONLY DO THIS SETUP ONCE. Do NOT make a new command, and add a load of new parameters every time you click the button. DOnt even think of putting database code in a button handler. Do it somewhere in its own module or class, do it once and leave it. Remember that you have created VARIABLES. You dont need to tear down the whole command and recreate everything just to vary the values.. So how do we do that? simply: oc.Parameters("pLastName").Value = "Smith" That command is ready for executing now. So we run it for Smith. What happens when we want to run it again for Jones? oc.Parameters("pLastName").Value = "Smith" oc.ExecuteQuery() oc.Parameters("pLastName").Value = "Jones" oc.ExecuteQuery() Right now the database is thanking us, for not making it recompile a whole new command text just because the text changed to jones Things That Cannot Be Parameterised You cannot parameterise column or table names: "SELECT pColName FROM :pTableName WHERE :pOtherCol = :pValue" For the same reason that when you write: Dim s as String That variable is called s in the code now, you have to recompile to change it. The only thing you can do, is change its value. That variable, no matter what its value, will always be called s in your code. Remember: only values. If you want to change the tablename you will have to compile another query.. but then, if you have 2 or more tables whose structure is so similar that you think oyu should be able to parameterize the name in a query.. then your database normalisation possibly is flawed.. That mess I wrote at the start: Code:
Dim oc as New OracleCommand( _
"SELECT a || '" & TextBox1.Text & "' as Thing FROM table1 " & _
"WHERE col1 = '" & TextBox2.Text.Replace("'"c, "''") & "' OR c" & _
"ol3 LIKE '" & TextBox3.Text & "%' AND col4date = #" & _
dateTimePicker1.Value.ToString("MM/dd/yyyy HH:mm:ss") & "#" & _
" AND someListCol5 NOT IN('" & TextBox6.Text.Split(","c)(0) & "'," & _
"'" & s & "','" & CInt(iText).ToString("000") &"')")
Becomes much nicer: Code:
Dim oc as New OracleCommand( _ "SELECT a || :pThingSuffix as Thing FROM table1 " & _ "WHERE col1 = :pTextBox2 OR col3 LIKE :pTextBox3 AND " & _ "col4date = :pSomeDate " & _ " AND someListCol5 NOT IN(:pInList1, :pInList2, :pInList3)" Remember; there is now no real reason to write anything other than a parameterized query. They can be cached, they are immune to SQL Injection, they dont mess up your dates, they can send data that cant be represented as string (binary) and they only need setting up once. Thats good news for your application performance and good news for the database server performance. Use them! :)
__________________
DW1: Data Walkthroughs 1.1...DW2: Data Walkthroughs 2.0...DDS: The DataSet Designer Surface...ANO: ADO.NET2 Orientation...DAN: Deeper ADO.NET...DNU...PQ |
|
||||
|
This thread (sticky) was split and discussion continues here: further discussions about parameterized queries (PQ)
__________________
Some useful links: Learning videoes, Code Samples, WMI Code Creator, MSDN, The Code Project, WindowsClient.net, ASP.net, W3 Schools, Regular-Expressions.info, GDI+ FAQ
How to format posts with code blocks etc - present the problem/post properly ![]() |
|
||||
|
In the ensuing debate from the original advisory, a couple of things came up. People asked how things like dynamic searches could be achieved. There are two ways that allow the use of parameters, and I'll present some code.
To write a query that can be used by the dataset designer (i.e. not dynamically changing the content of the string variable holding the SQL) create a query like this (SQLServer syntax, use ? for Access, ?name for MySql and :name for Oracle) SELECT * FROM table WHERE (col1 = @col1 OR @col1 IS NULL) AND (col1 = @col2 OR @col2 IS NULL) AND (col1 = @col3 OR @col3 IS NULL) Now just pass a null/Nothing into any parameter you wish to wildcard: myTableAdapter.FillByXXX(dt, Nothing, "Col2 Value", 3) You can get a bit more clever with your datatypes too. For strings, some optimisations can be made to have the SQL look simpler: SELECT * FROM table WHERE (col1 LIKE @col1 AND col2 LIKE @col2 AND col3 LIKE @col3) Now just pass a string "%" to any parameter to wildcard it: myTableAdapter.FillByXXX(dt, "%", "Col2 Value", "3") - The next level takes us back to building an SQL string dynamically using string concatenation. The basic premise is that, if you can write a routine that concatenates a value into an SQL string, you can easily write a routine that concatenates a parameter name into a string: Code:
'pseudocode !
Dim x as New DbCommand("SELECT * FROM table WHERE 1=1 ")
For Each parameter_you_want_to_add
x.CommandText &= " AND columnName = @parametername"
x.Parameters.AddWithValue("@parametername", value)
Next
'real code; Oracle example.
Dim oc as New OracleCommand("SELECT * FROM orders WHERE 1=1 ")
'I normally use bound data, so to ask the user for what they wish to
'search, I clear the datatable currently bound, then add one row into
'which they type their search terms
Dim srchTerms as MyDataRow = myBoundTable.Rows(0)
For Each dc as DataColumn in srchTerms.Table.Columns
If srchTerms.IsNull(dc) Then Continue
Dim cName as String = dc.Name
Dim pName as String = ":p" + dc.Name
Dim pVal as Object = srchTerms.Item(dc)
If TypeOf pVal Is String Then
oc.CommandText += string.Format("AND {0} LIKE {1}", cName, pName)
Else 'can add other type checks here
oc.CommandText += string.Format("AND {0} = {1}", cName, pName)
End If
oc.Parameters.AddWithValue(pName, pVal)
Next
SELECT * FROM table WHERE 1=1 AND parameter1 = @parameter1 AND … And a parameters collection with ready to use values.
__________________
DW1: Data Walkthroughs 1.1...DW2: Data Walkthroughs 2.0...DDS: The DataSet Designer Surface...ANO: ADO.NET2 Orientation...DAN: Deeper ADO.NET...DNU...PQ |
![]() |
| Bookmarks |
| Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
| Thread Tools | |
| Display Modes | |
|
|