Nested stored procedure, setting variable to output/result - sql

In DB2 I'm currently running a stored procedure, inside of which I'm calling another stored procedure with a parameter.
P1 : BEGIN ATOMIC
DECLARE V_ID INTEGER ;
CALL PROGRAMS. GET_ID_BY_NAME(P_NAME) ;
-- need output of above procedure to be set to V_ID for another call here
END P1
How can I set the result of that call to the variable V_ID so that I can use it down the line?

If the relationship between ID and NAME is 1:1 , or if you simply don't care which matching name gets returned, then it is easier to modify the stored procedure (or create a renamed clone of the stored-procedure and modify the cloned copy) so that the stored procedure has an OUTPUT parameter, which the stored procedure can simply SET to the matching name, and on return the caller stored-procedure simply uses that variable name to access its value.
You may want to defensively code for a no match by arranging that the output parameter is set to NULL for that case, and test for that in the calling code. There are plenty of examples of using stored-procedure output parameters if you search, either the official Db2 documentation or elsewhere.
Using an output-parameter from the stored procedure (instead of a result-set) is the simplest approach, fewer lines of code.
Otherwise, if the relationship between ID and NAME is 1:n, and the caller wants to choose which match, then the called-stored procedure should return a result set (0 or more rows ) via an open WITH RETURN cursor. Your caller code would need to handle both the 0 rows returned case, and choose which row to use (if more than one row is returned) if you want to choose between them (usually on the basis of another column in the result-set).
To access the result-set of a called stored-procedure from inside the caller procedure, your caller procedure needs to use statements like these [ASSOCIATE RESULT SET LOCATOR][1] (v1) WITH PROCEDURE PROGRAMS.GET_ID_BY_NAME, after the CALL PROGRAMS.GET_ID_BY_NAME(), then [ALLOCATE c1 CURSOR FOR RESULT SET][1] v1.
You now have a cursor which you can iterate over to FETCH each row into variables, and at end you must CLOSE the cursor. If you know there is only ever be a single row in the result set then you don't need to iterate.
If you search, there are plenty of examples of these statements, including in the IBM documentation for your Db2 version and platform. I have shown links for Db2-LUW.

Related

pl/sql procedure with variable numbers of parameters

I want to know if I can create a PL/SQL procedure that the number of parameters and their types changes.
For example procedure p1.
I can use it like this
p1 (param1, param2,......., param n);
i want to pass table name and data in procedure, but the attributes change for every table,
create or replace PROCEDURE INSERTDATA(NOMT in varchar2) is
num int;
BEGIN
EXECUTE IMMEDIATE 'SELECT count(*) FROM user_tables WHERE table_name = :1'
into num using NOMT ;
IF( num < 1 )
THEN
dbms_output.put_line('table not exist !!! ');
ELSE
dbms_output.put_line('');
-- here i want to insert parameters in the table,
-- but the table attributes are not the same !!
END IF;
NULL;
END INSERTDATA;
As far as I can tell, no, you can not. Number and datatypes of all parameters must be fixed.
You could pass a collection as a parameter (and have different number of values within it), but - that's still a single parameter.
Where would you want to use such a procedure?
If you need to store, update and query a variable amount of information, might I recommend switching to JSON queries and objects in Oracle. Oracle has deep support for both fixed and dynamic querying of json data, both in SQL and PLSQL.
i want to pass table name and data in procedure, but the attributes change for every table,
The problem with such a universal procedure is that something needs to know the structure of the target table. Your approach demands that the caller has to discover the projection of the table and arrange the parameters in a correct fashion.
In no particular order:
This is bad practice because it requires the calling program to do the hard work regarding the data dictionary.
Furthermore it breaks the Law Of Demeter because the calling program needs to understand things like primary keys (sequences, identity columns, etc), foreign key lookups, etc
This approach mandates that all columns must be populated; it makes no allowance for virtual columns, optional columns, etc
To work the procedure would have to use dynamic SQL, which is always hard work because it turns compilation errors into runtime errors, and should be avoided if at all possible.
It is trivially simple to generate a dedicated insert procedure for each table in a schema, using dynamic SQL against the data dictionary. This is the concept of the Table API. It's not without its own issues but it is much safer than what your question proposes.

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.

SQL send text as parameter to procedure

In Oracle this should be a very simple thing but I only started working with procedures a day ago and I'm having some trouble with this. I created a procedure that's supposed to receive a type of facility as a parameter, say 'healthcare' for instance.
create or replace
PROCEDURE Adminfacility(
v_facility_type IN VARCHAR2)
IS
BEGIN
...(SELECT goes here)...
END Adminfacility
Is this right? How do I make the procedure receive the parameter and then return a table of two columns? (Facility ID's and respective admins for instance). One problem I'm having is that it requires me to have an INTO after the SELECT statement. I've done something of the kind before with functions where you'd input a numerical ID and receive a number output, but I've never done this kind of thing before.
I've done a similar thing as a view (where it has a default facility type) and it works, but I can't get it to work as a procedure.
CREATE PROC Adminfacility
#text NVARCHAR(MAX)
AS
BEGIN
SELECT id,adminname
FROM TABLE
WHERE TEXT =#text
END
This is accepting text as parameter using it in where condition and then return a table.
if you want to change already existing proc then instead of CREATE write ALTER

How do I return all values from a stored procedure?

Forgive my naivety, but I am new to using Delphi with databases (which may seem odd to some).
I have setup a connection to my database (MSSQL) using a TADOConnection. I am using TADOStoredProc to access my stored procedure.
My stored procedure returns 2 columns, a column full of server names, and a 2nd column full of users on the server. It typically returns about 70 records...not a lot of data.
How do I enumerate this stored procedure programmatically? I am able to drop a DBGrid on my form and attach it to a TDataSource (which is then attached to my ADOStoredProc) and I can verify that the data is correctly being retrieved.
Ideally, I'd like to enumerate the returned data and move it into a TStringList.
Currently, I am using the following code to enumerate the ADOStoredProc, but it only returns '#RETURN_VALUE':
ADOStoredProc1.Open;
ADOStoredProc1.ExecProc;
ADOStoredProc1.Parameters.Refresh;
for i := 0 to AdoStoredProc1.Parameters.Count - 1 do
begin
Memo1.Lines.Add(AdoStoredProc1.Parameters.Items[i].Name);
Memo1.Lines.Add(AdoStoredProc1.Parameters.Items[i].Value);
end;
Call Open to get a dataset returned
StoredProc.Open;
while not StoredProc.EOF do
begin
Memo1.Lines.Add(StoredProc.FieldByName('xyz').Value);
StoredProc.Next;
end;
Use Open to get the records from the StoredProc
Use either design-time Fields, ad-hoc Fields grabbed with FieldByName before the loop or Fields[nn] to get the values.
procedure GetADOResults(AStoredProc: TADOStoredProc; AStrings: TStrings);
var
fldServer, fldUser: TField;
begin
AStoredProc.Open;
fldServer := AStoredProc.FieldByName('ServerName');
fldUser := AStoredProc.FieldByName('User');
while not AStoredProc.EOF do
begin
AStrings.Add(Format('Server: %s - / User: %s',[fldServer.AsString, fldUser.AsString]));
// or with FFields and Index (asumming ServerName is the 1st and User the 2nd) and no local vars
AStrings.Add(Format('Server: %s - / User: %s',[AStoredProc.Fields[0].AsString, AStoredProc.Fields[1].AsString]));
AStoredProc.Next;
end;
end;
//use like
GetADOResults(ADOStoredProc1, Memo1.Lines);
Note: Fields[nn] allows to write less code but beware if the StoredProc changes the order of the returned columns.
If your stored procedure returns a result set (rows of data), don't use ExecProc. It's designed to execute procedures with no result set. Use Open or Active instead, and then you can process them just as you are using Parameters:
ADOStoredProc.Open;
for i := 0 to ADOStoredProc1.Parameters.Count - 1 do
begin
Memo1.Lines.Add(ADOStoredProc1.Parameters.Items[i].Name);
Memo1.Lines.Add(ADOStoredProc1.Parameters.Items[i].Value);
end;
BTW, calling Open and then ExecProc causes problems; Open returns a result set, ExecProc then clears it because you're running the procedure a second time with no result set expected. I also don't think you need the Parameters.Refresh, but I'm not 100% sure of that.
Take a look at this (just Googled it):
[http://www.scip.be/index.php?Page=ArticlesDelphi12&Lang=EN#Procedure][1]
Basically, a SQL Server stored procedure always returns one return value, but it can also create a result set, which you need to process like the data set returned from a regular select statement.

Access to Result sets from within Stored procedures Transact-SQL SQL Server

I'm using SQL Server 2005, and I would like to know how to access different result sets from within transact-sql. The following stored procedure returns two result sets, how do I access them from, for example, another stored procedure?
CREATE PROCEDURE getOrder (#orderId as numeric) AS
BEGIN
select order_address, order_number from order_table where order_id = #orderId
select item, number_of_items, cost from order_line where order_id = #orderId
END
I need to be able to iterate through both result sets individually.
EDIT: Just to clarify the question, I want to test the stored procedures. I have a set of stored procedures which are used from a VB.NET client, which return multiple result sets. These are not going to be changed to a table valued function, I can't in fact change the procedures at all. Changing the procedure is not an option.
The result sets returned by the procedures are not the same data types or number of columns.
The short answer is: you can't do it.
From T-SQL there is no way to access multiple results of a nested stored procedure call, without changing the stored procedure as others have suggested.
To be complete, if the procedure were returning a single result, you could insert it into a temp table or table variable with the following syntax:
INSERT INTO #Table (...columns...)
EXEC MySproc ...parameters...
You can use the same syntax for a procedure that returns multiple results, but it will only process the first result, the rest will be discarded.
I was easily able to do this by creating a SQL2005 CLR stored procedure which contained an internal dataset.
You see, a new SqlDataAdapter will .Fill a multiple-result-set sproc into a multiple-table dataset by default. The data in these tables can in turn be inserted into #Temp tables in the calling sproc you wish to write. dataset.ReadXmlSchema will show you the schema of each result set.
Step 1: Begin writing the sproc which will read the data from the multi-result-set sproc
a. Create a separate table for each result set according to the schema.
CREATE PROCEDURE [dbo].[usp_SF_Read] AS
SET NOCOUNT ON;
CREATE TABLE #Table01 (Document_ID VARCHAR(100)
, Document_status_definition_uid INT
, Document_status_Code VARCHAR(100)
, Attachment_count INT
, PRIMARY KEY (Document_ID));
b. At this point you may need to declare a cursor to repetitively call the CLR sproc you will create here:
Step 2: Make the CLR Sproc
Partial Public Class StoredProcedures
<Microsoft.SqlServer.Server.SqlProcedure()> _
Public Shared Sub usp_SF_ReadSFIntoTables()
End Sub
End Class
a. Connect using New SqlConnection("context connection=true").
b. Set up a command object (cmd) to contain the multiple-result-set sproc.
c. Get all the data using the following:
Dim dataset As DataSet = New DataSet
With New SqlDataAdapter(cmd)
.Fill(dataset) ' get all the data.
End With
'you can use dataset.ReadXmlSchema at this point...
d. Iterate over each table and insert every row into the appropriate temp table (which you created in step one above).
Final note:
In my experience, you may wish to enforce some relationships between your tables so you know which batch each record came from.
That's all there was to it!
~ Shaun, Near Seattle
There is a kludge that you can do as well. Add an optional parameter N int to your sproc. Default the value of N to -1. If the value of N is -1, then do every one of your selects. Otherwise, do the Nth select and only the Nth select.
For example,
if (N = -1 or N = 0)
select ...
if (N = -1 or N = 1)
select ...
The callers of your sproc who do not specify N will get a result set with more than one tables. If you need to extract one or more of these tables from another sproc, simply call your sproc specifying a value for N. You'll have to call the sproc one time for each table you wish to extract. Inefficient if you need more than one table from the result set, but it does work in pure TSQL.
Note that there's an extra, undocumented limitation to the INSERT INTO ... EXEC statement: it cannot be nested. That is, the stored proc that the EXEC calls (or any that it calls in turn) cannot itself do an INSERT INTO ... EXEC. It appears that there's a single scratchpad per process that accumulates the result, and if they're nested you'll get an error when the caller opens this up, and then the callee tries to open it again.
Matthieu, you'd need to maintain separate temp tables for each "type" of result. Also, if you're executing the same one multiple times, you might need to add an extra column to that result to indicate which call it resulted from.
Sadly it is impossible to do this. The problem is, of course, that there is no SQL Syntax to allow it. It happens 'beneath the hood' of course, but you can't get at these other results in TSQL, only from the application via ODBC or whatever.
There is a way round it, as with most things. The trick is to use ole automation in TSQL to create an ADODB object which opens each resultset in turn and write the results to the tables you nominate (or do whatever you want with the resultsets). you can also do it in DMO if you enjoy pain.
There are two ways to do this easily. Either stick the results in a temp table and then reference the temp table from your sproc. The other alternative is to put the results into an XML variable that is used as an OUTPUT variable.
There are, however, pros and cons to both of these options. With a temporary table, you'll need to add code to the script that creates the calling procedure to create the temporary table before modifying the procedure. Also, you should clean up the temp table at the end of the procedure.
With the XML, it can be memory intensive and slow.
You could select them into temp tables or write table valued functions to return result sets. Are asking how to iterate through the result sets?