Return variable using OPENQUERY - sql-server

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".

Related

Save dynamic result from stored procedure into a dynamic table

I am facing some issues in saving the execution of stored procedure / scalar function into a table variable.
The function / stored procedure returns dynamic columns and I need to create a dynamic table to save the result of that function into it so that I can use the table.
Example: the stored procedure spGetEmployeeInfo could return employee name, employee id, etc. on such criteria they return only employee name,.
Is there a way to create a dynamic table and save the result into it after execute the stored procedure, or any suggestion.
Thanks
I don't like to get into this situation too often, but when I do, what I do is have the stored proc output into a global temp table. The name of the table is passed in as a parameter by the user. For instance:
create procedure dynamicBeCareful
#toggle bit,
#globalTempTableName varchar(50)
as
-- initializations
if left(#globalTempTableName,2) <> '##'
throw 50000, '#globalTempTableName must start with ##', 1;
declare #sql varchar(max);
-- build dynamic sql
if #toggle = 1
set #sql = 'select * into #tempTable from table1';
else
set #sql = 'select * into #tempTable from table2';
set #sql = replace(#sql, '#tempTable', #globalTempTableName);
-- terminations
exec (#sql);
declare #msg = 'Your results are in ' + #globalTempTableName;
print (#msg);
Then use it like this:
exec dynamicBeCareful 1, '##temp';
select * from ##temp;
Beyond just being dynamic in output, it also can get you out of nested insert-exec limitations.

Execute mixed sql: dynamic with static

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.

Dynamic sql query with stored procedure and table variable

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

SP_ExecuteSQL Generic stored_procedure call without output parameters, but catching the output

I'm scratching my head hard on this pb and I would like some help to figure out some solution.
Inside some TSQL programmable object (a stored procedure or a query in Management Studio)
I have a parameter containing the name of a stored procedure + the required argument for these stored procedures (for exemple it's between the brackets [])
Sample of #SPToCall
EX1 : [sp_ChooseTypeOfResult 'Water type']
EX2 : [sp_ChooseTypeOfXMLResult 'TABLE type', 'NODE XML']
EX3 : [sp_GetSomeResult]
I can't change thoses stored procedures (and I don't have a nice output param to cache, as I would need to change the stored procedure definition)
All these stored procedures 'return' a 'select' of 1 record the same datatype ie: NVARCHAR. Unfortunately there is no output param in those stored procedures definition (it would have been too easy otherwise :D)
So I'm working on something like this but I can't find anything working
DECLARE #myFinalVarFilledWithCachedOutput NVARCHAR(MAX);
DECLARE #SPToCall NVARCHAR(MAX) = N'sp_ChooseTypeOfXMLResult ''TABLE type'', ''NODE XML'';'
DECLARE #paramsDefintion = N'#CatchedOutput NVARCHAR(MAX) OUTPUT'
exec SP_executeSQL #SPToCall , #paramsDefinitions, #CatchedOutput = #myFinalVarFilledWithCachedOutput OUTPUT
-- my goal is to get something inside #myFinalVarFilledWithCachedOutput
Select #myFinalVarFilledWithCachedOutput
Any ideas ?
Here's an example of the syntax to take output of stored procedure that returns a selected value and pass that output to a table variable.
CREATE PROCEDURE tester
AS
BEGIN
DECLARE #var VARCHAR(10)
SET #var = 'beef'
SELECT #var
END
GO
DECLARE #outputtab TABLE ( varfield VARCHAR(100) )
INSERT #outputtab
( varfield )
EXEC tester
GO
SELECT *
FROM #outputtab
From there if you want to get it into a varchar variable:
DECLARE #outputvar VARCHAR(100)
SET #outputvar = ( SELECT TOP 1
*
FROM #outputtab
)

Insert/Update/Delete with function in SQL Server

Can we perform Insert/Update/Delete statement with SQL Server Functions. I have tried with but SQL Server error is occured.
Error:
Invalid use of side-effecting or time-dependent operator in 'DELETE' within a function.
AnyBody have any Idea why we can not use Insert/Update/Delete statements with SQL Server functions.
Waiting for your good idea's
No, you cannot.
From SQL Server Books Online:
User-defined functions cannot be used
to perform actions that modify the
database state.
Ref.
Yes, you can!))
Disclaimer: This is not a solution, it is more of a hack to test out something. User-defined functions cannot be used to perform actions that modify the database state.
I found one way to make INSERT, UPDATE or DELETE in function using xp_cmdshell.
So you need just to replace the code inside #sql variable.
CREATE FUNCTION [dbo].[_tmp_func](#orderID NVARCHAR(50))
RETURNS INT
AS
BEGIN
DECLARE #sql varchar(4000), #cmd varchar(4000)
SELECT #sql = 'INSERT INTO _ord (ord_Code) VALUES (''' + #orderID + ''') '
SELECT #cmd = 'sqlcmd -S ' + ##servername +
' -d ' + db_name() + ' -Q "' + #sql + '"'
EXEC master..xp_cmdshell #cmd, 'no_output'
RETURN 1
END
Functions in SQL Server, as in mathematics, can not be used to modify the database. They are intended to be read only and can help developer to implement command-query separation. In other words, asking a question should not change the answer. When your program needs to modify the database use a stored procedure instead.
You can't update tables from a function like you would a stored procedure, but you CAN update table variables.
So for example, you can't do this in your function:
create table MyTable
(
ID int,
column1 varchar(100)
)
update [MyTable]
set column1='My value'
but you can do:
declare #myTable table
(
ID int,
column1 varchar(100)
)
Update #myTable
set column1='My value'
Yes, you can.
However, it requires SQL CLR with EXTERNAL_ACCESS or UNSAFE permission and specifying a connection string. This is obviously not recommended.
For example, using Eval SQL.NET (a SQL CLR which allow to add C# syntax in SQL)
CREATE FUNCTION [dbo].[fn_modify_table_state]
(
#conn VARCHAR(8000) ,
#sql VARCHAR(8000)
)
RETURNS INT
AS
BEGIN
RETURN SQLNET::New('
using(var connection = new SqlConnection(conn))
{
connection.Open();
using(var command = new SqlCommand(sql, connection))
{
return command.ExecuteNonQuery();
}
}
').ValueString('conn', #conn).ValueString('sql', #sql).EvalReadAccessInt()
END
GO
DECLARE #conn VARCHAR(8000) = 'Data Source=XPS8700;Initial Catalog=SqlServerEval_Debug;Integrated Security=True'
DECLARE #sql VARCHAR(8000) = 'UPDATE [Table_1] SET Value = -1 WHERE Name = ''zzz'''
DECLARE #rowAffecteds INT = dbo.fn_modify_table_state(#conn, #sql)
Documentation: Modify table state within a SQL Function
Disclaimer: I'm the owner of the project Eval SQL.NET
You can have a table variable as a return type and then update or insert on a table based on that output.
In other words, you can set the variable output as the original table, make the modifications and then do an insert to the original table from function output.
It is a little hack but if you insert the #output_table from the original table and then say for example:
Insert into my_table
select * from my_function
then you can achieve the result.
We can't say that it is possible of not their is some other way exist to perform update operation in user-defined Function. Directly DML is not possible in UDF it is for sure.
Below Query is working perfectly:
create table testTbl
(
id int identity(1,1) Not null,
name nvarchar(100)
)
GO
insert into testTbl values('ajay'),('amit'),('akhil')
Go
create function tblValued()
returns Table
as
return (select * from testTbl where id = 1)
Go
update tblValued() set name ='ajay sharma' where id = 1
Go
select * from testTbl
Go
"Functions have only READ-ONLY Database Access"
If DML operations would be allowed in functions then function would be prety similar to stored Procedure.
No, you can not do Insert/Update/Delete.
Functions only work with select statements. And it has only READ-ONLY Database Access.
In addition:
Functions compile every time.
Functions must return a value or result.
Functions only work with input parameters.
Try and catch statements are not used in functions.
CREATE FUNCTION dbo.UdfGetProductsScrapStatus
(
#ScrapComLevel INT
)
RETURNS #ResultTable TABLE
(
ProductName VARCHAR(50), ScrapQty FLOAT, ScrapReasonDef VARCHAR(100), ScrapStatus VARCHAR(50)
) AS BEGIN
INSERT INTO #ResultTable
SELECT PR.Name, SUM([ScrappedQty]), SC.Name, NULL
FROM [Production].[WorkOrder] AS WO
INNER JOIN
Production.Product AS PR
ON Pr.ProductID = WO.ProductID
INNER JOIN Production.ScrapReason AS SC
ON SC.ScrapReasonID = WO.ScrapReasonID
WHERE WO.ScrapReasonID IS NOT NULL
GROUP BY PR.Name, SC.Name
UPDATE #ResultTable
SET ScrapStatus =
CASE WHEN ScrapQty > #ScrapComLevel THEN 'Critical'
ELSE 'Normal'
END
RETURN
END
Functions are not meant to be used that way, if you wish to perform data change you can just create a Stored Proc for that.
if you need to run the delete/insert/update you could also run dynamic statements. i.e.:
declare
#v_dynDelete NVARCHAR(500);
SET #v_dynDelete = 'DELETE some_table;';
EXEC #v_dynDelete
Just another alternative using sp_executesql (tested only in SQL 2016).
As previous posts noticed, atomicity must be handled elsewhere.
CREATE FUNCTION [dbo].[fn_get_service_version_checksum2]
(
#ServiceId INT
)
RETURNS INT
AS
BEGIN
DECLARE #Checksum INT;
SELECT #Checksum = dbo.fn_get_service_version(#ServiceId);
DECLARE #LatestVersion INT = (SELECT MAX(ServiceVersion) FROM [ServiceVersion] WHERE ServiceId = #ServiceId);
-- Check whether the current version already exists and that it's the latest version.
IF EXISTS(SELECT TOP 1 1 FROM [ServiceVersion] WHERE ServiceId = #ServiceId AND [Checksum] = #Checksum AND ServiceVersion = #LatestVersion)
RETURN #LatestVersion;
-- Insert the new version to the table.
EXEC sp_executesql N'
INSERT INTO [ServiceVersion] (ServiceId, ServiceVersion, [Checksum], [Timestamp])
VALUES (#ServiceId, #LatestVersion + 1, #Checksum, GETUTCDATE());',
N'#ServiceId INT = NULL, #LatestVersion INT = NULL, #Checksum INT = NULL',
#ServiceId = #ServiceId,
#LatestVersion = #LatestVersion,
#Checksum = #Checksum
;
RETURN #LatestVersion + 1;
END;

Resources