PL/SQL stored function return with row type - sql

CREATE OR REPLACE FUNCTION lab( f_name IN VARCHAR2 )
RETURN test%ROWTYPE
IS
total NUMBER := 0;
CURSOR c_app IS
SELECT count(*),LISTAGG(s.sname,',') WITHIN GROUP (ORDER BY s.sname)
FROM APPLICANT a INNER JOIN SPOSSESSED s ON a.A# = s.A#
WHERE a.fname = f_name;
rec_app c_app%ROWTYPE;
BEGIN
OPEN c_app;
LOOP
FETCH c_app into rec_app;
EXIT WHEN c_app%NOTFOUND;
END LOOP;
CLOSE c_app;
RETURN rec_app;
END lab;
/
Fail to compile with errors that expression wrong type?
Isn't it possible to return with rowtype result?
for example i run this function
select lab(fname) from position where fname='PETER';
so the result will be display like
PETER : aaaa,bbbb,cccc

You're declaring the return as test%rowtype, then trying to return rec_app, which is declared as c_app%rowtype - so the types don't match. You can't do that.
c_app is only in scope within this function so it would not have any meaning for any callers, and you can't use it as the return type. You can return something that is actually test%rowtype, assuming test is a table, but not an arbitrary different type. It isn't clear that there is any relationship at all between your cursor and its row type, and the test table.
You're also looping round to potentially fetch multiple rows, but only returning the last one (or trying to, anyway), which probably isn't what you mean to do.
The simplest way to get all the cursor rows back to the caller is with a ref cursor:
CREATE OR REPLACE FUNCTION lab( f_name IN VARCHAR2 )
RETURN SYS_REFCURSOR
IS
ref_cur SYS_REFCURSOR;
BEGIN
OPEN ref_cur FOR
SELECT count(*),LISTAGG(s.sname,',') WITHIN GROUP (ORDER BY s.sname)
FROM APPLICANT a INNER JOIN SPOSSESSED s ON a.A# = s.A#
WHERE a.fname = f_name;
RETURN ref_cur;
END lab;
/
If you create an external type you could use PIPELINED but that doesn't appear necessary here. neither is quite using a %rowtype though. You can only return a %rowtype if you have a table that has the columns you want to return.

Related

Execute a Select inside a loop PL/SQL and return cursor?

This is probably a simple question for who knows PL/SQL.
I have a stored procedure who takes an array of varchar in input:
TYPE MULTI is table of VARCHAR(15) index by BINARY_INTEGER;
PROCEDURE MYPROC(
SINGLE IN MULTI,
P_RESULT OUT MY_PCK.MYCURSOR,
P_SOMETHING OUT VARCHAR2,
);
The cursor works because i have tested it in other cases but this is the first with an array parameter.
I have a problem with the body, how can i assign each value i get from the select to the cursor?
BEGIN
FOR i IN SINGLE.first .. SINGLE.last
LOOP
--BEGIN OPEN P_RESULT FOR this --this on left gives me error
SELECT MT.DESCR INTO P_SOMETHING
FROM MYTABLE1 MT
WHERE MT.IDS = SINGLE(i)
AND and rownum < 2;
--dbms_output.put_line(SINGLE(i)); --if i use this instead of select i get the values i send to this procedure.
END LOOP;
i tried also:
SELECT MT.DESCR INTO P_RESULT but gives error
What i'm doing wrong?
Thanks in advice.
You can't use your PL/SQL collection type in a SQL statement in 11g. You could create a SQL collection type, or find one you already have access to (which has a suitable string length) and use that to at least verify the mechanism.
For instance, this uses a local variable of type SYS.HSBLKNAMLST, which is defined as a table of varchar2(30), more than enough to match your own PL/SQL type's string length. That variable is populated from your passed-in PL/SQL type, and that is then used in a query to open the cursor:
PROCEDURE MYPROC(
SINGLE IN MULTI,
P_RESULT OUT MY_PCK.MYCURSOR
) IS
LOCAL_COLL SYS.HSBLKNAMLST := SYS.HSBLKNAMLST();
BEGIN
FOR i IN SINGLE.first .. SINGLE.last
LOOP
LOCAL_COLL.extend();
LOCAL_COLL(LOCAL_COLL.last) := SINGLE(i);
END LOOP;
OPEN P_RESULT FOR
SELECT MT.DESCR
FROM MYTABLE1 MT
LEFT JOIN TABLE(LOCAL_COLL) LC
ON LC.COLUMN_VALUE = MT.IDS
WHERE LC.COLUMN_VALUE IS NULL;
END;
I'm a bit confused about the query you showed in your loop though; you seem to be attempting to re-open the cursor for each element of the array, though maybe you were trying to append the result of the query for each element to the same cursor - thought the != would mean that all rows would be included at some point. I've guess that you're trying to get all records with IDS values that are not in the array. If you actually want all that are then that would be:
OPEN P_RESULT FOR
SELECT MT.DESCR
FROM TABLE(LOCAL_COLL) LC
JOIN MYTABLE1 MT
ON MT.IDS = LC.COLUMN_VALUE;
You can see which built-in types are available to you by querying the data dictionary, e.g:
select owner, type_name, coll_type, elem_type_name, length
from all_coll_types
where elem_type_name = 'VARCHAR2'
and coll_type = 'TABLE'
and owner = 'SYS'
order by length;
It would be preferable to create your own SQL type if you're able to.

Oracle SQL - SELECT with Variable arguments stored procedure

I'm struggling with a variable argument stored procedure that has to perform a SELECT on a table using every argument passed to it in its WHERE clause.
Basically I have N account numbers as parameter and I want to return a table with the result of selecting three fields for each account number.
This is what I've done so far:
function sp_get_minutes_expiration_default(retval IN OUT char, gc IN OUT GenericCursor,
p_account_num IN CLIENT_ACCOUNTS.ACCOUNT_NUM%TYPE) return number
is
r_cod integer := 0;
begin
open gc for select account_num, concept_def, minutes_expiration_def from CLIENT_ACCOUNTS
where p_account_num = account_num; -- MAYBE A FOR LOOP HERE?
return r_cod;
exception
-- EXCEPTION HANDLING
end sp_get_minutes_expiration_default;
My brute force solution would be to maybe loop over a list of account numbers, select and maybe do a UNION or append to the result table?
If you cast your input parameter as a table, then you can join it to CLIENT_ACCOUNTS
select account_num, concept_def, minutes_expiration_def
from CLIENT_ACCOUNTS ca, table(p_account_num) a
where a.account_num = ca.account_num
But I would recommend you select the output into another collection that is the output of the function (or procedure). I would urge you to not use reference cursors.
ADDENDUM 1
A more complete example follows:
create or replace type id_type_array as table of number;
/
declare
ti id_type_array := id_type_array();
n number;
begin
ti.extend();
ti(1) := 42;
select column_value into n from table(ti) where rownum = 1;
end;
/
In your code, you would need to use the framework's API to:
create an instance of the collection (of type id_type_array)
populate the collection with the list of numbers
Execute the anonymous PL/SQL block, binding in the collection
But you should immediately see that you don't have to put the query into an anonymous PL/SQL block to execute it (even though many experienced Oracle developers advocate it). You can execute the query just like any other query so long as you bind the correct parameter:
select account_num, concept_def, minutes_expiration_def
from CLIENT_ACCOUNTS ca, table(:p_account_num) a
where a.column_value = ca.account_num

Stored Procedure Functions

I have two tables
Equipment(equipmentid, equipname, equipstatus, maxrentdurationdays)EquipmentID
Equipment_Hire(equipmentID, pickupdate, dropoffdate,clientNo)
I need to create a stored function that takes clientNo as input and adds up 20$per day if the equipment_hire table's dropoffdate-pickupdate > MaxRentDurationdays
I was getting errors while doing this in oracle 11g.
CREATE OR REPLACE FUNCTION Fines
(P_ClientNo Equipment_Hire.ClientNo%TYPE)
RETURN VARCHAR2 IS
V_ClientNo INTEGER;
V_DURATIONDEFD INTEGER;
V_TOTALFINE INTEGER;
BEGIN SELECT ClientNo, MAXDURATIONDAYS
INTO V_FName, V_LName, V_TotalSalary
FROM Equipment, Equipment_hire
WHERE P_CLientNo = Equipment_Hire.ClientNo;
IF Equipment_hire.dropoffdate-equipment_hire.pickupdate >
equipment.maxdurationdays THEN
Equipment_hire.dropoffdate-equipment_hire.pickupdate*20
RETURN Equipment_hire.dropoffdate-equipment_hire.pickupdate*20; ELSE
RETURN 'Date UNSPECIFIED';
END IF; END Fines;
Data: https://www.mediafire.com/?chl6fdcl9cs817w
You can only use two tables equipment, equipment_hire.
Formatting the code helps:
CREATE OR REPLACE FUNCTION Fines(P_ClientNo Equipment_Hire.ClientNo%TYPE)
RETURN VARCHAR2 IS
V_ClientNo INTEGER;
V_DURATIONDEFD INTEGER;
V_TOTALFINE INTEGER;
BEGIN
SELECT ClientNo, MAXDURATIONDAYS
INTO V_FName, V_LName, V_TotalSalary
FROM Equipment, Equipment_hire
WHERE P_CLientNo = Equipment_Hire.ClientNo;
IF Equipment_hire.dropoffdate - equipment_hire.pickupdate > equipment.maxdurationdays THEN
Equipment_hire.dropoffdate - equipment_hire.pickupdate * 20
RETURN Equipment_hire.dropoffdate - equipment_hire.pickupdate * 20;
ELSE
RETURN 'Date UNSPECIFIED';
END IF;
END Fines;
On a quick glance:
You select two columns, but your INTO clause contains three variables.
You are cross joining the tables. Who ever told you to use this out-dated join syntax anyway?
The cross join leads to multiple result rows that you cannot read into simple variables.
What is that first line after the IF supposed to do?
You are using table names and columns outside the query.
Multiplication has precedence over subtraction (or should have). So you are taking a date, multiply it by 20 and subtract the result from another date.
You seem to think that there is just one equipment hire you are dealing with, but can't a user have several hires?

Possible to extend static SQL statements with dynamic parts?

I'd like to create a Oracle package where I have a procedure that executes some dynamic SQL. This is no problem if I'm doing it all dynamic with EXECUTE IMMEDIATE but it would be better if the static parts of the query could be coded static (to have compile time checking).
Example of fully dynamic query:
-- v_stmt is built dynamically.
v_stmt := 'SELECT count(*) FROM <here some joins> WHERE <here some conditions>';
EXECUTE IMMEDIATE v_stmt
USING v_param1, v_param2
RETURNING INTO v_count;
Example of what I tried to make the FROM-part static:
-- v_stmt is built dynamically.
v_stmt := 'SELECT count(*) FROM my_package.my_function(:param1, :param2) WHERE <here some conditions>';
EXECUTE IMMEDIATE v_stmt
USING v_param1, v_param2
RETURNING INTO v_count;
FUNCTION my_function(
i_param1 IN VARCHAR2,
i_param2 IN NUMBER
)
RETURN SYS_REFCURSOR
AS
v_cursor SYS_REFCURSOR;
BEGIN
-- Open a cursor for different queries depending on params.
IF i_param2 = 1 THEN
OPEN v_cursor FOR <some static query>;
ELSE
OPEN v_cursor FOR <some other static query>;
END IF;
RETURN v_cursor;
END;
This doesn't work because it's not possible to select from a SYS_REFCURSOR (at least that's what I found with Google).
Is there any way to reach this goal?
edit: As requested, here are some examples:
Static queries:
SELECT a.*, ca.CUS_ID FROM adresses a INNER JOIN customer_adresses ca ON (ca.adr_id = a.adr_id);
SELECT p.*, cp.CUS_ID FROM persons p INNER JOIN customer_persons cp ON (cp.per_id = p.per_id);
Then they are extended dynamically like the following examples:
-- Checks if there is an adress in the customer where the zip is null.
SELECT count(*) FROM <static adresses query> q WHERE q.cus_id = :param1 AND a.zip IS NULL;
-- Checks if there is at least one person in the customer.
SELECT count(*) FROM <static persons query> q WHERE q.cus_id = :param1;
Sorry, but why the need to do this? Seems you're over complicating things by introducing a function that will return different types of data/tables depending on the parameter list. Very confusing imo. Besides, you have to do the work somewhere, you're just trying to hide it in this function (inside if param1=this then x if param1=that then y...)
Besides, even if you did implement a cursor function (even pipelined), it would be a bad idea in this case because you'd be forcing Oracle into doing work that it wouldn't necessarily need to do (ignore all the context switching for now). To just get a count, you'd have Oracle grab each an every row result and then count. Many times Oracle can just do a fast full index scan to get the count (depending on the query of course). And often same query run multiple times will not need to do all the work each time if blocks are found in buffer cache. I'd challenge you to run the count multiple times using straight SQL vs using a function returning a cursor. You might be surprised. And to my knowledge (check me on this) the new 11g function result cache won't work on a pipelined functions or a function returning a ref cursor (along with other issues like invalidations due to relies on tables).
So, what I'm saying is why not just do: select count(1) into v_variable from ...;
If you want to hide and modularize, then just know what you're potentially losing.
You may want to open a query in function1 and then pipeline the results of it as a table to function2 which then will add a where clause to this "table"
In this case you'll want to rewrite your function1 as a pipelined table function
v_stmt := 'SELECT count(*) FROM table(my_package.my_function(:param1, :param2)) WHERE <here some conditions>';
EXECUTE IMMEDIATE v_stmt
USING v_param1, v_param2
RETURNING INTO v_count;
CREATE TYPE object_row_type AS OBJECT (
OWNER VARCHAR2(30),
OBJECT_TYPE VARCHAR2(18),
OBJECT_NAME VARCHAR2(30),
STATUS VARCHAR2(7)
);
CREATE TYPE object_table_type AS TABLE OF object_row_type;
FUNCTION my_function(
i_param1 IN VARCHAR2,
i_param2 IN NUMBER
)
RETURN object_table_type PIPELINED AS
BEGIN
You can have compile time checking of expressions with Oracle expression filter.
It's probably more complicated than the other solutions, but if you really need to verify your conditions it can be helpful.

Querying multiple rows from Oracle table using package

I wrote a package to query rows from a table. This select query will call other functions and returns all the rows from table. But when i write a package with all functions and sprocs , my sproc with select statement gives me an error saying i cannot execute without into statement. But if i use into then it will return only one row. How can i retrieve all rows using oracle sp?
Procedure GetData As
BEGIN
Select Jobid, JobName, JobLocation, JobCompany, X(jobid) FROM jobsTable; END GetData;
END;
I had to change it to following make the error go away:
Procedure GetData As
r_Jobid jobsTable.jobid%type;
r_JobName jobsTable.jobName%type;
r_JobLocation jobsTable.jobLocation%type;
r_temp varhar2(10);
BEGIN
Select Jobid, JobName, JobLocation, JobCompany, X(jobid)
INTO r_jobid, r_jobName, r_jobLocation, r_temp
FROM jobsTable;
END GetData;
END;
This is a better approach to returning multiple rows from a function:
FUNCTION GET_DATA()
RETURN SYS_REFCURSOR IS
results_cursor SYS_REFCURSOR;
BEGIN
OPEN results_cursor FOR
SELECT t.jobid,
t.jobName,
t.joblocation,
t.jobcompany,
X(t.jobid)
FROM JOBSTABLE t;
RETURN results_cursor;
END;
I agree with afk though that this doesn't appear to be what you really need to be using. Here's my recommendation for using a cursor:
CURSOR jobs IS
SELECT t.jobid,
t.jobName,
t.joblocation,
t.jobcompany,
X(t.jobid)
FROM JOBSTABLE t;
v_row jobs%ROWTYPE; --has to be declared AFTER the cursor to be able to reference the row type
BEGIN
OPEN jobs;
FETCH jobs INTO v_row;
IF jobs%FOUND THEN
--do stuff here, per row basis
--access columns in the row using: v_row.jobid/etc
END IF;
CLOSE jobs;
END;
Are you aware that this:
Procedure GetData As
r_Jobid jobsTable.jobid%type;
r_JobName jobsTable.jobName%type;
r_JobLocation jobsTable.jobLocation%type;
r_temp varhar2(10);
...means you defined local variables? You won't be able to get information out of the procedure. If you do, you'd need parameters, like this:
Procedure GetData(IO_R_JOBID IN OUT JOBSTABLE.JOBID%TYPE,
IO_R_JOBNAME IN OUT JOBSTABLE.JOBNAME%TYPE,
IO_R_JOBLOCATION IN OUT JOBSTABLE.JOBLOCATION%TYPE,
IO_R_TEMP IN OUT VARCHAR2(10)) AS
I use the IO_ to note which parameters are IN/OUT. I'd use IN_ or OUT_ where applicable. But the key here is to define OUT if you want to get a parameter back out.
Also - packages are just logical grouping of procedures & functions, with the ability to define constants scoped to the package. The package itself doesn't execute any SQL - it's still a function or procedure that is executing. God how I wish SQL Server had packages...
You could use a pipelined function. For this example I'm only fetching the ID columns, but you just need to add the others.
CREATE PACKAGE jobsPkg AS
TYPE jobsDataRec IS RECORD ( jobid jobsTable.jobid%type );
TYPE jobsDataTab IS TABLE OF jobsDataRec;
FUNCTION getJobsData RETURN jobsDataTab PIPELINED;
END jobsPkg;
/
CREATE PACKAGE BODY jobsPkg AS
FUNCTION getJobsData RETURN jobsDataTab PIPELINED
IS
output_record jobsDataRec;
BEGIN
FOR input_record IN ( SELECT jobid FROM jobsTable ) LOOP
output_record.jobid := input_record.jobid;
PIPE ROW (output_record);
END LOOP;
END getJobsData;
END jobsPkg;
/
This function can then be used as a row source:
SELECT * FROM TABLE( jobsPkg.getJobsData );
You need to use a Cursor for multiple return results. Take a look at this article on using Cursors for some details.
You could do something like:
DECLARE
CURSOR myCur IS
SELECT Jobid, JobName, JobLocation, JobCompany, X(jobid)
FROM jobsTable;
BEGIN
FOR resultRow in myCur
LOOP
--do your stuff here
END LOOP;
END;