How should I select or display the out variable from a stored procedure call? - sql

I'm writing a pretty basic stored procedure that just takes values from the sample DB2 database and computes the standard deviation. I wrote the procedure itself just fine, and I can call it without error. But I can't figure out how to actually display my result or select it in a statement. Everything I try results in a syntax error and I haven't been able to find anyone doing this specific task in my google searches.
This is the gist of my code (snipped for brevity):
CREATE PROCEDURE SAL_STD_DEV
(OUT std_dev real)
LANGUAGE SQL
BEGIN
--do stuff
SET std_dev = 10; --changed for simplicity
END#
CALL SAL_STD_DEV(?)#
All this runs, but just CALL doesn't create any output. What's the syntax to SELECT the out variable? I can't put a DECLARE before the CALL because it's not in a stored procedure, and PRINT doesn't work either.
(# is my terminal character because I'm using ; in the stored procedure)
Edit: Both the create procedure and call statements are made in the same SQL file, the database is connect to through localhost and I'm using DB2 11.1.0.1527 and developing in IBM Data Studio 4.1.2.

From wherever the CALL is being made, that feature might present a Result Set, despite apparently not presenting the result of an OUT parameter. If so, then the stored procedure perhaps could be revised to return the OUT value [instead, or additionally] as a result set, so that the interface that accepts the CALL statement as input, might present that result-set. Regardless:
In a statement processor [e.g. that is not a GUI, but] for which SELECT query output is presented, the following scripted requests should likely suffice:
create variable my_real real
;
call SAL_STD_DEV(my_real)
;
select my_real from sysibm.sysdummy1
;

Related

Struggling to create a "stored procedure" beyond INSERT

Whenever I try to call a stored procedure in PostgreSQL that goes beyond inserting data, it takes forever to run, and it isn't that the query is complicated or the dataset is huge. The dataset is small. I cannot return a table from a stored procedure and I cannot even return 1 row or 1 data point from a stored procedure. It says it is executing the query for a very long time until I finally stop the query from running. It does not give me a reason. I can't let it run for hours. Any ideas on what might be happening? I have included stored procedures that I have tried to call.
Non-working example #1:
CREATE PROCEDURE max_duration(OUT maxD INTERVAL)
LANGUAGE plpgsql AS $$
DECLARE maxD INTERVAL;
BEGIN
SELECT max(public.bikeshare3.duration)
INTO maxD
FROM public.bikeshare3;
END;
$$ ;
CALL max_duration(NULL);
Non-working example #2:
CREATE PROCEDURE getDataByRideId2(rideId varchar(16))
LANGUAGE SQL
AS $$
SELECT rideable_type FROM bikeshare3
WHERE ride_id = rideId
$$;
CALL getDataByRideId2('x78900');
Working example
The only one that worked when called is an insert procedure:
CREATE OR REPLACE PROCEDURE genre_insert_data(GenreId integer, Name_b character varying)
LANGUAGE SQL
AS $$
INSERT INTO public.bikeshare3 VALUES (GenreId, Name_b)
$$;
CALL genre_insert_data(1, 'testName');
FUNCTION or PROCEDURE?
The term "stored procedure" has been a widespread misnomer for the longest time. That got more confusing since Postgres 11 added CREATE PROCEDURE.
You can create a FUNCTION or a PROCEDURE in Postgres. Typically, you want a FUNCTION. A PROCEDURE mostly only makes sense when you need to COMMIT in the body. See:
How to return a value from a stored procedure (not function)?
Nothing in your question indicates the need for a PROCEDURE. You probably want a FUNCTION.
Question asked
Adrian already pointed out most of what's wrong in his comment.
Your first example could work like this:
CREATE OR REPLACE PROCEDURE max_duration(INOUT _max_d interval = NULL)
LANGUAGE plpgsql AS
$proc$
BEGIN
SELECT max(b.duration) INTO _max_d
FROM public.bikeshare3 b;
END
$proc$;
CALL max_duration();
Most importantly, your OUT parameter is visible inside the procedure body. Declaring another instance as variable hides the parameter. You could access the parameter by qualifying with the function name, max_duration.maxD in your original. But that's a measure of last resort. Rather don't introduce duplicate variable names to begin with.
I also did away with error-prone mixed-case identifiers in my answer. See:
Are PostgreSQL column names case-sensitive?
I made the parameter INOUT max_d interval = NULL. By adding a default value, we don't have to pass a value in the call (that's not used anyway). But it must be INOUT instead of OUT for this.
Also, OUT parameters only work for a PROCEDURE since Postgres 14. The release notes:
Stored procedures can now return data via OUT parameters.
While using an OUT parameter, this advise from the manual applies:
Arguments must be supplied for all procedure parameters that lack
defaults, including OUT parameters. However, arguments matching OUT
parameters are not evaluated, so it's customary to just write NULL
for them. (Writing something else for an OUT parameter might cause
compatibility problems with future PostgreSQL versions.)
Your second example could work like this:
CREATE OR REPLACE PROCEDURE get_data_by_ride_id2(IN _ride_id text
, INOUT _rideable_type text = NULL) -- return type?
LANGUAGE sql AS
$proc$
SELECT b.rideable_type
FROM public.bikeshare3 b
WHERE b.ride_id = _ride_id;
$proc$;
CALL get_data_by_ride_id2('x78900');
If the query returns multiple rows, only the first one is returned and the rest is discarded. Don't go there. This only makes sense while ride_id is UNIQUE. Even then, a FUNCTION still seems more suitable ...

How to use SET OPTION within a DB2 stored procedure

I read (and tried) that I cannot use WITH UR in DB2 stored procedures. I am told that I can use SET OPTION to achieve the same. However, when I implement it in my stored procedure, it fails to compile (I moved around its location same error). My questions are:
Can I really not use WITH UR after my SELECT statements within a procedure?
Why is my stored procedure failing to compile with the below error
message?
Here is a simplified version of my code:
CREATE OR REPLACE PROCEDURE MySchema.MySampleProcedure()
DYNAMIC RESULT SETS 1
LANGUAGE SQL
SET OPTION COMMIT=*CHG
BEGIN
DECLARE GLOBAL TEMPORARY TABLE TEMP_TABLE AS (
SELECT 'testValue' as "Col Name"
) WITH DATA
BEGIN
DECLARE exitCursor CURSOR WITH RETURN FOR
SELECT *
FROM SESSION.TEMP_TABLE;
OPEN exitCursor;
END;
END
#
Error Message:
SQL0104N An unexpected token "SET OPTION COMMIT=*CHG" was found
following " LANGUAGE SQL
Here is code/error when I use WITH UR
CREATE OR REPLACE PROCEDURE MySchema.MySampleProcedure()
LANGUAGE SQL
DYNAMIC RESULT SETS 1
--#SET TERMINATOR #
BEGIN
DECLARE GLOBAL TEMPORARY TABLE TEMP_TABLE AS (
SELECT UTI AS "Trade ID" FROM XYZ WITH UR
) WITH DATA;
BEGIN
DECLARE exitCursor CURSOR WITH RETURN FOR
SELECT *
FROM SESSION.TEMP_TABLE;
OPEN exitCursor;
END;
END
#
line 9 is where the DECLARE GLOBAL TEMPORARY ... is
DB21034E The command was processed as an SQL statement because it was
not a valid Command Line Processor command. During SQL processing it
returned: SQL0109N The statement or command was not processed because
the following clause is not supported in the context where it is
used: "WITH ISOLATION USE AND KEEP". LINE NUMBER=9. SQLSTATE=42601
Specifying the isolation level:
For static SQL:
If an isolation-clause is specified in the statement, the value of that clause is used.
If an isolation-clause is not specified in the statement, the isolation level that was specified for the package when the package was bound to the database is used.
You need to bind the routine package with UR, since your DECLARE GTT statement is static. Before CREATE OR REPLACE use the following in the same session:
CALL SET_ROUTINE_OPTS('ISOLATION UR')
P.S.: If you want to run your routine not only 1 time in the same session without an error, use additional WITH REPLACE option of DECLARE.
If your Db2 server runs on Linux/Unix/Windows (Db2-LUW), then there is no such statement as SET OPTION COMMIT=*CHG , and so Db2 will throw an exception for that invalid syntax.
It is important to only use the matching Db2 Knowledge Centre for your Db2 platform and your Db2-version. Don't use Db2-Z/OS documentation for Db2-LUW development. The syntax and functionalities differ per platform and per version.
A Db2-LUW SQL PL procedure can use with ur in its internal queries, and if you are getting an error then something else is wrong. You have to use with ur in the correct syntax however, i.e in a statement that supports this clause. For your example you get the error because the clause does not appear to be valid in the depicted context. You can achieve the desired result in other ways, one of them being to populate the table in a separate statement from the declaration (e.g insert into session.temp_table("Trade ID") select uti from xyz with ur; ) and other ways are also possible.
One reason to use the online Db2 Knowledge Cenbtre documentation is that it includes sample programs, including sample SQL PL procedures, which are also available in source code form in the sample directory of your DB2-LUW server, in addition to being available on github. It is wise to study these, and get them working for you.

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.

Why can't we call procedure from sql

I know we can call the function from SQL if it doesn't contain out parameter or DML(except autonomous). But we can't call the procedure from SQL in any condition.
What is the reason for it?
Why can't we call the procedure from SQL? Any specific reason.
The reason is that the SQL ANSII standard specifies that only functions can be used in the SQL query.
ISO committee members did not define the use of procedures in SQL queries.
You can:
call dbms_output.put_line('Hello')
CALL is part of the SQL language.
Or we can embed a procedure in an inline function:
with function f (p varchar2)
return varchar2
as
begin
dbms_output.put_line('Hello');
return p;
end f;
select f('Demo')
from dual
If you mean a SELECT statement specifically, I can't see how you expect that to work. What result set would you expect a query like this to return?
select dbms_output.put_line('Hello')
from dual
or
select dbms_stats.gather_table_stats(user, table_name)
from user_tables
This isn't an arbitrary restriction by some standards committee. It just doesn't make any sense semantically.
You can call or execute a procedure easily in SQL. Both parametrized or non can be called.
EXEC dbo.procedure_name
I'm assuming, that you are asking about calling procedures from within other SQL statements (not just call a procedure on its own, which is obviously possible).
Why? That's a matter of opinion, and you would have to ask Oracle DB architects for a real cause.
It would seem, that introducing procedure calls into all possible SQL statements, would bring both syntax and implementation complexity, while not necessarily bringing much more value. Usually there are alternatives, which are not much harder to use, while allowing the same outcome.
In case of a query (a SELECT statement), the result should be a data set, and no changes in the database state (data or structure) should be done. A PL/SQL procedure does not return a data set, and can change the database state.
If you are in a situation, where the procedure call is needed to prepare the data, you'd like to query, then you have the possibility to call the procedure first, and then query the database.
You can also write a procedure, which will have an output parameter of a cursor reference, which will effectively give you a query result. (For an ad hoc case, you could use parameterized anonymous PL/SQL block.)
You can also write a tabular function, where you can do complex data processing, using PL/SQL, and return a data set. Such function can be used in a query.
If you are asking also about other types of SQL statements, then you can always call DML (INSERT / UPDATE / DELETE / MERGE), DDL (CREATE / ALTER / DROP) or DCL (GRANT / REVOKE) from a procedure or an anonymous PL/SQ block, that does those and allows you to mix PL/SQL logic in. No need to do this the other way (introducing PL/SQL into DML / DDL / DCL).

How can I create multiple views in a stored procedure?

I want to create a temporary stored procedure to create several views; so something like this:
create proc #t1 as
begin
create view v1 as select 1 as x
go
create view v2 as select 2 as x
end
Unfortunately, when I execute this in Microsoft SQL Server 2005, I get a syntax error on the first create view line.
Something like this works:
create proc #t1 as
begin
exec('create view v1 as select 1 as x')
exec('create view v2 as select 2 as x')
end
However, this seems like a terrible way of doing what I want.
So what's wrong with the first attempt, and what's the best way to create multiple views in a stored procedure?
You can't have a go inside a stored procedure. It's not a command in SQL, it's a separator between batches in the SQL Manager, so it will cut the procedure into two batches and cause syntax errors because neither batch is a complete command.
You don't have to write a full blown parser to make this work - all you need to do is what the command line tools/SSMS do - read lines from the file and accumulate them in a (in .Net, it's a stringbuilder, can't remember the equivalent in Java) until you encounter a line which starts with the word GO. Each time you reach that point, send your accumulated buffer to SQL Server, and then empty your buffer and start again.
So long as your current script has GO whenever it's required, the above should work.
This is easy you can achieve this using variable,assign the create view to the #variable then EXEC(#Variable) statements inside the procedure