pl pgsql move with variable value - sql

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.

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.

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

Passing table name to function Dynamically as Input parameter and using it [duplicate]

This question already has answers here:
Table name as a PostgreSQL function parameter
(8 answers)
Closed 8 years ago.
In the below function I am passing in my table name as text 'K' and wish to use it for cursor declaration inside my function.I am using postgres 8.2 (greenplum)
I used quote_ident(k), dint work either. Could some one help me out. THanks!
CREATE OR REPLACE FUNCTION vin_temp(K text) RETURNS text AS
$BODY$
DECLARE
r vin_res%rowtype;
r1 numeric default 0;
rfr numeric default 0;
rdu numeric default 0;
rav numeric default 0;
rfr1 numeric default 0;
rdu1 numeric default 0;
rav1 numeric default 0;
r2 numeric default 0;
i integer default 0;
x text default '';
curs2 CURSOR FOR SELECT * FROM k order by week_end asc;
BEGIN
open curs2;
LOOP
FETCH curs2 INTO r;
exit when not found;
if (i=1) then
r1:=r.v1;
rav:=r.v2;
rfr:=r.v3;
else
some logic here
end if;
END LOOP;
RETURN 'yes';
END
$BODY$
LANGUAGE 'plpgsql' ;
i tried the following code as well :
curs2 CURSOR FOR EXECUTE 'SELECT * from '||quote_ident(k)||' order by week_end asc';
An unbound cursor must be used for dynamic queries.
It is described in documentation for version 8.2 : http://www.postgresql.org/docs/8.2/interactive/plpgsql-cursors.html
37.8.2.2. OPEN FOR EXECUTE
OPEN unbound_cursor FOR EXECUTE query_string;
The cursor variable is opened and given the specified query to execute. The cursor cannot be open already, and it must have been declared as an unbound cursor (that is, as a simple refcursor variable). The query is specified as a string expression, in the same way as in the EXECUTE command. As usual, this gives flexibility so the query can vary from one run to the next.
An example:
OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident($1);
(emphasis mine)
Here is a working example, please take a look at this link: http://sqlfiddle.com/#!11/a0d8a/1
CREATE OR REPLACE FUNCTION vin_temp(K text) RETURNS int
AS
$BODY$
DECLARE
curs1 refcursor;
x int;
begin
OPEN curs1 FOR EXECUTE 'Select Sum( x ) From ' || $1;
Fetch curs1 Into x;
Close curs1;
Return x;
End
$BODY$ LANGUAGE 'plpgsql'
/

Querying the result of a Procedure, PL/SQL ( Oracle DBMS )

I have written a procedure which checks the amount of vacant rooms a property has.
CREATE OR REPLACE PROCEDURE prop_vacancy_query(
p_property_id properties.tracking_id%TYPE
)
IS
property_refcur SYS_REFCURSOR;
v_prop_rooms properties.num_rooms%TYPE;
BEGIN
OPEN property_refcur FOR
'SELECT COUNT(room_status) FROM rooms
JOIN properties ON
properties.property_id = rooms.property_id
WHERE room_status = :status AND properties.tracking_id = :track_id' USING 'VACANT', p_property_id;
LOOP
FETCH property_refcur INTO v_prop_rooms;
EXIT WHEN property_refcur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('Available Rooms: ' || v_prop_rooms);
END LOOP;
CLOSE property_refcur;
END;
I want to use this procedure in a trigger to automatically set the status of the property too 'OCCUPIED' when the amount of rooms available returns as 0.
I have tried
IF prop_vacancy_query(:NEW.property_status) = 0 THEN
:NEW.property_status := 'OCCUPIED';
END IF;
But this does not work. How would I go about calling this procedure with conditional logic in my trigger? Or is this not possible.
NOTE: I am also worried about the performance issues this may entail, I am not sure how else I could handle auto updating when the DB updates, any pointer to how I could solve this problem would be greatly appreciated.
First, if you have a piece of code whose only purpose is to run queries and return a value, use a FUNCTION, not a PROCEDURE. Procedures should be doing some sort of manipulation of the data.
Second, if you do not need dynamic SQL, don't use dynamic SQL. It's generally a bit slower but, more importantly, it is much, much harder to write, support, and debug. Plus, you're turning compile-time exceptions into run-time exceptions so you won't find your syntax errors until you try to run your code.
You can simplify your code rather significantly
CREATE OR REPLACE FUNCTION get_num_vacancies(
p_property_id properties.tracking_id%TYPE
)
RETURN NUMBER
IS
v_prop_rooms properties.num_rooms%TYPE;
BEGIN
SELECT COUNT(room_status)
INTO v_prop_rooms
FROM rooms
JOIN properties ON
properties.property_id = rooms.property_id
WHERE room_status = 'VACANT'
AND properties.tracking_id = p_property_id;
RETURN v_prop_rooms;
END;
Then you can call the function in the way that you originally wanted
IF prop_vacancy_query(:NEW.property_status) = 0 THEN
:NEW.property_status := 'OCCUPIED';
END IF;
CREATE OR REPLACE PROCEDURE prop_vacancy_query(
p_property_id properties.tracking_id%TYPE
status OUT NUMBER
)
.
.
.
status := 0
END;
/
And call this way
outstatus NUMBER:= -1;
prop_vacancy_query(:NEW.property_id,outstatus);
IF out status = 0 THEN
:NEW.property_status := 'OCCUPIED';
END IF;

PostgreSQL dynamic fetch foward count value

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;