Run stored procedure on linked server (linked server name as variable) - sql

I have a script and its works well:
DECLARE #RunSPSQL VARCHAR(60);
SET #RunSPSQL = 'EXEC master.dbo.sp_test';
EXEC (#RunSPSQL) AT LNK_SERVER_NAME;
Now I would like to change linked server name to variable, something like this:
DECLARE #RunSPSQL VARCHAR(60);
DECLARE #LNK_Name NVARCHAR(60);
SET #RunSPSQL = 'EXEC master.dbo.sp_test';
SET #LNK_Name = 'LNK_SERVER_NAME';
EXEC (#RunSPSQL) AT #LNK_Name;
But it doesn't work:
Incorrect syntax near '#LNK_Name'
I was looking for a solution, but no success for now.
If anyone, please help.

You can't use a variable to replace the name of an object. Instead you need to use some dynamic SQL to achieve this:
DECLARE #RunSPSQL varchar(60);
DECLARE #LNK_Name nvarchar(60);
DECLARE #SQL nvarchar(MAX);
SET #RunSPSQL = 'EXEC master.dbo.sp_test';
SET #LNK_Name = N'LNK_SERVER_NAME';
SET #SQL = N'EXEC (#RunSPSQL) AT ' + QUOTENAME(#LNK_Name) + N';';
EXEC sp_executesql #SQL, N'#RunSPSQL varchar(60)', #RunSPSQL = #RunSPSQL;
Just ensure you quote the linked server's name to avoid any injection.

Related

Setting SQL Variable via Dynamic SQL

I know I am overthinking this, but I've been banging against this for too long so I'm reaching out for help.
This is the statement I'm trying to run: SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]
BUT, the table name is a variable #reportTable
This doesn't work:
SET #sql = 'SELECT #cntMax = MAX(id) FROM #reportTable'
EXEC sp_executesql #sql
I even tried having the actual table name in the SET #sql and that doesn't work either.
I didn't think it would be this difficult, please tell me I'm missing something easy/obvious.
Here's the full bit of code for those who want it:
DECLARE
#inTable nvarchar(255) = 'Raw_Item',
#reportTable nvarchar(255),
#fieldName nvarchar(255),
#cnt int,
#cntMax int,
#sql nvarchar(max)
SET #reportTable = #inTable + '-FieldReport'
SET #cnt = 1
SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]
PRINT #cntMax
SET #cntMax = 0
SET #sql = 'SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]'
EXEC sp_executesql #sql
PRINT #cntMax
SQL Server 12.0.2008.8 (on Azure)
You need to use an output parameter, otherwise SQL Server has no idea how to connect #cntMax in the dynamic SQL to #cntMax not in the dynamic SQL, since they are different scopes. And to protect yourself from SQL injection (some tips here and here), always check that your object exists, and use QUOTENAME() as opposed to manually adding square brackets (and you should always use QUOTENAME() when building object names from user input or variables, even when they don't have bad characters like dashes):
DECLARE #sql nvarchar(max),
#inTable nvarchar(255) = N'Raw_Item',
#reportTable nvarchar(255);
SET #reportTable = N'dbo.' + QUOTENAME(#inTable + '-FieldReport');
IF OBJECT_ID(#reportTable) IS NOT NULL
BEGIN
SET #sql = N'SELECT #cntMax = MAX(id) FROM ' + #reportTable + N';';
EXEC sys.sp_executesql #sql,
N'#cntMax int output',
#cntMax = #cntMax OUTPUT;
PRINT #cntMax;
END
ELSE
BEGIN
PRINT 'Nice try, h#xx0rs!';
END
Always use schema reference (dbo), always use statement terminators, and please try to avoid naming things with invalid identifier characters like dash (-). And one additional tip: always use N prefix on N'nvarchar string literals'.

Query that saves data in a variable

I am writing an SP in SQL SERVER, and I need one of the steps to execute a query that comes with data from another variable and save that result in another variable for later use in SP. The code I am using displays the following error:
Procedure expects parameter '#handle' of type 'int'.
DECLARE #retorno INT, #sql NVARCHAR(4000), #parametro NVARCHAR(4000), #caminho NVARCHAR(4000)
SET #caminho = '\\arquivos\CÉLULA DE INOVAÇÃO\SQL\19072019.CSV'
SET #parametro = N'#retornoOut INT OUTPUT'
SET #sql = 'SELECT TOP 1 SUBSTRING(RIGHT('''+#caminho+''',12),1,8)'
PRINT #SQL
EXEC sp_execute #sql, #parametro, #retornoOut=#retorno OUTPUT;
The purpose of the code is from a file passed by the user, I can get the name from the given path. I don't know what could be wrong with id.
The problem is that youre using sp_execute , when you should use sp_executesql
this works fine
DECLARE #retorno INT, #sql NVARCHAR(4000), #parametro NVARCHAR(4000), #caminho NVARCHAR(4000)
SET #caminho = '\\arquivos\CÉLULA DE INOVAÇÃO\SQL\19072019.CSV'
SET #parametro = N'#retornoOut INT OUTPUT'
SET #sql = 'SELECT TOP 1 SUBSTRING(RIGHT('''+#caminho+''',12),1,8)'
PRINT #SQL
EXEC sp_executesql #sql, #parametro, #retornoOut=#retorno OUTPUT;
You don't need to use dynamic SQL for this. You can just use set or a simple select:
Using set:
SET #retorno = SUBSTRING(RIGHT(#caminho,12),1,8)
Using select:
SELECT #retorno = SUBSTRING(RIGHT(#caminho,12),1,8)

Invalid Column name error during execution of stored procedure

I am not able to execute the stored procedure. It is throwing an error
Invalid Column name 'DW201401'
Command used to execute the stored procedure:
exec RM_UTIL_MODE server,'DW201401'
Stored procedure code:
ALTER Procedure [dbo].[RM_UTIL_MODE]
#ServerName varchar(50),
#Value varchar(50)
As
Begin
declare #query nvarchar(max)
set #query = N'SELECT mode FROM ' + #ServerName +
N'.master.dbo.sysdatabases WHERE name =' + #Value
exec sp_executesql #query
End
But when I tried to run the query alone as shown below it is giving me result.
select mode, name
from server.master.dbo.sysdatabases
where name = 'DW201401'
Presumably, the issue is quotes around #Value:
declare #query nvarchar(max)
set #query = N'SELECT mode FROM '
+ #ServerName
+ N'.master.dbo.sysdatabases
WHERE name = '''+#Value+'''';
However, I would use parameter substitution instead:
declare #query nvarchar(max) ;
set #query = N'SELECT mode
FROM ' + #ServerName + N'.master.dbo.sysdatabases
WHERE name = #value';
exec sp_executesql #query, N'#value varchar(50)', #value = #value;
You are already using sp_executesql, so you might as well use it properly. Note: you cannot substitute the server name.
EDIT:
To elaborate on the comment, I would write the code this way:
declare #sql nvarchar(max) ;
set #sql = N'
SELECT mode
FROM #ServerName.master.dbo.sysdatabases
WHERE name = #value';
set #sql = replace(#sql, '#ServerName', quotename(#ServerName));
exec sp_executesql #sql, N'#value varchar(50)', #value = #value;
When using dynamic SQL, I no longer piece together the query using string concatenation. Instead, I put in place holders and use replace(). I find that concatenation is hard to maintain and often obscures what the SQL is doing. Although there is a bit more overhead in using replace() (and I often do it multiple times), it is worth it for preventing errors and maintaining the code (plus, my queries tend to run for a while anyway, so the overhead is minimal compared to the query time).
Your select looks like:
select mode, name from server.master.dbo.sysdatabases where name = DW201401
so you need to add escaped quotes in your dynamic query:
exec RM_UTIL_MODE cefmtqcfindv3,'DW201401'
ALTER Procedure [dbo].[RM_UTIL_MODE]
#ServerName varchar(50),#Value varchar(50)
As
Begin
declare #query nvarchar(max)
set #query = N'SELECT mode FROM '
+ #ServerName
+ N'.master.dbo.sysdatabases
WHERE name ='''+#Value+''''
exec sp_executesql #query
End
Just as a suggestion, when you are building a dynamic sql, try using PRINT instead of EXEC, then get what is printed and try it out. Most of the times you will know what went wrong.
Just as an example:
ALTER Procedure [dbo].[RM_UTIL_MODE]
#ServerName varchar(50),#Value varchar(50)
As
Begin
declare #query nvarchar(max)
set #query = N'SELECT mode FROM '
+ #ServerName
+ N'.master.dbo.sysdatabases
WHERE name ='''+#Value+''''
PRINT #query
--exec sp_executesql #query
End

T-SQL: can I use a variable as a database reference

I want to accomplish this:
update #sourceDatabase.dbo.PredictedPrices
and then set #sourceDatabase as a variable.
But I'm not allowed?
Incorrect syntax near '.'.
Is there another way?
DECLARE #Dynsql NVARCHAR(MAX)
DECLARE #sourceDatabase sysname
DECLARE #MinPrice MONEY
SET #sourceDatabase = 'foo'
SET #MinPrice = 1.00
SET #Dynsql = N'update ' + QUOTENAME(#sourceDatabase) + '.dbo.PredictedPrices
set MinPrice = #MinPrice'
EXECUTE sp_executesql #Dynsql,
N'#MinPrice money',
#MinPrice = #MinPrice;
For this to be done you need to use SP_ExecuteSQL . i.e dynamic query execution
Example:
EXECUTE sp_executesql
N'SELECT * FROM AdventureWorks2008R2.HumanResources.Employee
WHERE BusinessEntityID = #level',
N'#level tinyint',
#level = 109;
If you're running this script in SSMS, you can use SQLCMD Mode (found under the Query menu) to script a variable for your database name:
:setvar sourceDatabase YourDatabaseName
update $(sourceDatabase).dbo.PredictedPrices
set ...

Fully qualified table names with SP_ExecuteSql to access remote server

Trying to update a table on a linked server (SQL 2000/2005) but my server name will not be known ahead of time. I'm trying this:
DECLARE #Sql NVARCHAR(4000)
DECLARE #ParamDef NVARCHAR(4000)
DECLARE #SERVER_NAME VARCHAR(35)
SET #Sql = 'UPDATE
#server_name_param.dba_sandbox.dbo.SomeTable
SET SomeCol=''data'''
SET #ParamDef = N'#server_name_param VARCHAR(35)'
print #Sql
exec sp_executesql #Sql, #ParamDef, #server_name_param=#SERVER_NAME
Which returns this:
UPDATE
#server_name_param.dba_sandbox.dbo.SomeTable
SET SomeCol='data'
Msg 170, Level 15, State 1, Line 2
Line 2: Incorrect syntax near '.'.
Any ideas? Is there anyway I view the SQL statement that is being executed after the parameters are bound?
You'll have to do this, it can't be parameterised
....
SET #Sql = 'UPDATE ' + #server_name_param + '.dba_sandbox.dbo.SomeTable SET SomeCol=''data'''
....
Edit: There is another way which I used back in my pure DBA days
EXEC sp_setnetname 'AdhocServer', #SERVER_NAME
UPDATE AdhocServer.dba_sandbox.dbo.SomeTable SET SomeCol 'data'
EXEC sp_setnetname 'AdhocServer', 'MeaninglessValue'
sp_setnetname is there from SQL Server 2000 to 2008
Edit2. Permissions:
Try EXECUTE AS LOGIN = 'login_name' , where login_name is a superuser
I've not really used this (I use "AS USER" for testing), so not sure of the finer points...
Edit 3: for concurrency, consider using sp_getapplock and a stored procedure, or some other concurrency control mechanism.
You cannot do this with parameters directly - you would have to use dynamic SQL, or send the server name as a parameter to an SP that does dynamic SQL:
DECLARE #template NVARCHAR(4000)
DECLARE #Sql NVARCHAR(4000)
DECLARE #SERVER_NAME VARCHAR(35)
SET #template = 'UPDATE {#server_name_param}.dba_sandbox.dbo.SomeTable SET SomeCol=''data'''
SET #sql = REPLACE(#template, '{#server_name_param}', #SERVER_NAME)
print #Sql
exec sp_executesql #Sql -- OR EXEC ( #sql )
I like gbn's trick. I didn't know that one and I'm gonna have to research that some more.
Since I didn't know that trick, I've had to use dynamic sql in similar situations in the past (like what Cade posted). When that happens I would normally query an information schema view to make sure the parameter value is a real database object before building the query. That way I'm sure it's not an injection attempt.