How do you get nicely formatted results from an Oracle procedure that returns a reference cursor? - oracle

In MS SQL Server if I want to check the results from a Stored procedure I might execute the following in Management Studio.
--SQL SERVER WAY
exec sp_GetQuestions('OMG Ponies')
The output in the results pane might look like this.
ID Title ViewCount Votes
----- ------------------------------------------------- ---------- --------
2165 Indexed View vs Indexes on Table 491 2
5068 SQL Server equivalent to Oracle’s NULLS FIRST 524 3
1261 Benefits Of Using SQL Ordinal Position Notation? 377 2
(3 row(s) affected)
No need to write loops or PRINT statements.
To do the same thing in Oracle I might execute the following anonymous block in SQL Developer
--ORACLE WAY
DECLARE
OUTPUT MYPACKAGE.refcur_question;
R_OUTPUT MYPACKAGE.r_question;
USER VARCHAR2(20);
BEGIN
dbms_output.enable(10000000);
USER:= 'OMG Ponies';
recordCount := 0;
MYPACKAGE.GETQUESTIONS(p_OUTPUT => OUTPUT,
p_USER=> USER,
) ;
DBMS_OUTPUT.PUT_LINE('ID | Title | ViewCount | Votes' );
LOOP
FETCH OUTPUT
INTO R_OUTPUT;
DBMS_OUTPUT.PUT_LINE(R_OUTPUT.QUESTIONID || '|' || R_OUTPUT.TITLE
'|' || R_OUTPUT.VIEWCOUNT '|' || R_OUTPUT.VOTES);
recordCount := recordCount+1;
EXIT WHEN OUTPUT % NOTFOUND;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Record Count:'||recordCount);
CLOSE OUTPUT;
END;
This outputs like
ID|Title|ViewCount|Votes
2165|Indexed View vs Indexes on Table|491|2
5068|SQL Server equivalent to Oracle’s NULLS FIRST|524|3
1261|Benefits Of Using SQL Ordinal Position Notation?|377|2
Record Count: 3
So the SQL version has 1 line and the oracle has 18 and the output is ugly. Its exacerbated if there are a lot of columns and/or the data is numeric.
What's odd to me about this is that if I write this statement in either SQL Developer or Management studio...
SELECT
ID,
Title,
ViewCount,
Votes
FROM votes where user = 'OMG Ponies'
The results are fairly similar. This makes me feel like I'm either missing a technique or using the wrong tool.

If GetQuestions is a function returning a refcursor, which seems to be what you have in the SQL Server version, then rather you may be able to do something like this:
select * from table(MyPackage.GetQuestions('OMG Ponies'));
Or if you need it in a PL/SQL block then you can use the same select in a cursor.
You can also have the function produce the dbms_output statements instead so they're always available for debugging, although that adds a little overhead.
Edit
Hmmm, not sure it's possible to cast() the returned refcursor to a usable type, unless you're willing to declare your own type (and a table of that type) outside the package. You can do this though, just to dump the results:
create package mypackage as
function getquestions(user in varchar2) return sys_refcursor;
end mypackage;
/
create package body mypackage as
function getquestions(user in varchar2) return sys_refcursor as
r sys_refcursor;
begin
open r for
/* Whatever your real query is */
select 'Row 1' col1, 'Value 1' col2 from dual
union
select 'Row 2', 'Value 2' from dual
union
select 'Row 3', 'Value 3' from dual;
return r;
end;
end mypackage;
/
var r refcursor;
exec :r := mypackage.getquestions('OMG Ponies');
print r;
And you can use the result of the call in another procedure or function; it's just getting to it outside PL/SQL that seems to be a little tricky.
Edited to add: With this approach, if it's a procedure you can do essentially the same thing:
var r refcursor;
exec mypackage.getquestions(:r, 'OMG Ponies');
print r;

SQL Developer automatically catches the output from running your stored procedures. Running the stored procedure directly from our procedure editor, you can see this behavior detailed in my post here
SQL Developer Tip: Viewing REFCURSOR Output
Now, if you want to run the refcursor as part of an anon block in our SQL Worksheet, you could do something similar to this
var rc refcursor
exec :rc := GET_EMPS(30)
print rc
--where GET_EMPS() would be your sp_GetQuestions('OMG Ponies') call. The PRINT command sends the output from the 'query' which is ran via the stored procedure, and looks like this:
anonymous block completed
RC
-----------------------------------------------------------------------------------------------------
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
----------- -------------------- ------------------------- ------------------------- -------------------- ------------------------- ---------- ---------- -------------- ---------- -------------
114 Den Raphaely DRAPHEAL 515.127.4561 07-DEC-94 12.00.00 PU_MAN 11000 100 30
115 Alexander Khoo AKHOO 515.127.4562 18-MAY-95 12.00.00 PU_CLERK 3100 114 30
116 Shelli Baida SBAIDA 515.127.4563 24-DEC-97 12.00.00 PU_CLERK 2900 114 30
117 Sigal Tobias STOBIAS 515.127.4564 24-JUL-97 12.00.00 PU_CLERK 2800 114 30
118 Guy Himuro GHIMURO 515.127.4565 15-NOV-98 12.00.00 PU_CLERK 2600 114 30
119 Karen Colmenares KCOLMENA 515.127.4566 10-AUG-99 12.00.00 PU_CLERK 2500 114 30
Now, you said 10g. If you're in 12c, we have enhanced the PL/SQL engine to support implicit cursor results. So this gets a bit easier, no more setting up the cursor, you just make a call to get the data, as documented here:
http://docs.oracle.com/database/121/DRDAA/migr_tools_feat.htm#DRDAA230

/*
Create Sample Package in HR Schema
*/
CREATE OR REPLACE PACKAGE PRINT_REF_CURSOR
AS
PROCEDURE SP_S_EMPLOYEES_BY_DEPT (
p_DEPARTMENT_ID IN INTEGER,
Out_Cur OUT SYS_REFCURSOR);
END PRINT_REF_CURSOR;
CREATE OR REPLACE PACKAGE BODY PRINT_REF_CURSOR
AS
PROCEDURE SP_S_EMPLOYEES_BY_DEPT (
p_DEPARTMENT_ID IN INTEGER,
Out_Cur OUT SYS_REFCURSOR)
AS
BEGIN
OPEN Out_Cur FOR
SELECT *
FROM EMPLOYEES
WHERE DEPARTMENT_ID = p_DEPARTMENT_ID;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.Put_Line('SP_S_EMPLOYEES_BY_DEPT' || ',' || '-20000' || ',' );
WHEN OTHERS
THEN
DBMS_OUTPUT.Put_Line('SP_S_EMPLOYEES_BY_DEPT' || ',' || '-20001' || ',' );
END SP_S_EMPLOYEES_BY_DEPT;
END PRINT_REF_CURSOR;
/*
Fetch values using Ref Cursor and display it in grid.
*/
var RC refcursor;
DECLARE
p_DEPARTMENT_ID NUMBER;
OUT_CUR SYS_REFCURSOR;
BEGIN
p_DEPARTMENT_ID := 90;
OUT_CUR := NULL;
PRINT_REF_CURSOR.SP_S_EMPLOYEES_BY_DEPT ( p_DEPARTMENT_ID, OUT_CUR);
:RC := OUT_CUR;
END;
/
PRINT RC;
/************************************************************************/

Related

Retrieve all data from salary table and also calculate Gross Salary(Basic+HRA+DA+CA+Medical) using Stored procedure? in Oracle

I have created Procedure which calculates gross salary of all employees from salary table.When executing stored procedure using execute statement Error:"invalid SQL statement" occurs and when i am executing procedure using PLSQL block then Error":PLS-00905 Object HR.PROC_GROSSSALARY is invalid" occurs
-- Creating Stored procedure --
create or replace procedure proc_grosssalary
AS
begin
select s.*,(Basic+HRA+DA+CA+Medical) Gross_Salary from salary s;
end;
-- Calling SP using EXECUTE --
execute proc_grosssalary;
-- Calling SP using PLSQL Block --
begin
proc_grosssalary;
end;
display all data in salary table with calculated Gross_Salary in the form of table structure
your PL/SQL syntax is not correct
create or replace procedure proc_grosssalary (out gross_salary number)
AS
begin
select (Basic+HRA+DA+CA+Medical)
into gross_salary
from salary s;
end;
You should also consider the option of creating a View.
Create or replace view v_grosssalary
as select s.*,(Basic+HRA+DA+CA+Medical) Gross_Salary
from salary s;
and simply do select * from v_grosssalary to get the output.
With procedures, there's also a PRINT command(which works in SQL* Plus and SQL developer) using CURSOR bind variables
VARIABLE x REFCURSOR
create or replace procedure proc_grosssalary
AS
begin
OPEN cursor :x for
select s.*,(Basic+HRA+DA+CA+Medical) Gross_Salary from salary s;
end;
/
Print x -- This will display the required result.
But.. What do you want to do? If you want calculate salary for every employee and you have your information in the same row, with a simple SQL you could have it, something like this:
Select id_emp, name_emp, surname_emp, (Basic+HRAs+DA+CA+Medical) as salary
from salary;
And it would be more quick than have strange functions and procedures.. If you don't like it, create a view like these and you'll have a column with the salary calculated, more clean and less obscure
You need to use a cursor so that you can loop on records in the table.
Here is what I did -
#create a table for sample data
create table tsalary0918(Basic number,HRA number,DA number,CA number,Medical number)
#inserting sample data
insert into tsalary0918 values (100,100,100,100,100)
#checking if data is inserted
select * from tsalary0918;
#query output
BASIC HRA DA CA MEDICAL
100 100 100 100 100
#create a stored procedure
create or replace procedure testproc
AS
cursor cur is select s.*,(Basic +HRA + DA +CA +Medical ) Gross_salary from tsalary0918 s;
begin
for records in cur
loop
dbms_output.put_line('DA is ' || records.DA);
dbms_output.put_line('Gross Salary is ' || records.Gross_salary);
end loop;
end;
/
#execute the stored procedure
begin
testproc;
end;
/
#output of the above execution
dbms_output:
DA is 100
Gross Salary is 500
Check online - https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=1744281398fe62a6cb42250641947249
Task completed:
create or replace procedure p1
IS
cursor c1 is select s.*,(Basic+HRA+CA+DA+Medical) Gross_Salary from salary s;
emp_rec c1%rowtype;
begin
open c1;
dbms_output.put_line('Emp_No Basic HRA Gross_Salary');
dbms_output.put_line('---------------------------------------');
loop
fetch c1 into emp_rec;
exit when c1%notfound;
dbms_output.put_line(emp_rec.employee_number||' '||emp_rec.Basic||' '||emp_rec.HRA||' '||' '||emp_rec.Gross_Salary);
end loop;
close c1;
end;
-- Executing the procedure using PLSQL block --
begin
p1;
end;
Below is expected Final result:
----------------------------------------------
Employee_number Basic HRA Gross_salary
----------------------------------------------
1 25000 10000 36750
2 7000 2800 11650
3 10000 4000 15950

PL/SQL IF statement and variable declaration

I am trying to make the code posted below work, but I am receiving an error about the IF statement:
DECLARE
a number;
BEGIN
SELECT Pers_API.Is_Manager_(10018) INTO a from dual;
EXCEPTION
WHEN no_data_found THEN a := 0;
END;
IF (a >0) THEN
SELECT 1 from dual;
ELSE
SELECT 0 from dual;
END IF;
In the first block I am declaring an 'A' variable and setting it to 0 (by the way, the API call result will be 0 or 1, nothing else). The first block works fine or - at least - I do think so. But the IF block is not working and having this error:
PLS-00103:Encountered the symbol "IF"
Any help is appreciated.
Update: I have tried it this way:
DECLARE
a number:=0;
BEGIN
SELECT Pers_API.Is_Manager_(10018) INTO a from dual;
IF (:a >0) THEN
SELECT 1 from dual;
ELSE
SELECT 0 from dual;
END IF;
END;
I received the exception below:
ORA-01008: not all variables bound
Update
In other words, what I am trying to do is:
if Pers_API.Is_Manager_(10018) returns true (as 1), I have to do another select statement
if it's false (as 0), I have to return null.
Any other ideas are appreciated.
A few objections, if I may.
there's no need to select from dual in order to assign a value to a variable, when that value is returned by a function - simply assign it
exception handler seems to be superfluous. You said that the is_manager_ function returns 0 or 1 and nothing else. Therefore, it'll always return something, so - why do you expect that SELECT to return NO_DATA_FOUND? Besides, such a case should be handled by the function itself (i.e. you have to make sure that it returns 0 or 1 and nothing else)
the last paragraph you wrote is somewhat strange. You said that - if the function returns 1, you have to run another SELECT statement. Otherwise, you have to return null (while code you wrote, select 0 ... suggests a zero (0), not null); so, which one is true?
also, saying that you have to return something (null, 0, whatever) suggests that piece of code you wrote should be a function that returns a value. Is that so?
Code that actually compiles might look like in this example.
First, a function (based on Scott's schema) that checks whether an employee is a manager:
SQL> create or replace function is_manager_ (par_empno in number)
2 return number
3 is
4 /* function checks whether PAR_EMPNO belongs to a manager and returns:
5 - 1 - employee is a manager
6 - 0 - employee is NOT a manager
7 */
8 retval number := 0;
9 begin
10 select max(1)
11 into retval
12 from emp e
13 where mgr = par_empno;
14
15 return retval;
16 exception
17 when no_data_found then
18 return retval;
19 end;
20 /
Function created.
SQL>
Code you posted, rewritten so that it does something:
if an employee is a manager, returns his/her salary
otherwise, it does not
SQL> declare
2 a number := is_manager_(&&l_empno);
3 l_sal emp.sal%type;
4 begin
5 if a > 0 then
6 select sal
7 into l_sal
8 from emp
9 where empno = &&l_empno;
10 dbms_output.put_line('Employee is a manager and his/her salary is ' || l_sal);
11 else
12 null;
13 dbms_output.put_line('Employee is not a manager');
14 end if;
15 end;
16 /
Enter value for l_empno: 7698
Employee is a manager and his/her salary is 2850
PL/SQL procedure successfully completed.
SQL> undefine l_empno
SQL> /
Enter value for l_empno: 7369
Employee is not a manager
PL/SQL procedure successfully completed.
SQL> undefine l_empno
SQL> /
Enter value for l_empno: -1
Employee is not a manager
PL/SQL procedure successfully completed.
SQL>
See if you can adjust it so that it fits your needs. If not, do answer to my objections posted at the beginning of this message, and we'll see what to do next.
It appears to me that what you are really trying to achieve here is to run a query which must use the result obtained from the package function as a condition. If so, the PL/SQL block wouldn't be needed and can be written as a basic select operation.
What is still not clear from your explanation is whether your select query
SELECT 1 from dual is the actual query you are about to use in your code.Generally, in the select statement, we use CASE expression to evaluate the conditional logic, which essentially does what IF ELSE condition was supposed to do to return the desired result.The query would have a structure of this form,
SELECT
CASE
WHEN pers_api.is_manager_(10018) = 1 THEN some_expression
ELSE some_other_expression --If you omit the else condition, it defaults to null when all others are false
END
END
as
FROM DUAL;
More specifically, if you want to have the resultant expression come from another database table, the queries can take the form
SELECT
CASE
WHEN pers_api.is_manager_(10018) = 1 THEN some_expression
ELSE some_other_expression --If you omit the else condition, it defaults to null when all others are false
END
as
FROM table_to_run_query
WHERE where_clauses = some_values;
If you say that your resultant expression from the table involves many other constructs, the challenge would be to compose it in a single query cleverly to avoid any PL/SQL at all. That would be achievable, but it will require that you explain us with some sample data and expected result, as to what exactly you want,preferably in a new question.
It would not be complete if I don't mention the REFCURSOR technique to obtain results from a select query in PL/SQL. It is either through DBMS_SQL.RETURN_RESULT(Oracle 12c and above) or using a REFCURSOR bind variable running it in SQL* Plus or as Script in other tools (F5).
VARIABLE x refcursor;
DECLARE BEGIN
IF
pers_api.is_manager_(10018) = 1
THEN
OPEN :x FOR SELECT some_columns FROM some_tables;
ELSE
OPEN :x FOR SELECT some_columns FROM other_tables;
END IF;
--DBMS_SQL.RETURN_RESULT(:x); --12c and above
END;
/
PRINT x -- 11g

Moving from Oracle web toolkit to Apex 4.2

Suppose I have a procedure like this
CREATE OR REPLACE PROCEDURE my_proc IS
var_empno emp.empno%type;
var_ename emp.ename%type;
var_bonus emp.bonus%type;
var_budget number;
var_budget := 100000;
CURSOR EMP_CURSOR IS
select empno, ename, bonus from emp order by empno;
BEGIN
htp.print('EMPLOYEE NUMBER EMPLOYEE NAME BONUS');
open EMP_CURSOR;
LOOP
fetch EMP_CURSOR into var_empno, var_ename, var_bonus;
//------Give employee a extra $1,000 bonus if budget allows-----------.
EXIT when EMP_CURSOR%NOTFOUND;
IF (var_budget >= 1000) then
var_budget := var_budget - 1000;
var_bonus := var_bonus + 1000;
END IF;
//----DBMS_OUTPUT.put_line(var_empno || ' ' || var_ename || ' ' || var_bonus);
htp.print(var_empno || ' ' || var_ename || ' ' || var_bonus);
END LOOP;
close EMP_CURSOR;
END;
The procedure selects empno, name, and bonus from the emp table ordered by lowest empno. The cursor loops through and adds $1,000 to the bonus variable if var_budget has the funds for it. The report uses htp.print.
Of course this is just a simple example and my report html would be formatted better. We have over 100 procedures like this. What is the best way to handle logic like this in APEX where a cursor is used and the output is unique for each row. APEX seems to be good with simple select statements. But I am unclear how to convert procedures like this for APEX 4.2
Adapting Oracle REF CURSOR Data for APEX Report Regions
The following solution was developed on a demo hosted instance of Oracle Apex at apex.oracle.com; At the time this was done, the hosted Apex release version was: 4.2.5
Depending on which version of Oracle you are looking at, the implementation of REF CURSOR logic has changed in small ways. I found a good discussion here from Oracle-Base. The notable changes include:
Creation of a SYS_REFCURSOR type, which eliminates the need for developers to declare their own custom types independently to have a REF CURSOR type to reference in their code.
Programming languages/platforms such as Microsoft ADO or Java have syntax and structures that are equipped to open and iterate over the records stored in a REF CURSOR. This is probably why there are many shops like yours with procedural code in REF CURSOR data structures; they're simple and compatible with other programming languages.
APEX report region definitions work best with a data input defined by a SQL SELECT query.
Adapting Existing REF CURSOR Based PL/SQL Code from Oracle Web Toolkit
Here is the sample procedure from the OP with comments on initial changes to adapt it for use with an APEX Report Output region.
CREATE OR REPLACE PROCEDURE my_proc (bonus_increase IN number,
result_data OUT sys_refcursor) IS
-- (1) Replace Procedure Declaration to include output REF CURSOR
-- CREATE OR REPLACE PROCEDURE my_proc IS
-- (2) Remove variable references/placeholders used by procedure
-- for data output display reasons.
-- var_empno emp.empno%type;
-- var_ename emp.ename%type;
-- var_bonus emp.bonus%type;
-- var_budget number;
-- var_budget := 100000;
-- (3) A suggested practice to put a tolerance level within a
-- constant variable.
--
-- Removing literals from the SQL code segments improves
-- performance, because it allows the PL/SQL interpreter to
-- reuse the execution plans for multiple consecutive runs of
-- the cursor query.
c_var_budget_limit constant number:= 100000;
l_var_budget number;
CURSOR EMP_CURSOR IS
select empno, ename, bonus from emp order by empno;
BEGIN
-- (4) This task is reserved for APEX to handle in a REPORT REGION
-- definition.
-- htp.print('EMPLOYEE NUMBER EMPLOYEE NAME BONUS');
-- (5) I rewrote the cursor using the IMPLICIT cursor method.
l_var_budget:= c_var_budget_limit;
for result_data in EMP_CURSOR
LOOP
IF (l_var_budget >= bonus_increase) then
l_var_budget := l_var_budget - bonus_increase;
result_data.bonus:= result_data.bonus + bonus_increase;
END IF;
END LOOP;
-- (6) The web toolkit output is no longer necessary.
//----DBMS_OUTPUT.put_line(var_empno || ' ' || var_ename || ' '
-- || var_bonus);
-- htp.print(var_empno || ' ' || var_ename || ' ' || var_bonus);
END;
The commented sections show how much of the burden for display and output and formatting conventions have been removed. I found a good reference on how to use REF CURSORS as encapsulated queries of business logic in an article: Using Ref Cursors Reference posted in a reference on "oracle-base.com".
The cleaned up procedure, modeled after this reference looks like:
CREATE OR REPLACE PROCEDURE my_proc_data (result_data OUT sys_refcursor)
IS
BEGIN
OPEN result_data FOR
SELECT empno, ename, 0 as bonus
FROM emp
ORDER BY empno ASC;
END;
The calling procedure that opens and loops through the cursor contents looks similar to the OP procedure, in the form of an anonymous PL/SQL block:
DECLARE
l_cursor SYS_REFCURSOR;
l_empno emp.empno%TYPE;
l_ename emp.ename%TYPE;
l_bonus number;
c_var_budget_limit constant number:= 100000;
c_bonus_increase constant number:= 1000;
l_var_budget number;
BEGIN
my_proc_data (result_data => l_cursor);
l_var_budget := c_var_budget_limit;
LOOP
FETCH l_cursor
INTO l_empno, l_ename, l_bonus;
IF (l_var_budget >= c_bonus_increase) then
l_var_budget := l_var_budget - c_bonus_increase;
l_bonus:= l_bonus + c_bonus_increase;
DBMS_OUTPUT.PUT_LINE(to_char(l_empno) || ' | ' || l_ename ||
' | ' || to_char(l_bonus));
END IF;
EXIT WHEN l_cursor%NOTFOUND;
END LOOP;
CLOSE l_cursor;
END;
The result output:
7369 | SMITH | 1000
7499 | ALLEN | 1000
7521 | WARD | 1000
7566 | JONES | 1000
7654 | MARTIN | 1000
7698 | BLAKE | 1000
7782 | CLARK | 1000
7788 | SCOTT | 1000
7839 | KING | 1000
7844 | TURNER | 1000
7876 | ADAMS | 1000
7900 | JAMES | 1000
7902 | FORD | 1000
7934 | MILLER | 1000
7934 | MILLER | 2000
Statement processed.
This isn't the ending solution, remember, the data needs to be fed to APEX in the form of a SELECT statement.
Using Oracle PL/SQL Collections to Deliver REF CURSOR Data Through Apex Report Regions
Some additional changes to further prepare the original sample procedure for use with an Apex page region report:
Added two new SQL object types: EMPLOYEE_RECORD_TYPE and EMP_OUTPUT_TABLE_TYPE.
Changed to PL/SQL FUNCTION object type instead of PROCEDURE object type.
Changed output data type to NESTED TABLE collection type. (This allows us to QUERY the output data stored in this collection using direct SQL).
The separated code for the REF CURSOR and the report-output query were combined.
Converted the Anonymous PL/SQL block used in testing the first part into a FUNCTION object.
Changed (reduced) the MAX BUDGET value from OP so that we can see the looping logic at work (i.e., running out of budget money to award bonuses to employees)
Here's the revised code:
SQL Collection and Object Type Definitions (DDL)
CREATE OR REPLACE TYPE employee_record_type AS object (
empno number,
ename varchar2(10),
bonus number
);
CREATE OR REPLACE TYPE emp_output_table_type IS TABLE OF
employee_record_type;
New PL/SQL Function Definition (Containing REF CURSOR)
create or replace FUNCTION my_bonuses RETURN
emp_output_table_type IS
-- table collection type declared and initialized here:
l_output emp_output_table_type:= emp_output_table_type();
l_row_index pls_integer:= 0;
c_var_budget_limit constant number:= 100000;
c_bonus_increase constant number:= 1000;
l_var_budget number;
cursor l_cursor is
select empno, ename, 0 as bonus
from emp
order by empno ASC;
BEGIN
l_var_budget := c_var_budget_limit;
FOR i in l_cursor
LOOP
l_row_index := l_row_index + 1;
l_output.extend;
l_output(l_row_index):= employee_record_type(i.empno,
i.ename, i.bonus);
IF (l_var_budget >= c_bonus_increase) then
l_var_budget := l_var_budget - c_bonus_increase;
l_output(l_row_index).bonus:= l_output(l_row_index).bonus
+ c_bonus_increase;
END IF;
END LOOP;
RETURN l_output;
END;
Function MY_BONUSES When Queried from SQL Client
This is the SQL that will be referenced in the Apex page report definition as in the "Region Source" section of the Region Definition configuration page.
Comparison: Source Table Data vs. REF CURSOR query results
Some Closing Comments and Discussion
This example leaves room for lots of optimizations given the leaps in the product versions of the RDBMS between Oracle 9i, up to and including 11g and 12c. Some side-thoughts for consideration:
Use of bulk-binding operations and methods when loading data from ref-cursors to Oracle collection types.
Additional modularization. You can see the components of the OP procedure my_proc moved around a little during the development of this solution. Your conversion effort may benefit from better organization using packages and separation of processes (where it makes sense).
Read up on the documentation on Oracle PL/SQL collections. You will learn a few important distinctions:
a. Some collection types can be queried directly with SQL.
b. Composite data types defined in PL/SQL vs at the schema level each have their own limitations...
In general, choose wisely, or just stick with this example as it should get you most of the way towards converting an existing library of PL/SQL code with REF CURSOR driven parameters/outputs.
Onward!

COUNT in PLSQL ORACLE

I have asked this question before but I did not get any help.
I want to get the count of rows in two different table given an attribute.
This is my code .
Instead of fetching the total count where the condition holds, I am getting the whole count of the table
create or replace PROCEDURE p1( suburb IN varchar2 )
as
person_count NUMBER;
property_count NUMBER;
BEGIN
SELECT count(*) INTO person_count
FROM person p WHERE p.suburb = suburb ;
SELECT count(*) INTO property_count
FROM property pp WHERE pp.suburb = suburb ;
dbms_output.put_line('Number of People :'|| person_count);
dbms_output.put_line('Number of property :'|| property_count);
END;
/
Is there any other way to do this so that i can retrieve the real total count of people in that SUBURB
Some datas from PERSON TABLE
PEID FIRSTNAME LASTNAME
---------- -------------------- --------------------
STREET SUBURB POST TELEPHONE
---------------------------------------- -------------------- ---- ------------
30 Robert Williams
1/326 Coogee Bay Rd. Coogee 2034 9665-0211
32 Lily Roy
66 Alison Rd. Randwick 2031 9398-0605
34 Jack Hilfgott
17 Flood St. Bondi 2026 9387-0573
SOME DATA from PROPERTY TABLE
PNO STREET SUBURB POST
---------- ---------------------------------------- -------------------- ----
FIRST_LIS TYPE PEID
--------- -------------------- ----------
48 66 Alison Rd. Randwick 2031
12-MAR-11 Commercial 8
49 1420 Arden St. Clovelly 2031
27-JUN-10 Commercial 82
50 340 Beach St. Clovelly 2031
05-MAY-11 Commercial 38
Sorry for the way the table is looking .
This is the value I get when I run the above script.
SQL> exec p1('Randwick')
Number of People :50
Number of property :33
I changed the PROCEDURE ,this is what I get .
SQL> create or replace PROCEDURE p1( location varchar2 )
IS
person_count NUMBER;
property_count NUMBER;
BEGIN
SELECT count(p.peid) INTO person_count
FROM person p WHERE p.suburb = location ;
SELECT count(pp.pno) INTO property_count
FROM property pp WHERE pp.suburb = location ;
dbms_output.put_line('Number of People :'|| person_count);
dbms_output.put_line('Number of property :'|| property_count);
END;
/
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Procedure created.
SQL> exec p1('KINGSFORD')
Number of People :0
Number of property :0
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> exec p1('Randwick')
Number of People :0
Number of property :0
PL/SQL procedure successfully completed.
SQL>
The solution suppose to be this
SQL> exec p1('randwick');
Number of People: 7
Number of Property: 2
You named the variable the same as the field. In the query, suburb is first sought in the scope of the query, and it matches the field suburb even though it doesn't use the pp table alias.
So you're actually comparing the field with itself, therefore getting all records (where suburb is NOT NULL, that is). The procedure parameter isn't used in the query at all.
The solution: change the name of the procedure parameter.
To prevent errors like this, I always use P_ as a prefix for procedure/function parameters and V_ as a prefix for local variables. This way, they never mingle with field names.
Although I agree that the cause of the problem is a namespace issue between SQL and PL/SQL, in that the SQL engine has "captured" the name of the PL/SQL variable, I don't believe that changing the name of the parameter is the best approach. If you do this then you doom every developer to start prefixing every parameter name with "p_" or some other useless appendage, and to make sure that they never create a column with a P_ prefix.
If you look through the PL/SQL Supplied Packages documentation you see very few, if any, cases where Oracle themselves do this, although they have in the past done irritatingly inconsistent things like refer to table_name as "tabname".
A more robust approach is to prefix the variable name with the pl/sql procedure name when referencing it in SQL statements:
SELECT count(*)
INTO person_count
FROM person p WHERE p.suburb = p1.suburb ;
In your case you clearly wouldn't name your procedure "P1" so in fact you'd have something like:
SELECT count(*)
INTO person_count
FROM person p WHERE p.suburb = count_suburb_objects.suburb ;
Your code is now immune to variable name capture -- as a bonus your text editor might highlight all the instances where you've used a variable name in a SQL statement when you double-click on the procedure name.
First, create indices for case-insensitive search:
CREATE INDEX idx_person_suburb_u ON person(upper(suburb))
/
CREATE INDEX idx_property_suburb_u ON property(upper(suburb))
/
Second, use prefixes for procedure parameters and local variables:
CREATE OR REPLACE PROCEDURE p1(p_location VARCHAR2)
IS
v_person_count NUMBER;
v_property_count NUMBER;
v_location VARCHAR2(32767);
BEGIN
IF p_location IS NOT NULL THEN
v_location := upper(p_location);
SELECT count(*) INTO v_person_count
FROM person WHERE upper(suburb) = v_location ;
SELECT count(*) INTO v_property_count
FROM property WHERE upper(suburb) = v_location ;
ELSE
SELECT count(*) INTO v_person_count
FROM person WHERE upper(suburb) IS NULL;
SELECT count(*) INTO v_property_count
FROM property WHERE upper(suburb) IS NULL;
END IF;
dbms_output.put_line('Number of People :' || v_person_count);
dbms_output.put_line('Number of Property :' || v_property_count);
END;
/

ORA-00932: inconsistent datatypes: expected - got -

I have been using Oracle(10g.2) as a PHP programmer for almost 3 years, but when I gave an assignment, I have tried to use the ref cursors and collection types for the first time. And I
've searched the web, when I faced with problems, and this ora-00932 error really overwhelmed me. I need help from an old hand.
Here is what I've been tackling with,
I want to select rows from a table and put them in a ref cursor, and then with using record type, gather them within an associative array. And again from this associative array, make a ref cursor. Don't ask me why, I am writing such a complicated code, because I need it for more complex assignment. I might be sound confusing to you, thus let me show you my codes.
I have 2 types defined under the types tab in Toad. One of them is an object type:
CREATE OR REPLACE
TYPE R_TYPE AS OBJECT(sqn number,firstname VARCHAR2(30), lastname VARCHAR2(30));
Other one is collection type which is using the object type created above:
CREATE OR REPLACE
TYPE tr_type AS TABLE OF r_type;
Then I create a package:
CREATE OR REPLACE PACKAGE MYPACK_PKG IS
TYPE MY_REF_CURSOR IS REF CURSOR;
PROCEDURE MY_PROC(r_cursor OUT MY_REF_CURSOR);
END MYPACK_PKG;
Package Body:
CREATE OR REPLACE PACKAGE BODY MYPACK_PKG AS
PROCEDURE MY_PROC(r_cursor OUT MY_REF_CURSOR) AS
rcur MYPACK_PKG.MY_REF_CURSOR;
sql_stmt VARCHAR2(1000);
l_rarray tr_type := tr_type();
l_rec r_type;
BEGIN
sql_stmt := 'SELECT 1,e.first_name,e.last_name FROM hr.employees e ';
OPEN rcur FOR sql_stmt;
LOOP
fetch rcur into l_rec;
exit when rcur%notfound;
l_rarray := tr_type( l_rec );
END LOOP;
CLOSE rcur;
--OPEN r_cursor FOR SELECT * FROM TABLE(cast(l_rarray as tr_type) );
END MY_PROC;
END MYPACK_PKG;
I commented out the last line where I open ref cursor. Because it's causing another error when I run the procedure in Toad's SQL Editor, and it is the second question that I will ask.
And lastly I run the code in Toad:
variable r refcursor
declare
r_out MYPACK_PKG.MY_REF_CURSOR;
begin
MYPACK_PKG.MY_PROC(r_out);
:r := r_out;
end;
print :r
There I get the ora-00932 error.
The way you are using the REF CURSOR is uncommon. This would be the standard way of using them:
SQL> CREATE OR REPLACE PACKAGE BODY MYPACK_PKG AS
2 PROCEDURE MY_PROC(r_cursor OUT MY_REF_CURSOR) AS
3 BEGIN
4 OPEN r_cursor FOR SELECT e.empno,e.ENAME,null FROM scott.emp e;
5 END MY_PROC;
6 END MYPACK_PKG;
7 /
Corps de package crÚÚ.
SQL> VARIABLE r REFCURSOR
SQL> BEGIN
2 MYPACK_PKG.MY_PROC(:r);
3 END;
4 /
ProcÚdure PL/SQL terminÚe avec succÞs.
SQL> PRINT :r
EMPNO ENAME N
---------- ---------- -
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
[...]
14 ligne(s) sÚlectionnÚe(s).
I'm not sure what you are trying to accomplish here, you're fetching the ref cursor inside the procedure and then returning another ref cursor that will have the same data. I don't think it's necessary to fetch the cursor at all in the procedure. Let the calling app do the fetching (here the fetching is done by the print).
Update: why are you getting the unhelpful error message?
You're using a cursor opened dynamically and I think that's part of the reason you are getting the unhelpful error message. If we use fixed SQL the error message is different:
SQL> CREATE OR REPLACE PACKAGE BODY MYPACK_PKG AS
2 PROCEDURE MY_PROC(r_cursor OUT MY_REF_CURSOR) AS
3 TYPE type_rec IS RECORD (qn number,
4 firstname VARCHAR2(30),
5 lastname VARCHAR2(30));
6 lt_record type_rec; /* Record type */
7 lt_object r_type; /* SQL Object type */
8 BEGIN
9 OPEN r_cursor FOR SELECT e.empno,e.ENAME,null FROM scott.emp e;
10 FETCH r_cursor INTO lt_record; /* This will work */
11 FETCH r_cursor INTO lt_object; /* This won't work in 10.2 */
12 END MY_PROC;
13 END MYPACK_PKG;
14 /
Package body created
SQL> VARIABLE r REFCURSOR
SQL> BEGIN
2 MYPACK_PKG.MY_PROC(:r);
3 END;
4 /
BEGIN
*
ERREUR Ó la ligne 1 :
ORA-06504: PL/SQL: Return types of Result Set variables or query do not match
ORA-06512: at "APPS.MYPACK_PKG", line 11
ORA-06512: at line 2
I outlined that currently in 10.2 you can fetch a cursor into a PLSQL record but not in a SQL Object.
Update: regarding the PLS-00306: wrong number or types of arguments
l_rarray is a NESTED TABLE, it needs to be initialized and then extended to be able to store elements. For example:
SQL> CREATE OR REPLACE PACKAGE BODY MYPACK_PKG AS
2 PROCEDURE MY_PROC(r_cursor OUT MY_REF_CURSOR) AS
3 lr_array tr_type := tr_type(); /* SQL Array */
4 BEGIN
5 FOR cc IN (SELECT e.empno, e.ENAME, NULL lastname
6 FROM scott.emp e) LOOP
7 lr_array.extend;
8 lr_array(lr_array.count) := r_type(cc.empno,
9 cc.ename,
10 cc.lastname);
11 /* Here you can do additional procedural work on lr_array */
12 END LOOP;
13 /* then return the result set */
14 OPEN r_cursor FOR SELECT * FROM TABLE (lr_array);
15 END MY_PROC;
16 END MYPACK_PKG;
17 /
Corps de package crÚÚ.
SQL> print r
SQN FIRSTNAME LASTNAME
---------- ------------------------------ -----------
7369 SMITH
7499 ALLEN
7521 WARD
[...]
14 ligne(s) sÚlectionnÚe(s).
For further reading you can browse the documentation for PL/SQL collections and records.

Resources