SQL Server:exec('CALL' DB2; advice on managing result set columns - sql

Techies--
If I were issuing an openquery select, my problems would be solved--but as far as i know openquery doesn't allow the calling lingo/w. parameter(s) to remote db2 servers! :)
Here's what works:
declare #z varchar(max);
set #z = '999990480,888887530';
exec ('CALL S1CATALOG.HCMDEV.EMP_ALL_STARS(?)',#z) AT DB2I;
This stored proc (EMP_ALL_STARS) accepts the concatenated string as a clob, then returns roughly 35 columns. Not all the applcations with an interest in utilizing this sproc need all 35 columns. Any advice on how to manage the result set?

Do you have control over the remote DB2 procedure? If so, you could define multiple cursors inside the proc, each with a different set of columns in the result set. At runtime, when it's time for the proc to open a cursor that performs the query and returns the result set to the caller, a bit of logic can evaluate the relevant input parameters to determine which which cursor to open.

Related

How to use OPENROWSET to execute a stored procedure with parameters and insert result into a temp table

I want to insert the results of a stored procedure into a temp table using OPENROWSET. However, the issue I run into is I'm not able to pass parameters to my stored procedure.
This is my stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[N_spRetrieveStatement]
#PeopleCodeId nvarchar(10),
#StatementNumber int
AS
SET NOCOUNT ON
DECLARE #PersonId int
SELECT #PersonId = [dbo].[fnGetPersonId](#PeopleCodeId)
SELECT *
INTO #tempSpRetrieveStatement
FROM OPENROWSET('SQLNCLI', 'Server=PCPRODDB01;Trusted_Connection=yes;',
'EXEC Campus.dbo.spRetrieveStatement #StatementNumber, #PersonId');
--2577, 15084
SELECT *
FROM #tempSpRetrieveStatement;
OpenRowSet will not allow you to execute Procedure with input parameters. You have to use INSERT/EXEC.
INTO #tempSpRetrieveStatement(Col1, Col2,...)
EXEC PCPRODDB01.Campus.dbo.spRetrieveStatement #StatementNumber, #PersonId
Create and test a LinkedServer for PCPRODDB01 before running the above command.
The root of your problem is that you don't actually have parameters inside your statement that you're transmitting to the remote server you're connecting to, given the code sample you provided. Even if it was the very same machine you were connecting to, they'd be in different processes, and the other process doesn't have access to your session variables.
LinkedServer was mentioned as an option, and my understanding is that's the preferred option. However in practice that's not always available due to local quirks in tech or organizational constraints. It happens.
But there is a way to do this.
It's hiding in plain sight.
You need to pass literals into the string that will be executed on the other server, right?
So, you start by building the string that will do that.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[N_spRetrieveStatement]
#PeopleCodeId nvarchar(10),
#StatementNumber int
AS
SET NOCOUNT ON
DECLARE
#PersonId INT,
#TempSQL VARCHAR(4000) = '';
SELECT #PersonId = [dbo].[fnGetPersonId](#PeopleCodeId);
SET #TempSQL =
'EXEC Campus.dbo.spRetrieveStatement(''''' +
FORMAT(#StatementNumber,'D') +''''', ''''' +
FORMAT(#PersonId,'D') + ''''')';
--2577, 15084
Note the seemingly excessive number of quotes. That's not a mistake -- that's foreshadowing. Because, yes, OPENROWSET hates taking variables as parameters. It, too, only wants literals. So, how do we give OPENROWSET what it needs?
We create a string that is the entire statement, no variables of any kind. And we execute that.
SET #TempSQL =
'SELECT * INTO #tempSpRetrieveStatement ' +
'FROM OPENROWSET(''SQLNCLI'', ''Server=PCPRODDB01;Trusted_Connection=yes;'', ' + #TempSQL +
'EXEC Campus.dbo.spRetrieveStatement #StatementNumber, #PersonId';
EXEC (#TempSQL);
SELECT *
FROM #tempSpRetrieveStatement;
And that's it! Pretty simple except for counting your escaped quotes, right?
Now... This is almost beyond the scope of the question you asked, but it is a 'gotcha' I've experienced in executing stored procedures in another machine via OPENROWSET. You're obviously used to using temp tables. This will fail if the stored procedure you're calling is creating temp tables or doing a few other things that -- in a nutshell -- inspire the terror of ambiguity into your SQL server. It doesn't like ambiguity. If that's the case, you'll see a message like this:
"Msg 11514, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1
The metadata could not be determined because statement '…your remote EXEC statement here…' in procedure '…name of your local stored procedure here…' contains dynamic SQL. Consider using the WITH RESULT SETS clause to explicitly describe the result set."
So, what's up with that?
You don't just get data back with OPENROWSET. The local and remote servers have a short conversation about what exactly the local server is going to expect from the remote server (so it can optimize receiving and processing it as it comes in -- something that's extremely important for large rowsets). Starting with SQL Server 2012, sp_describe_first_result_set is the newer procedure for this, and normally it executes quickly without you noticing it. It's just that it's powers of divination aren't unlimited. Namely, it doesn't know how to get the type and name information regarding temp tables (and probably a few other things it can't do -- PIVOT in a select statement is probably right out).
I specifically wanted to be sure to point this out because of your reply regarding your hesitation about using LinkedServer. In fact, the very same reasons you're hesitant are likely to render that error message's suggestion completely useless -- you can't even predict what columns you're getting and in what order until you've got them.
I think what you're doing will work if, say, you're just branching upstream based on conditional statements and are executing one of several potential SELECT statements. I think it will work if you're just not confident that you can depend on the upstream component being fixed and are trying to ensure that even if it varies, this procedure doesn't have to because it's very generic.
But on the other hand you're facing a situation in which you literally cannot guarantee that SQL Server can predict the columns, you're likely going to have to force some changes in the stored procedure you're calling to insist that it's stable. You might, for instance work out how to ensure all possible fields are always present by using CASE expressions rather than any PIVOT. You might create a session table that's dedicated to housing what you need to SELECT just long enough to do that then DELETE the contents back out of there. You might change the way in which you transmit your data such that it's basically gone through the equivalent of UNPIVOT. And after all that extra work, maybe it'll be just a matter of preference if you use LinkedServer or OPENROWSET to port the data across.
So that's the answer to the literal question you asked, and one of the limits on what you can do with the answer.

Check number of stored procedures from a different database

I am trying to fix a stored procedure that we have created to count number of running procedures with a certain name.
What I am trying to do is get the number of running stored procedures that match a specific title and it works fine when the Stored Procedure executing the query is in the same Database as the procedures I am trying to count as running. But I don't understand how I can count the number of running procedures matching a name running in a different Database. I assume the dbid parameter has to be used in some way, but I don't understand how.
The database I want to count in is called "ScheduledJobs" rather than MySampleDB where I have to put the stored procedure (I can't move it to the other DB for different reasons).
Any suggestions are welcome. Here is my code:
USE [MySampleDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[sp_CheckRuns2]
#RowsAffected INT OUTPUT
AS
BEGIN
SELECT
object_name(st.objectid) as ProcName
FROM
sys.dm_exec_connections as qs
CROSS APPLY sys.dm_exec_sql_text(qs.most_recent_sql_handle) st
WHERE
object_name(st.objectid) is not null and OBJECT_NAME(st.objectid) like '%sp_UPDATER'
END
select #RowsAffected = ##rowcount
RETURN #RowsAffected
Just pass in dbid as an extra parameter
WHERE OBJECT_NAME(st.objectid, st.dbid) like '%sp_UPDATER'
Side note: You should remove RETURN #RowsAffected. It is not necessary, as you are anyway passing back an OUTPUT parameter. Note also that most client APIs will give the rowcount in any case.

SQL how to call user defined function by dynamic variable name

In SQL - I have list of user defined function names in a table. based on the logic i need to call/exec the function.
Please my high level code logic below,
DECLARE #MY_FUNCTION VARCHAR(1000);
DECLARE #MY_INPUT_PARAMETER INT;
DECLARE #MY_OUTPUT_PARAMETER INT;
SET #MY_FUNCTION = '' -- Dynamically function name will be provided based on some big logic
--Note: function has input and output parameter
--my query
-- call the function by #MY_FUNCTION (#MY_INPUT_PARAMETER )
#MY_OUTPUT_PARAMETER = EXEC #MY_FUNCTION (#MY_INPUT_PARAMETER)
--Some big sql script using #MY_OUTPUT_PARAMETER
(
-- Script goes here
)
You will need to construct the function with parameters inside the variable and then run sp_execute. Check out the samples in https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql?view=sql-server-ver15#c-using-the-output-parameter
Important
However, try to avoid this method of execution if possible. Let the application decide what SP to call and the SP can then use the right function to make the call. There are two advantages to this.
Your SP will be compiled and SQL will be able to have an execution plan and continue to fine tune it. Hence, better performance
You will have less chances of SQL injections depending on how the table with functions are populated.

How to suppress record sets returned by SELECT statements in a Stored Procedure

I'm writing a stored procedure which checks for the existence of various tables in various databases, as well as the permissions that the user executing the stored procedure has on those tables. The stored procedure itself resides within a user database (i.e. it's not in the Master db).
To perform my checks, my stored procedure contains lots of SELECT statements. Each of those obviously returns a record set. What I would like is to somehow suppress these record sets so that they are not returned by the stored procedure, and instead return my own, single record set which is just a collection of messages relating to each check the stored procedure performs.
I think the obvious answer is to use a table-valued function instead, but I've not been able to recreate my tests successfully in a Function as they appear in the stored procedure. For starters, I'm having to use temporary tables (not possible in a function) and dynamic SQL (not very compatible with table parameters).
I think I've basically got two choices:
Rewrite my stored procedure as a function and figure out how to do the checks a different way.
Continue using my stored procedure and use an OUTPUT parameter to return my result messages, probably as a delimited string, and in the associated ASP.NET application just ignore all the record sets the stored procedure returns .
Neither of these solutions is very satisfactory. Before I spend any more time pursuing either one, is there a way to discard the record sets produced by the SELECT statements in a stored procedure and explicitly define what record I want it to return?
Hmm, I only can speculate here...
Are you using something like
SELECT ...;
IF ##rowcount > 0
BEGIN
...
END;
?
Then you can rewrite it using something like
IF EXISTS (SELECT ...)
BEGIN
...
END;
or
DECLARE #variable integer;
SELECT #variable = count(*) ...;
IF #variable > 0
BEGIN
...
END;
In general point the results of your queries to a target (variable, table, expression, ...), then they don't get outputted.
And then just execute the query for your desired result in the end.
In my opinion, here is almost no reason to have stored procedures produce record sets. That is what stored functions are for. On occasion, it is needed, because of the use of dynamic SQL or other stored procedures, but not as a general practice. Much, much too often, I see stored procedures being used where stored functions or views are more appropriate.
What should you do? Even SELECT statement in the stored procedure should be one of the following:
Setting (local) variables.
Saving the results in a temporary table or table variable.
The logic for the stored procedure should be working on the local variables. The results should be returned using OUTPUT parameters.
If you need to return rows in a tabular format, you can do that using tables explicitly (such as a global temporary table or real table). Or, you can have one SELECT at the end that does return a single result set. However, if you need this and can phrase the stored procedure as a function, that is better in my opinion.

How to pass multiple values in place of single value to a stored procedure

I have a stored procedure, and it is executed perfectly, when i am using:
exec spSampleAmount 12212,0
Now i need to pass like "12212,12213,12214,12215" and these values coming from another query. How to pass this query result to a 2 argument stored proc.
exec spSampleAmount param1,param2;
param1 have set of values.
Store the output of first query in parameter. First declare the parameter and then assign the value of first query to that parameter then call exec command to execure the stored procedure.
DECLARE #V_Param1 VARCHAR(1000);
SELECT #V_Param1 = Rest of query
EXEC spSampleAmount #V_Param1, 0
if it is called by SSRS it's possible, i don't know exactly how it works, but it is.
You have 2 ways to call a stored procedure from ssrs, sql query and write execute. Or select execute stored procedure.
In that way you have the possibility to pass a selection from a multiple item selection combobox to the stored procedure and in your stored procedure do a : where myparam in (#paramreceived).
It should be also possible in .Net.
I tried to do that in tsql and i never succeeded, so i'm interrested too if there is a solution to that problem