Commit, savepoint, rollback to in PostgreSQL? - postgresql

Can someone please explain to me why does COMMIT in this function returns EXCEPTION ?
DECLARE
XNar CURSOR (forDATE Varchar) IS
SELECT NARUCENO, ISPORUKA_ID FROM XDATA_NARUDZBE
WHERE TO_CHAR(XDATA_NARUDZBE.DATUM, 'DD.MM.YYYY') = forDATE;
LastDate DATE;
OutResult INTEGER;
curNAR NUMERIC;
curISP VARCHAR;
RXNar RECORD;
BEGIN
OutResult := 1;
SELECT MAX(DATUM) INTO LastDate FROM XDATA_NARUDZBE;
FOR RXNar IN XNar(TO_CHAR(LastDate, 'DD.MM.YYYY')) LOOP
IF (RXNar.NARUCENO <> 0) AND (RXNar.ISPORUKA_ID = 'R01') THEN
UPDATE NARUDZBE SET ISPORUCENO = RXNar.NARUCENO
WHERE NARUDZBE.PP_ID = RXNar.PP_ID
AND NARUDZBE.ART_ID = RXNar.ART_ID
AND NARUDZBE.ISPORUKA_ID = 'R01';
END IF;
END LOOP;
COMMIT; <--- ????
RETURN OutResult;
EXCEPTION
WHEN OTHERS THEN
OUTRESULT := 0;
RAISE;
RETURN OutResult;
END;
and why I can not use ROLLBACK TO SavePoint when EXCEPTION block exists in function?

You can't use COMMIT in a stored procedure, the entire procedure is a transaction of it's own.

You can't commit in a plpgsql stored function/procedure using plpgsql as Frank Heikens answered. You can however work around this issue by using dblink(http://www.postgresql.org/docs/9.0/interactive/contrib-dblink-connect.html) or another store procedure language such as plperl(untrusted). Check out this link where this talked about.
http://postgresql.1045698.n5.nabble.com/Re-GENERAL-Transactions-within-a-function-body-td1992810.html
The high level is you open a new connection using one of these methods and issue a separate transaction on that connection. Works for most cases not ideal because you are opening a new connection, but may work fine for most use cases.

Begin with PostgreSQL11 there is an procedure module.
Demo based on Manual.
CREATE OR REPLACE PROCEDURE insert_data_drop(_a integer, _b integer)
LANGUAGE plpgsql
AS $$
begin
INSERT INTO tbl(a) VALUES (_a);
INSERT INTO tbl(b) VALUES (_b);
Rollback;
INSERT INTO tbl(a) VALUES (_a);
end
$$;
Now CALL insert_data_drop(1, 2); will insert 1 to column a, 2 will not be saved.
However, SAVEPOINT seems not working. It will show error like:
ERROR: unsupported transaction command in PL/pgSQL
CONTEXT: PL/pgSQL function insert_data_drop(integer,integer) line 5 at SQL stateme

Related

How to fetch PL/pgSQL cursors?

I am trying to get dynamic data using cursors :
CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$
BEGIN
OPEN $1 FOR SELECT * FROM users;
RETURN NEXT $1;
END;
$$ LANGUAGE plpgsql;
When i try to fetch data i keep getting this error : "a" is not a known variable ??
do $$ begin
SELECT * FROM myfunc('a', 'b');
FETCH ALL FROM a;
COMMIT;
end;
$$
If all you want to do is fetch the data from the cursor, you don't need PL/pgSQL code. All you need is a transaction, since cursors don't outlive a transaction. Don't mix up BEGIN from PL/pgSQL, which starts a code block, with BEGIN from SQL, which starts a transaction:
BEGIN;
SELECT * FROM myfunc('a', 'b');
FETCH ALL FROM a;
COMMIT;

Why am I getting a syntax error when using an IF statement in my Postgres function?

I am creating a function that allow me to conditionally update specific columns in a table. However, I get an error indicating that there is a syntax error at or near "IF" when I try to run the following code. I'm a bit new to Postgres so it's quite possible. I can't understand some concept/syntax thing in Postgres. Can someone help me by pointing out the mistake I must be making?
CREATE OR REPLACE FUNCTION profiles.do_something(
p_id UUID,
p_condition1 BOOLEAN,
p_condition2 BOOLEAN,
p_condition3 BOOLEAN
)
RETURNS void AS $$
BEGIN
IF p_condition1 IS TRUE THEN
UPDATE tablename SET column1 = null WHERE member_id = p_id;
END IF;
IF p_condition2 IS TRUE THEN
UPDATE tablename SET column2 = null WHERE member_id = p_id;
END IF;
IF p_condition3 IS TRUE THEN
UPDATE tablename SET column3 = null WHERE member_id = p_id;
END IF;
END;
$$ LANGUAGE 'sql';
tl;dr $$ LANGUAGE 'plpgsql'
$$ LANGUAGE 'sql';
^^^^^
You're tell it to parse the body of the function as sql. In SQL, begin is a statement which starts a transaction.
create or replace function test1()
returns void
language sql
as $$
-- In SQL, begin starts a transaction.
-- note the ; to end the statement.
begin;
-- Do some valid SQL.
select 1;
-- In SQL, end ends the transaction.
end;
$$;
In SQL you wrote begin if ... which is a syntax error.
The language you're using is plpgsql. In plpgsql, begin is a keyword which starts a block.
create or replace function test1()
returns void
language plpgsql
as $$
-- In PL/pgSQL, begin starts a block
-- note the lack of ;
begin
-- Do some valid SQL.
select 1;
-- In PL/pgSQL, end ends the block
end;
$$;

Postgresql ignoring if condition

I'm trying to understand transactions in plpgsql and i would like some explanations.
I have this code:
CREATE OR REPLACE PROCEDURE MaJ(mode IN INT)
AS
$$
DECLARE
r RECORD;
DECLARE
r RECORD;
BEGIN
FOR r IN SELECT id, fname, lname, bday FROM usr
LOOP
IF r.ID % 2 = 0 THEN
UPDATE usr SET lname = 'KONAN';
RAISE NOTICE E'fname : %\n', r.lname;
END IF;
END LOOP;
IF mode = 0 THEN
COMMIT;
ELSE IF mode = 1 THEN
ROLLBACK;
END IF;
END;
firs of all and after getting and trying all possible solutions, what's the best approach for acheiving the commit/rollback based on the procedure parameter.
second, aside from the comit rollback issue, postgres is ignoring the if IF r.ID % 2 = 0 THEN all together and updating all entries, thank you for any explanation.
I'm calling this procedure from another procedure using CALL MaJ(VAL);
Update:
Maybe this code portrays hte problem better:
Here's what I'm trying to do:
CREATE OR REPLACE PROCEDURE CRDM(Crtrn INOUT INT)
AS
$CRDM$
DECLARE
R RECORD;
BEGIN
FOR R IN SELECT * FROM usr
LOOP
IF R.ID % 2 = 0 THEN
UPDATE USR SET lname = 'MAGNI' WHERE USR.ID = R.ID;
END IF;
END LOOP;
IF Crtrn = 0 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END;
$CRDM$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE AMI()
AS
$AMI$
DECLARE
rtrn INT:=0;
BEGIN
BEGIN
CALL CRDM(rtrn);
END;
END;
$AMI$ LANGUAGE plpgsql;
DO
$$
BEGIN
CALL AMI();
END;
$$ LANGUAGE plpgsql;
Any help wih what I'm missing and how I can better think about transactions is welcome.
The possibility to use ROLLBACK or COMMIT are only in new versions. Check version that you use if these commands are supported.
Long time the Postgres stored procedures was without these commands and without problems. Using COMMIT, ROLLBACK inside PL/pgSQL is not too much native - mostly it was implemented for more easy migration from Oracle, but because transaction model of Oracle and Postgres is very different, there is lot of limits.
Oracle start implicit transaction after login and waits on explicit commit or rollback. After these commands, Oracle starts new transaction immediately.
In Postgres, every statement is executed under transaction like in Oracle. but if tranaction was started by user, then user is responsible for commit or rollback. If user didn't start transaction, then Postgres starts transaction, and Postgres implicitly runs rollback when operation raises an exception, or runs commit if not. After an exception only rollback is allowed.
Usually in Postgres is not necessary to use commit or rollback. Just raise an exception, and upper layers does all necessary work.
Your code is not nice, please, try to read documentation and some notes about programming style. Don't use Camel notation - in case insensitive language.
CREATE TABLE foo_tab(id int);
CREATE TABLE boo_tab(id int, b text);
INSERT INTO foo_tab VALUES(1);
INSERT INTO foo_tab VALUES(2);
CREATE OR REPLACE PROCEDURE foo(a bool, b text)
AS $$
DECLARE r record;
BEGIN
FOR r IN SELECT * FROM foo_tab
LOOP
INSERT INTO boo_tab VALUES(r.id, b);
END LOOP;
IF a THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE foo2(a bool, b text)
AS $$
BEGIN
CALL foo(a, b);
END;
$$ LANGUAGE plpgsql;
DO $$
BEGIN
CALL foo2(true, 'Ahoj');
CALL foo2(false, 'Nazdar');
END;
$$; -- LANGUAGE plpgsql is implicit here
postgres=# SELECT * FROM boo_tab;
┌────┬──────┐
│ id │ b │
╞════╪══════╡
│ 1 │ Ahoj │
│ 2 │ Ahoj │
└────┴──────┘
(2 rows)
Tested on Postgres 13
Your "COMMIT;" and "ROLLBACK;" logic looks OK to me after reading this: https://www.postgresql.org/docs/current/plpgsql-transactions.html
You might need to "BEGIN;" a transaction before you "CALL" your procedure: I'm not sure.
Your 2nd problem is simple: your UPDATE statement has no WHERE clause. Make sure you update only the current loop record by matching on the ID value.

"Relation does not exist" Postgresql

i have this error:
---------------------------
pgAdmin III
---------------------------
An error has occurred:
ERROR: relation "vettura_tariffa" does not exist
LINE 2: from vettura_tariffa
^
QUERY: SELECT ( select valore
from vettura_tariffa
where new.targa=vettura.targa)
CONTEXT: PL/pgSQL function "progettoBD".costout() line 14 at assignment
---------------------------
OK
---------------------------
the code is the following
create or replace function costout()
returns trigger AS
$$
DECLARE
giorno integer;
gg1 integer;
gg2 integer;
tariffa numeric(2,0) ;
costo integer;
BEGIN
gg1=extract (days from new.dataconsegna);
gg2=extract (days from new.dataritiro);
giorno=gg1-gg2;
tariffa=( select valore
from vettura join tariffa on vettura.tipotariffa=tariffa.tipo
where new.targa=vettura.targa);
costo=tariffa * giorni;
new.costoutilizzo=costo;
END;
$$ language plpgsql;
and this one is the trigger's code
create trigger costo_utilizzo
after insert on utilizzo
for each row
execute procedure costout();
It seems like it doesn't find the table vettura, it's the same with other tables, and if I try a simply "select * from vettura" it works fine.
I tried swapping the from with a view but the problem persists.
This error appears after the insert on "utilizzo".
Sorry for my bad english, thank you in advance.
Solution for JAVA problem.
We need to engage in double quotes and bars so that the command is accepted by postgresql.
sql = "INSERT INTO \"SCHEMA\".\"TABLE\"() values(?,?,?)
Ciao Atz34,
The error that is generated indicates that you are calling a function that makes a reference to vettura_tariffa while the code you post refers to vettura JOIN tariffa. Probably a simple mistake somewhere in your code. However, you have a few more issues with your trigger and trigger function.
First of all, you should call a BEFORE INSERT trigger when you modify any fields of NEW; on an AFTER INSERT trigger the changes will not be saved in the table (you can only do side effects like auditing or making changes to other tables).
Second, extract(date from ...) gives problems between months. Assuming your datacosegna and dataritiro are date columns, you can simply subtract them and add 1: NEW.dataritiro - NEW.dataconsegna + 1 (so same-day returns are not gratis).
Third, always RETURN NEW from an insert trigger.
You can then fold all statements into:
CREATE OR REPLACE FUNCTION costout() RETURNS trigger AS $$
BEGIN
SELECT valore * (NEW.dataritiro - NEW.dataconsegna + 1) INTO NEW.costoutilizzo
FROM vettura
JOIN tariffa ON vettura.tipotariffa = tariffa.tipo
WHERE NEW.targa = vettura.targa;
RETURN NEW;
END; $$ LANGUAGE plpgsql;

Endless loop in trigger function

This is a trigger that is called by either an insert, update or a delete on a table. It is guaranteed the calling table has all the columns impacted and a deletes table also exists.
CREATE OR REPLACE FUNCTION sample_trigger_func() RETURNS TRIGGER AS $$
DECLARE
operation_code char;
table_name varchar(50);
delete_table_name varchar(50);
old_id integer;
BEGIN
table_name = TG_TABLE_NAME;
delete_table_name = TG_TABLE_NAME || '_deletes';
SELECT SUBSTR(TG_OP, 1, 1)::CHAR INTO operation_code;
IF TG_OP = 'DELETE' THEN
OLD.mod_op = operation_code;
OLD.mod_date = now();
RAISE INFO 'OLD: %', (OLD).name;
EXECUTE format('INSERT INTO %s VALUES %s', delete_table_name, (OLD).*);
ELSE
EXECUTE format('UPDATE TABLE %s SET mod_op = %s AND mod_date = %s'
, TG_TABLE_NAME, operation_code, now());
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
The ELSE branch triggers an endless loop. There may be more problems.
How to fix it?
The ELSE branch can be radically simplified. But a couple more things are inefficient / inaccurate / dangerous:
CREATE OR REPLACE FUNCTION sample_trigger_func()
RETURNS TRIGGER AS
$func$
BEGIN
IF TG_OP = 'DELETE' THEN
RAISE INFO 'OLD: %', OLD.name;
EXECUTE format('INSERT INTO %I SELECT ($1).*', TG_TABLE_NAME || '_deletes')
USING OLD #= hstore('{mod_op, mod_datetime}'::text[]
, ARRAY[left(TG_OP, 1), now()::text]);
RETURN OLD;
ELSE -- insert, update
NEW.mod_op := left(TG_OP, 1);
NEW.mod_datetime := now();
RETURN NEW;
END IF;
END
$func$ LANGUAGE plpgsql;
In the ELSE branch just assign to NEW directly. No need for more dynamic SQL - which would fire the same trigger again causing an endless loop. That's the primary error.
RETURN NEW; outside the IF construct would break your trigger function for DELETE, since NEW is not assigned for DELETEs.
A key feature is the use of hstore and the hstore operator #= to dynamically change two selected fields of the well-known row type - that is unknown at the time of writing the code. This way you do not tamper with the original OLD value, which might have surprising side effect if you have more triggers down the chain of events.
OLD #= hstore('{mod_op, mod_datetime}'::text[]
, ARRAY[left(TG_OP, 1), now()::text]);
The additional module hstore must be installed. Details:
How to set value of composite variable field using dynamic SQL
Passing column names dynamically for a record variable in PostgreSQL
Using the hstore(text[], text[]) variant here to construct an hstore value with multiple fields on the fly.
The assignment operator in plpgsql is :=:
The forgotten assignment operator "=" and the commonplace ":="
Note that I used the column name mod_datetime instead of the misleading mod_date, since the column is obviously a timestamp and not a date.
I added a couple of other improvements while being at it. And the trigger itself should look like this:
CREATE TRIGGER insupdel_bef
BEFORE INSERT OR UPDATE OR DELETE ON table_name
FOR EACH ROW EXECUTE PROCEDURE sample_trigger_func();
SQL Fiddle.