T-SQL variable show Incorrect syntax near '#tableName' - sql-server

It shows an error ,when try to run this
declare #tableName VARCHAR(250)
select #tableName='['+SCHEMA_NAME(schema_id)+'].['+name+']'
FROM sys.tables
WHERE '['+SCHEMA_NAME(schema_id)+'].['+name+']'='[Management].[Table_1]'
print #tableName
TRUNCATE table #tableName
Incorrect syntax near '#tableName'.

That is correct. You cannot use variables to pass table names.
You can use dynamic SQL:
declare #sql nvarchar(max);
set #sql = replace('TRUNCATE table #tableName', '#tableName', #tableName);
exec sp_executesql #sql;
SQL statements are allowed to have parameters for constants, but not for identifiers. This is not only a SQL Server limitation, but a limitation in all (?) databases. Dynamic sql is often used for this purpose.
TSQL sp_executesql

Related

Is it possible to issue CREATE statements using sp_executesql with parameters?

I'm trying to dynamically create triggers, but ran into a confusing issue around using sp_executesql and passing parameters into the dynamic SQL. The following simple test case works:
DECLARE #tableName sysname = 'MyTable';
DECLARE #sql nvarchar(max) = N'
CREATE TRIGGER TR_' + #tableName + N' ON ' + #tableName + N' FOR INSERT
AS
BEGIN
PRINT 1
END';
EXEC sp_executesql #sql
However, I want to be able to use #tableName (and other values) as variables within the script, so I passed it along to the sp_executesql call:
DECLARE #tableName sysname = 'ContentItems';
DECLARE #sql nvarchar(max) = N'
CREATE TRIGGER TR_' + #tableName + N' ON ' + #tableName + N' FOR INSERT
AS
BEGIN
PRINT #tableName
END';
EXEC sp_executesql #sql, N'#tableName sysname', #tableName=#tableName
When running the above, I get an error:
Msg 156, Level 15, State 1, Line 2
Incorrect syntax near the keyword 'TRIGGER'.
After trying I few things, I've discovered that even if I don't use #tableName in the dynamic SQL at all, I still get this error. And I also get this error trying to create a PROCEDURE (except, obviously, the message is Incorrect syntax near the keyword 'PROCEDURE'.)
Since the SQL runs fine either directly or when not supplying parameters to sp_executesql, this seems like I'm running into a true limitation in the SQL engine, but I don't see it documented anywhere. Does anyone know if there is a way to accept to a dynamic CREATE script, or at least have insight into the underlying limitation that's being run into?
Update
I can add a PRINT statement, and get the below SQL, which is valid, and runs successfully (when run directly). I still get the error if there's nothing dynamic in the SQL (it's just a single string with no concatenation).
CREATE TRIGGER TR_ContentItems ON ContentItems FOR INSERT
AS
BEGIN
PRINT #tableName
END
I also get the same error whether using sysname or nvarchar(max) for the parameter.
If you execute your create trigger statement that you said you printed... you will find that it does not work. The print statement in the body of the trigger is trying to output #tablename, but is never defined, so you will get an error:
Must declare the scalar variable "#tableName".
But that is not your main issue. As for why you can't seem to execute a DDL statement with execute_sql with parameters, I couldn't find any documentation to explain why... but your experience and others proves that it's troublesome. I believe this post has a pretty good theory: sp_executesql adds statements to executed dynamic script?
You can however execute dynamic sql with DDL statements using the EXECUTE statement. So what you could do is create a parameterized sp_executesql statement that validates your table name and then creates a dynamic sql string to execute with the EXECUTE statement.
It doesn't look pretty, but it works:
DECLARE #tableName sysname = 'MyTable';
DECLARE #sql nvarchar(max) =
N'
set #tableName = (SELECT name FROM sys.tables WHERE OBJECT_ID = OBJECT_ID(#tableName)) --validate table
DECLARE #CreateTriggerSQL as varchar(max) =
''
CREATE TRIGGER '' + QUOTENAME(''TR_'' + #tableName) + '' ON '' + QUOTENAME( #tableName) + '' FOR INSERT
AS
BEGIN
PRINT '''''' + #tableName + ''''''
END
''
print isnull(#CreateTriggerSQL, ''INVALID TABLE'')
exec (#CreateTriggerSQL)
';
EXEC sp_executesql #sql, N'#tableName sysname', #tableName=#tableName;
You could also convert this into a stored procedure with parameters instead of running sp_executesql if that were more convenient. It looks a bit cleaner:
CREATE PROCEDURE sp_AddTriggerToTable (#TableName AS sysname) AS
set #tableName = (SELECT name FROM sys.tables WHERE OBJECT_ID = OBJECT_ID(#tableName)) --validate table
DECLARE #CreateTriggerSQL as varchar(max) =
'
CREATE TRIGGER ' + QUOTENAME('TR_' + #tableName) + ' ON ' + QUOTENAME( #tableName) + ' FOR INSERT
AS
BEGIN
PRINT ''' + #tableName + '''
END
'
print isnull(#CreateTriggerSQL, 'INVALID TABLE')
exec (#CreateTriggerSQL)
GO
I would strongly caution against using Dynamic SQL with table names. You are setting yourself up for some serious SQL Injection issues. You should validate anything that goes into the #tableName variable.
That said, in your example...
DECLARE #tableName sysname = 'ContentItems';
DECLARE #sql nvarchar(max) = N'
CREATE TRIGGER TR_' + #tableName + N' ON ' + #tableName + N' FOR INSERT
AS
BEGIN
PRINT #tableName
END';
EXEC sp_executesql #sql, N'#tableName sysname', #tableName=#tableName
... you are trying to input your declared #tableName into the text you're creating for #sql, and then you're trying to pass a parameter through spexecutesql. This makes your #sql invalid when trying to call it.
You can try:
DECLARE #tableName sysname = 'ContentItems';
DECLARE #sql nvarchar(max) = N'
CREATE TRIGGER TR_'' + #tableName + N'' ON '' + #tableName + N'' FOR INSERT
AS
BEGIN
PRINT #tableName
END';
EXEC sp_executesql #sql, N'#tableName sysname', #tableName=#tableName
... which will give you the string ...
'
CREATE TRIGGER TR_' + #tableName + N' ON ' + #tableName + N' FOR INSERT
AS
BEGIN
PRINT #tableName
END'
... which can then accept the parameter you pass through ...
EXEC sp_executesql #sql, N'#tableName sysname', #tableName=#tableName ;
Again, I'd use some heavy validation (and white-listing) before passing anything into dynamic SQL that will use a dynamic table name.
NOTE: As noted below, I believe you are limited on DML statements that can be executed with sp_executesql(), and I think parameterization is limited also. And based on your other comments, it doesn't sound like you're really needing a dynamic process but a way to repeat a specific task for a handful of elements. If that's the case, my recommendation is to do it manually with a copy/paste then execute the statements.
Since the SQL runs fine either directly or when not supplying
parameters to sp_executesql, this seems like I'm running into a true
limitation in the SQL engine, but I don't see it documented anywhere.
This behavior is documented, albeit not intuitive. The relevant excerpt from the documentation under the trigger limitations topic:
CREATE TRIGGER must be the first statement in the batch
When you execute a parameterized query, the parameter declarations are counted as being part of the batch. Consequently, a CREATE TRIGGER batch (and other CREATE statements for programmability objects like procs, functions, etc.) cannot be executed as a parameterized query.
The invalid syntax error message you get when you attempt to run CREATE TRIGGER as a parameterized query isn't particularly helpful. Below is an simplified version of your code using the undocumented and unsupported internal parameterized query syntax.
EXECUTE(N'(#tableName sysname = N''MyTable'')CREATE TRIGGER TR_MyTable ON dbo.MyTable FOR INSERT AS');
This at least yields an error calling out the CREATE TRIGGER limitation:
Msg 1050, Level 15, State 1, Line 73 This syntax is only allowed for
parameterized queries. Msg 111, Level 15, State 1, Line 73 'CREATE
TRIGGER' must be the first statement in a query batch.
Similarly executing another parameterized statement with this method runs successfully:
EXECUTE (N'(#tableName sysname = N''MyTable'')PRINT #tableName');
But if you don't actually use the parameter in the batch, an error results
EXECUTE (N'(#tableName sysname = N''MyTable'')PRINT ''done''');
Msg 1050, Level 15, State 1, Line 75 This syntax is only allowed for
parameterized queries.
The bottom line is that you need to build the CREATE TRIGGER statement as a string without parameters and execute the statement as a non-parameterized query to create a trigger.
Is it possible to issue CREATE statements using sp_executesql with
parameters?
Simple answer is "No", you can't
According to MSDN
Generally, parameters are valid only in Data Manipulation Language
(DML) statements, and not in Data Definition Language (DDL) statements
You can check more details about this Statement Parameters
What is the issue?
Parameters are only allowed in place of scalar literals, like quoted strings or dates, or numeric values. You can't parameterise a DDL operation.
What can be done?
I believe that you want to use parameterized sp_executesql is to avoid any SQL Injection Attack. To achieve this for the DDL operations you can do following thing to minimize the possibility of attack.
Use Delimiters : You can use QUOTENAME() for SYSNAME parameters like Trigger Name, Table Names and Column names.
Limiting Permissions : User Account you are using to run the dynamic DDL, should have only limited permission. Like on a
specific schema with only CREATE permission.
Hiding Error Message : Don't throw the actual error to the user. SQL Injection are mainly performed by trial and error approach. If
you hide the actual error message, it will become tough to crack it.
Input Validation : You can always have a function which validates the input string, escape the required characters, check
for specific keywords like DROP.
Any workaround?
If you want to parameterized your statement using sp_executesql, in that case you can get the query to be executed in a OUTPUT variable and run the query in next statement like following.
By this, the first call to sp_executesql will parameterized your query, and the actual execution will be performed by the second call to sp_executesql
For example.
DECLARE #TableName VARCHAR(100) = 'MyTable'
DECLARE #returnStatement NVARCHAR(max);
DECLARE #sql1 NVARCHAR(max)=
N'SELECT #returnStatement = ''CREATE TRIGGER TR_''
+ #TableName + '' ON '' + #TableName + '' FOR INSERT AS BEGIN PRINT 1 END'''
EXEC Sp_executesql
#sql1,
N'#returnStatement VARCHAR(MAX) OUTPUT, #TableName VARCHAR(100)',
#returnStatement output,
#TableName
EXEC Sp_executesql #returnStatement
Is it possible to issue CREATE statements using sp_executesql with
parameters?
The answer is "Yes", but with small adjustment:
USE msdb
DECLARE #tableName sysname = 'sysjobsteps';
DECLARE #sql nvarchar(max) = N'
EXECUTE ('' -- Added nested EXECUTE()
CREATE TRIGGER [TR_'' + #tableName + N''] ON ['' + #tableName + N''] FOR INSERT
AS
BEGIN
PRINT '''''+#tableName+'''''
END''
)' -- End of EXECUTE()
EXEC sp_executesql #sql, N'#tableName sysname', #tableName=#tableName
Adjsutments list:
Extra EXECUTE involved, comment below explains why
Extra square brackets added to make SQL Injections slightly harder
I'm looking for specific (ideally, documented) restrictions of
sp_executesql with parameters and if there are any workarounds for
those specific restrictions (beyond not using parameters)
in this case it is a limitation of DDL commands, not sp_executesql. DDL statements cannot be parametrized using variables. Microsoft documentation says:
Variables can be used only in expressions, not in place of object
names or keywords. To construct dynamic SQL statements, use EXECUTE.
source: DECLARE (Transact-SQL)
Therefore, the solution with EXECUTE is provided by me as a workaround
Personally I hate triggers and try to avoid them most of the time ;)
However if you really, really need this dynamic stuff you should use sp_MSforeachtable and avoid injection (as pointed out by Shawn) at any cost:
EXEC sys.sp_MSforeachtable
#command1 = '
DECLARE #sql NVARCHAR(MAX)
SET #sql = CONCAT(''CREATE TRIGGER TR_''
, REPLACE(REPLACE(REPLACE(''?'', ''[dbo].'', ''''),''['',''''),'']'','''')
, '' ON ? FOR INSERT
AS
BEGIN
PRINT ''''?'''';
END;'');
EXEC sp_executesql #sql;'
, #whereand = ' AND object_id IN (SELECT object_id FROM sys.objects
WHERE name LIKE ''%ContentItems%'')';
If you want to use the parameter as string, add double ' before and after the parameter name
like this :
DECLARE #tableName sysname = 'ContentItems';
DECLARE #sql nvarchar(max) = N'
CREATE TRIGGER TR_' + #tableName + N' ON ' + #tableName + N' FOR INSERT
AS
BEGIN
print ''' + #tableName
+''' END';
EXEC sp_executesql #sql
And if you want to use it as table name, use select instead of print ,
like this :
DECLARE #tableName sysname = 'ContentItems';
DECLARE #sql nvarchar(max) = N'
CREATE TRIGGER TR_' + #tableName + N' ON ' + #tableName + N' FOR INSERT
AS
BEGIN
select * from ' + #tableName
+' END';
EXEC sp_executesql #sql

SQLServer: Dynamic sql raise exception Could not find stored procedure

This is my dynamic sql.
DECLARE #SQL varchar(MAX)
DECLARE #Data varchar(MAX)
SET #Data='ALFKI'' OR ContactName=''Ana Trujillo'''
SET #SQL='select * from Customers Where CustomerID='''+#Data+''
print #SQL
exec (#SQL)
when i print then i get this sql select * from Customers Where CustomerID='ALFKI' this sql is right one but when i replace print #SQL with exec #SQL and execute the dynamic sql again then i am getting error called
Msg 2812, Level 16, State 62, Line 8 Could not find stored procedure
'select * from Customers Where CustomerID='ALFKI''.
not clear where i made the mistake. please give me some hint where is the problem in above dynamic sql. thanks
There is EXEC to execute a stored procedure. Of course there is no SP with the name select * from Customers....
And there is EXEC(), a function!, which is used to execute dynamically created statements.
Just use EXEC(#SQL) instead.
Another way with some more options is sp_executesql with wide support for parameters. You can use this to pass the ALFKI as parameter. Otherwise you might be open for SQL injection...

How to pass a variable value in a FROM clause in Microsoft TSQL

sorry I am uploading a pic of my query as I dont know to format my text...as a newbie its confusing.
So you want to execute:
select * from File_20170703 -- where your table name is a variable.
It is not possible to use variables for table or column names, what you need to do is to build a dynamic sql and execute it using sp_executesql.
here is an example:
DECLARE #sql nvarchar(4000)
SELECT #sql = 'SELECT * FROM File_20170703'
EXEC sp_executesql #sql
More info about dynamic sql
A simple TSQL "dynamic sql" looks like this:
DECLARE #file_name AS VARCHAR(100)
DECLARE #query AS VARCHAR(MAX)
SET #file_name = 'file_20170101'
SET #query = 'SELECT * FROM ' + #file_name
execute(#query)
Basically you need to create a valid sql query by concatenating various parts of the query together, then you can execute that whole big string as your query.
You can use SQL Cursor along with while loop. Examples are given here:
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/while-transact-sql

SQL variable for Database Name

I am trying to pass a database name in as a parameter and execute some dynamic SQL. As a test I created this:
declare #HRMSDatabase_1 nvarchar(50) = N'FirstDatabase',
#Example_1 nvarchar(max) =
'select #HRMSDatabase'
execute sp_executesql #Example_1, N'#HRMSDatabase nvarchar(50)', #HRMSDatabase_1
which returns FirstDatabase as I expected.
When I try this:
declare #HRMSDatabase_2 nvarchar(50) = N'FirstDatabase',
#Example_2 nvarchar(max) =
'select
''Test''
from
#HRMSDatabase.dbo.hrpersnl hp'
execute sp_executesql #Example_2, N'#HRMSDatabase nvarchar(50)', #HRMSDatabase_2
I get an error message:
Msg 102, Level 15, State 1, Line 29
Incorrect syntax near '.'.
Is what I am trying to do possible? I cannot simply use a USE FirstDatabase as I have a few databases I have to query in the same dynamic SQL using inner joins.
Also, I cannot use SQLCMD as this script gets executed from a GUI.
Basically, I don't believe you can parameterize the database name in the table specifier. Instead try this,
DECLARE #HRMSDatabase NVARCHAR(50) = N'FirstDatabase';
DECLARE #Example3 NVARCHAR(MAX) ='SELECT
''Test''
FROM
' + QUOTENAME(#HRMSDatabase) + '.[dbo].[hrpersnl] hp';
EXEC sp_executesql #Example3;
As you'll note, it's important that the #HRMSDatabase is not recieved from user input as this would be susceptible to injection attacks.

SQL - Can I pass a variable to be used in the FROM statement?

Can I pass a variable to a SELECT statement?
I keep getting an error message saying I need to declare it.
However, it is declared.
SELECT (list of columns)
FROM #database_table
You are looking to use Dynamic SQL to perform this type of query.
The Curse and Blessings of Dynamic SQL
Here is a quick sample
declare #sqlstatement nvarchar(4000)
declare #table sysname
set #table = 'yourTableName'
set #sqlstatement = 'SELECT * FROM ' + QUOTENAME(#table)
exec(#sqlstatement)
Yes, use dynamic sql statements to build your select statement.
-- Procedure input parameters
#TableName varchar(50)
-- Query guts
Declare #sql varchar(2000)
Set #sql = 'Select columnname from ' + #TableName
exec (#sql)
The one time you can do what you want is when you use table variables. You have to define the variables as:
declare #name table (<column list>)
This is alternative method of declaring a temporary table.
Other than this, I fully agree with bluefeet. You should read the link he posted.

Resources