• Anonymous (unregistered) in reply to Cyresse
    Cyresse:
    Anonymous:
    Of course, the whole procedure body could (and should) consist of a single SELECT statement, the format of which is so obvious that it's not even worth typing it in here.


    For us nubs who haven't had to use overly much SQL yet, could you post the single SELECT statement?

    (Or better yet, a link to a guide to good and speedy ways to use SQL? Or a link to bad and slow ways, purely for entertainments sake? Like the UI halls of shame...)



    Hopefully this isn't too late for you to see it, but the obvious query (to exactly preserve the content of current results) is shown below.  There is no need for ISNULL or CASE, and they don't actually seem appropriate here, unless a change in logic is required.

    This would almost certainly result in a table scan, but the current solution does that and then some.  If you wanted to be able to make use of indexes for particular search criteria, you would need to generate the WHERE clause on the fly.

    SELECT sProjectID, sProjectName, ..., 0 AS iFoundFlag -- remove iFoundFlag if callers don't reference it
    FROM tbl_Projects
    WHERE (<font>LEN</font>(@sProjectID) = 0 OR <font>LTRIM</font>(<font>RTRIM</font>(sProjectID)) = <font>LTRIM</font>(<font>RTRIM</font>(@sProjectID)))

    -- 4 more selection criteria

    AND (<font>LEN</font>(@sProjectName) = 0 OR sProjectName <font>LIKE</font> @sProjectName)


  • (cs) in reply to Anonymous

    Before relying on that method you might want to check it.

    DECLARE @sProjectID AS VARCHAR(32)
    SELECT LEN(@sProjectID)

    -----------
    NULL

    (1 row(s) affected)


  • (cs) in reply to Anonymous
    Anonymous:


    SELECT sProjectID, sProjectName, ..., 0 AS iFoundFlag -- remove iFoundFlag if callers don't reference it
    FROM tbl_Projects
    WHERE (<FONT size=+0>LEN</FONT>(@sProjectID) = 0 OR <FONT size=+0>LTRIM</FONT>(<FONT size=+0>RTRIM</FONT>(sProjectID)) = <FONT size=+0>LTRIM</FONT>(<FONT size=+0>RTRIM</FONT>(@sProjectID)))

    -- 4 more selection criteria

    AND (<FONT size=+0>LEN</FONT>(@sProjectName) = 0 OR sProjectName <FONT size=+0>LIKE</FONT> @sProjectName)



    "Clever irony" or just plain stupid?   That's some scary lookin' SQL my friend!  Then again, I guess the beauty of code like this is that you don't have to worry about bothersome database tasks like setting up indexes or relations or anything like that ...

  • (cs) in reply to SeekerDarksteel
    Anonymous:

    I am also fairly inexperienced in SQL, but whenever I've had a situation where I needed a dynamic query with optional parameters I also use something similar to LIKE "%" & param1 & "%".  <font color="#000000">Does running a query like this come with any sort of performance hit over doing it some other way?  </font>



    Warning; if "param1" is coming straight from the outside world, you're ripe for an SQL injection attack. Stop it now.
  • (cs) in reply to rsynnott
    rsynnott:
    Anonymous:

    I am also fairly inexperienced in SQL, but whenever I've had a situation where I needed a dynamic query with optional parameters I also use something similar to LIKE "%" & param1 & "%".  <FONT color=#000000>Does running a query like this come with any sort of performance hit over doing it some other way?  </FONT>



    Warning; if "param1" is coming straight from the outside world, you're ripe for an SQL injection attack. Stop it now.

    rsynnott --

    No, it is NOT.  That is completely wrong, there is absolutely no danger at all of sql injection. It is scary how many people really have no idea what sql injection truly means. 

    The biggest issue with the code shown, as myself and others explained, is the performance.

    Typically, for dynamic criteria on a varchar() column, I tend to prefer:

    WHERE Column LIKE COALESCE(@Value,'%')

    which in many cases performs better than using an OR. 

  • (cs) in reply to Jeff S
    Jeff S:
    Anonymous:


    SELECT sProjectID, sProjectName, ..., 0 AS iFoundFlag -- remove iFoundFlag if callers don't reference it
    FROM tbl_Projects
    WHERE (<font size="+0">LEN</font>(@sProjectID) = 0 OR <font size="+0">LTRIM</font>(<font size="+0">RTRIM</font>(sProjectID)) = <font size="+0">LTRIM</font>(<font size="+0">RTRIM</font>(@sProjectID)))

    -- 4 more selection criteria

    AND (<font size="+0">LEN</font>(@sProjectName) = 0 OR sProjectName <font size="+0">LIKE</font> @sProjectName)



    "Clever irony" or just plain stupid?   That's some scary lookin' SQL my friend!  Then again, I guess the beauty of code like this is that you don't have to worry about bothersome database tasks like setting up indexes or relations or anything like that ...



    Why is this SQL statement so "scary?"  It doesn't seem all that complicated to me.  The only thing that looks a little scary to me are some of the where conditions.  But in this case, he was just copying those conditions from the original post.

    Anyway, I'm not trying to be snide; I'd just like some clarification if you care to give it.
  • (cs) in reply to Jeff S
    Jeff S:

    rsynnott --

    No, it is NOT.  That is completely wrong, there is absolutely no danger at all of sql injection. It is scary how many people really have no idea what sql injection truly means. 

    The biggest issue with the code shown, as myself and others explained, is the performance.

    Typically, for dynamic criteria on a varchar() column, I tend to prefer:

    WHERE Column LIKE COALESCE(@Value,'%')

    which in many cases performs better than using an OR. 



    Um.

    Yes it is.

    Anytime you simply concatenate a value onto a SQL command string you subject yourself to an injection attack.  That's the basis of an injection attack.  That's it.  No more, no less.  The amount of damage that can be done varies based on the setup.

    If param1 is a value taken from the outside world, stored in a local variable, and you simply concatenate it to a SQL string, you're setting yourself up for a problem.

    http://www.somesystem.com/listproducts.asp?productid=regular

    You take the query string, pull out the product id of regular, stuff it in param1, and use it to create a SQL statement.

    Sql = "SELECT * FROM Products WHERE CategoryId = '" & param1 & "'"

    Sql is now equal to "Sql = "SELECT * FROM Products WHERE CategoryId = 'regular'"

    It works.  You get a page of products.

    Joe Blow comes along and modifies your URL.

    http://www.somesystem.com/listproducts.asp?CategoryId=regular' or 1=1--

    You take the query string, pull out the product id of regular, stuff it in param1, and use it to create a SQL statement.

    Sql = "SELECT * FROM Products WHERE CategoryId = '" & param1 & "'"

    Sql is now equal to "SELECT * FROM Products WHERE CategoryId = 'regular' or 1=1--'

    Now the page is listing all products, regardless of category, because 1 always equals 1.

    That's not SQL injection to you?

    Sql.Command = "SELECT * FROM Products WHERE CategoryId = @Param1";
    Sql.Parameters.Add(new SqlParameter("@Param1", Param1));

    Allows you to use the parameter value obtained from the user without risk of injection, yes.

    But concatenating a value to a dynamic SQL string exposes you to SQL injection.
    Period.

    If you want a "real" example...

    http://www.somesystem.com/listproducts.asp?CategoryId=regular'; exec master..xp_cmdshell 'ping 1.2.3.4'--

    Sql is now equal to Sql.Command = "SELECT * FROM Products WHERE CategoryId = regular'; exec master..xp_cmdshell 'ping 1.2.3.4'--

    Since SQL Server runs by default as the local SYSTEM account you have access to xp_cmdshell which allows you to remotely execute any command you want.  Using ; ends the first command and executes the second.

    I don't know what you think SQL injection is, but I think you're the one who's either wrong, or not explaining himself very well.


  • (cs) in reply to bmschkerke

    I can't edit the last post.

    A white paper on SQL Injection by Spidynamics:  http://www.spidynamics.com/whitepapers/WhitepaperSQLInjection.pdf

    Good read for those who want to learn more.

  • (cs) in reply to bmschkerke

    The problem is all the poster said is they do

    LIKE '%' + @somevar + '%'

    that's all they indicated; they did not indicate that they are building sql strings dynamically.  You cannot assume that, and to see a snippet of code like that to assume it is a dynamically executed SQL statement is wrong.  That is like saying anytime you concatenate any variables with constaints in any SQL statment it is open to injection; wrong!  The key is the dynamic execution of a SQL string.

    SELECT * FROM TABLE WHERE Col1 LIKE '%' + @SomeVar + '%'

    is not open to sql injection unless it is dynamically executed.

     

  • (cs) in reply to bmschkerke
    bmschkerke:
    Jeff S:

    rsynnott --

    No, it is NOT.  That is completely wrong, there is absolutely no danger at all of sql injection. It is scary how many people really have no idea what sql injection truly means. 

    The biggest issue with the code shown, as myself and others explained, is the performance.

    Typically, for dynamic criteria on a varchar() column, I tend to prefer:

    WHERE Column LIKE COALESCE(@Value,'%')

    which in many cases performs better than using an OR. 



    Um.

    Yes it is.

    Anytime you simply concatenate a value onto a SQL command string you subject yourself to an injection attack.  That's the basis of an injection attack.  That's it.  No more, no less.  The amount of damage that can be done varies based on the setup.

    If param1 is a value taken from the outside world, stored in a local variable, and you simply concatenate it to a SQL string, you're setting yourself up for a problem.

    http://www.somesystem.com/listproducts.asp?productid=regular

    You take the query string, pull out the product id of regular, stuff it in param1, and use it to create a SQL statement.

    Sql = "SELECT * FROM Products WHERE CategoryId = '" & param1 & "'"

    Sql is now equal to "Sql = "SELECT * FROM Products WHERE CategoryId = 'regular'"

    It works.  You get a page of products.

    Joe Blow comes along and modifies your URL.

    http://www.somesystem.com/listproducts.asp?CategoryId=regular' or 1=1--

    You take the query string, pull out the product id of regular, stuff it in param1, and use it to create a SQL statement.

    Sql = "SELECT * FROM Products WHERE CategoryId = '" & param1 & "'"

    Sql is now equal to "SELECT * FROM Products WHERE CategoryId = 'regular' or 1=1--'

    Now the page is listing all products, regardless of category, because 1 always equals 1.

    That's not SQL injection to you?

    Sql.Command = "SELECT * FROM Products WHERE CategoryId = @Param1";
    Sql.Parameters.Add(new SqlParameter("@Param1", Param1));

    Allows you to use the parameter value obtained from the user without risk of injection, yes.

    But concatenating a value to a dynamic SQL string exposes you to SQL injection.
    Period.

    If you want a "real" example...

    http://www.somesystem.com/listproducts.asp?CategoryId=regular'; exec master..xp_cmdshell 'ping 1.2.3.4'--

    Sql is now equal to Sql.Command = "SELECT * FROM Products WHERE CategoryId = regular'; exec master..xp_cmdshell 'ping 1.2.3.4'--

    Since SQL Server runs by default as the local SYSTEM account you have access to xp_cmdshell which allows you to remotely execute any command you want.  Using ; ends the first command and executes the second.

    I don't know what you think SQL injection is, but I think you're the one who's either wrong, or not explaining himself very well.




    Didn't we just go over this?  :)

    As far as I know you're certainly correct, but I think that Mr. Jeff S. is assuming that this is not a dynamically created SQL statement that gets executed all willy nilly.
  • (cs) in reply to UncleMidriff

    Dang, insta-posted.

  • (cs) in reply to UncleMidriff
    UncleMidriff:
    Jeff S:
    Anonymous:


    SELECT sProjectID, sProjectName, ..., 0 AS iFoundFlag -- remove iFoundFlag if callers don't reference it
    FROM tbl_Projects
    WHERE (<FONT size=+0>LEN</FONT>(@sProjectID) = 0 OR <FONT size=+0>LTRIM</FONT>(<FONT size=+0>RTRIM</FONT>(sProjectID)) = <FONT size=+0>LTRIM</FONT>(<FONT size=+0>RTRIM</FONT>(@sProjectID)))

    -- 4 more selection criteria

    AND (<FONT size=+0>LEN</FONT>(@sProjectName) = 0 OR sProjectName <FONT size=+0>LIKE</FONT> @sProjectName)



    "Clever irony" or just plain stupid?   That's some scary lookin' SQL my friend!  Then again, I guess the beauty of code like this is that you don't have to worry about bothersome database tasks like setting up indexes or relations or anything like that ...



    Why is this SQL statement so "scary?"  It doesn't seem all that complicated to me.  The only thing that looks a little scary to me are some of the where conditions.  But in this case, he was just copying those conditions from the original post.

    Anyway, I'm not trying to be snide; I'd just like some clarification if you care to give it.

    I guess you may be right; he is just copying the WHERE clause as verbatim as possible from the original poster's code.  So, while it is still a horribly constructed conditional WHERE (it cannot use any indexes at all and it is and also wrong -- Len(null) = Null) I should not have assumed the poster actually might have thought it was good code.

  • (cs) in reply to Jeff S
    Jeff S:

    The problem is all the poster said is they do

    LIKE '%' + @somevar + '%'

    that's all they indicated; they did not indicate that they are building sql strings dynamically.  You cannot assume that, and to see a snippet of code like that to assume it is a dynamically executed SQL statement is wrong.  That is like saying anytime you concatenate any variables with constaints in any SQL statment it is open to injection; wrong!  The key is the dynamic execution of a SQL string.

    SELECT * FROM TABLE WHERE Col1 LIKE '%' + @SomeVar + '%'

    is not open to sql injection unless it is dynamically executed.



    Gotcha.

    This is safe, not subject to SQL injection:

    CREATE PROCEDURE [dbo].[TestProc]
    (
         @TestParameter AS varchar(240)
    ) AS SELECT * FROM SampleTable WHERE SampleColumn LIKE '%' + @TestParameter + '%'

    GO

    This is not safe, subject to SQL injection:

    CREATE PROCEDURE [dbo].[TestProc]
    (
         @TestParameter AS varchar(240)
    ) AS EXEC('SELECT * FROM SampleTable WHERE SampleColumn LIKE ''%''' + @TestParameter + '''%''') GO

    I would like to note that both methods are subject to SQL injection if the SQL statement is prepared on the client as a string, which is then sent to the server for processing. (This methodology is how most SQL injection attacks are created.)

    My apologies Jeff.

  • Anonymous (unregistered) in reply to Jeff S
    Jeff S:
    UncleMidriff:
    Jeff S:
    Anonymous:


    SELECT sProjectID, sProjectName, ..., 0 AS iFoundFlag -- remove iFoundFlag if callers don't reference it
    FROM tbl_Projects
    WHERE (<font size="+0">LEN</font>(@sProjectID) = 0 OR <font size="+0">LTRIM</font>(<font size="+0">RTRIM</font>(sProjectID)) = <font size="+0">LTRIM</font>(<font size="+0">RTRIM</font>(@sProjectID)))

    -- 4 more selection criteria

    AND (<font size="+0">LEN</font>(@sProjectName) = 0 OR sProjectName <font size="+0">LIKE</font> @sProjectName)



    "Clever irony" or just plain stupid?   That's some scary lookin' SQL my friend!  Then again, I guess the beauty of code like this is that you don't have to worry about bothersome database tasks like setting up indexes or relations or anything like that ...



    Why is this SQL statement so "scary?"  It doesn't seem all that complicated to me.  The only thing that looks a little scary to me are some of the where conditions.  But in this case, he was just copying those conditions from the original post.

    Anyway, I'm not trying to be snide; I'd just like some clarification if you care to give it.

    I guess you may be right; he is just copying the WHERE clause as verbatim as possible from the original poster's code.  So, while it is still a horribly constructed conditional WHERE (it cannot use any indexes at all and it is and also wrong -- Len(null) = Null) I should not have assumed the poster actually might have thought it was good code.



    If you read my original post, I was showing how you could replace the body of the stored procedure with a single SELECT statement which generates results identical to the current stored procedure.  Your suggested "fix" would change existing logic.  It is a major WTF to assume you know better without checking that against user expectations.

    I also pointed out in that message that as written, it would not use any indexes, and what to do if you wanted that.

    Please read the posts before you respond to them.

    As far as how I tend to deal with such issues, I tend to write a framework which builds up the WHERE clause at run-time, so that it will use available indexes.  I've made a career out of writing frameworks for doing that.

  • (cs) in reply to Anonymous

    I already indicated that I was probably wrong about your post (in the very post you quoted); maybe YOU should read the posts a little more closely too.

  • tl (unregistered) in reply to Ytram

    because you need to store a row in the DB that indicates (a) to whom the mail was sent and (b) what the contents of the email were.

    in other words, sending it using C#, or within the "middle tier" may mean you have to call back to your DB and INSERT a record into your SENT_EMAILS table... there's no need to bring that logic outside of the DB.

    why store records of sent emails?

    so you can later run queries on them and determine things like reply rates, and patterns in customer support, or how many people from Boise bought your product.


  • (cs) in reply to Maurits

    The way MS SQL does equals, the LTRIM and RTRIM are not necessary. 

  • (cs)
    Alex Papadimoulis:
    <font color="#000099"></font>    <font color="#000099">WHERE</font> <font color="#000099">LTRIM</font>(<font color="#000099">RTRIM</font>(a.sProjectID)) = <font color="#000099">LTRIM</font>(<font color="#000099">RTRIM</font>(@sProjectID))


    So many people have commented so thoroughly on the big WTFs here, but I'd like to mention a small one.  The code assumes the project ID it is passed might be space-padded, and the value in the DB might be as well.  Neither of these things is likely to be true, and both could be guaranteed to NOT be true, if the rest of the app took the effort to do so.  And basing the WHERE condition on a function like this will keep the DB from using an index on the sProjectID column, forcing a full table scan and massively slowing down the query.  (A poster remarked that the way SQL Server handles the equals operator makes this use of LTRIM and RTRIM unnecessary.  Is that true?  I don't know, I'm an Oracle developer.  (So be nice to me.))

    On the other issues that have been raised, I would say 1. Use bind variables; 2. Use bind variables; and 3. Even if you use dynamic SQL to include or exclude terms in the WHERE clause for optional parameters, use bind variables anyway for the values.
  • (cs) in reply to DrCode
    DrCode:
    Alex Papadimoulis:
    <FONT color=#000099></FONT>    <FONT color=#000099>WHERE</FONT> <FONT color=#000099>LTRIM</FONT>(<FONT color=#000099>RTRIM</FONT>(a.sProjectID)) = <FONT color=#000099>LTRIM</FONT>(<FONT color=#000099>RTRIM</FONT>(@sProjectID))



    So many people have commented so thoroughly on the big WTFs here, but I'd like to mention a small one.  The code assumes the project ID it is passed might be space-padded, and the value in the DB might be as well.  Neither of these things is likely to be true, and both could be guaranteed to NOT be true, if the rest of the app took the effort to do so.  And basing the WHERE condition on a function like this will keep the DB from using an index on the sProjectID column, forcing a full table scan and massively slowing down the query.  (A poster remarked that the way SQL Server handles the equals operator makes this use of LTRIM and RTRIM unnecessary.  Is that true?  I don't know, I'm an Oracle developer.  (So be nice to me.))

    On the other issues that have been raised, I would say 1. Use bind variables; 2. Use bind variables; and 3. Even if you use dynamic SQL to include or exclude terms in the WHERE clause for optional parameters, use bind variables anyway for the values.

    Trailing spaces do not matter in T-SQL, but leading spaces do.

    The implied WTF with using LTRIM(TRIM()) is that your database is storing ProjectID values (which are presumably important and should have foreign key references) inconsistently and in a manner in which you cannot index them or use foreign key constraints.

    Trimming the input it one thing, but needing to trim the values in the database for leading spaces on primary key columns???? *gasp*

     

  • Chris Spiess (unregistered)
    <font color="#0000ff" face="Arial" size="2">Ok, I almost feel stupid for not even being able to comprehend the thought pattern that would lead to this solution.  This is quite possibly, and I mean this literally, the worst thing I have ever seen.  Ranks up there with someone I worked with who used to put SLEEPs in his code and then go back changes that resulted in 'miraculous' performance improvements.  Of course, that was just moral vapidness, this stored procedure is just outright idiocy.</font>
  • GenearlPF (unregistered) in reply to joodie

    It doesn't really have a race condition because each calls creates a separate, isolated temporary table for individual fsckwittery.

  • Mr A (unregistered)

    I wonder if this is a case of shifting understanding.

    I once had to implement an 'intelligent' search that took between 1 and 50 different criteria, sorting the results by exact, close and possible matches. This was impossible as a WHERE clause; it used temporary tables, cursors and calculated 'match' vectors.

    As more of the function was implemented (and the requirements expanded), the stored procedure became so complicated that we re-wrote it in a far simpler form in the middle layer. The SP kept shrinking until only the cursor was left and it looked quite like the offending one in this article. It stayed that way for several months before someone noticed and replaced it with 'SELECT * FROM .. WHERE ..'

    It's a possibility...

Leave a comment on “SELECTing The Hard Way”

Log In or post as a guest

Replying to comment #:

« Return to Article