I have table student_list it contains student_id only one column and 100 rows.
i need to fetch 10-10 records concurrently and then perform some operation.
CREATE OR REPLACE FUNCTION loop_fetch()
RETURNS void AS
$BODY$
DECLARE
myrow student_list%rowtype;
cur1 CURSOR FOR SELECT * FROM student_list ;
BEGIN
OPEN cur1;
LOOP
-- i need to fetch rows based on limit
FETCH NEXT 10 FROM cur1 INTO myrow;
exit when myrow IS NULL;
INSERT INTO new_tbl SELECT myrow.student_id ;
END LOOP;
CLOSE cur1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Any suggestion to achive this method - FETCH NEXT 5 FROM cur1 INTO myrow
Your code should not to work. FETCH NEXT 10 takes 10 rows from cursor, but INTO clause takes only first and other are lost. MyRow is composite variable - it can hold only one row.
I got a error:
ERROR: FETCH statement cannot return multiple rows
LINE 6: fetch 10 from r into re;
^
It is correct result.
The short and probably most correct solution is using FOR IN SELECT. This statement uses cursors internally, and it fetch 10 rows in first iteration, and 50 in other iterations.
So:
DECLARE r record;
BEGIN
FOR r IN SELECT * FROM student_list
LOOP
INSERT INTO newtbl VALUES(r.*);
END LOOP;
END;
does almost all what you want.
If you want to use cursors explicitly (and really you want to fetch in block), you need to use more cycles. The following code is little bit strange, and I write it here only for education - don't think so it has any benefit for practical life.
CREATE OR REPLACE FUNCTION loop_fetch()
RETURNS void AS
$BODY$
DECLARE
myrow student_list%rowtype;
cur1 CURSOR FOR SELECT * FROM student_list ;
rows int DEFAULT 0;
BEGIN
LOOP
-- i need to fetch rows based on limit
FOR myrow IN cur1
LOOP
INSERT INTO new_tbl SELECT myrow.student_id ;
END LOOP;
EXIT WHEN NOT FOUND;
rows := rows + 1;
EXIT WHEN rows = 10;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
This code should to work, but it is crazy.
or you can use use FOR IN EXECUTE
do $$
declare
c cursor for select * from generate_series(1,100);
r record;
begin
-- *** UGLY CODE, DON'T DO IT!!! ***
open c;
-- iteration over FETCH is possible only via
-- dynamic SQL. FOR statement uses FETCH internally
-- by default, and is nonsense use FETCH 2x
for r in execute 'fetch 10 from c'
loop
raise notice '%', r;
end loop;
close c;
-- *** UGLY CODE, DON'T DO IT!!! ***
end;
$$;
Internally FOR IN query use a cursor. So FOR IN EXECUTE FETCH is reading via cursor from another cursor, what is performance nonsense and it really ugly code.
Important things - PostgreSQL has not tabular variables - so you cannot to assign more rows to one variable, and you cannot to fill more rows from one variable.
But your request looks like premature optimization. The most fast and most effective is command:
INSERT INTO newtbl SELECT studentId FROM student_list
SQL can process very well massive operations. 1M rows is nothing. Use cursors only when you really really need it.
Related
I'm pretty new to Oracle and Database.
I'm trying to write a stored procedure with cursors. How do I write a select statement inside the cursor loop, and how do I loop through the result set that I get from the select inside that cursor loop?
For example:
Open Curs1;
Exit When Curs1%NotFound;
Loop
Select column1,column2 from table -- Returns multiple records. How to loop through this record set and perform CRUD operations.
End loop;
Close Curs1;
Use a FOR-loop cursor - they are faster and simpler than the open/fetch/close syntax.
begin
for results1 in
(
select ...
) loop
--Do something here
for results2 in
(
select ...
where some_table.some_column = results1.some_column
) loop
--Do something here
end loop;
end loop;
end;
/
Although this literally answers the question, you generally do not want to have loops inside loops like this. If possible, it would be better to combine the two SQL statements with a join and then loop through the results.
Try to use the following example that fetches rows one at a time from the cursor variable emp_cv into the user-defined record emp_rec:
declare
TYPE YourType IS ref cursor return YourTable%rowtype;
tab_cv YourType;
tab_rec YourTable%rowtype;
begin
loop
fetch tab_cv into emp_rec;
exit when tab_cv%notfound;
...
end loop;
end;
The BULK COLLECT clause lets you fetch entire columns from the result set, or the entire result set at once. The following example, retrieves columns from a cursor into a collection:
declare
type NameList IS table of emp.ename%type;
names NameList;
cursor c1 is select ename from emp where job = 'CLERK';
begin
open c1;
fetch c1 bulk collect into names;
...
close c1;
end;
The following example uses the LIMIT clause. With each iteration of the loop, the FETCH statement fetches 100 rows (or less) into index-by table acct_ids. The previous values are overwritten.
declare
type NumList is table of number index by binary_integer;
cursor c1 is select acct_id from accounts;
acct_ids NumList;
rows natural := 100; -- set limit
begin
open c1;
loop
/* The following statement fetches 100 rows (or less). */
fetch c1 bulk collect into acct_ids limit rows;
exit when c1%notfound;
...
end loop;
close c1;
end;
You need to declare the CURSOR and FETCH the records in the loop.
DECLARE
CURSOR curs1
IS
SELECT column1,
column2
FROM yourtable;
v_column1 yourtable.column1%TYPE;
v_column2 yourtable.column2%TYPE;
BEGIN
OPEN curs1;
LOOP
FETCH curs1
INTO v_column1,
v_column2;
EXIT
WHEN curs1%NOTFOUND;
INSERT INTO yourtable2(col1)VALUES( '000'||v_column1 );
-- A sample DML operation.
--Do other operations on individual records here.
END LOOP;
CLOSE curs1;
END;
I'm working with a stored procedure that I didn't write, which is long and contains numerous columns and joins. The procedure returns a cursor, which the application server (.NET, incidentally) picks up and iterates through.
I'm trying to intercept the cursor using SQLPlus and PL/SQL but I'm having a hard time trying to figure out how to set up the script. Here's what I have so far:
DECLARE
cur sys_refcursor;
BEGIN
adv_schema.report_proc('CAL','01-JAN-2011','01-JAN-2012','Y',cur);
OPEN cur FOR --??????
LOOP
FETCH cur INTO column1, column2;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.Put_Line ('First Name: '||column1||' Last Name: '||column2);
END LOOP;
END;
/
What do I put in the OPEN statement? All the examples I've seen of how to do this are overly simplified examples where some table 't' is created right there in the PL/SQL block, then a cursor is opened with a query to that table, to loop over. What about when a procedure returns a cursor to a complex query with multiple tables?
Assuming that the report_proc procedure is returning the cursor (i.e. the fourth parameter is defined as an OUT SYS_REFCURSOR), there is no need to OPEN the cursor in your code. The procedure is already opening it. You just need to fetch from it.
As #Justin has already mentioned, there is no need to open the cursor returned by the report_proc procedure, you only need to fetch from that cursor. Ref cursors are weak cursors(basically cursors that return no type) and in order to fetch from a weakly typed cursor, you need to know what to fetch. When you know what type a cursor returns you can declare a local structure to fetch into, like so:
DECLARE
-- example of record
-- in your case you have to know exactly
-- how many column and of which datatype your ref cursor returns
type T_Record is record(
column_1 number,
column_2 number
);
l_record T_Record;
cur sys_refcursor;
BEGIN
adv_schema.report_proc('CAL','01-JAN-2011','01-JAN-2012','Y',cur);
LOOP
FETCH cur
INTO l_record;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.Put_Line ('First Name: '||l_record.column1
||' Last Name: '||l_record.column2);
END LOOP;
END;
Also, if you simply need to print the content of a ref cursor, you can do it in SQL*Plus as follows:
SQL> variable cur refcursor;
SQL> exec adv_schema.report_proc('CAL','01-JAN-2011','01-JAN-2012','Y', :cur);
And then use print command to print the refcursor cur:
SQL> print cur;
Do I need to fetch every column that the cursor returns, or can I fetch a subset, say the first three.
No, you fetch everything, you cannot be selective in what column to fetch. It's not impossible however, but it will involve using dbms_sql package, specifically dbms_sql.describe_columns procedure to get information about columns for a cursor.
Just for consideration, If you know that a specific column is definitely present in a cursor, you could use xmlsequence() function to fetch a specific column specifying its name in the extract() function:
SQL> declare
2 type T_List is table of varchar2(123);
3 l_names T_List;
4 l_ref_cur sys_refcursor;
5
6 begin
7 open l_ref_cur
8 for select first_name, last_name
9 from employees
10 where rownum <= 5;
11
12 SELECT t.extract('ROW/FIRST_NAME/text()').getstringval()
13 bulk collect into l_names
14 FROM table(xmlsequence(l_ref_cur)) t;
15
16 for indx in l_names.first..l_names.last
17 loop
18 dbms_output.put_line(l_names(indx));
19 end loop;
20 end;
21 /
Result:
Ellen
Sundar
Mozhe
David
Hermann
PL/SQL procedure successfully completed
I am reading a lot of stuff about repeating Select statements within a loop but I am having some difficulties as I have not found something clear till now. I want to execute some queries (Select queries) several times, like in a FOR loop. Can anyone help with some example please?
The basic structure of what you are asking can be seen below.
Please provide more information for a more specific code sample.
DECLARE
l_output NUMBER;
BEGIN
FOR i IN 1..10 LOOP
SELECT 1
INTO l_output
FROM dual;
DBMS_OUTPUT.PUT_LINE('Result: ' || l_output);
END LOOP;
END;
PS: If you need to enable output in SQL*Plus, you may need to run the command
SET SERVEROUTPUT ON
UPDATE
To insert your results in another table:
DECLARE
-- Store the SELECT query in a cursor
CURSOR l_cur IS SELECT SYSDATE DT FROM DUAL;
--Create a variable that will hold each result from the cursor
l_cur_rec l_cur%ROWTYPE;
BEGIN
-- Open the Cursor so that we may retrieve results
OPEN l_cur;
LOOP
-- Get a result from the SELECT query and store it in the variable
FETCH l_cur INTO l_cur_rec;
-- EXIT the loop if there are no more results
EXIT WHEN l_cur%NOTFOUND;
-- INSERT INTO another table that has the same structure as your results
INSERT INTO a_table VALUES l_cur_rec;
END LOOP;
-- Close the cursor to release the memory
CLOSE l_cur;
END;
To create a View of your results, see the example below:
CREATE VIEW scott.my_view AS
SELECT * FROM scott.emp;
To view your results using the view:
SELECT * FROM scott.my_view;
I have the following function used to retrieve a batch of Ids from a table. This function is being used as the OFFSET and LIMIT clauses seem to offer poor performance.
CREATE OR REPLACE FUNCTION getBatch(_workloadId VARCHAR, _offSet INT, _limit INT)
RETURNS SETOF NUMERIC AS $$
DECLARE
c SCROLL CURSOR FOR
SELECT id FROM workload WHERE workload_id = $1 ORDER BY id ASC;
BEGIN
OPEN c;
MOVE FORWARD $2 IN c;
RETURN QUERY FETCH FORWARD 10 FROM c;
END;
$$ LANGUAGE plpgsql;
I want the FETCH FORWARDcount to be passed in as a parameter, but I'm unable to find a way to do this. Referencing $3 does not work, I've also tried the following:
EXECUTE 'RETURN QUERY FETCH FORWARD ' || $3 || ' FROM c;';
Any help would be greatly appreciated.
You are confusing SQL cursors with PL/pgSQL cursors, which are similar but not the same. In particular, there is no FETCH FORWARD count in PL/pgSQL:
The direction clause can be any of the variants allowed in the
SQL FETCH command except the ones that can fetch more than one
row; namely, it can be NEXT, PRIOR, FIRST, LAST, ABSOLUTE
count, RELATIVE count, FORWARD, or BACKWARD.
In PL/pgSQL you can only fetch one row at a time and process (or return) it.
There is also a disclaimer in the manual:
Note: This page describes usage of cursors at the SQL command level. If you are trying to use cursors inside a PL/pgSQL function,
the rules are different.
You could open that cursor in PL/pgSQL and loop to return rows. But that's only relevant if you want to fetch multiple distinct pieces from a big cursor to save overhead. Else a plain FOR loop (with automatic cursor) or a simple SELECT with OFFSET and LIMIT are certainly faster. Cursors are primarily meant to be returned to and used by the client:
CREATE OR REPLACE FUNCTION getbatch_ref(_cursor refcursor, _workload_id text)
RETURNS refcursor
LANGUAGE plpgsql AS
$func$
BEGIN
OPEN $1 SCROLL FOR
SELECT id
FROM workload
WHERE workload_id = $2
ORDER BY id;
RETURN $1;
END
$func$;
You can use this function in SQL:
BEGIN;
SELECT getbatch_ref('c', 'foo');
MOVE FORWARD 10 IN c;
FETCH FORWARD 10 FROM c;
ROLLBACK; -- or COMMIT;
You could also just use plain SQL:
BEGIN;
DECLARE c SCROLL CURSOR FOR
SELECT id
FROM workload
WHERE workload_id = 'foo'
ORDER BY id;
-- OPEN c; -- only relevant in plpgsql
-- The PostgreSQL server does not implement an OPEN statement for cursors;
-- a cursor is considered to be open when it is declared.
MOVE FORWARD 10 IN c;
FETCH FORWARD 10 FROM c;
ROLLBACK; -- or COMMIT;
I'm writing a stored procedure that needs to have a lot of conditioning in it. With the general knowledge from C#.NET coding that exceptions can hurt performance, I've always avoided using them in PL/SQL as well. My conditioning in this stored proc mostly revolves around whether or not a record exists, which I could do one of two ways:
SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....
-or-
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....
The second case seems a bit more elegant to me, because then I can use NEEDED_FIELD, which I would have had to select in the first statement after the condition in the first case. Less code. But if the stored procedure will run faster using the COUNT(*), then I don't mind typing a little more to make up processing speed.
Any hints? Am I missing another possibility?
EDIT
I should have mentioned that this is all already nested in a FOR LOOP. Not sure if this makes a difference with using a cursor, since I don't think I can DECLARE the cursor as a select in the FOR LOOP.
I would not use an explicit cursor to do this. Steve F. no longer advises people to use explicit cursors when an implicit cursor could be used.
The method with count(*) is unsafe. If another session deletes the row that met the condition after the line with the count(*), and before the line with the select ... into, the code will throw an exception that will not get handled.
The second version from the original post does not have this problem, and it is generally preferred.
That said, there is a minor overhead using the exception, and if you are 100% sure the data will not change, you can use the count(*), but I recommend against it.
I ran these benchmarks on Oracle 10.2.0.1 on 32 bit Windows. I am only looking at elapsed time. There are other test harnesses that can give more details (such as latch counts and memory used).
SQL>create table t (NEEDED_FIELD number, COND number);
Table created.
SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);
1 row created.
declare
otherVar number;
cnt number;
begin
for i in 1 .. 50000 loop
select count(*) into cnt from t where cond = 1;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 1;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.70
declare
otherVar number;
begin
for i in 1 .. 50000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 1;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:03.06
Since SELECT INTO assumes that a single row will be returned, you can use a statement of the form:
SELECT MAX(column)
INTO var
FROM table
WHERE conditions;
IF var IS NOT NULL
THEN ...
The SELECT will give you the value if one is available, and a value of NULL instead of a NO_DATA_FOUND exception. The overhead introduced by MAX() will be minimal-to-zero since the result set contains a single row. It also has the advantage of being compact relative to a cursor-based solution, and not being vulnerable to concurrency issues like the two-step solution in the original post.
An alternative to #Steve's code.
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
FOR foo_rec IN foo_cur LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
The loop is not executed if there is no data. Cursor FOR loops are the way to go - they help avoid a lot of housekeeping. An even more compact solution:
DECLARE
BEGIN
FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
Which works if you know the complete select statement at compile time.
#DCookie
I just want to point out that you can leave off the lines that say
EXCEPTION
WHEN OTHERS THEN
RAISE;
You'll get the same effect if you leave off the exception block all together, and the line number reported for the exception will be the line where the exception is actually thrown, not the line in the exception block where it was re-raised.
Stephen Darlington makes a very good point, and you can see that if you change my benchmark to use a more realistically sized table if I fill the table out to 10000 rows using the following:
begin
for i in 2 .. 10000 loop
insert into t (NEEDED_FIELD, cond) values (i, 10);
end loop;
end;
Then re-run the benchmarks. (I had to reduce the loop counts to 5000 to get reasonable times).
declare
otherVar number;
cnt number;
begin
for i in 1 .. 5000 loop
select count(*) into cnt from t where cond = 0;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 0;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:04.34
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 0;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.10
The method with the exception is now more than twice as fast. So, for almost all cases,the method:
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....
is the way to go. It will give correct results and is generally the fastest.
If it's important you really need to benchmark both options!
Having said that, I have always used the exception method, the reasoning being it's better to only hit the database once.
Yes, you're missing using cursors
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
OPEN foo_cur;
FETCH foo_cur INTO foo_rec;
IF foo_cur%FOUND THEN
...
END IF;
CLOSE foo_cur;
EXCEPTION
WHEN OTHERS THEN
CLOSE foo_cur;
RAISE;
END ;
admittedly this is more code, but it doesn't use EXCEPTIONs as flow-control which, having learnt most of my PL/SQL from Steve Feuerstein's PL/SQL Programming book, I believe to be a good thing.
Whether this is faster or not I don't know (I do very little PL/SQL nowadays).
Rather than having nested cursor loops a more efficient approach would be to use one cursor loop with an outer join between the tables.
BEGIN
FOR rec IN (SELECT a.needed_field,b.other_field
FROM table1 a
LEFT OUTER JOIN table2 b
ON a.needed_field = b.condition_field
WHERE a.column = ???)
LOOP
IF rec.other_field IS NOT NULL THEN
-- whatever processing needs to be done to other_field
END IF;
END LOOP;
END;
you dont have to use open when you are using for loops.
declare
cursor cur_name is select * from emp;
begin
for cur_rec in cur_name Loop
dbms_output.put_line(cur_rec.ename);
end loop;
End ;
or
declare
cursor cur_name is select * from emp;
cur_rec emp%rowtype;
begin
Open cur_name;
Loop
Fetch cur_name into Cur_rec;
Exit when cur_name%notfound;
dbms_output.put_line(cur_rec.ename);
end loop;
Close cur_name;
End ;
May be beating a dead horse here, but I bench-marked the cursor for loop, and that performed about as well as the no_data_found method:
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
otherVar := foo_rec.NEEDED_FIELD;
end loop;
otherVar := 0;
end;
end loop;
end;
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.18
The count(*) will never raise exception because it always returns actual count or 0 - zero, no matter what. I'd use count.
The first (excellent) answer stated -
The method with count() is unsafe. If another session deletes the row that met the condition after the line with the count(*), and before the line with the select ... into, the code will throw an exception that will not get handled.
Not so. Within a given logical Unit of Work Oracle is totally consistent. Even if someone commits the delete of the row between a count and a select Oracle will, for the active session, obtain the data from the logs. If it cannot, you will get a "snapshot too old" error.