Change dynamic SQL from using Exec to sp_executesql - sql

I am a beginner in SQL and esspecially dynamic sql execution.
I am trying to convert some dynamic sql which is inside a store procedure from using EXEC to using sp_executesql.
The issue I have is I don't know what are the exact rules for representing multiple lines of dynamic sql. The existing code is in the form of:
Set #cmd = 'WITH vdate AS' + char(13)
+' (SELECT valueID,' + char(13)
+'username)' +char(13)
+'FROM dbo.table1' + char(13)
+'WHERE username = ''' + convert(varchar(11), #username, 106) + ''' AND' + char(13)
Exec (#cmd)
The above is just a snippet of what it looks like. There are alot more lines and more complicated stuff going on.
I now want to be able to use the sp_executesql way of executing this code because I want to change the username attribute to be a table which accepts multiple usernames.
Please can you advise me what the syntax is for this and how do i make the line before last work?
I have seen that code looks very similar so what i did I changed the +' to be N' at the start of each line and the code compiled but it didn't execute when I tried to use the query in my application.
Thanks, Jetnor.

The only difference is that instead of EXEC (#cmd) you would use EXEC sp_executesql #cmd. Technically speaking, sp_executesql takes NVARCHAR arguments. In order to create an NVARCHAR string you should prefix the string with N like so:
DECLARE #cmd NVARCHAR(1000);
SET #cmd = N'My string'
This denotes that the string is a Unicode string which may contain national character sets. Of course you can omit the N but if you have anything other than ASCII characters in it then it won't work properly and you'll end up with strings that contain a lot of question marks instead of your special characters (CJK - Chinese, Japanese, Korean - character sets for instance) so it is good practice to always prefix NVARCHAR strings with the N.
Some of your confusion arises because you are replacing the string concatenation operator, +, with the national character set identifier, N. You will need both. It is always worth including proper spacing in your code, especially in dynamic SQL because it's easier to read. My preference is to add the spaces at the end of lines where possible, but that's just my personal preference. Also, drop the CHAR(13) stuff unless you really want to print it out in a certain way. Your example would become:
DECLARE #cmd NVARCHAR(1000) --Note this is NVARCHAR, not VARCHAR.
SET #cmd = N'WITH vdate AS '
+ N'(SELECT valueID, '
+ N'username) '
+ N'FROM dbo.table1 '
+ N'WHERE username = ''' + convert(NVARCHAR(11), #username, 106) + ''' AND ' --Note that you are missing a chunk here - AND what?
EXEC sp_executesql #cmd
Note that your CONVERT is incorrect: Firstly you should convert to NVARCHAR (if #username isn't already), and secondly the 106 part does nothing - that's for date formatting. Also you are missing the end of the statement as there is an AND and then nothing. It's always good to use PRINT #cmd to see if the SQL you've generated is valid (it will output the contents of #cmd and you can then copy that and paste it into a workshet in SSMS).
Now, you might want to have output parameters in your dynamic SQL as well which is no problem.
DECLARE #ID INT;
DECLARE #outputValue NVARCHAR(50);
DECLARE #cmd NVARCHAR(1000);
SET #cmd = N'SELECT #value = Value FROM MyTable WHERE ID = ' +
CAST(#ID AS NVARCHAR(5));
EXEC sp_executesql #cmd, '#value NVARCHAR(50) OUT', #outputValue OUT;
SELECT #outputValue;
Traditionally, the main reason for wanting to use sp_executesql as opposed to just EXEC is that you have a better chance of the execution plan being cached and therefore re-used later. I confess I'm not sure if that's still true or not, but sp_executesql is generally the preferred method of executing dynamic SQL. A lot of poeple have an instinctive hatred of dynamic SQL but frankly, like all things, it has it's uses so long as it isn't abused and used where it just isn't required.

Related

How to Create DELETE Statement Stored Procedure Using TableName, ColumnName, and ColumnValue as Passing Parameters

Here is what i'm trying to do. I'm trying to create a stored procedure where I could just enter the name of the table, column, and column value and it will delete any records associated with that value in that table. Is there a simple way to do this? I don't know too much about SQL and still learning about it.
Here is what I have so far.
ALTER PROCEDURE [dbo].[name of stored procedure]
#TABLE_NAME varchar(50),
#COLUMN_NAME varchar(50),
#VALUE varchar(5)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #RowsDeleted int;
DECLARE #sql VARCHAR(500);
SET #sql = 'DELETE FROM (name of table).' + #TABLE_NAME + ' WHERE ' + #COLUMN_NAME + '=' + '#VALUE'
EXEC(#sql)
SET #RowsDeleted=##ROWCOUNT
END
GO
Couple issues
First, you don't need (name of table)
SET #sql = 'DELETE FROM ' + #TABLE_NAME + etc.
In general you should try to include the appropriate schema prefix
SET #sql = 'DELETE FROM dbo.' + #TABLE_NAME + etc.
And in case your table name has special characters perhaps it should be enclosed in brackets
SET #sql = 'DELETE FROM dbo.[' + #TABLE_NAME + ']' + etc.
Since #Value is a string, you must surround it with single quotes when computing the value for #SQL. To insert a single quote into a string you have to escape it by using two single quotes, like this:
SET #SQL = 'DELETE FROM dbo.[' + #TABLE_NAME + '] WHERE [' + #COLUMN_NAME + '] = '''' + #VALUE + ''''
If #VALUE itself contains a single quote, this whole thing will break, so you need to escape that as well
SET #SQL = 'DELETE FROM dbo.[' + #TABLE_NAME + '] WHERE [' + #COLUMN_NAME + '] = '''' + REPLACE(#VALUE,'''','''''') + ''''
Also, ##ROWCOUNT will not populate from EXEC. If you want to be able to read ##ROWCOUNT, use sp_ExecuteSQL instead
EXEC sp_ExecuteSql #SQL
And finally, let me editorialize for a minute--
This sort of stored procedure is not a great idea. I know it seems pretty cool because it is flexible, and that kind of thinking is usually smart when it comes to other languages, but in the database world this approach causes problems, e.g. there are security issues (e.g. injection, and the fact that you need elevated privileges to call sp_executeSql) and there issues with precompilation/performance (because the SQL isn't known ahead of time, SQL Server will need to generate a new query plan each and every time you call this) and since the caller can supply any value for table and column name you have no idea whether this delete statement will be efficient and use indexes or if it will cause a huge performance issue because the table is large and the column is not indexed.
The proper approach is to have a series of appropriate stored procedures with strongly-typed inputs that are specific to each data use case where you need to delete based on criteria. Database engineers should not be trying to make things flexible; you should be forcing people to think through what exactly they are going to need, and implement that and only that. That is the only way to ensure people are following the rules, keeping R/I intact, efficient use of indexes, etc.
Yes, this may seem like repetitive and redundant work, but c'est la vie. There are tools available to generate the code for CRUD operations if you don't like the extra typing.
In addition to some of the information John Wu provided you have to worry about data types and ##ROWCOUNT may not be accurate if there are triggers on your tables and things..... You can get around both of those issues though by casting to nvarchar() and using OUTPUT clause with a temp table to do the COUNT().
So just for fun here is a way you can do it:
CREATE PROCEDURE dbo.[ProcName]
#TableName SYSNAME
,#ColumnName SYSNAME
,#Value NVARCHAR(MAX)
,#RecordCount INT OUTPUT
AS
BEGIN
DECLARE #SQL NVARCHAR(1000)
SET #SQL = N'IF OBJECT_ID(''tempdb..#DeletedOutput'') IS NOT NULL
BEGIN
DROP TABLE #DeletedOutput
END
CREATE TABLE #DeletedOutput (
ID INT IDENTITY(1,1)
ColumnValue NVARCHAR(MAX)
)
DELETE FROM dbo.' + QUOTENAME(#TableName) + '
OUTPUT deleted.' + QUOTENAME(#ColumnName) + ' INTO #DeletedOutput (ColumnValue)
WHERE CAST(' + QUOTENAME(#ColumnName) + ' AS NVARCHAR(MAX)) = ' + CHAR(39) + #Value + CHAR(39) + '
SELECT #RecordCountOUT = COUNT(ID) FROM #DeletedOutput
IF OBJECT_ID(''tempdb..#DeletedOutput'') IS NOT NULL
BEGIN
DROP TABLE #DeletedOutput
END'
DECLARE #ParmDefinition NVARCHAR(200) = N'#RecordCountOUT INT OUTPUT'
EXECUTE sp_executesql #SQL, #ParmDefinition, #RecordCountOUT = #RecordCount OUTPUT
END
So the use of QOUTENAME will help against the injection attack but not be perfect. And I use CHAR(39) instead of the escape sequence for a single quote on value because I find it easier when string building at that point.... By using Parameter OUTPUT from sp_executesql you can still return your count.
Keep in mind just because you can do something in SQL doesn't always mean you should.

How to run a more than 8000 characters SQL statement from a variable?

I can use the following code for tiny little queries:
DECLARE #sql VARCHAR(8000)
SET #sql = 'SELECT * FROM myTable'
Exec #sql
The above method is very useful in order to maintain large amounts of code, especially when we need to make changes once and have them reflected everywhere.
My problem is my query (it's only one single query) that I want to feed into the #sql variable uses more than 25 table joins, some of them on temporary table variables, incorporates complex operations and it is hence much more than 8000 characters long.
I wished to use TEXT data type to store this query, but MSDN shows a warning message that Microsoft is planning to remove Text, NText and Image data types from their next versions. I wish my code to run in future too.
I thought of storing this query in a separate file, but as it uses joins on table variables and other procedure-specific parameters, I doubt if this is possible.
Kindly tell me a method to store a large query into a variable and execute it multiple times in a procedure.
The problem is with implicit conversion.
If you have Unicode/nChar/nVarChar values you are concatenating, then SQL Server will implicitly convert your string to VarChar(8000), and it is unfortunately too dumb to realize it will truncate your string or even give you a Warning that data has been truncated for that matter!
When concatenating long strings (or strings that you feel could be long) always pre-concatenate your string building with CAST('' as nVarChar(MAX)) like so:
SET #Query = CAST('' as nVarChar(MAX))--Force implicit conversion to nVarChar(MAX)
+ 'SELECT...'-- some of the query gets set here
+ '...'-- more query gets added on, etc.
What a pain and scary to think this is just how SQL Server works. :(
I know other workarounds on the web say to break up your code into multiple SET/SELECT assignments using multiple variables, but this is unnecessary given the solution above.
For those who hit a 4000 character max, it was probably because you had Unicode so it was implicitly converted to nVarChar(4000).
Warning:
You still Cannot have a Single Unbroken Literal String Larger than 8000 (or 4000 for nVarChar).
Literal Strings are those you hard-code and wrap in apostrophe's.
You must Break those Strings up or SQL Server will Truncate each one BEFORE concatenating.
I add ' + ' every 20 lines (or so) to make sure I do not go over.
That's an average of at most 200 characters per line - but remember, spaces still count!
Explanation:
What's happening behind the scenes is that even though the variable you are assigning to uses (MAX), SQL Server will evaluate the right-hand side of the value you are assigning first and default to nVarChar(4000) or VarChar(8000) (depending on what you're concatenating). After it is done figuring out the value (and after truncating it for you) it then converts it to (MAX) when assigning it to your variable, but by then it is too late.
If you are on SQL Server 2008 or newer you can use VARCHAR(MAX)
DECLARE #sql VARCHAR(MAX)
DECLARE #sql VARCHAR(max)
SET #sql = 'SELECT * FROM myTable'
Exec #sql
Note:
Print(#sql)
only show the first 8000 characters!
use
EXEC
(
'
--your sql script here
'
)
Problem is because your string has limit 8000 symbols by default. To prevent this you should convert it to (N)VARCHAR(MAX)
DECLARE #sql VARCHAR(8000)
SET #sql = CAST('SELECT * FROM myTable' AS VARCHAR(MAX))
--Check length of variable
PRINT 'Length is: '+CAST(LEN(#sql) AS VARCHAR)+ 'symbols'
Exec #sql
You should read the answer of this post which explains extremely well the situation :
SQL NVARCHAR and VARCHAR Limits
If the length x of your string is below 4000 characters, a string will be transformed into nvarchar(x)
If the length y is between 4000 and 8000, varchar(y)
If the length is more than 8000 characters, nvarchar(max) which can store up to 2GB.
Problem is that nvarchar(max) + varchar(y) = nvarchar(max) + nvarchar(4000) ; SQL will convert your varchar(y) into nvarchar(y) or nvarchar(4000) if y is greater than 4000 and lesser than 8000, truncating your string !
Well I ran to this before (in SQL 2005) and I can tell you that you have two options:
1 - Use the sys.sp_sqlexec stored procedure that can take a param of type text (IMO this is the way to go). Don't mind the warning. In SQL 2008 ntext is still supported, and if you do the varchar(max) thingy there, it will work. So basically, if you have 2008, both the text solution and the varchar(max) will work, so you will have time to change it =-). In 2012 though, only the varchar(max) will work, therefore you'll have to change it before upgrading.
2- (This is what I did at first) Check THIS post: http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=52274 and do what user "Kristen" says. Worked like a charm for me. Don't forget to pre-set them to an empty string. If you understood my post you know by now that in SQL 2008 or newer is silly to do this.
I had the same issue. I have a SQL which was more than 21,000 characters. For some reason,
Declare #SQL VARCHAR(MAX)
EXEC(#SQL)
would come up with several issues
I had to finally split it up in multiple variables equally and then it worked.
Declare #SQL1 VARCHAR(MAX) = 'First Part'
Declare #SQL2 VARCHAR(MAX) = 'Second Part'
Declare #SQL3 VARCHAR(MAX) = 'Third Part'
Declare #SQL4 VARCHAR(MAX) = 'Fourth Part'
Set #SQL= #SQL1 + #SQL2 + #SQL3 + #SQL4
EXEC(#SQL)
There is no solution for this along the way that you are doing it. MsSql as of 2012 supports Ntext for example that allows you to go beyond 8000 characters in a variable. The way to solve this is to make multiple variables or multiple rows in a table that you can iterate through.
At best with a MsSql version the max size of a variable is 8000 characters on the latest version as of when this was typed. So if you are dealing with a string of say 80,000 characters. You can parse the data into ten variables of 8000 characters each (8000 x 10 = 80,000) or you can chop the variable into pieces and put it into a table say LongTable (Bigstring Varchar(8000)) insert 10 rows into this and use an Identity value so you can retrieve the data in the same order.
The method you are trying will not work with MsSql currently.
Another obscure option that will work but is not advisable is to store the variable in a text file by using command shell commands to read/write the file. Then you have space available to you beyond 8000 characters. This is slow and less secure than the other methods described above.
ALTER PROCEDURE [dbo].[spGetEmails]
AS
BEGIN
SET NOCOUNT ON;
-- Insert statements for procedure here
declare #p varbinary(max)
set #p = 0x
declare #local table (col text)
SELECT #p = #p + 0x3B + CONVERT(varbinary(100), Email)
FROM tbCarsList
where email <> ''
group by email
order by email
set #p = substring(#p, 2, 10000000)
insert #local values(cast(#p as varchar(max)))
select col from #local
END
I have been having the same problem, with the strings being truncated. I learned that you can execute the sp_executesql statement multiple times.
Since my block of code was well over the 4k/Max limit, I break it out into little chunks like this:
set #statement = '
update pd
set pd.mismatchtype = 4
FROM [E].[dbo].[' + #monthName + '_P_Data] pd
WHERE pd.mismatchtype is null '
exec sp_executesql #statement
set #statement = 'Select * from xxxxxxx'
exec sp_executesql #statement
set #statement = 'Select * from yyyyyyy '
exec sp_executesql #statement
end
So each set #Statement can have the varchar(max) as long as each chunk itself is within the size limit (i cut out the actual code in my example, for space saving reasons)
Before print convert into cast and change datatype.
PRINT CAST(#sql AS NTEXT)
Now, try it.
If what you are trying to accomplish is to do this in Management Studio, the script below might help.
DECLARE #Len INT = 5
DECLARE #Str VARCHAR(MAX) = '1111122222333334444455555'
DECLARE #TmpStr VARCHAR(MAX)
DECLARE #Return TABLE (RetStr VARCHAR(MAX))
WHILE(LEN(#Str) > 0)
BEGIN
SET #TmpStr = LEFT(#Str, #Len)
IF(LEN(#Str) > #Len)
SET #Str = RIGHT(#Str, LEN(#Str) - #Len)
ELSE
SET #Str = ''
INSERT INTO #Return SELECT #Str
END
SELECT * FROM #Return
There #Len should be 8000, as this is the maximum length Management Studio shows. #Str is the text that is longer than 8000 characters.

nvarchar(max) still being truncated

So I'm writing a stored procedure in MS SQL Server 2008. It's a really long query and I have to write it dynamically, so I create a variable called #Query and make it of type NVARCHAR(MAX). Now, I have been told that in modern versions of SQL Server, NVARCHAR(MAX) can hold a ridiculous amount of data, way more than the original 4000 character maximum. However, #Query is still getting truncated to 4000 characters when I try to print it out.
DECLARE #Query NVARCHAR(max);
SET #Query = 'SELECT...' -- some of the query gets set here
SET #Query = #Query + '...' -- more query gets added on, etc.
-- later on...
PRINT LEN(#Query) -- Prints out 4273, which is correct as far as I can tell
PRINT #Query -- Truncates value to 4000 characters
EXEC sp_executesql #Query -- totally crashes due to malformed (truncated) query
Am I doing something incorrectly, or am I completely wrong about how NVARCHAR(MAX) works?
The problem is with implicit conversion.
If you have Unicode/nChar/nVarChar values you are concatenating, then SQL Server will implicitly convert your string to nVarChar(4000), and it is unfortunately too dumb to realize it will truncate your string or even give you a Warning that data has been truncated for that matter!
When concatenating long strings (or strings that you feel could be long) always pre-concatenate your string building with CAST('' as nVarChar(MAX)) like so:
SET #Query = CAST('' as nVarChar(MAX))--Force implicit conversion to nVarChar(MAX)
+ 'SELECT...'-- some of the query gets set here
+ '...'-- more query gets added on, etc.
What a pain and scary to think this is just how SQL Server works. :(
I know other workarounds on the web say to break up your code into multiple SET/SELECT assignments using multiple variables, but this is unnecessary given the solution above.
For those who hit an 8000 character max, it was probably because you had no Unicode so it was implicitly converted to VarChar(8000).
Warning:
You still Cannot have a Single Unbroken Literal String Larger than 4000 (or 8000 for VarChar).
Literal Strings are those you hard-code and wrap in apostrophe's.
You must Break those Strings up or SQL Server will Truncate each one BEFORE concatenating.
I add ' + ' every 20 lines (or so) to make sure I do not go over.
That's an average of at most 200 characters per line - but remember, spaces still count!
Explanation:
What's happening behind the scenes is that even though the variable you are assigning to uses (MAX), SQL Server will evaluate the right-hand side of the value you are assigning first and default to nVarChar(4000) or VarChar(8000) (depending on what you're concatenating). After it is done Concatenating and figuring out the value (and after truncating it for you) it then converts it into (MAX) when assigning it to your variable, but by then it is too late.
Problem seems to be associated with the SET statement. I think the expression can't be more than 4,000 bytes in size. There is no need to make any changes to any settings if all you are trying to do is to assign a dynamically generated statement that is more than 4,000 characters. What you need to do is to split your assignment. If your statement is 6,000 characters long, find a logical break point and then concatenate second half to the same variable. For example:
SET #Query = 'SELECT ....' [Up To 4,000 characters, then rest of statement as below]
SET #Query = #Query + [rest of statement]
Now run your query as normal i.e. EXEC ( #Query )
To see the dynamic SQL generated, change to text mode (shortcut: Ctrl-T), then use SELECT
PRINT LEN(#Query) -- Prints out 4273, which is correct as far as I can tell
--SET NOCOUNT ON
SELECT #Query
As for sp_executesql, try this (in text mode), it should show the three aaaaa...'s the middle one being the longest with 'SELECT ..' added. Watch the Ln... Col.. indicator in the status bar at bottom right showing 4510 at the end of the 2nd output.
declare #n nvarchar(max)
set #n = REPLICATE(convert(nvarchar(max), 'a'), 4500)
SET #N = 'SELECT ''' + #n + ''''
print #n -- up to 4000
select #n -- up to max
exec sp_Executesql #n
Print truncates the varchar(MAX) to 8000, nvarchar(MAX) to 4000 chars.
But;
PRINT CAST(#query AS NTEXT)
will print the whole query.
Results to text only allows a maximum of 8192 characters.
I use this approach
DECLARE #Query NVARCHAR(max);
set #Query = REPLICATE('A',4000)
set #Query = #Query + REPLICATE('B',4000)
set #Query = #Query + REPLICATE('C',4000)
set #Query = #Query + REPLICATE('D',4000)
select LEN(#Query)
SELECT #Query /*Won't contain any "D"s*/
SELECT #Query as [processing-instruction(x)] FOR XML PATH /*Not truncated*/
Your first problem is a limitation of the PRINT statement. I'm not sure why sp_executesql is failing. It should support pretty much any length of input.
Perhaps the reason the query is malformed is something other than truncation.
The problem with creating dynamic SQL using string expression is that SQL does limit the evaluation of string expressions to 4,000 chars. You can assign a longer string to an nvarchar(max) variable, but as soon as you include + in the expression (such as + CASE ... END + ), then the expression result is limited to 4,000 chars.
One way to fix this is to use CONCAT instead of +. For example:
SET #sql = CONCAT(#sql, N'
... dynamic SQL statements ...
', CASE ... END, N'
... dynamic SQL statements ...
')
Where #sql is declared as nvarchar(max).
I have encountered the same problem today and found that beyond that 4000 character limit, I had to split the dynamic query into two strings and concatenate them when executing the query.
DECLARE #Query NVARCHAR(max);
DECLARE #Query2 NVARCHAR(max);
SET #Query = 'SELECT...' -- some of the query gets set here
SET #Query2 = '...' -- more query gets added on, etc.
EXEC (#Query + #Query2)
Use this PRINT BIG function to output everything:
IF OBJECT_ID('tempdb..#printBig') IS NOT NULL
DROP PROCEDURE #printBig
GO
CREATE PROCEDURE #printBig (
#text NVARCHAR(MAX)
)
AS
--DECLARE #text NVARCHAR(MAX) = 'YourTextHere'
DECLARE #lineSep NVARCHAR(2) = CHAR(13) + CHAR(10) -- Windows \r\n
DECLARE #off INT = 1
DECLARE #maxLen INT = 4000
DECLARE #len INT
WHILE #off < LEN(#text)
BEGIN
SELECT #len =
CASE
WHEN LEN(#text) - #off - 1 <= #maxLen THEN LEN(#text)
ELSE #maxLen
- CHARINDEX(REVERSE(#lineSep), REVERSE(SUBSTRING(#text, #off, #maxLen)))
- LEN(#lineSep)
+ 1
END
PRINT SUBSTRING(#text, #off, #len)
--PRINT '#off=' + CAST(#off AS VARCHAR) + ' #len=' + CAST(#len AS VARCHAR)
SET #off += #len + LEN(#lineSep)
END
Source:
https://www.richardswinbank.net/doku.php?id=tsql:print_big
I was creating a JSON-LD to create a site review script.
**DECLARE #json VARCHAR(MAX);** The actual JSON is about 94K.
I got this to work by using the CAST('' AS VARCHAR(MAX)) + #json, as explained by other contributors:-
so **SET #json = CAST('' AS VARCHAR(MAX)) + (SELECT .....**
2/ I also had to change the Query Options:-
Query Options -> 'results' -> 'grid' -> 'Maximum Characters received' -> 'non-XML Data' SET to 2000000.
(I left the 'results' -> 'text' -> 'Maximum number of characters displayed in each column' as the default)

How to use a varying database?

I want to use a database which name is stored in a variable. How do I do this?
I first thought this would work but it doesn't:
exec('use '+#db)
That will not change database context
Suggestions anyone?
Unfortunately I don't know of a direct solution to this one. The nearest working version is:
DECLARE #db nvarchar(MAX)
SET #db = 'use DBname'
Exec sp_executesql #db
but this only changes the context for the length of the procedure call. However, more statements can be included in that call to make use of the context:
DECLARE #sql nvarchar(MAX)
SET #sql = 'use DBName SELECT * FROM Table1'
Exec sp_executesql #sql
If you absolutely have to do this using dynamic SQl, I prefer this:
DECLARE #sql nvarchar(MAX)
declare #databasename varchar (20)
Set #databasename = mydatabase
SET #sql = 'SELECT * FROM ' + #databasename + 'dbo.Table1'
Exec sp_executesql #sql
The reason I prefer it is that you can extend it to use multipe datbases in the same query if need be.
I havea a concern that you don't know the datbase name for each table already without resorting to dynamic means. In other words, why can't you write:
SELECT * FROM mydatabase.dbo.Table1
If you have multiple databases with the same table names, likely you have a design problem.
The use statement is only in scope inside the exec block. Therefore you would have to do everything else in the same exec:
exec('use '+ #db + '
--do other stuff'
)
Presumably you know all the possible database names. One (slightly inelligant) way of doing this would be to use a CASE or multiple IF statements to test the variable and hardcode the USE statement for each case.

SQL Server: Sanitizing #param against injection attacks

For the sake of argument, let's just say I have to create a local variable containing a SQL query that has an INSERT:
DECLARE #insert NVARCHAR(MAX)
SELECT #insert = 'INSERT INTO [dbo].[' + #table + '] VALUES...
EXEC (#insert)
This INSERT is also going to contain a column value:
DECLARE #insert NVARCHAR(MAX)
SELECT #insert =
'INSERT INTO [dbo].[' + #table + '] VALUES (N''' + #message + ''')'
EXEC (#insert)
Now, I'm obviously concerned about an injection attack, and would like to ensure that #message's value can't make #insert's value malicious or malformed as a query to EXEC.
This brings us to my question: is escaping the ' characters in #message sufficient? Are there any other characters that could appear in #message that could escape out?
Example:
DECLARE #insert NVARCHAR(MAX)
SELECT #message = REPLACE(#message,'''','''''')
SELECT #insert =
'INSERT INTO [dbo].[' + #table + '] VALUES (N''' + #message + ''')'
EXEC (#insert)
(When I say "have to", this is because my query is in a stored procedure, and this stored procedure accepts #table, which is the destination table to INSERT into. I'm not interested in discussing my architecture or why the table to INSERT into is "dynamically" specified via a procedure parameter. Please refrain from commenting on this unless there's another way besides EXEC()ing a query to specify a table to INSERT into when then table name is received as a procedure parameter.)
Use sp_executesql and the built-in quotename(). This article, The Curse and Blessings of Dynamic SQL, is pretty much the definitive reference.
You could first query the schema information with regular T-SQL and make sure the table name exists first. This way, if it's malformed SQL, it won't execute as code. It will just be a VARCHAR table name.
DECLARE #Table AS VARCHAR(MAX)
DECLARE #Exists AS BIT
SET #Table = 'Vicious malformed dynamic SQL'
SELECT #Exists = COUNT(TABLE_NAME)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #Table
IF (#Exists = 1)
BEGIN
PRINT 'Table exists'
-- Execute dynamic SQL.
END
ELSE
PRINT 'Invalid table'
(Or simply use IF EXISTS (SELECT ....) )
Rather than calling EXEC(#somesql), I suggest using the sp_executesql stored procedure. Specifically, this allows you to pass parameters, and the system will check that the parameters are valid.
Apparently there's a 128-length limit to quotename(), even in 2008 according to my test, since it expects a SQL identifier. The reference suggests creating a quotestring() function, which does the same thing as:
REPLACE(#variable,'''','''''')
Therefore I am proposing that the answer is to create a function out of the REPLACE() above, like so:
CREATE FUNCTION quotestring(#string nvarchar(MAX))
RETURNS nvarchar(MAX) AS
BEGIN
RETURN(REPLACE(#string,'''',''''''))
END
...Unless I've misunderstood something.
When writing dynamic SQL you'll want to parameterise as much as possible, and only resort to character escaping when you absolutely have to. You can't parameterise #table, but you can parameterise #message.
DECLARE #insert NVARCHAR(MAX)
set #insert = 'INSERT INTO [dbo].' + quotename(#table) + ' values(#message)'
exec sys.sp_executesql #insert, N'#message nvarchar(max)', #message = #inMessage;
There are a lot of ways attackers can exploit dynamic SQL, including buffer length attacks and using unicode equivalent characters. I encountered an example once where escaping the single quote char still had a vulnerability where one of the unicode equivalents of the quote char could be passed in. Part of the software stack was doing a unicode to ascii conversion, so it was possible to inject a quote back in after they were escaped. Ouch.