Dynamic SQL parameter error, Incorrect syntax near '#myparametername' - sql-server

I'm building a fun stored procedure that will use dynamic SQL, sp_executesql with parameters, to allow some alter statements for a column in all database tables if the column name exists ( As you can see I used a cursor for loop all the tables on DB)
I built a test but the parameter doesn't work, I get the next error on each alter table statement that runs
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '#parTablename'.
The next is the code
SET NOCOUNT ON;
GO
DECLARE #tablename varchar(100);
DECLARE #alteredColumn varchar(100)='[mycolumn] [datetimeoffset](0) NOT NULL;';
DECLARE #column varchar(100)='mycolumn';
DECLARE #parDefinition nvarchar(500) = N'#parTablename nvarchar(100)';
DECLARE #sqlCommand nvarchar(1000)= N'ALTER TABLE #parTablename ALTER COLUMN '+#alteredColumn;
DECLARE ALTERCURSOR CURSOR LOCAL FAST_FORWARD FOR
SELECT name AS tablename
FROM sys.Tables
OPEN ALTERCURSOR;
FETCH NEXT FROM ALTERCURSOR INTO #tablename
WHILE ##FETCH_STATUS = 0
BEGIN
--print #tablename
IF EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tablename AND COLUMN_NAME = #column)
BEGIN
EXECUTE sp_executesql #sqlCommand, #parDefinition,#parTablename = #tablename
END
FETCH NEXT FROM ALTERCURSOR INTO #tablename
END
CLOSE ALTERCURSOR;
DEALLOCATE ALTERCURSOR;
SET NOCOUNT OFF;
GO
SOLUTION
Apparently is not possible to send a table name as a parameter, instead of that I used the #SeanLange option for degub with a little modification
SET #sqlCommand =Replace(#sqlCommand, '#parTablename',QUOTENAME(#tablename))
EXECUTE sp_executesql #sqlCommand

You can't stick a parameter in the middle of your dynamic sql like this. You need to use PRINT instead of EXECUTE to debug this. I wouldn't use a cursor for this myself but if you go that path you will have to do something like this before the EXECUTE statement.
Set #sqlCommand = Replace(sqlCommand, '#parTablename', #parTablename)

Related

Running statements in cursor returns Invalid object name error (SQL Server 2014)

I am trying to execute statements gained from a cursor, and I keep getting an "Invalid object name" error. To start with, the cursor pulls information from a table that has a database name in one column, and a SQL statement in another. It is defined as such:
DECLARE commands CURSOR FOR
SELECT
REPLACE(DBNAME, DBNAME, 'USE ' + DBNAME),
REPLACE(REPLACE(REPLACE(DESCRIPTION, 'OLDLINKEDSERVER', 'NEWLINKEDSERVER'), 'CREATE VIEW', 'ALTER VIEW'), 'CREATE VIEW', 'ALTER VIEW') AS CMD
FROM
#TMP2
Then, I define two commands:
DECLARE #cmd1 NVARCHAR(MAX)
DECLARE #cmd2 NVARCHAR(MAX)
So cmd1 is really just "USE DBName" where DBName is the name of the database, and cmd2 is a SQL statement to be run on that database.
For some reason, whenever I then iterate through the cursor:
OPEN Commands
FETCH NEXT FROM Commands INTO #cmd1, #cmd2
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC sp_executesql #cmd1
EXEC sp_executesql #cmd2
FETCH NEXT FROM Commands INTO #cmd1, #cmd2
END
CLOSE commands
DEALLOCATE commands
I get an error that says:
Msg 208, Level 16, State 6, Procedure NameOfView, Line 34
Invalid object name 'dbo.NameOfView'.
I've tried adding a "GO" command to cmd1, but it didn't work (I guess GO is only good for client tools). I tried using "exec(#cmd1)" instead of exec sp_executesql(#cm1)". If I tried to make it one command instead of two, it then says "ALTER VIEW must be the first statement in a query batch."
What can I do to get my cursor to work correctly?
EDIT: The following code is the working solution I found, thanks to IsItGreyOrGray (not sure if I'm supposed to edit it into my original post, or create a new one):
code for cursor
declare commands cursor for
SELECT REPLACE(DBNAME,DBNAME, 'USE ' + DBNAME),
REPLACE(REPLACE(REPLACE(DESCRIPTION,'OLDLINKEDSERVER', 'NEWLINKEDSERVER'),'CREATE VIEW', 'ALTER VIEW'), '''', '''''') AS CMD
FROM #TMP2
declarations
declare #cmd1 varchar(max)
declare #cmd2 varchar(max)
iterate through cursor
open commands
fetch next from commands into #cmd1, #cmd2
while ##FETCH_STATUS=0
begin
declare #cmd3 VARCHAR(MAX) = CONCAT(#cmd1, '; declare #cmd1 varchar(max) = ''', #cmd2, ''' exec(#cmd1)')
exec(#cmd3)
fetch next from commands into #cmd1, #cmd2
end
close commands
deallocate commands
Your issue is context switching.
Printing out the results will give you a working script that changes the databases correctly and makes the schema changes. But that's because it will all run in the context of your query window.
However, each EXEC runs in its own context.
If I run the following script, the output is "master". In #cmd1 I successfully switch to MSDB, but as soon as that command is complete, the context is lost and I'm back in MASTER. #Cmd2 then runs in its own context, which has not been switched from the database it was called from, so it runs in MASTER
USE master
GO
DECLARE #cmd1 VARCHAR(1000) = 'USE msdb'
DECLARE #cmd2 VARCHAR(1000) = 'SELECT DB_NAME()'
EXEC(#cmd1)
EXEC(#cmd2)
In order to make your cursor executions work, you'll need to combine #cmd1 with #cmd2 to make a single executable variable with something along the lines of
'USE [database1]; CREATE TABLE test (id INT)'
Since you're getting the query batch failures you might need to nest your executions...
This fails:
USE master
GO
DECLARE #cmd1 VARCHAR(1000) = 'USE msdb'
DECLARE #cmd2 VARCHAR(1000) = 'create procedure test as SELECT DB_NAME()'
DECLARE #cmd3 VARCHAR(MAX) = CONCAT(#cmd1, ' ', #cmd2)
EXEC (#cmd3)
This succeeds:
DECLARE #cmd1 VARCHAR(1000) = 'USE msdb'
DECLARE #cmd2 VARCHAR(1000) = 'create procedure test as SELECT DB_NAME()'
DECLARE #cmd3 VARCHAR(MAX) = CONCAT(#cmd1, '; declare #cmd1 varchar(max) = ''', #cmd2,''' exec(#cmd1)' )
EXEC (#cmd3)

Not displaying select results from dynamic SQL within Cursor

I'm currently learning SQL and trying to think of exercises for myself and I can't seem to make this one work even though it seems simple:
I'm trying to run a cursor through all the filtered tables within my db so that then I could pass that table name to a variable which will be used within a DynamicSQL inside the cursor. The end result should be all values from every column that has the column 'empid' in them.
However, the message returns as "Commands completed successfully" but I get to see no results despite my select statement.
I'm trying to run something like this:
declare #tablename nvarchar(200);
declare #empid int;
declare #sql nvarchar(200) = N'select * from ' + #tablename + N' where empid = ' +#empid;
declare tablecursor cursor for select table_name from information_schema.tables where col_length(table_name, 'empid') is not null;
open tablecursor;
fetch next from tablecursor into #tablename;
while ##fetch_status = 0
begin
execute sp_executesql #sql, 825
fetch next from tablecursor into #tablename;
end
close tablecursor;
deallocate tablecursor;
I've been searching everywhere for answers to make this work but can't find anything. I've tried putting into a stored procedure and then executing it from there but that didn't work either.
Help would be highly appreciated.
DECLARE #SQL Should be outside but assigning the Variable inside the while loop
SET #SQL = N'SELECT * FROM ' + #tableName
Should be in while loop.
The other thing is to increase the length of #SQL Variable.
Thank you kindly for the help. After I listened to your advice I've encountered more errors but at least for these I was able to find answers online. What I also learnt is that you can't have your sql string in quotes when you execute it as that will make SSMS treat #SQL as an actual string and not a variable. I've managed to get it working and my code now looks something like this:
create proc cdr #empid nvarchar(5) as
declare #tablename nvarchar(200);
declare tablecursor cursor for select table_name from information_schema.tables where col_length(table_name, 'empid') is not null;
open tablecursor;
fetch next from tablecursor into #tablename;
declare #sql nvarchar(max)
while ##fetch_status = 0
begin
set #sql = N'select * from ' + #tablename + N' where empid = ' + #empid;
execute sp_executesql #sql
fetch next from tablecursor into #tablename;
end
close tablecursor;
deallocate tablecursor;

update statement value in multi table at once

I have over 100 tables in SQL Server 2000 with the same column name in each table. Now I want to update a value in 100 tables at once using a SQL update statement.
How do I do that? I try to google and stackoverflow but not really help.
Thanks so much
Create a cursor for all table in your database and using dynamic query to execute.
This script will be help you do this.
--USE [Your DB]
--GO
DECLARE #tableName VARCHAR(100)
DECLARE #sqlQuery VARCHAR(MAX)
DECLARE curTable CURSOR FOR SELECT name FROM sys.objects WHERE type_desc = 'USER_TABLE' AND name NOT IN ('sysdiagrams')
OPEN curTable
FETCH NEXT FROM curTable INTO #tableName
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #sqlQuery = 'UPDATE ' + #tableName + 'SET [YourCol1] = [YourVal1], [YourCol2] = [YourVal2] ...'
PRINT #sqlQuery
EXECUTE sp_executesql #sqlQuery
FETCH NEXT FROM curTable INTO #tableName
END
CLOSE curTable
DEALLOCATE curTable

sql use statement with variable

I'm trying to switch the current database with a SQL statement.
I have tried the following, but all attempts failed:
USE #DatabaseName
EXEC sp_sqlexec #Sql -- where #Sql = 'USE [' + #DatabaseName + ']'
To add a little more detail.
EDIT: I would like to perform several things on two separate database, where both are configured with a variable. Something like this:
USE Database1
SELECT * FROM Table1
USE Database2
SELECT * FROM Table2
The problem with the former is that what you're doing is USE 'myDB' rather than USE myDB.
you're passing a string; but USE is looking for an explicit reference.
The latter example works for me.
declare #sql varchar(20)
select #sql = 'USE myDb'
EXEC sp_sqlexec #Sql
-- also works
select #sql = 'USE [myDb]'
EXEC sp_sqlexec #Sql
exec sp_execsql #Sql
The DB change only lasts for the time to complete #sql
http://blog.sqlauthority.com/2007/07/02/sql-server-2005-comparison-sp_executesql-vs-executeexec/
I have the same problem, I overcame it with an ugly -- but useful -- set of GOTOs.
The reason I call the "script runner" before everything is that I want to hide the complexity and ugly approach from any developer that just wants to work with the actual script. At the same time, I can make sure that the script is run in the two (extensible to three and more) databases in the exact same way.
GOTO ScriptRunner
ScriptExecutes:
--------------------ACTUAL SCRIPT--------------------
-------- Will be executed in DB1 and in DB2 ---------
--TODO: Your script right here
------------------ACTUAL SCRIPT ENDS-----------------
GOTO ScriptReturns
ScriptRunner:
USE DB1
GOTO ScriptExecutes
ScriptReturns:
IF (db_name() = 'DB1')
BEGIN
USE DB2
GOTO ScriptExecutes
END
With this approach you get to keep your variables and SQL Server does not freak out if you happen to go over a DECLARE statement twice.
Just wanted to thank KM for his valuable solution.
I implemented it myself to reduce the amount of lines in a shrinkdatabase request on SQLServer.
Here is my SQL request if it can help anyone :
-- Declare the variable to be used
DECLARE #Query varchar (1000)
DECLARE #MyDBN varchar(11);
-- Initializing the #MyDBN variable (possible values : db1, db2, db3, ...)
SET #MyDBN = 'db1';
-- Creating the request to execute
SET #Query='use '+ #MyDBN +'; ALTER DATABASE '+ #MyDBN +' SET RECOVERY SIMPLE WITH NO_WAIT; DBCC SHRINKDATABASE ('+ #MyDBN +', 1, TRUNCATEONLY); ALTER DATABASE '+ #MyDBN +' SET RECOVERY FULL WITH NO_WAIT'
--
EXEC (#Query)
try this:
DECLARE #Query varchar(1000)
DECLARE #DatabaseName varchar(500)
SET #DatabaseName='xyz'
SET #Query='SELECT * FROM Server.'+#DatabaseName+'.Owner.Table1'
EXEC (#Query)
SET #DatabaseName='abc'
SET #Query='SELECT * FROM Server.'+#DatabaseName+'.Owner.Table2'
EXEC (#Query)
I case that someone need a solution for this, this is one:
if you use a dynamic USE statement all your query need to be dynamic, because it need to be everything in the same context.
You can try with SYNONYM, is basically an ALIAS to a specific Table, this SYNONYM is inserted into the sys.synonyms table so you have access to it from any context
Look this static statement:
CREATE SYNONYM MASTER_SCHEMACOLUMNS FOR Master.INFORMATION_SCHEMA.COLUMNS
SELECT * FROM MASTER_SCHEMACOLUMNS
Now dynamic:
DECLARE #SQL VARCHAR(200)
DECLARE #CATALOG VARCHAR(200) = 'Master'
IF EXISTS(SELECT * FROM sys.synonyms s WHERE s.name = 'CURRENT_SCHEMACOLUMNS')
BEGIN
DROP SYNONYM CURRENT_SCHEMACOLUMNS
END
SELECT #SQL = 'CREATE SYNONYM CURRENT_SCHEMACOLUMNS FOR '+ #CATALOG +'.INFORMATION_SCHEMA.COLUMNS';
EXEC sp_sqlexec #SQL
--Your not dynamic Code
SELECT * FROM CURRENT_SCHEMACOLUMNS
Now just change the value of #CATALOG and you will be able to list the same table but from different catalog.
If SQLCMD is an option, it supports scripting variables above and beyond what straight T-SQL can do. For example: http://msdn.microsoft.com/en-us/library/ms188714.aspx
You can do this:
Declare #dbName nvarchar(max);
SET #dbName = 'TESTDB';
Declare #SQL nvarchar(max);
select #SQL = 'USE ' + #dbName +'; {can put command(s) here}';
EXEC (#SQL);
{but not here!}
This means you can do a recursive select like the following:
Declare #dbName nvarchar(max);
SET #dbName = 'TESTDB';
Declare #SQL nvarchar(max);
SELECT #SQL = 'USE ' + #dbName + '; ' +(Select ... {query here}
For XML Path(''),Type)
.value('text()[1]','nvarchar(max)');
Exec (#SQL)
Use exec sp_execsql #Sql
Example
DECLARE #sql as nvarchar(100)
DECLARE #paraDOB datetime
SET #paraDOB = '1/1/1981'
SET #sql=N'SELECT * FROM EmpMast WHERE DOB >= #paraDOB'
exec sp_executesql #sql,N'#paraDOB datetime',#paraDOB
-- If you are using a variable for the database name.
-- Try something like this.
DECLARE #DBName varchar(50)
Set #DBName = 'Database1'; /* could be passed in by a parameter. */
IF( #DBName = 'Database1')
Begin
USE [Database1];
SELECT FROM Table1;
End
IF( #DBName = 'Database2')
Begin
USE [Database2];
SELECT FROM Table2;
End
IF( #DBName is null)
Begin
USE [Database1];
End

Creating stored procedure in another database

Any idea if it's possible to create a procedure in another database using T-SQL alone, where the name of the database is not known up front and has to be read from a table? Kind of like this example:
Use [MasterDatabase]
Declare #FirstDatabase nvarchar(100)
Select Top 1 #FirstDatabase=[ChildDatabase] From [ChildDatabases]
Declare #SQL nvarchar(4000)
Declare #CRLF nvarchar(10) Set #CRLF=nchar(13)+nchar(10)
Set #SQL =
'Use [+'#Firstdatabase+']'+#CRLF+
'Go'+#CRLF+
'Create Proc [Test] As Select 123'
Exec (#SQL)
See what I'm trying to do? This example fails because Go is actually not a T-SQL command but it something recognised by the query analyser/SQL management studio and produces an error. Remove the Go and it also fails because Create Proc must be the first line of the script. Arrgg!!
The syntax of T-SQL doesn't allow you do things like this:
Create [OtherDatabase].[dbo].[Test]
Which is a shame as it would work a treat! You can do that with Select statements, shame it's inconsistent:
Select * From [OtherDatabase]..[TheTable]
Cheers, Rob.
It's a pain, but this is what I do. I took this from an example I found on sqlteam, I think - you might have some quoting issues with the way I did the indiscriminate REPLACE:
DECLARE #sql AS varchar(MAX)
DECLARE #metasql as varchar(MAX)
DECLARE #PrintQuery AS bit
DECLARE #ExecQuery AS bit
SET #PrintQuery = 1
SET #ExecQuery = 0
SET #sql =
'
CREATE PROCEDURE etc.
AS
BEGIN
END
'
SET #metasql = '
USE OtherDatabase
EXEC (''' + REPLACE(#sql, '''', '''''') + ''')
'
IF #PrintQuery = 1
PRINT #metasql
IF #ExecQuery = 1
EXEC (#metasql)
DECLARE #UseAndExecStatment nvarchar(4000),
#SQLString nvarchar(4000)
SET #UseAndExecStatment = 'use ' + #DBName +' exec sp_executesql #SQLString'
SET #SQLString = N'CREATE Procedure [Test] As Select 123'
EXEC sp_executesql #UseAndExecStatment,
N'#SQLString nvarchar(4000)', #SQLString=#SQLString
This is how i have done with Alter Procedure:
DECLARE #metasql as varchar(MAX)
DECLARE #sql AS varchar(MAX)
SET #sql =
'ALTER PROCEDURE [dbo].[GetVersion]
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP(1)[Version] from VersionTable
END'
SET #metasql = '
USE MyProdDb
IF (OBJECT_ID(''GetVersion'') IS NOT NULL OR OBJECT_ID(''GetVersion'', ''P'') IS NOT NULL)
BEGIN
EXEC (''' + REPLACE(#sql, '''', '''''') + ''')
END
'
--PRINT #metasql
EXEC (#metasql)
You could shell out to osql using xp_cmdshell, I suppose.
I dont think this can be done with TSQL.
You could use an SSIS package that looped the names and connected to the servers dynamically which creates the schema (procs ) you need.
This is probably what I would do as it means it is all contained within the package.
Configuration can be kept separate by either using a table or external xml file that contained the list of server/databases to deploy the schema to.
It's not necessary to use EXEC within EXEC.
You can simply use OtherDatabase.sys.sp_executesql
DECLARE #sql AS varchar(MAX) = N'
CREATE PROCEDURE etc.
AS
BEGIN
-- whatever
END
';
PRINT #sql;
EXEC OtherDatabase.sys.sp_executesql #sql;

Resources