Data is not showing when I tried to fetch data from cursor. dbo.show_cities_multiple2 is procedure which returns two cursors.
DO $$
<<first_block>>
DECLARE
counter refcursor := 0;
ca_cur refcursor:=null;
tx_cur refcursor:=null;
BEGIN
call dbo.show_cities_multiple2(ca_cur, tx_cur);
EXECUTE 'FETCH ALL from "' || tx_cur || '"';
RAISE NOTICE 'The current value of counter is %', ca_cur;
END first_block $$;
Your problem here is that you aren't actually doing anything with the results of your EXECUTE FETCH ... EXECUTE merely runs the statement that comes after it. A plpgsql function does not return anything unless you explicitly specify RETURN ...,RETURN NEXT ... or RETURN QUERY .... Furthermore, an anonymous DO $$ block cannot return any results. So if you want to actually retrieve rows from the cursor, you need to name your cursors and execute your fetch outside of the do block:
DO $$
<<first_block>>
DECLARE
counter integer := 0;
ca_cur refcursor:='ca_cur'; --name your cursors first, otherwise they will be anonymous!
tx_cur refcursor:='tx_cur';
BEGIN
call dbo.show_cities_multiple2(ca_cur, tx_cur);
...
END first_block $$;
--Fetch
FETCH ALL FROM tx_cur;
Alternatively, you can define a standard function and use RETURN NEXT OR RETURN QUERY to return the result sets from the cursors:
CREATE FUNCTION return_city_data() RETURNS SETOF cities AS $$
DECLARE
counter integer = 0;
tx_cur refcursor = 'tx_cur';
ca_cur refcursor = 'ca_cur';
rec RECORD;
BEGIN
CALL dbo.show_cities_multiple(tx_cur,ca_cur);
-- Iterate over each record in the cursor
FOR rec in EXECUTE 'FETCH ALL FROM ' || tx_cur LOOP
counter = counter + 1;
-- RETURN NEXT means "Append each value to the function's result set".
RETURN NEXT rec;
END LOOP;
FOR rec in EXECUTE 'FETCH ALL FROM ' || ca_cur LOOP
counter = counter + 1;
RETURN NEXT rec;
END LOOP;
--All records have been appended to the result set. the cursors can now be closed.
CLOSE tx_cur;
CLOSE ca_cur;
RAISE NOTICE 'The current value of counter is %', "counter";
END
$$ LANGUAGE plpgsql;
OR
CREATE FUNCTION return_city_data() RETURNS SETOF cities AS $$
DECLARE
counter integer = 0;
tx_cur refcursor = 'tx_cur';
ca_cur refcursor = 'ca_cur';
rec RECORD;
BEGIN
CALL dbo.show_cities_multiple(tx_cur,ca_cur);
-- RETURN QUERY means "append all rows from the query to the function's result set"
RETURN QUERY EXECUTE 'FETCH ALL FROM ' || tx_cur;
RETURN QUERY EXECUTE 'FETCH ALL FROM ' || ca_cur;
CLOSE tx_cur;
CLOSE ca_cur;
END
$$ LANGUAGE plpgsql;
You can use either of these forms with a simple SELECT statement:
SELECT * FROM return_city_data();
Note that unlike RETURN, RETURN NEXT and RETURN QUERY do not terminate your function and you can use them multiple times in your blocks. The function will then naturally terminate at the end of the last block.
Related
I'm trying to run a DELETE and UPDATE query inside a trigger function in PL/pgSQL.
CREATE OR REPLACE FUNCTION trg_delete_order_layer()
RETURNS trigger AS
$BODY$
DECLARE index_ INTEGER;
DECLARE a RECORD;
BEGIN
IF EXISTS (SELECT * FROM map_layers_order WHERE id_map = OLD.id_map)
THEN index_ := position FROM map_layers_order WHERE id_map = OLD.id_map AND id_layer = OLD.id_layer AND layer_type = 'layer';
ELSE index_ := 0;
END IF;
RAISE NOTICE 'max_index % % %', index_, OLD.id_map, OLD.id_layer;
EXECUTE 'DELETE FROM map_layers_order WHERE id_map = $1 AND id_layer = $2 AND layer_type = $3' USING OLD.id_map, OLD.id_layer, 'layer';
EXECUTE 'UPDATE map_layers_order SET position = position -1 WHERE id_map = $1 AND position > $2' USING OLD.id_map, index_;
VALUES (OLD.id_map, OLD.id_layer, 'layer', index_);
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql;
I don't know why i'm getting this error in the line where it runs the DELETE query:
Query has no destination for result data
Why is this error happen and how can I solve this?
Obviously if I use EXECUTE...INTO, I get a more reasonable error saying that the query has no result.
I have got a cursor, it is pointing to a SELECT, but this select is generated dynamically. I want to assign the statement after the declarement.
I have done an example working and another example NOT working. This is a simple example to print some data only.
This is the table:
CREATE TABLE public.my_columns (
id serial NOT NULL,
"name" varchar(30) NOT NULL,
/* Keys */
CONSTRAINT my_columns_pkey
PRIMARY KEY (id)
) WITH (
OIDS = FALSE
);
CREATE INDEX my_columns_index01
ON public.my_columns
("name");
INSERT INTO public.my_columns
("name")
VALUES
('name1'),
('name2'),
('name3'),
('name4'),
('name5'),
('name6');
This is the function(I have put the working code and the code not working):
CREATE OR REPLACE FUNCTION public.dynamic_table
(
)
RETURNS text AS $$
DECLARE
v_sql_dynamic varchar;
--NOT WORKING:
--db_c CURSOR IS (v_sql_dynamic::varchar);
--WORKING:
db_c CURSOR IS (SELECT id, name from public.my_columns);
db_rec RECORD;
BEGIN
v_sql_dynamic := 'SELECT id, name from public.my_columns';
FOR db_rec IN db_c LOOP
RAISE NOTICE 'NAME: %', db_rec.name;
END LOOP;
RETURN 'OK';
EXCEPTION WHEN others THEN
RETURN 'Error: ' || SQLERRM::text || ' ' || SQLSTATE::text;
END;
$$ LANGUAGE plpgsql;
Any ideas?
Thank you.
Do you really need the explicit cursor? If you need iterate over dynamic SQL, then you can use FOR IN EXECUTE. It is loop over implicit (internal) cursor for dynamic SQL
FOR db_rec IN EXECUTE v_sql_dynamic
LOOP
..
END LOOP
Little bit more complex solution is described in documentation - OPEN FOR EXECUTE:
do $$
declare r refcursor; rec record;
begin
open r for execute 'select * from pg_class';
fetch next from r into rec;
while found
loop
raise notice '%', rec;
fetch next from r into rec;
end loop;
close r;
end $$;
With this kind of cursor, you cannot to use FOR IN
I wrote function to update or insert into log table some information.
Every query should return around 100 records, but below function always updating or inserting only last row.I thing that I should put RETURN NEXT before END LOOP but it is not working.
CREATE OR REPLACE FUNCTION plugins.update_log()
RETURNS void AS
$BODY$
DECLARE
rec record;
BEGIN
FOR rec IN
SELECT na1_nr::integer,
CASE WHEN c.record_status_id::integer = 8 THEN c.niezainteresowany_powod::text ELSE ots.name::text END reason
FROM temp_id c
LEFT JOIN plugins.outbounds_telcom_statuses ots ON (ots.telcom_status_id::integer=c.telcom_status_id::integer AND ots.outbound_id::integer = 33)
LOOP
UPDATE plugins.boss_release_log
SET count = count::integer +1
WHERE na1_nr::integer = rec.na1_nr::integer
AND reason::text = rec.reason::text;
IF found THEN
RETURN;
END IF;
BEGIN
INSERT INTO plugins.boss_release_log(na1_nr,reason,count)
VALUES (rec.na1_nr, rec.reason, 1);
RETURN;
EXCEPTION WHEN unique_violation THEN
END;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION plugins.update_log()
OWNER TO ss0;
Move your RETURN statement to the end of your function. Your function MUST have a RETURN statement in it, but when it is hit it exits (for set-returning functions you use RETURN NEXT but that is not applicable here).
I need to create trigger to check if new inserted element's id isn't repeated. The problem is in the LOOP statement, console spit out the error:
CONTEXT: SQL statement in PL/PgSQL function "foo" near line 7
LINE 1: move forward 1 from $1
Here is my function:
create function foo() returns trigger as'
declare xyz cursor for select id from accounts;
begin
LOOP
if NEW.id = xyz then
raise notice ''Id is just used!'';
else
move forward 1 from xyz;
end if;
END LOOP;
return NEW;
close xyz;
end;
' language 'plpgsql';
create trigger foo before insert on accounts for each
row execute procedure foo();
Your example has no sense (you can't compare scalar value with cursor). Cursor self is like pointer without any value.
if NEW.id = xyx then
Why you don't do
BEGIN
IF EXISTS(SELECT * FROM accounts a WHERE a.id = NEW.id) THEN
RAISE NOTICE ''Id is used''; -- better RAISE EXCEPTION
END IF;
RETURN NEW;
END;
second nonsense
RETURN NEW;
CLOSE xyz; -- no statement is executed after RETURN
CREATE OR REPLACE FUNCTION "Test"(character varying[],character varying[])
RETURNS refcursor AS
$BODY$
DECLARE
curr refcursor;
filter text;
counter integer;
BEGIN
counter = 1;
filter = '';
IF array_length($1,1) > 0 THEN
filter = 'AND ';
WHILE ($1[counter] <> '') LOOP
filter = filter||'LOWER('||$1[counter]||'::character varying) LIKE ''%''||LOWER($2['||counter||'])||''%'' AND ';
counter = counter + 1;
END LOOP;
filter = substring(filter FROM 1 FOR (char_length(filter)-4));
OPEN curr FOR
EXECUTE 'SELECT "Reservation".* FROM "Reservation" WHERE "Reservation"."id" > 0 '||filter;
return curr;
END IF;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
SELECT "Test"(ARRAY['"Reservation"."status"'],'{"waiting"}');
FETCH ALL IN "<unnamed portal 1>";
I tried to print out the query:
"SELECT "Reservation".* FROM "Reservation" WHERE "Reservation"."id" > 0 AND LOWER("Reservation"."status"::character varying) LIKE '%'||LOWER($2[1])||'%' "
But when it's executed it said that there was no parameter $2. So I realize that it can't access that stored procedure's parameter.
I don't have to worry about the first parameter of sql injection since it's hard coded. But the second param has to be passed into the execution. How do I do that?
I've found out that I could pass the parameter into EXECUTE using the "USING" statement.
Here's the final working code:
CREATE OR REPLACE FUNCTION "Test"(character varying[],character varying[])
RETURNS refcursor AS
$BODY$
DECLARE
curr refcursor;
filter text;
counter integer;
BEGIN
counter = 1;
filter = '';
IF array_length($1,1) > 0 THEN
filter = 'AND ';
WHILE ($1[counter] <> '') LOOP
filter = filter||'LOWER('||$1[counter]||'::character varying) LIKE ''%''||LOWER($1['||counter||'])||''%'' AND ';
counter = counter + 1;
END LOOP;
filter = substring(filter FROM 1 FOR (char_length(filter)-4));
OPEN curr FOR
EXECUTE 'SELECT "Reservation".* FROM "Reservation" WHERE "Reservation"."id" > 0 '||filter USING $2;
return curr;
END IF;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
SELECT "Test"(ARRAY['"Reservation"."status"'],ARRAY['no-show']);
FETCH ALL IN "<unnamed portal 1>";
Note that I have $1 as the value in the EXECUTE statement, because it accepts $2 as its first parameter.