It is probably a lame error but I do not find it.
In a procedure I have this :
PROCEDURE myProcedure(returnCode OUT NUMBER) IS
CURSOR myCursor IS
SELECT column1, column2, column3, column4 FROM MyTable WHERE columX IS NULL AND columnY = 'PS';
TYPE myType IS RECORD (
name1 MyTable.Column1%TYPE,
name2 MyTable.Column2%TYPE,
name3 MyTable.Column3%TYPE,
name4 MyTable.Column4%TYPE
);
myVar myType;
myVar2 typeA
BEGIN
FOR myVar IN myCursor
LOOP
myVar2 := myVar.name2;
END LOOP;
END;
ERROR :
PLS-00302 component name2 must be declared
What is wrong?
Ty
The myVar cursor loop variable is not related to your myVar record-type variable; it's scope effectively overrides the type definition. You'd get the same error if you removed that type.
From the documentation for cursor for loop:
The cursor FOR LOOP statement implicitly declares its loop index as a record variable of the row type that a specified cursor returns.
and from a related section:
The cursor FOR LOOP statement implicitly declares its loop index as a %ROWTYPE record variable of the type that its cursor returns. This record is local to the loop and exists only during loop execution.
With this syntax, myVar is implicitly the same rowtype as the cursor itself, and has a column2 field rather than a name2 field; so this works:
BEGIN
FOR myVar IN myCursor
LOOP
myVar2 := myVar.column2;
END LOOP;
END;
and the both the myVar variable declaration and myType type declaration are redundant.
To use your record type instead you would need more explicit cursor handling:
BEGIN
OPEN myCursor;
LOOP
FETCH myCursor INTO myVar;
EXIT WHEN myCursor%NOTFOUND;
myVar2 := myVar.name2;
END LOOP;
CLOSE myCursor;
END;
Now myVar is still your myType record type variable - nothing is overriding that - so it has the field names you specified.
You could also explicitly define myVar as myCursor%rowType to avoid needing your own record type, but then it's just a longer way of writing the first loop, and you'd need to go back to referencing myVar.column2.
Related
Something happens that I dont understand.
here is a type declared:
type my_type is record (
amount number(18,2),
my_date date
)
my_variable my_type
...
for my_variable in (select sum(table_amount), table_date from table group by table_date)
loop
dbms_output.put_line(my_variable.my_date);
dbms_output.put_line(my_variable.amount)
end loop;
my_variable.my_date works good but my_variable.amount triggers a PLS00302 error that I dont understand.
table structure:
table_date date,
table_amount number(10,2)
The loop declares a local variable my_variable that shadows the previous declaration. You need to reference the column names and not the record's attributes:
DECLARE
type my_type is record (
amount number(18,2),
my_date date
);
my_variable my_type;
BEGIN
for my_variable in (select table_amount, table_date from table_name)
loop
dbms_output.put_line(my_variable.table_date);
dbms_output.put_line(my_variable.table_amount);
end loop;
END;
/
Which is the same as:
DECLARE
type my_type is record (
amount number(18,2),
my_date date
);
my_variable my_type;
BEGIN
for not_my_variable in (select table_amount, table_date from table_name)
loop
dbms_output.put_line(not_my_variable.table_date);
dbms_output.put_line(not_my_variable.table_amount);
end loop;
END;
/
(But is slightly clearer as you do not have the cursor loop variable shadowing the declared variable.)
db<>fiddle here
You seem to be confused about explicit record types and implicit cursor for-loop record types. The my_type and my_variable you are declaring are never used. The my_variable in the cursor loop is a completely independent construct, that you just happen to have given the same name to. From the documentation that is the:
Name for the loop index that the cursor FOR LOOP statement implicitly declares as a %ROWTYPE record variable of the type that cursor or select_statement returns.
record is local to the cursor FOR LOOP statement. Statements inside the loop can reference record and its fields. They can reference virtual columns only by aliases. Statements outside the loop cannot reference record. After the cursor FOR LOOP statement runs, record is undefined.
So in a construct like:
for my_variable in (select sum(table_amount), table_date from table group by table_date)
the my_variable has the row type of the cursor query projection, and fields corresponding to the column expressions or their aliases.
You need to alias the sum(), and then refer to that alias, something like:
for my_variable in (
select sum(table_amount) as amount, table_date
from my_table
group by table_date
)
loop
dbms_output.put_line(my_variable.table_date);
dbms_output.put_line(my_variable.amount);
end loop;
db<>fiddle with some names changed to be more consistent with what you are describing. Note that the record type and variable declaration are not needed.
Really need help regarding Ref Cursor. I have a Stored Procedure GET_PERSONROLES that have parameter type ref cursor. I just wanted to pupulate this ref cursor manually like inserting a row to the refcursor.
Can I insert a row into a refcursor though a loop?
Thank you in advance.
The procedure depends on this publicly declared type:
create or replace package types
as
type cursorTypePersonRole is ref cursor;
end;
Here is my pseudo-codeL
create or replace PROCEDURE GET_PERSONROLES
(
P_CURSOR IN OUT types.cursorTypePersonRole
) AS
REFCUR SYS_REFCURSOR;
TYPE REFTABLETYPE IS RECORD (
IS_MANAGER_LEVEL1 VARCHAR2(1),
IS_MANAGER_LEVEL2 VARCHAR2(1)
);
TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
PERSONROLES_TABLETYPE TABLETYPE;
BEGIN
--calls another stored proc to populate REFCUR with data without problem
MY_STOREDPROC('12345', REFCUR);
LOOP
FETCH REFCUR BULK COLLECT INTO PERSONROLES_TABLETYPE;
EXIT WHEN PERSONROLES_TABLETYPE.COUNT = 0;
FOR indx IN 1 .. PERSONROLES_TABLETYPE.COUNT
LOOP
-- I'm able to query perfectly the values of IS_MANAGER_LEVEL1 and IS_MANAGER_LEVEL 2
-- I'm aware that the below codes are wrong
-- However this means I wanted to insert these values to a row of the cursor if possible
-- Do some logic to know what data will be assigned in the row.
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL1 = 'Y' then
P_CURSOR := <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>
end if;
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL2 = 'Y' then
P_CURSOR := <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>
end if;
END LOOP;
END LOOP;
CLOSE REFCUR;
END GET_PERSONROLES;
A ref cursor is not a variable: it is a pointer to a result set which is consumed by the act of reading it. The result set itself is immutable.
Immutability makes sense, because it reflects Oracle's emphasis on read consistency.
The simplest way to produce the output you appear to want is to create a SQL Type
open P_CURSOR for
select IS_MANAGER_LEVEL1,
IS_MANAGER_LEVEL2
from table ( PERSONROLES_TABLETYPE );
This will work in 12c; in earlier versions to use the table() call like this you may need to declare REFTABLETYPE and TABLETYPE as SQL types( rather than in PL/SQL).
"Ok edited it now"
Alas your requirements are still not clear. You haven't given us the structure of the output ref cursor or shown what other processing you want to undertake.
However, given the title of your question, let's have a guess. So:
create or replace PROCEDURE GET_PERSONROLES ( P_CURSOR IN OUT types.cursorTypePersonRole) AS
REFCUR SYS_REFCURSOR;
TYPE REFTABLETYPE IS RECORD (IS_MANAGER_LEVEL1 VARCHAR2(1),
IS_MANAGER_LEVEL2 VARCHAR2(1));
TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
PERSONROLES_TABLETYPE TABLETYPE;
personrole_rec PersonRole%rowtype;
type personrole_nt is table of PersonRole%rowtype;
personroles_recs personrole_nt := new personrole_nt() ;
BEGIN
MY_STOREDPROC('12345', REFCUR);
FETCH REFCUR BULK COLLECT INTO PERSONROLES_TABLETYPE;
FOR indx IN 1 .. PERSONROLES_TABLETYPE.COUNT LOOP
/* in the absence of requirements I'm just making some stuff up */
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL1 = 'Y' then
personrole_rec.whatever1 := 'something';
else
personrole_recc.whatever1 := null;
end if;
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL2 = 'Y' then
personrole_rec.whatever2 := 'something else';
else
personrole_recc.whatever2 := null;
end if;
if personrole_rec.whatever1 is not null
or personrole_rec.whatever2 is mot null then
personroles_recs.exend();
personroles_recs(personroles_recs.count()) := personroles_rec;
end if;
END LOOP;
CLOSE REFCUR;
open p_cursor for
select * from table ( personroles_recs );
END GET_PERSONROLES;
This code uses a second collection to store the desired output. Like your code it reads the populated collection and evaluates the attributes of each row. If a value which means the criteria it sets an attribute in a rowtype variable. If one or both attributes are set it populates a new row in a second collection. At the end of the procedure it opens the ref cursor using a table() function call on the second collection.
Note that you do not need the nested loop: you're not using the LIMIT clause so your coder reads the entire cursor into the collection in one swoop.
The implemented rules may not be exactly what you want (because you haven't explained exactly what you want) but this should give you the general idea.
Note that, depending on exactly what processing is masked by <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>, the simpler approach could still be feasible:
open P_CURSOR for
select case when IS_MANAGER_LEVEL1 = 'Y' then 'YES' else 'NO' end,
case when IS_MANAGER_LEVEL2 = 'Y' then 'YES' else 'NO' end
from table ( PERSONROLES_TABLETYPE );
I'm having trouble with dynamic sql, Issue is (I think) reading and setting global variable. Here's what I have and any help at all is greatly appreciated. Please let me know if you need table data too although I have included the data in comments.
CREATE OR REPLACE PACKAGE data_load
IS
curr_rec NUMBER;
curr_rule VARCHAR2(200);
curr_sql VARCHAR2(4000);
curr_sql_two VARCHAR2(4000);
curr_data_element VARCHAR2 (200);
curr_rule_text VARCHAR2(200);
curr_error_code VARCHAR2(10);
curr_error_flag VARCHAR2(10);
curr_flag_val NUMBER;
v_check NUMBER;
v_ID NUMBER;
cur_hdl INT ;
rows_processed NUMBER;
PROCEDURE check_rules;
END data_load;
The package body:
create or replace PACKAGE BODY data_load IS
PROCEDURE check_rules IS
CURSOR c1
IS
SELECT * FROM STAGING_TABLE where rownum < 3;
CURSOR c2
IS
SELECT * FROM ERROR_CODES WHERE rule_text IS NOT NULL AND status =1;
BEGIN
FOR rec1 IN c1
LOOP
FOR rec2 IN c2
LOOP
curr_data_element := 'rec1.'||rec2.data_element; --- this results in value "rec1.SHIP_FROM_ACCOUNT_ORG_CODE" without quotes
curr_rule_text := rec2.rule_text; --- this value is "is not null" without quotes
curr_error_flag := rec2.error_flag; --this value is "FLAG_03" without quotes
curr_flag_val := to_number(rec2.error_code); --- this value is 31
curr_sql :='begin if :curr_data_element '||curr_rule_text||' then update table_with_column_FLAG_03 set '||curr_error_flag ||' = 0; else update table_with_column_FLAG_03 set '||curr_error_flag ||' = '||curr_flag_val||'; end if; end;';
dbms_output.put_line(curr_sql); -- results in "begin if :curr_data_element is null then update table_with_column_FLAG_03 set FLAG_03 = 0; else update table_with_column_FLAG_03 set FLAG_03 = 31; end if; end;"
EXECUTE IMMEDIATE curr_sql USING curr_data_element ; -- this always updates the column with 31 even when curr_data_element/ rec1.SHIP_FROM_ACCOUNT_ORG_CODE is null and that's the problem
COMMIT;
END LOOP;
curr_rec := curr_rec+1;
END LOOP;
dbms_output.put_line(curr_rec);
END check_rules;
END data_load;
You've already highlighted the problem really:
curr_data_element := 'rec1.'||rec2.data_element; --- this results in value "rec1.SHIP_FROM_ACCOUNT_ORG_CODE" without quotes
You can't refer to cursor columns dynamically. You are creating a string with value 'rec1.SHIP_FROM_ACCOUNT_ORG_CODE'; there is no mechanism to evaluate what that represents. You can't, for instance, try to dynamically select that from dual because the rec1 is not in scope for a SQL call, even dynamically.
When you bind that string value it is never going to be null. You are using that string, not the value in the outer cursor that it represents, and essentially you cannot do that.
The simplest way to deal with this, if you have a reasonably small number of columns in your staging table that might appear as the rec2.data_element value, is to use a case expression to assign the appropriate actual rec1 column value to the curr_data_element variable, based on the rec2.data_element value:
...
BEGIN
FOR rec1 IN c1
LOOP
FOR rec2 IN c2
LOOP
curr_data_element :=
case rec2.data_element
when 'SHIP_FROM_ACCOUNT_ORG_CODE' then rec1.SHIP_FROM_ACCOUNT_ORG_CODE
when 'ANOTHER_COLUMN' then rec1.ANOTHER_COLUMN
-- when ... -- repeat for all possible columns
end;
curr_rule_text := rec2.rule_text;
...
If you have a lot of columns you could potentially do that via a collection but it may not be worth the extra effort.
The curr_sql string stays the same, all that's changing is that you're binding the actual value from the relevant rec1 column, rather than never-null string you were forming.
I have a requirement where i have a table column containing an expression that should get appended as a where clause to a select stmt in a procedure.
The expression has reference to variables available in the procedure.
Declare
Query varchar2(4000);
Expression varchar2(4000);
Value varchar2(10):= 'abc';
Valuex varchar2 := 'def';
... n number of variables declared
Cursor c1 is select expression from tableabc where some condition;
Begin
Expression := output of c1 cursor
Query := ‘select 1 from dual where 1 = 1 ';
Query := query || expression;
-- now i will use a ref cursor to evaluate query
End;
Table abctable has a expression column thta stores :
And 'abc'=value
The value being referred in this expression is the one declaree in pl sql block.
How can i achieve this as this is not working.
I want the value in expression column to be replaced with the one available in pl sql block. I dont want to use replace function as expression column can refer valuex variable or any of the n variable declared in pl sql block. Can the expression be stored in the column in a way that once retrieved in pl sql block it gets replaced with the variable value.
Thanks
Declare
Query varchar2(4000);
Value varchar2(10):= 'abc';
c1 sys_refcursor;
Begin
select 'select 1 from dual where 1 = 1 ' || expression
into query
from tableabc where some condition;
-- now i will use a ref cursor to evaluate query
-- suppose the expression looks like "and col1 = :v and col2 = :v"
open rc for query using value;
loop
-- fetch
-- exit when rc%notfound;
-- process
end loop;
close rc;
End;
I am using SQL*Plus. When I am using the below query, it is giving error
Error report:
ORA-06550: line 4, column 1:
PLS-00428: an INTO clause is expected in this SELECT statement
Query
declare
id varchar2(80) :='test123';
begin
select test_quote,test_id from order_link where id = 'test123';
end;
Not sure why you're using a PL/SQL block for that. You aren't using the id you declare, and it would be better to give it a name different to the column name to avoid confusion.
You can declare a bind variable in SQL*Plus though, and select into that:
var l_test_quote varchar2(80); -- or whatever type/size you need
var l_test_id varchar2(80);
declare
l_id varchar2(80) :='test123';
begin
select test_quote, test_id
into :l_test_quote, :l_test_id
from order_link
where id = l_id;
end;
/
print l_test_quote
print l_test_id
Note the : before the references to the variables defined outside the block, indicating they are bind variables. l_id is declared inside the block so it does not have a preceding :.
In this case you could also define l_id outside the block, and avoid PL/SQL while still using a bind variable for that:
var l_id varchar2(80);
exec :l_id := 'test123';
select test_quote, test_id
from order_link
where id = :l_id;
Because the main query isn't PL/SQL any more (although the exec is; that's just a shorthand for a one-line anonymous block), you don't need to select ... into so you don't need to declare those variables.
try this:
declare
id varchar2(80) :='test123';
v_test_quote order_link.test_quote%type;
v_test_id order_link.test_id%type;
begin
select test_quote,test_id
into v_test_qoute, v_test_id
from order_link
where id = 'test123';
end;