I'm using SQL Server 2019 and I use a linked server to connect to another SQL Server.
I need to run a system function on the linked server, I'm using OPENQUERY to run the function on the linked server which is working fine in many cases. However one of the functions expects a BINARY parameter. I have got no clue on how to pass a binary variable to the linked server using OPENQUERY. Please can someone help? see my code sample below.
DECLARE #To_LSN AS binary(10);
DECLARE #To_LSN_Timestamp datetime;
SELECT #To_LSN = ReturnValue
FROM OPENQUERY (LNK_SQL_SERVER,
'SELECT MY_DB.sys.fn_cdc_get_max_lsn() AS ReturnValue;');
SELECT #To_LSN_Timestamp = ReturnValue
FROM OPENQUERY (LNK_SQL_SERVER,
'SELECT MY_DB.sys.fn_cdc_map_lsn_to_time(#To_LSN) AS ReturnValue;');
-- it won't accept the #To_LSN as a parameter.
In the second query above the function expects a binary parameter (#To_LSN), so if I try to split the query as a dynamic SQL it throws error as conversion between varchar and binary fails.
If you just need the LSN timestamp, why not do it in a single step and avoid any string or binary conversions/concatenation?
SELECT #To_LSN_Timestamp = ReturnValue
FROM OPENQUERY
(
LNK_SQL_SERVER,
N'SELECT MY_DB.sys.fn_cdc_map_lsn_to_time(
MY_DB.sys.fn_cdc_get_max_lsn()
) AS ReturnValue;');
Even better:
EXEC LNK_SQL_SERVER.MY_DB.sys.sp_executesql
N'SELECT #rv = sys.fn_cdc_map_lsn_to_time(
sys.fn_cdc_get_max_lsn());',
N'#rv datetime OUTPUT',
#rv = #To_LSN_Timestamp OUTPUT;
If you need both the LSN and the timestamp outside, then:
EXEC LNK_SQL_SERVER.MY_DB.sys.sp_executesql
N'SELECT #lsn = sys.fn_cdc_get_max_lsn();
SELECT #rv = sys.fn_cdc_map_lsn_to_time(#lsn);',
N'#lsn binary(10) output, #rv datetime OUTPUT',
#lsn = #To_LSN OUTPUT,
#rv = #To_LSN_Timestamp OUTPUT;
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 have a stored procedure A on server 1 that takes 2 parameters from the user, and then using a linked server (ew), pulls in the results (a table) from server 2.
ALTER PROCEDURE [DW].[StoredProcA]
#InvFromDate date OUTPUT,
#InvToDate date OUTPUT
AS
WITH CTE_Labor AS
(
SELECT blabla
FROM LinkedServer.Database.schema.table
<lots more ctes, etc.>
For performance, I'd like to instead have a stored procedure A still accept the 2 parameters, but then pass them on to stored procedure B that sits on Server 2, and return those results back to the user.
Say - I can put the stored procedure on server 2, and call it from Server 1
DECLARE #return_value int
EXEC #return_value = [LinkedServer].[DB].[Schema].[StoredProcB]
#InvFromDate = '2022-10-01',
#InvToDate = '2022-10-31'
That works.
But I'm not clear on the syntax to do the above, but have those 2 parameters be entered by the user in stored procedure 1.
Clearly this attempt is wrong:
ALTER PROCEDURE dbo.StoredProc1
#InvFromDate DATE,
#InvToDate DATE
AS
BEGIN
DECLARE #return_value int;
EXEC #return_value = [LinkedServer].[DB].[Schema].[StoredProcB]
#InvFromDate = #InvFromDate,
#InvToDate = #InvToDate;
RETURN #return_value;
END
Edit: Maybe this attempt isn't wrong.
It works when I right click and run the stored procedure, returning both the desired table and Return Value = 0. It just doesn't work when I point our front-end GUI at it. But that might not be a question for here.
Since you are already using a linked server you could utilise this openquery approach Insert results of a stored procedure into a temporary table
Noting the following:
OPENQUERY/ linked servers are generally bad but I'm sure you're all over this
parameter string concatenation is bad
Your wrapper proc has output parameters but I don't see any reason for it... so I've removed them. See if it makes a difference.
--
ALTER PROCEDURE [DW].[StoredProcA]
#InvFromDate date,
#InvToDate date
AS
DECLARE #sql VARCHAR(4000)
SET #sql = 'EXEC [DB].[Schema].[StoredProcB] #InvFromDate = ''' + FORMAT(#InvFromDate + 'yyyy-MM-dd') + ''',#InvToDate = ''' + FORMAT(#InvToDate,'yyy-MM-dd') + ''''
PRINT(#sql) -- for degbugging cause this never works first time
SELECT *
INTO #tmpTable
FROM OPENQUERY([LinkedServer], #SQL)
SELECT * FROM #tmpTable
Got it.
1.) For this method, have to go into the Linked Server, and set [Enable Promotion of Distribution Transaction] = FALSE.
2.) Syntax
Alter proc [dbo].[999_Test]
#InvFromDate date
,#InvToDate date
as
IF OBJECT_ID('tempdb..#tmpbus') IS NOT NULL drop table #tmpbus;
CREATE TABLE #tmpBus
(
Column 1 (datatype),
Column 2 (datatype),
etc. )
INSERT INTO #tmpBus
EXEC [LinkedServer].[DB].Schema.[StoredProcInLinkedServerO]
#InvFromDate,
#InvToDate;
select *
from #tmpBus
GO
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 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.
I have a stored procedure which encloses the following sp_execute_external_script command inside of a while-loop and I am getting as output every returned result set, separately. I am using SQL Server 2016 to do all this stuff.
EXECUTE sp_execute_external_script #language = N'R',#script = N' some R code here ' ,#input_data_1 = N' SELECT * FROM #TempTable;'
WITH
RESULT
SETS
(
(
[filename] NVARCHAR(MAX),
[mobile_fraction] NVARCHAR(MAX),
[t_half] NVARCHAR(MAX),
[r_square] NVARCHAR(MAX)
)
);
I would like to store all the returned data of the result sets into a #variable (or into a #temptable) in order for me to be able to continue the processing of these data. I have tried both the above cases (for instance INSERT INTO #variable EXEC "myStoredProcedure") but I am getting the following error
Incorrect syntax near 'SETS'.
If I remove the INSERT INTO #variable line everything works fine. So, I was wondering if I could use such a "technique" with execute sp_execute_external_script command.
INSERT..EXEC will work if you remove the WITH RESULTS SETS as long as the output schema matches the table schema.
DECLARE #Temp TABLE
(
Test NVARCHAR(MAX)
)
INSERT INTO #Temp
EXECUTE sp_execute_external_script #language = N'R',#script = N'OutputDataSet <- as.data.frame(c("test"))' ,#input_data_1 = N''
SELECT * FROM #TEMP