PostgreSQL dynamic fetch foward count value - sql

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;

Related

postgresql iterate n records

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.

pl pgsql move with variable value

I have the following function that implements pagination using cursors. Function accept parameters i_limit and i_offset.
begin
-- Search resources.
select into found_keys trgm_search_resources(i_search_query);
-- Open cursor
open master_event_curs(found_keys);
-- Reposition cursor if the #i_offset is specified
-- TODO: Replace 5 with variable
move forward i_offset in master_event_curs;
-- Limit number of retrieved items
loop
exit when i >= i_limit;
-- Fetch data to the record
fetch master_event_curs into recordvar;
exit when not found;
i = i + 1;
-- Return master event info.
return next (select get_master_event_info(recordvar."master_event_uid" :: bigint, i_return_langs));
end loop;
-- Return info of found master events.
return;
end;
When I use move forward 5 in master_event_curs and everything works fine, but when I try to replace 5 with dynamic i_offset variable, the SQL shows syntax error {char} unexpected.
I tried to use explicit casting, execute this command using execute statement but nothing works.
Could someone give me a hint how to do this?
Use execute format():
execute format('move forward %s in master_event_curs', i_offset);
What is version of your Postgres? Modern PostgreSQL allows any expression there:
do $$
declare s cursor for select * from pg_class;
r record;
i_offset int default 5;
begin
open s;
move forward i_offset in s;
fetch s into r;
raise notice '%', r;
close s;
end;
$$;
NOTICE: (pg_toast_2609,99,11585,0,10,0,2834,0,0,0,0,0,t,f,p,t,3,0,f,f,f,f,f,f,t,n,f,0,561,1,,,)
Maybe you are using too old version of PostgreSQL, or maybe there was some any different issue, but MOVE, FETCH commands supports expressions, so there is not necessary to use dynamic SQL.

How to read multiple refcursor return by other procedure to another procedure

i'm having one procedure which returns setof cursors
Now i have to call that procedure to another procedure and access the data
that return by that procedure
is their any way to do this in postgres.
This is code for 1st procedure,
CREATE OR REPLACE FUNCTION public.returns_multiple_cursor( )
RETURNS SETOF refcursor
LANGUAGE 'plpgsql'
COST 100.0
AS $function$
DECLARE
_daily refcursor := 'first_cur';
_fac_hourly refcursor := 'second_cur';
BEGIN
open first_cur for
select * from x;
return next first_cur;
open second_cur for
select * from y;
return second_cur;
END
$function$;
ALTER FUNCTION public.returns_multiple_cursor();
Here code for other second procedure
CREATE OR REPLACE FUNCTION public.access_cursor( )
RETURNS SETOF refcursor
LANGUAGE 'plpgsql'
COST 100.0
AS $function$
DECLARE
BEGIN
-- what code will be here to access the cursor data in this procedure
select public.returns_multiple_cursor();
END;
ALTER FUNCTION public.access_cursor();
Unfortunately, you cannot use the FOR <recordvar> IN <cursor> loop, because it only works for bound cursors (which refcursors are not).
But you can still loop through them, with the old-fashioned FETCH:
declare
rec record;
cur refcursor;
begin
for cur in select returns_multiple_cursor() loop
loop
fetch next from cur into rec;
exit when not found;
-- do whatever you want with the single rows here
end loop;
close cur;
end loop;
end
Unfortunately, there is still another limitation: PostgreSQL caches the first cursor's plan (at least, it seems it does something like that), so you must use cursors, which uses the same column types (you'll have to use the same column names anyway, to be able to refer them in the inner loop, like rec.col1).
Complete, working example: http://rextester.com/FNWG91106 (see f.ex. what happens, when you remove casting from the cursors' queries).
If you have fix number of cursors (like in your example), but differently structured underlying queries, it might be easier to declare your returns_multiple_cursor as:
create or replace function returns_multiple_cursor(out first_cur refcursor,
out second_cur refcursor)
-- returns record (optional)
language plpgsql
-- ...
This way, you could access your cursors more directly in the calling context.
Update: it seems that when you don't use explicit column names, just generic record processing (via f.ex. JSON or hstore), plan caching does not cause any trouble:
http://rextester.com/QHR6096

PL/SQL dynamic row printing?

I'm a newbie in SQL and I was wondering if you can print the contents of a cursor%rowtype dynamically ?
For example:
cursor cur is select * from ...;
current_row cur%rowtype;
begin
open cur;
loop
fetch cur into current_row;
-- dbms_output.put_line( current_row ); Would that be possible ?
exit when cur%notfound;
end loop;
Or do I have to do the boring part myself, that is specifying each member I need to print, which is quite lousy and lame, e.g.:
dbms_output.get_line( current_row.first || current_row.second || .... || current_row.last );
Is there a simple way to achieve dynamic printing to some extent in PL/SQL? Im using PL/SQL developer.
Thanks
Use the DBMS_SQL package to archive that. Of course it takes more lines (than the simple %rowtype cursor loop in your example) to create and loop through the cursor, but once this is done, you can process any SELECT query and output the results, no matter how many columns the query returns of what types the columns have.

Check Values in sys_refcursor

I have the following code in a function
CREATE OR REPLACE FUNCTION my_func (
v_dt events.raised_date%TYPE
)
RETURN SYS_REFCURSOR
IS
p_events SYS_REFCURSOR;
OPEN p_events FOR
SELECT event_id
FROM events
WHERE raised_date = v_dt;
RETURN p_events;
END;
I would like to check whether 100 exists in p_events cursor or not. How can I do this inside my function.
Any help is highly appreciable.
A ref cursor is just a pointer to query. There is nothing "in" it. So the only way to find out whether the result set identified by the ref cursor contains a specific record - or indeed any records - is to fetch the cursor and read through the records.
Bear in mind that a ref cursor is a one-shot thang. We cannot fetch the same cursor more than once. We have to close and re-open it. But that means we run the risk of the second fetched result set differing from the first (unless we change the transaction's isolation level).
So the upshot is, just code the consuming procedure to fetch and use the ref cursor, and make sure it handles both the presence and absence of interesting records.
It is not good idea to check it inside of the function. You are missing why the cursor is returned. Instead do it outside of the function.
DECLARE
l_rc SYS_REFCURSOR := my_func();
TYPE events_ntt IS TABLE OF NUMBER;
l_events events_ntt;
l_lookup events_ntt := events_ntt(100);
l_diff events_ntt;
BEGIN
FETCH l_rc BULK COLLECT INTO l_events;
l_diff := l_events MULTISET INTERSECT DISTINCT l_lookup;
IF l_diff.COUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE('100 EXISTS');
ELSE
DBMS_OUTPUT.PUT_LINE('100 DOES NOT EXIST');
END IF;
END;
Using Cursor Variables (REF CURSORs)
Like a cursor, a cursor variable points to the current row in the
result set of a multi-row query. A cursor variable is more flexible
because it is not tied to a specific query. You can open a cursor
variable for any query that returns the right set of columns.
You pass a cursor variable as a parameter to local and stored
subprograms. Opening the cursor variable in one subprogram, and
processing it in a different subprogram, helps to centralize data
retrieval. This technique is also useful for multi-language
applications, where a PL/SQL subprogram might return a result set to a
subprogram written in a different language, such as Java or Visual
Basic.
What Are Cursor Variables (REF CURSORs)?
Cursor variables are like pointers to result sets. You use them when
you want to perform a query in one subprogram, and process the results
in a different subprogram (possibly one written in a different
language). A cursor variable has datatype REF CURSOR, and you might
see them referred to informally as REF CURSORs.
Unlike an explicit cursor, which always refers to the same query work
area, a cursor variable can refer to different work areas. You cannot
use a cursor variable where a cursor is expected, or vice versa.
Source: http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/sqloperations.htm#i7106
(Oracle Database PL/SQL User's Guide and Reference)
It can be done like this
declare
evt EVENTS%ROWTYPE;
found_100 boolean := false;
begin
loop
fetch p_events into evt;
exit when p_events%NOTFOUND;
if evt.event_id = 100 then
found_100 := true;
exit;
end if;
end loop;
end;
but however it's very inefficient because you're possibly fetching millions of records where you actually only need 1 fetch.