Using for loop in shell script over OracleDB sqlplus - bash

I am using this script:
#!/bin/sh
for i in $(seq 1 20);
do
echo "CREATE TABLE eugene_$i (id NUMBER NOT NULL);
! sleep 10
DECLARE j NUMBER;
BEGIN
FOR j IN 1..20 LOOP
select * from eugene_$i;
! sleep 10
END LOOP;
END;
DROP TABLE eugene_$i;" | sqlplus system/password &
done
wait
My intent is to keep the connection alive, and run repetitive queries for testing.
The goal is wait 10 seconds after each select and call the next one.
I am not getting selects at all. I assume the inner for loop has some syntax problem.

When you issue a select statement (either as an implicit or explicit cursor) in PL/SQL, you need to fetch the values into a variable. You can return one row or bulk collect many rows into one or more variables (e.g. one scalar variable per column returned, or a record per row).
The PL/SQL inside your script should therefore look something like:
DECLARE
-- no need to declare the j variable; that's implicit to the `FOR ... loop`
id number;
BEGIN
FOR j IN 1..20 LOOP
select *
into id
from eugene_$i;
! sleep 10 -- I'm not sure this will work in your script; it's not Oracle syntax, but may get translated by your script?
END LOOP;
END;
/

Related

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;

Need help to create purging operation executing in loop

I am going to run one pl/sql block which deletes all the data which is older than 30 days in a table.
Condition is like this:
It will delete 200k data at a time and after deleting 200k data I have to give wait period for 10-15 sec. There are around 100 million records in that table. The whole deletion process I want to do it in automation script.
DECLARE
CURSOR c6
IS
SELECT /*+parallel(a,32)*/
a.rowid,a.*
FROM EB_O.CCO_DIR_CONTRS_ES_GG a
WHERE a.GG_CREATE_DATE < SYSDATE-30
AND ROWNUM <=200001;
TYPE contact_point_id_tab IS TABLE OF c6%ROWTYPE
INDEX BY PLS_INTEGER;
l_contact_point_id_tab contact_point_id_tab;
BEGIN
OPEN c6;
LOOP
FETCH c6
BULK COLLECT INTO l_contact_point_id_tab
LIMIT 10000;
EXIT WHEN l_contact_point_id_tab.COUNT = 0;
IF l_contact_point_id_tab.COUNT > 0
THEN
FORALL i
IN l_contact_point_id_tab.FIRST .. l_contact_point_id_tab.LAST
DELETE FROM EB_O.CCO_DIR_CONTRS_ES_GG
WHERE rowid =l_contact_point_id_tab (i).rowid;
COMMIT;
END IF;
l_contact_point_id_tab.delete;
END LOOP;
END;
This is the above plsql block I have written. How to do this operation in multiple loop and after every loop there will be wait period of 10-15 sec and again the deletion operation will happen for next 200k data. the loop will continue until all the data will be deleted.
N.B.: for wait period, grant is no there for DBMS_LOCK.sleep
I would not do this by looping over a cursor. Instead, I would do something like:
begin
loop
delete FROM EB_O.CCO_DIR_CONTRS_ES_GG
WHERE GG_CREATE_DATE < SYSDATE-30
AND ROWNUM <=200000;
exit when sql%rowcount = 0;
commit;
dbms_lock.sleep(10);
end loop;
end;
/
This way, you avoid the potential snapshot-too-old error (because you have committed across a cursor fetch loop), and you're no longer fetching information into memory to pass back to the database, which makes things more efficient.
If you don't have access to sleep function, just create your own.
Store the output of below query in a variable, create a loop which exits when sysdate is equal to the value stored in your variable.
select sysdate+(10 / (24*60*60)) from dual;
Also, instead of hardcoding 10 you can take the seconds as input variable and pass the same in the query.

Looping in cursors in oracle pl/sql

Basically, a cursor is an area of memory which is used to store the result of a particular query. One question I have is do cursors implicitly loop through all the records? Suppose I write a code snippet like the following:
declare
cursor cur_dum is
select name,class,enroll_id from table_student;
begin
fetch cur_dum into the_name, the_class, the_enroll_id;
update table_log set statement = the_name || '-'||'-'||to_char(the_enroll_id)
where roll_id = the_enroll_id;
close cur_dum;
end;
Will this code snippet,without any explicit statement of loop, automatically loop through all the records in table_student and perform the corresponding update in table_log ?Do I need to add a loop after the fetch statement ? What difference would it make if I use a Bulk collect statement during fetching ?
From the answer , I got it that explicitly stating a loop is necessary .
I came across a snippet of code which used a loop for a cursor and also used a for loop inside it .The following is the code snippet :
Cursor CurSquirell IS
select Name,a_val,b_val,col_ID from table_temp;
BEGIN
LoopCounter := 0;
commit;
LOOP
FETCH CurSquirell BULK COLLECT INTO my_name,my_a_val,my_b_val,my_col_id LIMIT 1000;
LoopCounter := LoopCounter + 1;
FOR intIndex IN 1 .. my_col_id.COUNT LOOP
counter := counter +1;
BEGIN
select t.tender_val,t.tender_pay, t.page_no, t.loc
into my_tender_val,my_tender_pay,my_page_no , my_loc
from bussiness_trans bt, tender_details t
where t.account_no = bt.account_no
and bt.external_id=my_col_id(intIndex)
and trim(replace(t.tender_pay,'0',' ')) = trim(replace(a_val(intIndex),'0',' '))
and bt.id_type=1;
BEGIN
select pp.lock_id into my__lock_id
from pay_roll pp
where pp.pay_points= my_tender_pay
and bt.id_type=5;
BEGIN
update tab_cross_exchange tce
set tce.cross_b_val = my_b_val(intIndex)
where tce.lock_id = my_lock_id;
..............................sql statements...
...sql statements...
end;
end;
end;
When in the code loop has already been used to go through the records one by one , why has the for loop been used ? In what situations would you require a for loop like this inside a cursor loop ? Does the bulk collect has to do anything to force the usage of For loop ?
"a cursor is an area of memory which is used to store the result of a particular query"
Not quite. A cursor is a pointer to an area of memory used to store information about a query. Results of the query are stored in other areas of memory.
The PL/SQL syntax you use specifies a variable which defines a query. To execute the query you need to
Open the cursor
Fetch the data into target variable(s)
When finished, close the cursor
Each fetch returns one row. To exhaust the query you need to execute the fetch in a loop. This is the verbose way of doing so:
declare
cursor cur_dum is
select name,class,enroll_id from table_student;
rec_dum cur_dum%rowtype;
begin
open cur_dum;
loop
fetch cur_dum into rec_dum;
exit when cur_dum%notfound;
update table_log
set statement = rec_dum.name || '-'||'-'||to_char(rec_dum.enroll_id)
where roll_id = rec_dum.enroll_id;
end loop;
close cur_dum;
end;
Note: one benefit of this explicit cursor notation is that we can define a variable typed to the projection of the cursor's query (rec_dum above).
This is the same logic using implicit cursor notation:
declare
cursor cur_dum is
rec_dum cur_dum%rowtype;
begin
for rec_dum in (select name,class,enroll_id from table_student)
loop
update table_log
set statement = rec_dum.name || '-'||'-'||to_char(rec_dum.enroll_id)
where roll_id = rec_dum.enroll_id;
end loop;
end;
" Does the bulk collect has to do anything to force the usage of For loop ?"
BULK COLLECT is the the syntax which allows us to populate a nested table variable with a set of records and so do bulk processing rather than the row-by-row processing of the basic FETCH illustrated above; the snippet you quote grabs a sub-set of 1000 records at a time, which is necessary when dealing with large amounts of data because variables populate private (session) memory rather than global (shared memory). The code you quoted is very poor, not least because the FETCH ... BULK COLLECT INTO statement is not followed by a test for whether the FETCH returned any values. Because there's no test the subsequent code will fail at runtime.
"Does the usage of for loop inside the cursor loop make the code poor ? "
No, not automatically. For instance when doing bulk processing we may often do something like this:
<< batch_loop >>
loop
fetch dum_cur bulk collect into dum_recs limit 1000;
exit when dum_recs.count() = 0;
<< row_loop >>
for idx in dum_recs.first()..dum_recs.last()
loop
do_something(dum_recs(idx));
end loop row_loop;
end loop batch_loop;
However, we should be suspicious of nested CURSOR FOR loops. Nested loops are common in 3GL programs like Java:
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 10; j++) {
So developers familiar with that style of coding often reach for nested loops when moving to PL/SQL. But SQL is a set-based paradigm. There are usually better ways of implementing that logic, such as a JOIN: make the two cursors into one.

Displaying the output of update query into shell script

I am trying to display the result of an update statement "5 rows inserted" on my shell script.
One way i could figure out is
Declare count number;
Begin
Update tablename set colname="k";
count=sql%rowcount;
Dbms.out.put_line(count);
Commit;
End
The sql plus count variable is getting the value but i am unable to use it in my shell script.
I have to copy the value of sql plus variable into a shell script variable .but dont know how to do it.
Please help , also let me know if there is any other way to do it .
Thanks in advance
Have SQL*Plus exit with with the value you want. Here's an example (run this as a UNIX script):
sqlplus my_username/my_password#my_database << EOF
variable count number;
begin
update mtl_system_items -- your update goes here!
set last_update_date = last_update_date
where rownum <= 5;
:count := SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(:count);
ROLLBACK;
END;
/
EXIT :COUNT;
EOF
echo Return code was $?
That is, when you EXIT :COUNT from SQL*Plus, the value of your COUNT variable will be accessible in $? in your UNIX script. You'll need to save it off immediately into another variable, since $? will get overwritten if you run any other commands in your script.
I suggest writing a variable assignment in a spool file and then reading that back in your shell script. For example:
sqlplus -s apps/apps#vis <<EOF
set feedback off lines 150 pages 0 head off termout off serveroutput on size 10000 echo off
variable count number;
begin
-- Emulation of update that updates 40000 rows
:count := 40000;
end;
/
spool outvars.sh
begin
dbms_output.put_line('COUNT=' || :count);
end;
/
spool off
exit
EOF
. outvars.sh
echo "Back in shell, COUNT=$COUNT"
will result in this output:
COUNT=40000
Back in shell, COUNT=40000
Although slightly more indirect it has the advantage of being able to pass multiple values with a pretty arbitrary content, not just a single number limited to a small integer range.

Cursor occasionally lose data when open in run time

This is my cursor with SELECT statement which always return right result. But when i use it in a procedure, it sometimes get wrong result (return only one row).
This problem appears with a large frequency (~5-10 times per 1000 procedure call). Maybe this is known issues with oracle 10gR2 or some disadvantages in my procedure.
I hope I can understand why this happening and get the better solutions because I get ~5000 procedure calls per day.
This is my cursor which declared in procedure:
CURSOR cur_result (var_num NUMBER)
IS
SELECT
m_username,
m_password
FROM
M_USERS
WHERE
m_status = 1
AND rownum <= var_num;
This is my procedure body:
p_num:=5;
-- the IF statement for guarantee the SELECT statement always
-- get more rows than p_num
LOCK TABLE M_USERS IN EXCLUSIVE MODE;
OPEN cur_result (p_num);
LOOP
FETCH
cur_result
INTO
p_username,
p_password;
-- the problems here: only 1 row is queried
-- the LOOP will exit after second time fetch
IF (cur_result%NOTFOUND AND (cur_result%ROWCOUNT < p_num) ) THEN
iserror := 1;
EXIT;
END IF;
EXIT WHEN cur_result%NOTFOUND;
UPDATE
M_USERS
SET
m_status = 2
WHERE
m_username = p_username
AND m_password = p_password;
END LOOP;
CLOSE cur_result;
Thanks in advance.

Resources