Return ref_cursor from for loop of cursor object - plsql

I am trying to get a ref_cursor to be assigned to a variable inside a for loop then returned at the end of a function. The loop in going through a local cursor if it gets more than 1 result.
I have noted where the error occurs in the code. I am not sure how to create a loop where i can get a ref_cursor for the current point in the loop, assign it to a variable and then return it to the function. Could someone someone help me figure out how to do that? Below is my i-th attempt at logic based of reading around the google searches.
The error is "PLS-00382: expression is of wrong type" and i know that i obviously am not assigning the correct variably type based on this error but the code below with the error is an illustration of what I want to do and what I need help accomplishing.
FUNCTION GET_PARCEL(p_lat in number, p_long in number) return sys_refcursor
IS
v_distance number(10) := 100000000;
v_shortest_dist number(10) := v_distance;
v_centroid SDO_GEOMETRY;
v_rc_ref_cursor sys_refcursor;
v_ref_geom SDO_GEOMETRY := mdsys.SDO_GEOMETRY(2001, 8311, NULL, SDO_ELEM_INFO_ARRAY(1, 1, 1), SDO_ORDINATE_ARRAY(120.3214, -10.7088));
cursor query_cursor is select * from PARCEL_TABLE where code = 20134;
BEGIN
for query_row in query_cursor loop
v_centroid := SDO_GEOM.SDO_CENTROID(query_row.geometry, 0.05);
IF (v_centroid is not null) then
v_distance := SDO_GEOM.SDO_DISTANCE(v_centroid, v_ref_geom, 0.05);
IF v_distance < v_shortest_dist THEN
v_shortest_dist := v_distance;
v_rc_ref_cursor := query_row; -- Error on this line
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE('Centroid is not initialised for some reason.');
END IF;
end loop;
return v_rc_ref_cursor;
END;

As far as I know, you cannot build up a cursor. Cursors are created and maintained by the database with connections to their source data, transaction and session context, and the like.
I would suggest you declare a type, instantiate it, build up the values in the type.
When you are done, create a cursor by selecting * from table (cast (variable as your_type)).
Munge the cursor into a ref_cursor and return that.

Pro tip is to remember you have data in your table and you can use it for logic. Turns out if I use the IDENTIFIER or ROWID of the row/s which i want then i can do a where clause into a ref cursor that looks up by IDENTIFIER or ROWID.
eg.
open v_rc_ref_cursor for select * from PARCEL_TABLE n where n.identifier = query_row.identifier;
super simple stuff :)

Related

PL/SQL if then else statements not running

I have written following code in oracle pl/sql
create or replace procedure sorting_criteria(criteria in varchar)
as
begin
if(criteria='lowest price')
then
declare
p_name product.p_name%type;
cursor ptr is select p_name from product order by amount ASC;
begin
open ptr;
loop
fetch ptr into p_name;
exit when ptr%notfound;
dbms_output.put_line(p_name);
end loop;
close ptr;
end;
else if(criteria='highest price')
then
declare
p_name product.p_name%type;
cursor ptr is select p_name from product order by amount DESC;
begin
open ptr;
loop
fetch ptr into p_name;
exit when ptr%notfound;
dbms_output.put_line(p_name);
end loop;
close ptr;
end;
else
dbms_output.put_line('Enter valid criteria!');
end if;
end;
/
But it is giving following error: Error at line 35: PLS-00103: Encountered the symbol ";" when expecting one of the following: Please help
The ELSE-IF statement in PL/SQL has to be written as ELSIF. Otherwise, you should close the second IF with an other END IF; statement.
You can solve the issue by changing the ELSE IF at line 17 to an ELSIF
The answer by #GregorioPalamà correctly addresses your issues. But you can drastically reduce the workload by changing your thinking away from "If...then...else" to the "set of" and letting SQL do the work. In this case the only difference is sorting either ascending or descending on amount. The same effect can be achieved by sorting ascending on amount or minus amount; and SQL can make that decision. So you can reduce the procedure to validating the parameter and a single cursor for loop:
create or replace procedure sorting_criteria(criteria in varchar2)
as
cursor ptr(c_sort_criteria varchar2) is
select p_name
from product
order by case when c_sort_criteria = 'lowest price'
then amount
else -amount
end ;
begin
if criteria in ('lowest price', 'highest price')
then
for rec in ptr(criteria)
loop
dbms_output.put_line('Product: ' || rec.p_name );
end loop;
else
dbms_output.put_line('Enter valid criteria!');
end if;
end sorting_criteria;
/
See demo here. For demonstration purposed I added the amount to the dbms_output.
A couple notes:
While it is not incorrect using p_... as a column name, it is also
not a good idea. A very common convention (perhaps almost a
standard) to use p_... to indicate parameters. This easily leads to
confusion; confusion amongst developers is a bad thing.
IMHO it is a bug to name a local variable the same as a table
column name. While the compiler has scoping rules which one to use
it again leads to confusion. The statement "where table.name = name"
is always true, except when at least one of them is null, which possible could lead to updating/deleting every row in your table. In this
case p_name is both a column and a local variable.

expression is of wrong type for function

CREATE OR REPLACE FUNCTION k_w_b_salary(k IN NUMBER, b IN BOOLEAN)
RETURN EMP.ENAME%TYPE IS
names name_table;
BEGIN
IF (b = true) THEN
SELECT ENAME BULK COLLECT INTO names
FROM
(SELECT *
FROM EMP
ORDER BY SAL ASC)
WHERE ROWNUM <= k;
RETURN names;
ELSIF (b = false) THEN
SELECT ENAME BULK COLLECT INTO names
FROM
(SELECT *
FROM EMP
ORDER BY SAL DESC)
WHERE ROWNUM <= k;
RETURN names;
END IF;
END;
And I get this error:
12/9 PL/SQL: Statement ignored
12/16 PLS-00382: expression is of wrong type
20/9 PL/SQL: Statement ignored
20/16 PLS-00382: expression is of wrong type
I have this function that tries to find the best/worst paid employees. But i get the above error.
I think it's something to do with the ROWNUM but I am not sure.
I think the lines the error points out are not the lines with the error.
I had this function writen differently and the lines in the error where pointing to the ROWNUM <= k lines.
I have tried putting a fixed number there (<= 3) for example and I got the same error.
I have no idea what else to try, i can't really understand why this is not working.
It's not obvious to me why this is not working. I think it should work fine but obviously it dousen't.
The code for the table i use is :
CREATE OR REPLACE TYPE name_table IS TABLE OF VARCHAR2(10);
Any help is appreciated!
In the function declaration, you said
RETURN EMP.ENAME%TYPE
I assume the data type of column ENAME in table EMP is some sort of string (VARCHAR2(40) or similar) - right?
In the declarations section, you declare a variable names of data type name_table. You didn't show us the definition of the name_table type (that must be given outside the function, not in it); we can probably assume it is a nested table of some sort. Right? [EDIT - I take that back; you did show us your definition of name_table, at the end of your question.]
In the end, your function returns names. Which is of type name_table. But you said the function returns something else: EMP.ENAME%TYPE. In particular, you said the function returns a scalar data type, but you are returning a collection.
This will not work even if the collection has a single element. A table with a single "record" is not the same data type as the "record" itself - even if an actual table has a single "record" in it.
(And, much more so, when the table has three records in it!)
Rather: It seems that you want a table function: one that returns a table of things. If so, then declare the function that way. Perhaps you want the function to
RETURN NAME_TABLE
(at the top, in the function declaration)

PL/SQL Query for column and use it in the function call

I am trying this but sure I am missing a lot
declare
my_id table.ISR_ID%type;
begin
select NVL(MAX(table.ISR_ID)+1,1) into isr_id
from table;
select my_pkg.getFunction(InputToFunction=> isr_id); -- from ?
end;
If you declared MY_ID variable, you should have selected into it, not into ISR_ID (which is never declared).
Also, you should return function's result into something (probably another variable?). I've declared it as FUN_RES - see the comment within the PL/SQL anonymous block.
Saying that you are missing a lot doesn't help much; you should specify which errors you get. Anyway: try such a code, say whether it works or not and - if not - say why not (possible errors, etc.).
declare
my_id table.ISR_ID%type;
fun_res number; --> function result should be returned into this variable.
-- I don't know what it returns, so I set it to be a NUMBER.
-- Change it, if necessary.
begin
select NVL(MAX(table.ISR_ID) + 1, 1)
into my_id
from table;
fun_res := my_pkg.getFunction(my_id);
end;
[EDIT]
If you have to select function's value for every ISR_ID in a table, then you don't need PL/SQL but
select isr_id,
my_pkg.getfunction(isr_id) fun_res
from table;
If you want PL/SQL, then do it in a loop, for example:
begin
for cur_r in (select isr_id from table) loop
dbms_output.put_line(cur_r.isr_id ||', result = ' || my_pkg.getfunction(cur_r.isr_id));
end loop;
end;
/

PL/SQL variable scope in nested blocks

I need to run some SQL blocks to test them, is there an online app where I can insert the code and see what outcome it triggers?
Thanks a lot!
More specific question below:
<<block1>>
DECLARE
var NUMBER;
BEGIN
var := 3;
DBMS_OUTPUT.PUT_LINE(var);
<<block2>>
DECLARE
var NUMBER;
BEGIN
var := 200;
DBMS_OUTPUT.PUT_LINE(block1.var);
END block2;
DBMS_OUTPUT.PUT_LINE(var);
END block1;
Is the output:
3
3
200
or is it:
3
3
3
I read that the variable's value is the value received in the most recent block so is the second answer the good one? I'd love to test these online somewhere if there is a possibility.
Also, is <<block2>> really the correct way to name a block??
Later edit:
I tried this with SQL Fiddle, but I get a "Please build schema" error message:
Thank you very much, Dave! Any idea why this happens?
create table log_table
( message varchar2(200)
)
<<block1>>
DECLARE
var NUMBER;
BEGIN
var := 3;
insert into log_table(message) values (var)
select * from log_table
<<block2>>
DECLARE
var NUMBER;
BEGIN
var := 200;
insert into log_table(message) values (block1.var || ' 2nd')
select * from log_table
END block2;
insert into log_table(message) values (var || ' 3rd')
select * from log_table
END block1;
In answer to your three questions.
You can use SQL Fiddle with Oracle 11g R2: http://www.sqlfiddle.com/#!4. However, this does not allow you to use dbms_output. You will have to insert into / select from tables to see the results of your PL/SQL scripts.
The answer is 3 3 3. Once the inner block is END-ed the variables no longer exist/have scope. You cannot access them any further.
The block naming is correct, however, you aren't required to name blocks, they can be completely anonymous.
EDIT:
So after playing with SQL Fiddle a bit, it seems like it doesn't actually support named blocks (although I have an actual Oracle database to confirm what I said earlier).
You can, however, basically demonstrate the way variable scope works using stored procedures and inner procedures (which are incidentally two very important PL/SQL features).
Before I get to that, I noticed three issues with you code:
You need to terminate the insert statements with a semi-colon.
You need to commit the the transactions after the third insert.
In PL/SQL you can't simply do a select statement and get a result, you need to select into some variable. This would be a simple change, but because we can't use dbms_output to view the variable it doesn't help us. Instead do the inserts, then commit and afterwards select from the table.
In the left hand pane of SQL Fiddle set the query terminator to '//' then paste in the below and 'build schema':
create table log_table
( message varchar2(200)
)
//
create or replace procedure proc1 as
var NUMBER;
procedure proc2 as
var number;
begin
var := 200;
insert into log_table(message) values (proc1.var || ' 2nd');
end;
begin
var := 3;
insert into log_table(message) values (var || ' 1st');
proc2;
insert into log_table(message) values (var || ' 3rd');
commit;
end;
//
begin
proc1;
end;
//
Then in the right hand panel run this SQL:
select * from log_table
You can see that proc2.var has no scope outside of proc2. Furthermore, if you were to explicitly try to utilize proc2.var outside of proc2 you would raise an exception because it is out-of-scope.

PLS-00487 Error-Invalid reference to Variable 'CHAR'

I'm designing a function that is part of a larger package. The function is intended to take a District Code and return a collection of unique IDs for 10-15 stores that are assigned to that district. The function is intended to return a collection that can be queried like a table, i.e., using the TABLE function in a SQL statement.
I've created the following Types:
Schema Level type:
create or replace TYPE HDT_CORE_ORGIDS AS TABLE OF CHAR(20);
and a Type inside the Package
TYPE CORE_ORGIDS IS TABLE OF CHAR(20) INDEX BY BINARY_INTEGER;
Here's the function code:
FUNCTION FindDistrictOrgs(
ParamOrgCode VARCHAR2
)
RETURN HDT_CORE_ORGIDS
AS
ReturnOrgs HDT_CORE_ORGIDS := HDT_CORE_ORGIDS();
FDOTemp HDT_CORE_MAIN.CORE_ORGIDS;
i BINARY_INTEGER := 0;
CURSOR FDOCurr IS
SELECT org.id AS OrgID
FROM tp2.tpt_company org
WHERE LEVEL = 2
START WITH org.name = ParamOrgCode
CONNECT BY PRIOR org.id = org.parent_id;
BEGIN
OPEN FDOCurr;
LOOP
i := i +1;
FETCH FDOCurr INTO FDOTemp(i);
EXIT WHEN FDOCurr%NOTFOUND;
END LOOP;
IF FDOTemp.EXISTS(FDOTemp.FIRST) THEN
ReturnOrgs.EXTEND(FDOTemp.LAST);
FOR x IN FDOTemp.FIRST .. FDOTemp.LAST LOOP
ReturnOrgs(x) := FDOTemp(x).OrgID;
END LOOP;
END IF;
CLOSE FDOCurr;
RETURN ReturnOrgs;
END FindDistrictOrgs ;
I'm getting the PLS-00487:Invalid Reference to variable 'CHAR' at the line:
ReturnOrgs(x) := FDOTemp(x).OrgID;
I've double-checked at the value returned by the SQL (the org.id AS OrgID) is of the CHAR(20 BYTE) datatype.
So...what's causing the error?
Any help is appreciated! :)
OrgID is the alias you gave the column in your cursor, it has no meaning to the collection. Since both collections are of simple types you should just be doing:
ReturnOrgs(x) := FDOTemp(x);
The syntax you're using is implying FDOTemp is a collection of objects and you're trying to reference the OrgID attribute of an object; but since CHAR isn't an object type, this errors. The error message even makes some sense when viewed like that, though it's not terribly helpful if you don't already know what's wrong... and not entirely helpful when you do.
Incidentally, you could use a bulk collect to populate the collection without the cursor or loops, or the extra collection:
SELECT org.id
BULK COLLECT INTO ReturnOrgs
FROM tp2.tpt_company org
WHERE LEVEL = 2
START WITH org.name = ParamOrgCode
CONNECT BY PRIOR org.id = org.parent_id;
RETURN ReturnOrgs;

Resources