I'm trying to execute mixed sql: dynamic with static. I have a stored proc with many queries and select-into-temp-table constructions. Portions of it need to be dynamic. Here are some extracted snippets of what I'm trying to do:
#DynamicPrefix = '0001' -- this is passed in by caller
#EngineCd = '070123456' -- this is passed in by caller
DECLARE #DynamicSQL VARCHAR(1000)
DECLARE #EngineKey INT
SET #DynamicSQL = 'set #EngineKey = (select optionnumber from lookup_' + #DynamicPrefix + '_option_001
where salescode = ' + #EngineCd + ')'
EXEC (#DynamicSQL)
Then further down:
Select MyCol
into #Eng
from myTable
where EngineKey = #EngineKey
There's a lot of static sql before, in between, and after my code block above.
The whole reason I'm bothering about dynamic sql is because I don't know certain table names until run time. So #DynamicPrefix enables me to construct the correct table names at execution time.
I can create the proc without errors, but when I run it I get the error Must declare the scalar variable "#EngineKey". It's clear to me that because #EngineKey is inside dynamic sql, it's invisible from within the static sql further down.
I suspect I need to use exec sp_executesql but I can't quite figure out the usage, so I had just started with EXEC.
How can I get this to work? Thanks in advance.
This should do the job:
#DynamicPrefix = '0001'; -- this is passed in by caller
#EngineCd = '070123456'; -- this is passed in by caller
DECLARE #EngineKey INT;
DECLARE #SQL NVARCHAR (MAX);
SET #SQL =N'set #EngineKey = (select optionnumber from lookup_'+
#DynamicPrefix +
'_option_001 where salescode = '+
#EngineCd +')';
EXECUTE sp_executesql
#SQL,
N'#EngineKey INT OUTPUT, #EngineCd VARCHAR(10)',
#EngineKey OUTPUT, #EngineCd;
You have to specify your output parameter with OUTPUT keyword, and set your variables and their datatypes as you can see in the code.
If you don't use the OUTPUT keyword, your variable will always return NULL.
There are examples provided in the docs, see sp_executesql.
Related
I've got a procedure call that is used by several groups/processes etc.
The call works as follows:
EXEC LWP_PAYMENT_URL #order_no, #dept
and it returns a string like this
NzI2NzU4NabNzEyMj24Ny1zYQ=
I'm given the assignment to create a url path as follows
DECLARE #url_path VARCHAR(4000)
SET #url_path = 'https://www.website.com/payment?code='
DECLARE #ReturnValue VARCHAR(4000) = ''
EXEC #ReturnValue = LWP_PAYMENT_URL #order_no, #dept
SET #url_path = #url_path + #ReturnValue
SELECT #ReturnValue, #url_path
My goal is to take the hard coded url_path and get the encoded string from the execute and save it in a variable and concatenate it to the url_path.
What I'm seeing is that the string is returned part of the execute call instead of setting it to #ReturnValue and then looks like I get a zero value being saved and concatenated.
Added these are the final two lines of the LWP_PAYMENT_URL procedure.
DECLARE #Encoded VARCHAR(500) = CONVERT(VARCHAR(500), (SELECT CONVERT(VARBINARY, #string) FOR XML PATH(''), BINARY BASE64))
SELECT #Encoded AS [Encoded]
Thank you
Your stored procedure should be doing this instead:
CREATE OR ALTER PROCEDURE dbo.LWP_PAYMENT_URL
...#input parameters...,
#encoded varchar(500) = NULL OUTPUT
AS
BEGIN
SET NOCOUNT ON;
...
SET #Encoded = CONVERT(varchar(500),
(SELECT CONVERT(VARBINARY, #string) FOR XML PATH(''), BINARY BASE64));
END
And then the caller says:
DECLARE #ReturnValue varchar(500);
EXEC dbo.LWP_PAYMENT_URL #order_no, #dept,
#Encoded = #ReturnValue output;
If you can't change the stored procedure, create a separate one, or a table-valued UDF as suggested in the comments, or (assuming there are no other SELECTs in the procedure we can't see):
CREATE TABLE #foo(ReturnValue varchar(500));
INSERT #foo EXEC dbo.LWP_PAYMENT_URL ...;
DECLARE #ReturnValue varchar(500);
SELECT #ReturnValue = ReturnValue FROM #foo;
That's gross, though, and basically an abuse of how data sharing should work in SQL Server.
Ideally what you should do is, if the logic is the same for all uses, put that logic in some type of module that is much easier to reuse (e.g. a table-valued function). Then this existing stored procedure can maintain the current behavior (except it would call the function instead of performing the calculation locally), and you can create a different stored procedure (or just call the function directly, if this is all your code is doing), and the logic doesn't have to be duplicated, and you don't have to trample on their stored procedure.
I am trying to make a simple function that reads a table from an ORACLE database and returns a sequence number. I would either like to return it directly or store the value inside of #cwpSeq and return that to the calling program.
Right now I am getting error:
RETURN statements in scalar valued functions must include an argument.
Can anyone assist me.
create function dbo.get_cwpSeq_from_oracle(#COIL nvarchar(100) )
returns int as
begin
DECLARE #cwpSeq int, #SQL nvarchar(1000);
set #SQL = N'select * from openquery(DEV, 'select cwp_seq from apps.custom_wip_pieces where lot_number = ''' + #COIL + '')';
return execute sp_executesql #SQL;
end;
As already mentioned, in this case you should use a procedure with an output parameter instead of a function. If you want to fully execute the query on the Oracle linked server side and return some value after that, I would suggest using dynamic as follows:
Create Or Alter Procedure dbo.get_cwpSeq
#COIL nvarchar(100),
#cwp_seq Int Output
As
Declare #QueryText nVarChar(max)
Select #QueryText = 'Select #cwp_seq=cwp_seq
From Openquery(DEV,
''Select cwp_seq
From apps.custom_wip_pieces
Where lot_number= ''''' + #COIL + ''''''') As Ora';
Execute sp_executesql #QueryText, N'#COIL nvarchar(100), #cwp_seq Int Output', #COIL = #COIL, #cwp_seq = #cwp_seq Output
As far as I understand in your case:
Linked server is "DEV", Owner of the table is "apps".
I'm stuck on an error in MS SQL Server.
I'm making a stored procedure in which I declare a value, I set it as a select statement, and execute it. Then I want to use it later in the same stored procedure, but It still sees it as a select statement, and not as a result of the statement.
Declaring:
Declare #functie varchar(255);
Set #functie = 'Select Functie From Medewerker m, Logins l where m.mdwnr = l.mdwnr And gebruikersnaam = ''' + #GebruikersNaam + ''''
EXECUTE(#functie)
Print #functie
Comparing it afterwards (does not work):
If '+ #functie + ' = ''Administrator''
Print ' + #functie + '
The prints are used to debug, and are not necessary.
How can I fix this?
You don't need dynamic sql here at all. Here is your query setting the value of the variable.
Declare #functie varchar(255);
Select #functie = Functie
From Medewerker m
join Logins l on m.mdwnr = l.mdwnr
And gebruikersnaam = #GebruikersNaam
Also, you should get in the habit of using ANSI-92 style joins instead of the out of data comma separated list of tables. The "newer" join syntax is nearly 30 years old now.
Bad habits to kick : using old-style JOINs
You can get data from EXEC only to the table, then you can parse that info depending on your requirements. Here is possible example of that:
DECLARE #sql nvarchar(255) = 'Select ''test'''
DECLARE #result TABLE (functie nvarchar(100))
DECLARE #functie nvarchar(100)
INSERT INTO #result EXECUTE(#sql)
SET #functie = (SELECT TOP(1) functie FROM #result)
PRINT(#functie)
However previous comment is perfect, you don't need Dynamic SQL for this operation, and parametrized query may save your ass in case of SQL Injection (which looks possible in your code).
The prior answer my resolve your issue - I wanted to note also that your comparison code is incorrect - using two single quote characters in a row resolves to one single quote character (rather than being treated as a text delimiter).
If you will be getting single quotes back as part of your result, then that code should look like:
If '' + #functie + '' = '''Administrator'''
Print #functie
I want to call a stored procedure dynamically as my names of the procedures are stored somewhere, also I need to store the result of that procedure into a table variable. Hence I had to write following sql code,
In following code
#tblEmailIds is the table variable which I want to store the result of SP in
#tempEmailSource is the name of the procedure
#tempRecipientsIdsCSV is the first argument that my SP is accepting
#ObjectMasterId is the second argument that SP is accepting (optional)
DECLARE #tempTypeName NVARCHAR(100),
#tempTypeId INT,
#tempEmailSource NVARCHAR(100),
#tempRecipientsIdsCSV NVARCHAR(MAX),
#tempIsObjectSpecific BIT,
#sqlQuery NVARCHAR(MAX) = 'INSERT INTO #tblEmailIds '
SELECT TOP 1 #tempTypeName = NAME,
#tempTypeId = Id,
#tempEmailSource = EmailListSourceName
FROM #tbleRecipientsTypes WHERE IsEmailIdsFetched = 0
SELECT #tempRecipientsIdsCSV = SUBSTRING(
(SELECT ',' + CAST(RT.EmailRecipientId AS NVARCHAR(50))
FROM #tbleRecipientsTypes RT WHERE RT.Id = #tempTypeId
ORDER BY RT.EmailRecipientId
FOR XML PATH('')),2,200000)
SELECT #tempRecipientsIdsCSV
SET #sqlQuery = #sqlQuery + 'EXEC ' + #tempEmailSource +' ' +'''' + #tempRecipientsIdsCSV +''''
IF (#tempIsObjectSpecific = 1)
BEGIN
SET #sqlQuery = #sqlQuery + ' ' + #ObjectMasterId
END
PRINT #SQLQUERY
EXECUTE SP_EXECUTESQL
#SqlQuery,'#IdsCSV NVARCHAR(MAX) OUTPUT,
#ObjectMasterId INT = NULL OUTPUT', #tblEmailIds
I am getting the following error
Msg 214, Level 16, State 3, Procedure sp_executesql, Line 6 Procedure
expects parameter '#params' of type 'ntext/nchar/nvarchar'.
There are quite a few problems here.
As the error message clearly states, the parameter list needs to be NVARCHAR so just prefix that string literal with an N (as also stated in #VR46's answer).
Table variables do not work the way that you are trying to use them. First, you do not ever declare #tblEmailIds, but even if you did, the scope of a table variable is local, and they cannot be used as OUTPUT parameters. Instead, you need to create a local temporary table (i.e. #tblEmailIds) and do INSERT INTO #tblEmailIds.
You reference another table variable, #tbleRecipientsTypes, that has not been declare or populated.
You do declare #tempIsObjectSpecific but never set it so it will always be NULL.
Why is #IdsCSV (in the sp_executesql call parameter list) declared as OUTPUT? Not only are you not passing in #tempRecipientsIdsCSV (to be #IdsCSV in the dynamic SQL), it isn't even necessary to be a parameter since you are directly concatenating the value of #tempRecipientsIdsCSV into the Dynamic SQL, and there is no #tempRecipientsIdsCSV variable in the Dynamic SQL to begin with. So remove #IdsCSV NVARCHAR(MAX) OUTPUT, from the parameter list.
You say that "#tempRecipientsIdsCSV is the first argument that my SP is accepting", but then you declare it in the code, which should result in an error.
What datatype is #ObjectMasterId? You say that it is passed into the proc and I see that it is concatenated into the Dynamic SQL, so it needs to either be a string type (i.e. not INT like is shown in the sp_executesql parameter list) or it needs to be in a CONVERT(NVARCHAR(10), #ObjectMasterId.
If #ObjectMasterId is being passed in, then why is it declared as OUTPUT in the sp_executesql parameter list? But the better question is probably: why are you even passing it into sp_executesql anyway since you directly concatenate it into the Dynamic SQL? There is no #ObjectMasterId variable being used in the Dynamic SQL.
Prefix N to the #params of SP_EXECUTESQL.
Also you need to store the result of OUTPUT parameter
Declare #IdsCSV NVARCHAR(MAX),#ObjectMasterId INT = NULL
EXECUTE SP_EXECUTESQL
#SqlQuery,
N'#IdsCSV NVARCHAR(MAX) OUTPUT,#ObjectMasterId INT = NULL OUTPUT',
#IdsCSV = #IdsCSV OUTPUT,
#ObjectMasterId = #ObjectMasterId OUTPUT
In Oracle I have lots of stored procedures using a package which basically stores (encapsulates) and initializes all variables used by these procedures. There is one function in the package as well which takes care of initializing all it's package variables.
My question is: how to port this to SQL Server?
My first attempt is to declare all package variables and use them as OUTPUT parameters for a procedure to initialize them, but then I need to declare these variables over and over again in each procedure using them (and there are a lots of them in the package). Is there any better (and DRY) way to do this on SQL Server?
Some code to explain it:
ORACLE:
The package:
create or replace
PACKAGE MYPARAMS AS
/**
param container
*/
type rc_params is record
(
var1 varchar2(30),
var2 integer
);
/**
init param container
use: v_params rc_pkp_plan_params := MYPARAMS.f_get_params(initvar)
*/
function f_get_params(initvar number) return rc_params;
END MYPARAMS;
/
The package body:
CREATE OR REPLACE
PACKAGE BODY MYPARAMS AS
function f_get_params(initvar number) return rc_params AS
retval rc_params;
BEGIN
retval.var1 := 'MY_VAR1';
retval.var2 := initvar;
return retval;
END f_get_params;
END MYPARAMS;
/
Some usage example:
declare
initvar integer := 22;
v_params MYPARAMS.rc_params := MYPARAMS.f_get_params(initvar);
begin
dbms_output.put_line(v_params.var1 || ' initialized by ' || v_params.var2);
end;
SQL Server:
if exists (select * from sysobjects where id = object_id('f_get_params'))
drop procedure f_get_params
go
create procedure f_get_params(
#initvalue integer,
#var1 varchar(30) OUTPUT,
#var2 integer OUTPUT
)
as
set #var1 = 'MYVAR1'
set #var2 = #initvalue
go
-- this block i would like to avoid:
declare
#initvalue integer = 33,
#var1 varchar(30),
#var2 integer
exec f_get_params #initvalue, #var1 OUTPUT, #var2 OUTPUT
print #var1 + ' initialized by ' + convert(varchar(2), #var2)
Hope the description is clear enough...
Unfortunately, T-SQL doesn't have anything like Oracle's packages, package variables, or structures of variables. Oh that it did.
What you've done is probably the easiest way to accomplish it in T-SQL, even if it does require duplicating the variables.
You can use a # table, i.e. create a #params table in f_get_parms that contains all of the variables, then use that # table in all of the other procs to retrieve them. The downside is that you still either have to declare the variables in the calling procedures, or use DML to access the columns in the # table, which is a lot more cumbersome than having them as variables.
Another way I've used before is to use XML to pass multiple variables around but treat them as a single one. It's still more cumbersome to access the values than in variables, but it has the advantage of allowing you to use a function instead of a procedure to get the values.
CREATE FUNCTION dbo.uf_get_params (
#initvar int
)
RETURNS xml
AS
BEGIN
DECLARE #xml xml,
#var1 varchar(30) = 'MYVAR' -- setting value on DECLARE requires SQL2008+
SELECT #xml = (SELECT #var1 AS var1,
#initvar AS initvar
FOR XML RAW('params'))
RETURN #xml
END
go
In your calling procedure, you would have
DECLARE #params xml = (SELECT dbo.uf_get_parms(12))
to get the parameters, then use standard XML/XQUERY functions to retrieve the individual variables (attributes) from the #params XML variable.