How to return a result set of a table in DB2-LUW without using a cursor? - sql

I'm trying to create a stored procedure in DB2-LUWv10.5. I need to return a result set of a table without making use of a Cursor(WITH RETURN).
I am running the below code on DB2-LUWv10.5. When I use Approach2, it runs fine and returns expected result set as output i.e. Salary and bonus of all employees. However, when I use Approach1, it gives me an error.
Errors encountered:
"," was expected instead of ";".
"salary, bonus" appears to be misplaced.
End of text was reached after "p1".
Approach 1:
CREATE PROCEDURE read_emp()
SPECIFIC read_emp
LANGUAGE SQL
DYNAMIC RESULT SETS 1
p1: BEGIN
SELECT salary, bonus
FROM employee;
END p1
Approach 2:
CREATE PROCEDURE read_emp()
SPECIFIC read_emp
LANGUAGE SQL
DYNAMIC RESULT SETS 1
p1: BEGIN
DECLARE c_emp CURSOR WITH RETURN FOR
SELECT salary, bonus
FROM employee;
OPEN c_emp;
END p1
Expected output -
Salary Bonus
25000 2500
30000 3000
50000 5000
40000 4000
45000 4500

Your "Approach 1" is invalid syntax, and Db2 will reject it, because that's the design.
A stored procedure that wants to return a result-set via the dynamic result set syntax must declare and open at least one cursor.
But a stored procedure does not necessarily have to use the dynamic result set syntax. A stored procedure is free to use an OUTPUT parameter that is either a weakly-typed-cursor or a strongly-typed-cursor data-type. The stored procedure must still open the cursor however, just that it can pass the opened cursor back as a parameter. The caller will still need to consume that cursor however.
User Defined Functions can return tables, this is an alternative approach, so callers don't then need to use explicit cursors to access the resulting data.

Related

User defined function can only have select statements

One of the main differences between UDF and SP is that UDF can only have select statements inside it and not insert/update/delete statements. Can someone please explain the reason behind this?The below function:
create function test(..)
...
BEGIN
insert into EMPLOYEE('22',12000,'john');
return 0;
END
is not valid. But why is this so?
The insert statement inside your function is missing the values keyword;
insert into EMPLOYEE('22',12000,'john');
should be
insert into EMPLOYEE values ('22',12000,'john');
though it's better to include the list of column names too. From the small part of the code you showed that is the only thing that is invalid. There could be other errors in the bits you have omitted. (If the first column in your table is numeric then you shouldn't be passing a string - it works but does implicit conversion and is best avoided. And if the column is a string, should it be really?)
UDF can only have select statements inside it and not insert/update/delete statements
That is not correct. You can have DML (insert/update/delete) in a function, but you can only call it from a PL/SQL context (though even in PL/SQL, it's often said that functions should query data with no side effects and only procedures should modify data; but that is not restricted by the language itself):
create table employee (id varchar2(3), salary number, name varchar2(10));
Table EMPLOYEE created.
create function test(unused number)
return number as
BEGIN
insert into EMPLOYEE (id, salary, name)
values ('22',12000,'john');
return 0;
END;
/
Function TEST compiled
declare
rc number;
begin
rc := test(42);
end;
/
PL/SQL procedure successfully completed.
select * from employee;
ID SALARY NAME
--- ---------- ----------
22 12000 john
But you cannot call it from a SQL context:
select test(42) from dual;
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "MYSCHEMA.TEST", line 4
The documentation lists restrictions on functions called from SQL, and goes into more detail in this warning:
Because SQL is a declarative language, rather than an imperative (or procedural) one, you cannot know how many times a function invoked by a SQL statement will run—even if the function is written in PL/SQL, an imperative language.
If the function was allowed to do DML then you would have no control over how many times that DML was performed. If it was doing an insert, for instance, it might try to insert the same row twice and either duplicate data or get a constraint violation.
Just to summarise the comments, you can have DML inside a PL/SQL function.
What you can't do is call that function from SQL, because a select statement shouldn't also apply updates and deletes and so on as a hidden side effect.
For one thing, the SQL language reserves the right to execute the query any way it chooses, in any order, and with any caching it decides make use of. (It might even stop and restart during execution. That's up to the SQL engine.) Therefore your function might get called once or a hundred times, in any order, depending on the execution plan, and so the results would be unpredictable.

Can an Oracle Procedure produce output records?

I am new to Oracle 10g and know that in MS SQL Server I can create a procedure such as the one below to generate an output record. Is this possible with a standard Oracle Procedure or do I need to use a package/function?
CREATE PROCEDURE SAMPLE_STORED_PROCEDURE
#USERNAME varchar(10)
AS
BEGIN
SELECT NAME as Output from Employee where Username = #USERNAME
END
GO
Thank you
Assuming that username is unique in employee (which seems like a reasonable guess to me), you probably want a function
CREATE OR REPLACE FUNCTION function_name( p_username IN employee.username%type )
RETURN employee.name%type
IS
l_name employee.name%type;
BEGIN
SELECT name
INTO l_name
FROM employee
WHERE username = p_username;
RETURN l_name;
END function_name;
You could also use a procedure with an OUT parameter
CREATE OR REPLACE PROCEDURE procedure_name( p_username IN employee.username%type,
p_name OUT employee.name%type )
AS
BEGIN
SELECT name
INTO p_name
FROM employee
WHERE username = p_username;
END procedure_name;
Generally, in PL/SQL you'd want to use a function whenever you want to write some sort of "getter" that reads data from the database and a procedure whenever you want to write some sort of "setter" that writes data to the database. So a function would make more sense here. Both functions and procedures can and should be organized into packages that allow you to group together related functionality. It would probably make sense, for example, to have a package that groups together all the functions and procedures related to adding, modifying, deleting, and reading information about employees.
If my original guess that username is not unique in employee is incorrect and you expect your select statement to return multiple rows, there are a number of different choices depending on exactly what you're going to be doing with that data. You can write a function that returns a sys_refcursor. You can write a function that returns a collection. You can write a pipelined table function. Without knowing more, however, it's impossible to know which of these options would make more sense in your case.

Dynamic Cursors

I use a cursor for the statement:
SELECT NAME FROM STUDENT WHERE ROLL = 1;
I used:
CURSOR C IS SELECT NAME FROM STUDENT WHERE ROLL = roll;
--roll is a variable I receive via a procedure, and the procedure works fine for the received parameter.
Upon executing this, I am able to retrieve all records with roll = 1.
Now, I need to retrieve the records of a group (possibly via a cursor), just like:
SELECT NAME FROM STUDENT WHERE ROLL IN (2, 4, 6);
But the values in the IN clause are known only at run time. How should I do this? That is, is there any way I could assign parameters to the WHERE clause of the cursor?
I tried using an array in the declaration of the cursor, but an error pops up telling something like: standard types cannot be used.
I used:
CURSOR C IS SELECT NAME FROM STUDENT WHERE ROLL IN (rolls);
--rolls is an array initialized with the required roll numbers.
First, I assume that the parameter to your procedure doesn't actually match the name of a column in the STUDENT table. If you actually coded the statement you posted, roll would be resolved as the name of the column, not the parameter or local variable so this statement would return every row in the STUDENT table where the ROLL column was NOT NULL.
CURSOR C
IS SELECT NAME
FROM STUDENT
WHERE ROLL = roll;
Second, while it is possible to use dynamic SQL as #Gaurav Soni suggests, doing so generates a bunch of non-sharable SQL statements. That's going to flood the shared pool, probably aging other statements out of cache, and use a lot of CPU hard-parsing the statement every time. Oracle is built on the premise that you are going to parse a SQL statement once, generally using bind variables, and then execute the statement many times with different values for the bind variables. Oracle can go through the process of parsing the query, generating the query plan, placing the query in the shared pool, etc. only once and then reuse all that when you execute the query again. If you generate a bunch of SQL statements that will never be used again because you're using dynamic SQL without bind variables, Oracle is going to end up spending a lot of time caching SQL statements that will never be executed again, pushing useful cached statements that will be used again out of the shared pool meaning that you're going to have to re-parse those queries the next time they're encountered.
Additionally, you've opened yourself up to SQL injection attacks. An attacker can exploit the procedure to read any data from any table or execute any function that the owner of the stored procedure has access to. That is going to be a major security hole even if your application isn't particularly security conscious.
You would be better off using a collection. That prevents SQL injection attacks and it generates a single sharable SQL statement so you don't have to do constant hard parses.
SQL> create type empno_tbl is table of number;
2 /
Type created.
SQL> create or replace procedure get_emps( p_empno_arr in empno_tbl )
2 is
3 begin
4 for e in (select *
5 from emp
6 where empno in (select column_value
7 from table( p_empno_arr )))
8 loop
9 dbms_output.put_line( e.ename );
10 end loop;
11 end;
12 /
Procedure created.
SQL> set serveroutput on;
SQL> begin
2 get_emps( empno_tbl( 7369,7499,7934 ));
3 end;
4 /
SMITH
ALLEN
MILLER
PL/SQL procedure successfully completed.
create or replace procedure dynamic_cur(p_empno VARCHAR2) IS
cur sys_refcursor;
v_ename emp.ename%type;
begin
open cur for 'select ename from emp where empno in (' || p_empno || ')';
loop
fetch cur into v_ename;
exit when cur%notfound;
dbms_output.put_line(v_ename);
end loop;
close cur;
end dynamic_cur;
Procedure created
Run the procedure dynamic_cur
declare
v_empno varchar2(200) := '7499,7521,7566';
begin
dynamic_cur(v_empno);
end;
Output
ALLEN
WARD
JONES
Note:As mentioned by XQbert,dynamic cursor leads to SQL injection ,but if you're not working on any critical requirement ,where security is not involved then you can use this .
Maybe you can pass rolls as a set of quoted comma separated values.
e.g. '1', '2' etc
If this value is passes into the procedure in a varchar input variable, the it can be used to get multiple rows as per the table match.
Hence the cursor
SELECT NAME FROM STUDENT WHERE ROLL IN (rolls);
will be evaluated as
SELECT NAME FROM STUDENT WHERE ROLL IN ('1','2');
Hope it helps

Viewing query results with a parameters in Oracle

I need to run big queries (that was a part of SP) and look at their results (just trying to find a bug in a big SP with many unions. I want to break it into parts and run them separately).
How can I do that if this SP have few parameters? I don't want to replace them in code, it would be great just to add declare in a header with a hardcode for this parameter.
I've tried something like this:
DECLARE
p_asOfDate DATE := '22-Feb-2011';
BEGIN
SELECT * from myTable where dateInTable < p_asOfDate;
END
But it says that I should use INTO keyword. How can I view this results in my IDE? (I'm using Aqua data studio)
I need to do that very often, so will be very happy if will find a simple solution
You are using an anonymous block of pl/sql code.
In pl/sql procedures you need to specify a target variable for the result.
So you first need to define a variable to hold the result in the declare section
and then insert the result data into it.
DECLARE
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
select * into p_result from myTable where dateInTable < p_asOfDate;
END
That said you will probaply get more than one row returned, so I would use
a cursor to get the rows separately.
DECLARE
CURSOR c_cursor (asOfDate IN DATE) is
select * from myTable where dateInTable < asOfDate;
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
OPEN c_cursor(p_asOfDate);
loop
FETCH c_cursor into p_result;
exit when c_cursor%NOTFOUND;
/* do something with the result row here */
end loop;
CLOSE c_cursor;
END
To output the results you can use something like this for example:
dbms_output.put_line('some text' || p_result.someColumn);
Alternatively you can execute the query on an sql command-line (like sqlplus)
and get the result as a table immediately.
I hope I understood your question correctly...
update
Here is a different way to inject your test data:
Use your tools sql execution environemnt to submit your sql statement directly without a pl/sql block.
Use a "&" in front of the variable part to trigger a prompt for the variable.
select * from myTable where dateInTable < &p_asOfDate;
The Result should be displayed in a formatted way by your tool this way.
I do not know about Aqua, but some tools have functions to define those parameters outside the sql code.

PL/SQL Query with Variables

I have a fairly complex query that will be referencing a single date as a start or stop date multiple times throughout. I'm going to have to run this query for 3 different fiscal years, and don't want to have to hunt down the date 17 times in order to change it throughout my query.
Is there a way to set a variable at the beginning of my query and reference it throughout? I'm not looking to write a whole function, just reference a variable throughout my query.
Yes, depends how you want to do it.
You could use an anonymous procedure IE:
BEGIN
v_date DATE := TO_DATE(your_date, your_date_mask);
[your query referencing v_date where ever you need];
END;
Or if you run the query in SQLPlus, you use & to note variables (IE: &your_date), and will be prompted for the value when you run the script.
As OMG Ponies says, inside PL/SQL you can always refer to any PL/SQL variable (including parameters) right in the SQL as long as it's static SQL. Outside PL/SQL, or if your SQL is dynamic (because native dynamic SQL doesn't support reusable named parameters at least as of 10g) you can use the following trick. Add the following before the WHERE clause in your query:
CROSS JOIN (SELECT :dateparam Mydate FROM dual) Dateview
And everywhere you want to refer to that value in your main query, call it Dateview.Mydate Then when you execute the query, you need only pass in the one bind parameter.
You're not really saying how you reference this so I'll just show from SQL*Plus point of view.
Two ways
Have it prompt you for the value. Since you use the same variable many times you'll want to use the && operator.
SQL> SELECT &&var, &&var FROM Dual;
Enter value for var: 'PUMPKIN'
old 1: SELECT &&var, &&var FROM Dual
new 1: SELECT 'PUMPKIN', 'PUMPKIN' FROM Dual
'PUMPKI 'PUMPKI
------- -------
PUMPKIN PUMPKIN
Alternatively you could set it before you ran your SQL.
SQL> VARIABLE new_var VARCHAR2(20);
SQL> EXECUTE :new_var := 'PUMPKIN PIE';
PL/SQL procedure successfully completed.
SQL> SELECT :new_var, :new_var FROM DUAL;
:NEW_VAR :NEW_VAR
-------------------------------- --------------------------------
PUMPKIN PIE PUMPKIN PIE
If you use Toad with second mouse button -> Execute as Script, so not prompt you for values:
var myVar varchar2(20);
exec :req := 'x';
delete from MYTable where Field =
:myVar;