How to fetch values from cursor into variables? - oracle

I have problems fetching values from cursor into variables:
create or replace
procedure projectinfo(num clubs.clubid%type)
as
--identify variables
p_cln clubs.clubname%type;
p_projn projects.projectname%type;
p_projnum number;
p_taskn tasks.taskname%type;
p_tasknum number;
cursor cur is
select c.clubname, p.projectname, t.taskname
from clubs c join projects p on c.clubid=p.clubid
join tasks t on t.projectid=p.projectid
where c.clubid=num;
--I have checked the above cursor and it's worked fine!!!
begin
p_projnum:=0;
p_tasknum:=0;
open cur;
loop
fetch cur into p_cln,p_projn, p_taskn;
dbms_output.put_line(p_cln|| ' ' ||p_projn|| ' ' || p_taskn);
-- the above command does not print variable values!!!
exit when cur%notfound;
p_projnum:=p_projnum+1;
dbms_output.put_line(' ************************ ');
dbms_output.put_line(' club name : ' ||p_cln);
dbms_output.put_line( ' project ' ||p_projnum|| ' ' || p_projn);
loop
p_tasknum:=p_tasknum+1;
dbms_output.put_line('Task: ' ||p_tasknum|| ' ' || p_taskn);
fetch cur into p_cln,p_projn, p_taskn;
exit when cur%notfound;
end loop;
end loop;
close cur;
end projectinfo;
I have checked my cursor and itdoes contain all values that I need. My programm compiles FINE but does not print any output!!!

What tool are you using to run your procedure? By default, most tools will not allocate a buffer for dbms_output to write to and will not display anything written to dbms_output. That's why you'd never depend on dbms_output for any real code.
If you are using SQL*Plus, you need to enable serveroutput before executing your procedure
SQL> set serveroutput on;
SQL> exec projectinfo( <<some number>> );
If you are using a GUI, the GUI will almost certainly have a way to enable dbms_output. But that will be very different for different applications. In SQL Developer, for example, you'd need to ensure that the DBMS Output window is visible, click the green "plus sign" icon, and choose your connection to enable dbms_output.

Related

How to call a stored procedure in pl/sql developer

I am new at this and have a simple question.
I have created a procedure like so in pl/sql developer
CREATE OR REPLACE PROCEDURE myproc2 AS
BEGIN
SELECT cd_desc des, cd_value cd FROM v_codes WHERE cd_type='CVS02'
END;
Now I want to call the procedure and see the output however when I run this
BEGIN
myproc2;
END;
in Pl/sql I am getting an error saying object myproc2 is invalid
How do I call a stored procedure in PL/SQL?
You're calling it right, but the procedure is wrong. If you check its status, it is invalid.
In PL/SQL, a SELECT requires INTO:
CREATE OR REPLACE PROCEDURE myproc2 AS
l_cd_desc v_codes.cd_desc%type;
l_cd_value v_codes.cd_value%type;
BEGIN
SELECT v.cd_desc, v.cd_value
INTO l_cd_desc, l_cd_value
FROM v_codes v
WHERE v.cd_type = 'CVS02';
END;
Beware of possible NO_DATA_FOUND or TOO_MANY_ROWS exception.
Also, although it'll now run OK (I guess), you won't see anything because it is unknown what you'll do next. You could, for example, choose to display values you fetched. In that case, add
<snip>
WHERE v.cd_type = 'CVS02';
dbms_output.put_line(l_cd_desc ||', '|| l_cd_value);
END;
Don't forget to enable serveroutput.
As you commented, you got too_many_rows. How to handle it? It depends on what you want to do. One option is to switch to a cursor FOR loop; now you don't need local variables and - as there's no SELECT statement itself - no INTO clause either:
CREATE OR REPLACE PROCEDURE myproc2
AS
BEGIN
FOR cur_r IN (SELECT v.cd_desc, v.cd_value
FROM v_codes v
WHERE v.cd_type = 'CVS02')
LOOP
DBMS_OUTPUT.put_line (cur_r.cd_desc || ', ' || cur_r.cd_value);
END LOOP;
END;
One great thing about Oracle SQL Developer is the GUI and that it does things for you.
You can open a sheet and run it the traditional way:
BEGIN
PROCEDURENAME(PARAM);
END;
or you can use the GUI, find the object with the (View->) Find DB object, find it, click on it and use the green arrow in the toolbar. It will open a UI for any parameters you used within the procedure.
In SQL Developer, if you want to see the output then you can return a cursor:
CREATE OR REPLACE PROCEDURE myproc2(
o_cursor OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN o_cursor FOR
SELECT cd_desc AS des,
cd_value AS cd
FROM v_codes
WHERE cd_type='CVS02'; -- You need a ; statement terminator here.
END;
/
Then you can use:
-- Declare a cursor bind variable
VARIABLE cur SYS_REFCURSOR;
BEGIN
-- Call the cursor outputting into the bind variable.
myproc2(:cur);
END;
/
-- Print the cursor
PRINT :cur;
And run it as a script (using F5).

ORA-06511: PL/SQL: cursor already open. I am closing my cursor but no luck

I am getting the ORA-06511: PL/SQL: cursor already open ERROR.
Not sure why I am getting this error since I am closing my cursor.
Please see code below.
BEGIN
OPEN findem; ---OPENING HERE!
FOR crfindem IN findem LOOP
FETCH findem into other1, other2, other3;
l_CollectionOfRows(Counter).tmps_key := other1;
l_CollectionOfRows(Counter).tmps_cfb_rate := other2;
l_CollectionOfRows(Counter).tmps_engagement_pay_rate := other3;
Counter := Counter + 1;
END LOOP;
CLOSE findem;---CLOSING HERE!
FORALL i IN l_CollectionOfRows.FIRST .. l_CollectionOfRows.LAST
UPDATE Base.Table
SET MARGIN = :PAGE56_MARGIN,
PERCENT = :PAGE56_MARGIN + l_CollectionOfRows(i).rate,
PAY_RATE = (l_CollectionOfRows(i).pay_rate * (:PAGE56_MARGIN + l_CollectionOfRows(i).rate)) + l_CollectionOfRows(i).pay_rate
WHERE tmps_key = l_CollectionOfRows(i).tmps_key;
END;
I read from some online threads that for every Insert/Update statement, Oracle will create an implicit cursor. If this is the case how do you treat those implicit cursors that Oracle creates?
You are getting that error because you are opening the same cursor twice: the FOR construct already does all these things for you:
FOR opens the cursor
FOR implicitly declares a record variable (the one named crfindem, in your code) that will receive the values for each row read from the cursor
FOR loops on every row and assigns the values of the current row to the crfindem variable
FOR automatically closes the cursor at the end of the loop
so you don't need any OPEN/CLOSE/FETCH .. INTO commands if you are using a FOR loop:
see this simple example: itjust works.
declare
cursor cur is select * from user_tab_comments;
begin
for c in cur loop
DBMS_OUTPUT.PUT_LINE( c.table_name || ' - ' || c.comments);
end loop;
end;
but if i try to open the cursor before using the for loop, I will get your same error because the cursor is already open and the for construct is trying to open it again:
declare
cursor cur is select * from user_tab_comments;
begin
open cur; -- this is not needed and will cause problems
for c in cur loop --! ERROR: here I am trying to open AGAIN the same cursor
DBMS_OUTPUT.PUT_LINE( c.table_name || ' - ' || c.comments);
end loop;
end;
so, you either must choose if you want to write this code:
declare
cursor cur is select * from user_tab_comments;
begin
for c in cur loop
DBMS_OUTPUT.PUT_LINE( c.table_name || ' - ' || c.comments);
end loop;
end;
or avoid using the FOR construct and do all the open/fetch/close operations by yourself, by writing this:
declare
-- I cant' use "select *" here:
-- if I use "fetch into" to a precise list of variables,
-- I have to extract exactly the fields I want to assign:
cursor cur is
select table_name,comments
from user_tab_comments;
tabname varchar(100);
tabcomment varchar2(4000);
begin
open cur;
loop
fetch cur into tabname,tabcomment;
exit when cur%notfound;
DBMS_OUTPUT.PUT_LINE( tabname || ' - ' || tabcomment);
end loop;
close cur;
end;
Your error is that your code is trying to do both these things at the same time.
you should have written this:
-- OPEN findem; NO NEED TO OPEN THE CURSOR (when using FOR)
FOR crfindem IN findem LOOP
--- FETCH findem into other1, other2, other3; FOR ALREADY DOES THIS: the values are in crfindem
l_CollectionOfRows(Counter).tmps_key := crfindem.name_of_the_first_field;
l_CollectionOfRows(Counter).tmps_cfb_rate := crfindem.name_of_the_second_field;
l_CollectionOfRows(Counter).tmps_engagement_pay_rate := crfindem.name_of_the_third_field;
Counter := Counter + 1;
END LOOP;
--- CLOSE findem; NO NEED TO CLOSE THE CURSOR (when using FOR)
Now let me add some considerations about your code (and about this example):
I don't see where you initialize your Counter variable: you MUST initialize it to 0 before entering the loop, because otherwise its initial value will be NULL and will stay null for the whole operation because (NULL + 1) evaluates again to NULL.
I don't see how your cursor is declared, so I don't know the names of the fields it extracts. in the code above I used the "fake" names name_of_the_first_field, name_of_the_second_field, name_of_the_third_field... but you must use the correct field names returned by your query
if your cursor returns some calculated value (like "select 1+2, sysdate, null from dual") you must assign a name to the calculated column to make it accessible by giving an alias to each calculated column you extract ("select 1+2 AS first_name, sysdate AS second_name, null as third_name from dual")
Edit... another info: you don't really need to declare a variable for each field even when you are explicitly using open/fetch/close: you can declare a RECORD variable (that will contain all column values with the same column names, exactly like the for loop does) by using the %ROWTYPE syntax. my example becomes like this, using %rowtype:
declare
cursor cur is select * from user_tab_comments;
-- here I am declaring a variable named c that is a RECORD variable:
-- it can contain a whole row returned by cursor cur
c cur%rowtype;
begin
open cur;
loop
fetch cur into c;
exit when cur%notfound;
DBMS_OUTPUT.PUT_LINE( c.table_name || ' - ' || c.comments);
end loop;
close cur;
end;

My long time SQL*Plus loop doesn't print DBMS_OUTPUT.PUT_LINE output during execution

I know that in order to print something on sqlplus like below:
begin
dbms_output.put_line('Hello!');
end;
/
I need to call
set serveroutput on;
before that.
I also know that is not needed, but I can also call
DBMS_OUTPUT.enable;
before, just in case. This is working for me.
But what if I want to keep printing the progress of a long loop? It seems impossible to me. I've tried everything to print some progress on the loop below but just doesn't work. Is there some way of doing that? I even tried to spool to a file and didn't work.
Note 1: I can't truncate or partition this table as the DBA doesn't want to help me with that, so I have to use this nasty loop...
Note 2: I've noticed that once the loop is done, the whole output is printed. Looks like oracle is buffering the output and printing everything at the end. I'm not sure how to avoid that and print on every loop iteration.
set serveroutput on;
declare
e number;
i number;
nCount number;
f number;
begin
DBMS_OUTPUT.enable;
dbms_output.put_line('Hello!');
select count(*) into e from my_big_table where upd_dt < to_date(sysdate-64);
f :=trunc(e/10000)+1;
for i in 1..f
loop
delete from my_big_table where upd_dt < to_date(sysdate-64) and rownum<=10000;
commit;
DBMS_OUTPUT.PUT_LINE('Progress: ' || to_char(i) || ' out of ' || to_char(f));
end loop;
end;
Thank you for any answer.
There are 2 standard ways for such things:
set module and action in your session DBMS_APPLICATION_INFO.SET_MODULE:
SQL> exec DBMS_APPLICATION_INFO.SET_MODULE('my_long_process', '1 from 100');
PL/SQL procedure successfully completed.
SQL> select action from v$session where module='my_long_process';
ACTION
----------------------------------------------------------------
1 from 100
set session_longops:
DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS
I'd recommend it in your case since that is exactly designed for long operations.
Example on Oracle-Base.
----
PS: dbms_output,put_line saves all output in a collection (nested table) variable of dbms_output package, so you can't get it from another session and client can't get it during user call (execution). In addition to set serveroutput on you can also get the output using dbms_output.get_lines: http://orasql.org/2017/12/10/sqlplus-tips-8-dbms_output-without-serveroutput-on/
Btw, in case if you need to filter or analyze output from dbms_output, sometimes it's convenient to get output in a query, so you can use filter strings in where clause or aggregate them: https://gist.github.com/xtender/aa12b537d3884f4ba82eb37db1c93c25
DBMS_OUTPUT will only ever be displayed after the PL/SQL code has terminated and control has returned to the calling program.
Output is, as you found, buffered. When your PL/SQL code finishes, then the calling program (e.g. SQL*Plus) can go and fetch that output.
Insert into another table, maybe call it "MYOUTPUT".
Create the table:
create table myoutput (lineno number, outline varchar2(80));
Add this after your delete:
insert into MYOUTPUT values (i,'Progress: ' || to_char(i) || ' out of ' || to_char(f));
Then select from MYOUTPUT periodically to see progress.
select outline from myoutput order by lineno;
Bobby
You can use UTL_FILE to write output to an external file, as in:
DECLARE
fh UTL_FILE.FILE_TYPE;
nRow_count NUMBER := 0;
BEGIN
fh := UTL_FILE.FOPEN('DIRECTORY_NAME', 'some_file.txt', 'w');
FOR aRow IN (SELECT *
FROM SOME_TABLE)
LOOP
nRow_count := nRow_count + 1;
IF nRow_count MOD 1000 = 0 THEN
UTL_FILE.PUT_LINE(fh, 'Processing row ' || nRow_count);
UTL_FILE.FFLUSH(fh);
END IF;
-- Do something useful with the data in aRow
END LOOP; -- aRow
UTL_FILE.FCLOSE_ALL; -- Close all open file handles, including
-- the ones I've forgotten about...
END;

How can I compile PL/SQL source saved in table?

How do I compile PL/SQL source code that I currently have saved in an Oracle table? (I'm making a copy of the source code from the USER_SOURCE view, then I'm deleting those objects and want to restore from my saved copy.)
I'm sure there is an easy way, but I'm just not entering the right search terms.
Try this:
declare
text varchar2(4000);
begin
select code into text from bkp_table;
execute immediate 'create or replace ' || text;
end;
/
Ok this works if all lines of code are stored in single line. If you want to execute code that is stored in multiple lines you should go for something like:
declare
text varchar2(32767);
begin
select listagg(text, ' ') within group (order by line) into text from all_source where name = 'MYPROC';
execute immediate 'create or replace ' || text;
end;
/
Problem starts when 32767 characters is too few. In such case this may be a solution:
declare
text clob;
begin
for x in (select text from all_source where name = 'LONGTEST') loop
text := text || x.text;
end loop;
execute immediate 'create or replace ' || text;
end;
/
Please also have a look on that why it is a bit odd thing.
EDIT
As suggested changed to dbms_lob and in that case clob needs to be initialised:
declare
text clob := ' ';
begin
for x in (select text from all_source where name = 'LONGTEST') loop
dbms_lob.append(text, x.text);
end loop;
execute immediate 'create or replace ' || text;
end;
/

anonymous block completed in oracle SQL

Whilst trying to create a query cursor as follows:
DECLARE CURSOR Query1
IS
SELECT * FROM RACE
WHERE Race_Time='22-SEP-14 12.00.00.000000000';
BEGIN
OPEN Query1;
END;
I get the following error. anonymous block completed. Does anyone know how to fix this? I tried setting the
'SET SERVEROUTPUT ON;'
before the declare but this did not seem to fix the error. Thanks in advance!
It seems that dbms_output is turned off
you can see you out put if you put SET SERVEROUTPUT ON; in the beginning of your script.
or you can view dbms_output window (View then DBMS Output) then press the "+" at the top of the Dbms Output window and then select an open database
"anonymous block completed" means your PL/SQL code was successfully executed.
To Display: try using a output statement...
For example:
BEGIN
dbms_output.put_line ('Hello, world!');
END;
If you want to control the process in PL/SQL, you could do something like
DECLARE
l_race_rec race%rowtype;
CURSOR Query1
IS
SELECT *
FROM RACE
WHERE Race_Time='22-SEP-14 12.00.00.000000000';
BEGIN
OPEN Query1;
LOOP
FETCH query1 INTO l_race_rec;
EXIT WHEN query1%notfound;
dbms_output.put_line( l_race_rec.column1 || ' ' || l_race_rec.column2 || ... || l_race_rec.columnN );
END LOOP;
CLOSE Query1;
END;
Unless your assignment requires the use of explicit cursors, though, implicit cursors are likely easier to use
BEGIN
FOR x IN( SELECT *
FROM RACE
WHERE Race_Time='22-SEP-14 12.00.00.000000000')
LOOP
dbms_output.put_line( x.column1 || ' ' || x.column2 || ... || x.columnN );
END LOOP;
END;
If you are using SQL*Plus, you can also do something like
VAR rc REFCURSOR;
BEGIN
OPEN :rc
FOR SELECT *
FROM race
WHERE race_time = '22-SEP-14 12.00.00.000000000';
END;
PRINT rc
If race_time is really a timestamp, you should really be comparing a timestamp with another timestamp rather than comparing a timestamp to a string. Use explicit conversion with an explicit format mask to avoid errors due to different sessions having different NLS settings
WHERE race_time = to_timestamp( '22-SEP-14 12.00.00.000000000',
'DD-MON-RR HH24:MI:SS.FF9' )
Of course, I'm not sure why you would use a timestamp in the first place here-- it seems unlikely that you really know the nanosecond at which a race started.

Resources