Move/fetch cursor in loop (PostgreSQL) - postgresql

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

Related

Postgresql: fetch all not showing any data

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.

How do i use cursor to print new things i added to table in postgresql?

I want to write SQL trigger in postgresql.
create trigger add_car on samochod
after insert
as
begin
declare k_inserted cursor
for select car_model, mileage from inserted
declare #car_model(30), #mileage varchar(30)
open k_inserted
fetch next from k_inserted into #car_model(30), #mileage varchar(30)
while ##FETCH_STATUS = 0
begin
print 'Added car: '
print 'Model: ' + #car_model
print 'Mileage: ' + #mileage
fetch next from k_inserted into #car_model(30), #mileage varchar(30)
end
close k_inserted
deallocate k_inserted
end
INSERT INTO car(car_model, mileage)
VALUES ('Audi', 15011646);
That trigger actually works for me. Then i tried in postgresql.
create function add_car()
RETURNS TRIGGER
as
$$
declare
car_model varchar(30);
mileage varchar(30);
k_inserted cursor for select new.car_model, new.mileage from car;
begin
open k_inserted;
fetch next from k_inserted into car_model, mileage;
while (FOUND)
LOOP
RAISE NOTICE 'Added car: ';
RAISE NOTICE 'Marka: %', car_model;
RAISE NOTICE 'Mileage: %', mileage;
fetch next from k_inserted into car_model, mileage;
end LOOP;
close k_inserted;
return new;
end;
$$ LANGUAGE plpgsql;
CREATE TRIGGER add_car
after INSERT on car
for each row EXECUTE PROCEDURE add_car();
It works but it prints me 6 times the same thing (i have 6 cars in my table)
How can I fix it so that it prints as many times as the cars I added
You declared your trigger as for each row so there is no need for a loop or a select "from inserted" (something that SQL Server doesn't have). The trigger will be called once for each row you insert, so just print the values from the new record
create function add_car()
RETURNS TRIGGER
as
$$
begin
RAISE NOTICE 'Added car: ';
RAISE NOTICE 'Marka: %', new.car_model;
RAISE NOTICE 'Mileage: %', new.mileage;
return new;
end;
$$ LANGUAGE plpgsql;
If you do want a statement level trigger to mimic SQL Server's behaviour, you can do the following:
A trigger function using the inserted transition table:
create function add_car()
RETURNS TRIGGER
as
$$
declare
l_row record;
begin
for l_row in select * from inserted
LOOP
RAISE NOTICE 'Added car: ';
RAISE NOTICE 'Marka: %', l_row.car_model;
RAISE NOTICE 'Mileage: %', l_row.mileage;
end LOOP;
return new;
end;
$$ LANGUAGE plpgsql;
And the corresponding definition of a statement level trigger:
CREATE TRIGGER add_car
after INSERT on car
REFERENCING NEW TABLE AS inserted
for each statement EXECUTE PROCEDURE add_car();

How to pass NEW.* to EXECUTE in trigger function

I have a simple mission is inserting huge MD5 values into tables (partitioned table), and have created a trigger and also a trigger function to instead of INSERT operation. And in function I checked the first two characters of NEW.md5 to determine which table should be inserted.
DECLARE
tb text;
BEGIN
IF TG_OP = 'INSERT' THEN
tb = 'samples_' || left(NEW.md5, 2);
EXECUTE(format('INSERT INTO %s VALUES (%s);', tb, NEW.*)); <- WRONG
END IF;
RETURN NULL;
END;
The question is how to concat the NEW.* into the SQL statement?
Best with the USING clause of EXECUTE:
CREATE FUNCTION foo ()
RETURNS trigger AS
$func$
BEGIN
IF TG_OP = 'INSERT' THEN
EXECUTE format('INSERT INTO %s SELECT $1.*'
, 'samples_' || left(NEW.md5, 2);
USING NEW;
END IF;
RETURN NULL;
END
$func$ LANGUAGE plpgsql;
And EXECUTE does not require parentheses.
And you are aware that identifiers are folded to lower case unless quoted where necessary (%I instead of %s in format()).
More details:
INSERT with dynamic table name in trigger function
How to dynamically use TG_TABLE_NAME in PostgreSQL 8.2?

Dynamically generated CURSOR in Postgresql

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

postgresql - loop doesn't work properly

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).