How to pass parameter values to a T-SQL query - sql-server

I am using the following T-SQL query in SQL server 2005 (Management Studio IDE):
DECLARE #id int;
DECLARE #countVal int;
DECLARE #sql nvarchar(max);
SET #id = 1000;
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = #id';
EXEC (#sql) AT oracleServer -- oracleServer is a lined server to Oracle
I am not sure how to pass the input parameter #id to the EXEC query, and pass the count result out to #countVal. I saw some examples for Microsoft SQL server like:
EXEC (#sql, #id = #id)
I tried this for Oracle but I got a statement error:
OLE DB provider "OraOLEDB.Oracle" for linked server "oracleServer"
returned message "ORA-00936: missing expression"

Try this:
EXEC sp_executesql #sql, N'#id int', #id
More info at this great article: http://www.sommarskog.se/dynamic_sql.html
As for the output, your SELECT needs to look something like this:
SELECT #countVal = COUNT(id) FROM owner.myTable WHERE id = #id
I'm selecting 'id' instead of '*' to avoid pulling unnecessary data...
Then your dynamic sql should be something like this:
EXEC sp_executesql #sql,
N'#id int, #countVal int OUTPUT',
#id,
#countVal OUTPUT
This example is adapted from the same article linked above, in the section sp_executesql.
As for your Oracle error, you will need to find out the exact SQL that sp_executesql is sending to Oracle. If there is a profiler or query log in Oracle, that may help. I have limited experience with Oracle, but that would be the next logical step for troubleshooting your problem.

The quick and dirty way is to just build the string before using the EXEC statement, however this is not the recommended practice as you may open yourself up to SQL Injection.
DECLARE #id int;
DECLARE #countVal int;
DECLARE #sql nvarchar(max);
SET #id = 1000;
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = ' + #id
EXEC (#sql) AT oracleServer -- oracleServer is a lined server to Oracle
The correct way to do this is to use the system stored procedure sp_executesql as detailed by magnifico, and recommended by Microsoft in Books Online is:
EXEC sp_executesql #sql, N'#id int', #id

I don't know why you are pass id separately.
You could do the following
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = ' + #id
Edit: Don't do the above. Use parameterized sql statements.

Related

Making dynamic queries for linked server in SQL Server

I want to create a dynamic query in SQL Server which will run on linked server. I am trying to do it as follows.
USE [MYDB]
GO
DECLARE #company AS nvarchar(50);
DECLARE #id nvarchar(MAX);
DECLARE #query nvarchar(MAX);
SET #company = 'mycompany.com';
SET #query = N'SELECT #csid = id FROM OPENQUERY(LINKSERVER12,
''SELECT id from company where name = #comp'')';
EXECUTE sp_executesql #company_query, N'#comp nvarchar(50), #csid
nvarchar(MAX) OUTPUT', #comp = #company,#csid = #id OUTPUT
In the above script, I want to pass the value for #comp dynamically. For that I tried setting input and output variable while executing SQL with sp_executesql.
I am getting the following error
Syntax error in SQL statement. Syntax error line 1 at or after token .[10179].
Msg 7321, Level 16, State 2, Line 4
An error occurred while preparing the query "SELECT id from company where name = #comp" for execution against OLE DB provider "MSDASQL" for linked server "LINKSERVER12".
The error is happening at the dynamic query
N'SELECT #csid = id FROM OPENQUERY(LINKSERVER12,
''SELECT id from company where name = #comp'')'
I tried replacing #comp in the SQL query with ''#comp'', ''''#comp'''' with no luck. Any help is greatly appreciated.
Just build the string query wihtout parameters.
USE [companyDB]
GO
DECLARE
#companyName AS nvarchar(50)
,#id nvarchar(MAX)
,#query NVARCHAR(MAX)
SET #companyName = 'AMAZON'
DECLARE #idTable TABLE
(
id INT
)
--Repace Server, UID and PWD
SET #query =
N'SELECT
[id]
FROM OPENROWSET
(
N''SQLNCLI''
,N''Server=10.111.1.111;UID=username;PWD=password123;''
,N''SELECT [id]
FROM [companyDB]
WHERE [name] = '''''+#companyName+'''''''
)'
INSERT INTO #idTable
EXECUTE (#query)
SELECT TOP 1
#id = id
FROM #idTable

SQL Server 2012 dynamic SQL - stored procedure - getting syntax error

I am writing scripts to generate stored procedures within a database whose current schema notation will be unknown (think shared hosting).
I have decided to use dynamic SQL within the stored procedures so that the web application can pass the database schema based on a user defined setting to the SQL Server in order for it to fire properly.
When I started writing the stored procedures, I noticed that dynamic SQL opens up a whole SQL injection problem I would not normally have so I re-wrote the procedure to combat this. However even though SQL allows me to run the script to generate the stored procedure, each time I try to run the test stored procedure, I get a syntax error
Incorrect syntax near the keyword 'WHERE'
I believe this is to do with the parameter for the schema but I am at a loss as to why this is not working? I am entering the value dbo for the schema.
/*
Name : usp_GetTestTicker
Description : returns test ticker
*/
if not exists (select * from dbo.sysobjects
where id = object_id(N'usp_GetTestTicker')
and OBJECTPROPERTY(id, N'IsProcedure') = 1)
BEGIN
DECLARE #sql as nvarchar(150)
SET #sql = 'CREATE procedure usp_GetTestTicker AS'
EXEC(#sql)
END
GO
ALTER PROCEDURE usp_GetTestTicker
#schema VARCHAR(25),
#TickerItemId INT
AS
SET NOCOUNT ON
BEGIN
DECLARE #sql_cmd NVARCHAR(MAX)
DECLARE #sql_params NVARCHAR(MAX)
SET #sql_cmd = N'SELECT * FROM #schema.TickerItem WHERE TickerItemId = #TickerItemId'
SET #sql_params = N'#schema VARCHAR(25), #TickerItemId INT'
EXEC sp_executesql #sql_cmd, #sql_params, #schema, #TickerItemId
END
GO
To prevent SQL injection, you will need to validate the schema against the sys.schemas table, e.g.
ALTER PROCEDURE usp_GetTestTicker
#schema NVARCHAR(25),
#TickerItemId INT
AS
BEGIN
SET NOCOUNT ON
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = #schema)
BEGIN
-- throw an error here. Your web code will have to handle the error and report an invalid schema
END
ELSE
BEGIN
DECLARE #sql_cmd NVARCHAR(MAX), #sql_params NVARCHAR(MAX)
SET #sql_cmd = N'SELECT * FROM ' + #schema + '.TickerItem WHERE TickerItemId = #TickerItemId'
SET #sql_params = N'#TickerItemId INT'
EXEC sp_executesql #sql_cmd, #sql_params, #TickerItemId
END
END

Insert varchar with sql string

I want to make a EXEC in my sql with Transact-SQL, like:
set #name = 'test'
set #sql = 'insert into TempTable values('+#name+')'
EXEC( #sql)
but i can't insert a varchar in the sql string because i can't put ' into the string so it to knows that #name is a varchar.
first declare the variable first with declare , set and then u can use it.
below the complete code :
DECLARE #name VARCHAR (500)
SET #name = 'test';
set #sql = 'insert into TempTable values('''+#name+''')'
EXEC( #sql)
use (''') three quote to add your varchar.
A simple insert statement like this does not need to executed using Dynamic SQL. you can easily achieve the same using the query below.
DECLARE #name VARCHAR (500)
SET #name = 'test';
insert into TempTable values(#name)
You can refer to the link below for more details on issues related to dynamic sql.
http://www.sommarskog.se/dynamic_sql.html
Please forget using EXEC when you are working with dynamic queries. In SQL Server you should use sp_executesql
DECLARE #name VARCHAR(500);
SET #name = 'test';
DECLARE #query = NVARCHAR(MAX) = 'INSERT INTO TempTable VALUES (#name)';
EXEC sp_executesql
#stmt = #query,
#params = N'#name VARCHAR(500)',
#name = #name
With this
You can avoid unnecessary type conversions (INT -> VARCHAR, etc)
Native data types could be passed
A bit more secure (will not solve all the problems, but a bit bette, than injecting variable values into the query string)

Passing concat query parameters to SQL OPENQUERY

Due to the constraints within the workplace I have to use a local stored procedure to call another remote stored proc on a linked sql server, however the problem lies in passing a necessary parameter to the remote stored proc.
This is the query I constructed:
select *
from OPENQUERY([REMOTE_SRVR],'exec db.dbo.dwStoredProc_sp ''#id''')
In order to pass #id to the remote stored proc I understand I could concatenate the above as a string and then use exec
Something along the lines of:
set #query = 'select * from OPENQUERY([REMOTE_SRVR], ''EXEC db.dbo.dwStoredProc_sp '' #id '''''
exec(#query)
I cannot get the local stored proc to successfully call the other. The single quote mess doesn't help!
I get the error: Could not find stored procedure 's'
To help with the quote mess I like to do this in steps. It is more code but easier to understand. I am not sure from your example if #id is an integer. In that case you can lose the double quotes around __ID__.
set #query = 'EXEC db.dbo.dwStoredProc_sp ''__ID__'''
set #query = REPLACE(#query,'__ID__',#id)
set #query = REPLACE(#query,'''','''''')
set #query = REPLACE('SELECT * FROM OPENQUERY([REMOTE_SRVR], ''__REMOTEQUERY__'')','__REMOTEQUERY__',#query)
You could avoid dynamic queries by simply by using EXEC (..., ParamValue) AT LinkedServer (see product's documentation, example [L. Using a parameter with EXECUTE and AT linked_server_name]):
1) On target server:
CREATE PROCEDURE dbo.Proc1( #id NVARCHAR(50) )
AS
SELECT #id AS [id];
GO
2) On the source server you create the linked server and then you can call the stored procedure using EXEC ... AT ... syntax:
DECLARE #p1 NVARCHAR(50);
SET #p1 = N'DROP TABLE dbo.CocoJambo'
EXECUTE (N'dbo.Proc1 ? ' , #p1 ) AT LOCALINKEDSEREV
Output:
id
------------------------
DROP TABLE dbo.CocoJambo

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

Resources