Scenario:
Need to pass n arguments to a stored procedure. One of the arguments is of type varchar(x). That varchar argument needs to be constructed from a handful of other varchar variables. This problem uses SQL Server 2005, but this behaviour applies to all versions of SQL Server.
Setup:
DECLARE #MyString varchar(500), #MyBar varchar(10), #MyFoo varchar(10)
SELECT #MyBar= 'baz '
SELECT #MyFoo= 'bat '
-- try calling this stored procedure!
EXEC DoSomeWork #MsgID, 'Hello ' + #MyBar + '" world! "' + #MyFoo + '".'
This produces the exception in SQL Server: Incorrect syntax near '+'. Typically you might think that the datatype would be wrong (i.e. the variables are of different types, but that would produce a different error message).
Here's a correct implementation that compiles without error:
SELECT #MyString= 'Hello ' + #MyBar + '" world! "' + #MyFoo + '".';
EXEC DoSomeWork #ID, #MyString
Question: Why is it that T-SQL can't handle the concatenation of a varchar as an argument? It knows the types, as they were declared properly as varchar.
The EXECUTE statement simply has a different grammar then other statements like SELECT and SET. For instance, observe the syntax section at the top of the following two pages.
EXECUTE statement: http://msdn.microsoft.com/en-us/library/ms188332.aspx
SET statement: http://msdn.microsoft.com/en-us/library/ms189484.aspx
The syntax for EXECUTE only accepts a value
[[#parameter =] {value | #variable
[OUTPUT] | [DEFAULT]]
Whereas the syntax for SET accepts an expression
{#local_variable = expression}
A value is basically just a hard coded constant, but an expression is going to be evaluated. It's like having the varchar 'SELECT 1 + 1'. It's just a varchar value right now. However, you can evaluate the string like this:
EXEC('SELECT 1 + 1')
I suppose all I'm pointing out is that the EXEC command doesn't allow expressions by definition, which you apparently found out already. I don't know what the intention of the developers of T-SQL where when they made it that way. I suppose the grammar would just get out of hand if you where allowed to throw subqueries within subqueries in the parameter list of a stored procedure.
T-SQL Expression: http://msdn.microsoft.com/en-us/library/ms190286.aspx
You cannot do something like this either
exec SomeProc getdate()
you have to put all that stuff in a param like you are doing at your bottom query
It might be because it is non deterministic (at least for functions)
It's a limitation on the EXEC statement. See The curse and blessings of dynamic SQL for more information.
Related
I am trying to implement a stored procedure that gets the two parameters #startsWith and #endsWith and constructs this query string:
#startswith + '%' + #endsWith
To search for entries of a single column (Name) that start end end with the parameters. Here is the stored procedure:
CREATE PROCEDURE termNameStartsEndsWith(
#startsWith AS nvarchar,
#endsWith AS nvarchar
)
AS
BEGIN
SELECT * FROM Term WHERE
Name LIKE (#startsWith + '%' + #endsWith)
END;
However, I get unexpected results when one of the two query parameters is empty (''). Here is an example where I would expect only results where the Term column entry starts with 'water', but i get a bunch of additional rows:
I dont get these results when executing as a query:
So I expect that the problem is coming from the empty string concatenation being handled differently in a stored procedure? If so, how can I adapt the procedure accordingly?
Thanks for the help in advance.
As noted by Larnu in the comments, the issue isn't the query, it's your parameter declarations.
You have two NVARCHAR(n) parameters declared, but there is no length declared for either of them. From the documentation (emphasis added):
When n is not specified in a data definition or variable declaration statement, the default length is 1. When n is not specified with the CAST function, the default length is 30.
So both parameters are exactly one character long. Conveniently, SQL Server will let you assign a longer value to that parameter, and then just take the first character and silently truncate the rest.
Modify your parameters to have length definitions, and you should be in business.
I need to create a INSERT statement using parameters. Say I have two variable name #DestinationFields, #InsertValues.
Here #DestinationFields contain the column name like: product,price and #InsertValues contains the values for those two columns, like: Book,100.
Now, How i create a insert command to insert those values where each value need to add a quotation mark .I already tried as
I already tried as
EXEC('INSERT into tbl_test('+#DestinationFields+')values('+#InsertValues+')')
But it's returning an error.
The name "book" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some
contexts) variables. Column names are not permitted.
How do I do it? Thanks in advance.
Pretending there is no problem of SQL injection here*, you can quickly fix your code by adding quotation marks around Book. The value of # InsertValues should be
'Book', 100
instead of simply
Book, 100
You need to add quotation marks around each string value; otherwise, strings are interpreted as names, which is not valid.
EDIT : (in response to a comment) If all columns are of varchar type, you can put quotes around the entire string, and replace all commas with the quote-comma-quote pattern, like this:
values('''+REPLACE(#InsertValues,',',''',''')+''')'
* You should not put code like this into production, because it can be manipulated to harm your system rather severely. Here is a good illustration of the problem (link).
Try:
DECLARE #DestinationFields VARCHAR(200);
SET #DestinationFields = 'Col1, Col2, Col3'
DECLARE #InsertValues VARCHAR(200);
SET #InsertValues = '1, 2, 3'
DECLARE #SQLString VARCHAR(1000);
SET #SQLString = 'INSERT INTO tbl_test (' + #DestinationFields + ') VALUES (' + #InsertValues + ')';
EXEC (#SQLString)
However, this is very open to SQL Injection attacks. But, it will do what you require.
The Curse and Blessing of Dynamic SQL
For some unknown reason I'm running into a problem when passing a variable to a full text search stored procedure performs many times slower than executing the same statement with a constant value. Any idea why and how can that be avoided?
This executes very fast:
SELECT * FROM table
WHERE CONTAINS (comments, '123')
This executes very slowly and times out:
DECLARE #SearchTerm nvarchar(30)
SET #SearchTerm = '123'
SET #SearchTerm = '"' + #SearchTerm + '"'
SELECT * FROM table
WHERE CONTAINS (comments, #SearchTerm)
Does this make any sense???
I've seen the same issue with trying to use a variable for top. SQL Server is not able to tune a query that is using a variable in this way.
You should try using the execsql command.
does this run slow: SELECT * FROM table WHERE CONTAINS (comments, N'123') ??
you are using a varchar '123' in the first example and a nvarchar variable in the second example. This type conversion could be causing you the problem. What is the column defined as?
Also why wrap the variable's value in " double qoutes, but not do the same in the first example. When you run the exact same queries using a literal and a variable do the run differently?
I think Matt b is right. The first query you are searching for
123
In the second query, you are searching for
'123'
The second query with the quotes is probably returning no results, and your program is probably timing out, not the query.
I am passing a comma-delimited list of values into a stored procedure. I need to execute a query to see if the ID of an entity is in the comma-delimited list. Unfortunately, I think I do not understand something.
When I execute the following stored procedure:
exec dbo.myStoredProcedure #myFilter=N'1, 2, 3, 4'
I receive the following error:
"Conversion failed when converting the varchar value '1, 2, 3, 4' to data type int."
My stored procedure is fairly basic. It looks like this:
CREATE PROCEDURE [dbo].[myStoredProcedure]
#myFilter nvarchar(512) = NULL
AS
SET NOCOUNT ON
BEGIN
-- Remove the quote marks so the filter will work with the "IN" statement
SELECT #myFilter = REPLACE(#myFilter, '''', '')
-- Execute the query
SELECT
t.ID,
t.Name
FROM
MyTable t
WHERE
t.ID IN (#myFilter)
ORDER BY
t.Name
END
How do I use a parameter in a SQL statement as described above? Thank you!
You could make function that takes your parameter, slipts it and returns table with all the numbers in it.
If your are working with lists or arrays in SQL Server, I recommend that you read Erland Sommarskogs wonderful stuff:
Arrays and Lists in SQL Server 2005
You need to split the string and dump it into a temp table. Then you join against the temp table.
There are many examples of this, here is one at random.
http://blogs.microsoft.co.il/blogs/itai/archive/2009/02/01/t-sql-split-function.aspx
Absent a split function, something like this:
CREATE PROCEDURE [dbo].[myStoredProcedure]
#myFilter varchar(512) = NULL -- don't use NVARCHAR for a list of INTs
AS
SET NOCOUNT ON
BEGIN
SELECT
t.ID,
t.Name
FROM
MyTable t
WHERE
CHARINDEX(','+CONVERT(VARCHAR,t.ID)+',',#myFilter) > 0
ORDER BY
t.Name
END
Performance will be poor. A table scan every time. Better to use a split function. See: http://www.sommarskog.se/arrays-in-sql.html
I would create a function that takes your comma delimited string and splits it and returns a single column table variable with each value in its own row. Select that column from the returned table in your IN statement.
I found a cute way of doing this - but it smells a bit.
declare #delimitedlist varchar(8000)
set #delimitedlist = '|1|2|33|11|3134|'
select * from mytable where #delimitedlist like '%|' + cast(id as varchar) + '|%'
So... this will return all records with an id equal to 1, 2, 33, 11, or 3134.
EDIT:
I would also add that this is not vulnerable to SQL injection (whereas dynamic SQL relies on your whitelisting/blacklisting techniques to ensure it isn't vulnerable). It might have a performance hit on large sets of data, but it works and it's secure.
I have a couple of blog posts on this as well, with a lot of interesting followup comments and dialog:
More on splitting lists
Processing list of integers
We are trying to update our classic asp search engine to protect it from SQL injection. We have a VB 6 function which builds a query dynamically by concatenating a query together based on the various search parameters. We have converted this to a stored procedure using dynamic sql for all parameters except for the keywords.
The problem with keywords is that there are a variable number words supplied by the user and we want to search several columns for each keyword. Since we cannot create a separate parameter for each keyword, how can we build a safe query?
Example:
#CustomerId AS INT
#Keywords AS NVARCHAR(MAX)
#sql = 'SELECT event_name FROM calendar WHERE customer_id = #CustomerId '
--(loop through each keyword passed in and concatenate)
#sql = #sql + 'AND (event_name LIKE ''%' + #Keywords + '%'' OR event_details LIKE ''%' + #Keywords + '%'')'
EXEC sp_executesql #sql N'#CustomerId INT, #CustomerId = #CustomerId
What is the best way to handle this and maintaining protection from SQL injection?
You may not like to hear this, but it might be better for you to go back to dynamically constructing your SQL query in code before issuing against the database. If you use parameter placeholders in the SQL string you get the protection against SQL injection attacks.
Example:
string sql = "SELECT Name, Title FROM Staff WHERE UserName=#UserId";
using (SqlCommand cmd = new SqlCommand(sql))
{
cmd.Parameters.Add("#UserId", SqlType.VarChar).Value = "smithj";
You can build the SQL string depending on the set of columns you need to query and then add the parameter values once the string is complete. This is a bit of a pain to do, but I think it is much easier than having really complicated TSQL which unpicks lots of possible permutations of possible inputs.
You have 3 options here.
Use a function that converts lists tables and join into it. So you will have something like this.
SELECT *
FROM calendar c
JOIN dbo.fnListToTable(#Keywords) k
ON c.keyword = k.keyword
Have a fixed set of params, and only allow the maximum of N keywords to be searched on
CREATE PROC spTest
#Keyword1 varchar(100),
#Keyword2 varchar(100),
....
Write an escaping string function in TSQL and escape your keywords.
Unless you need it, you could simply strip out any character that's not in [a-zA-Z ] - most of those things won't be in searches and you should not be able to be injected that way, nor do you have to worry about keywords or anything like that. If you allow quotes, however, you will need to be more careful.
Similar to sambo99's #1, you can insert the keywords into a temporary table or table variable and join to it (even using wildcards) without danger of injection:
This isn't really dynamic:
SELECT DISTINCT event_name
FROM calendar
INNER JOIN #keywords
ON event_name LIKE '%' + #keywords.keyword + '%'
OR event_description LIKE '%' + #keywords.keyword + '%'
You can actually generate an SP with a large number of parameters instead of coding it by hand (set the defaults to '' or NULL depending on your preference in coding your searches). If you found you needed more parameters, it would be simple to increase the number of parameters it generated.
You can move the search to a full-text index outside the database like Lucene and then use the Lucene results to pull the matching database rows.
You can try this:
SELECT * FROM [tablename] WHERE LIKE % +keyword%