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.
Related
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;
$$;
I have a syntax error on a procedure (PostgreSQL) and I can't find the mistake. I need a procedure with the variable declaration, a for loop, and a transaction.
Here is a sample of the structure I'm using.
CREATE OR REPLACE PROCEDURE repor_dados()
LANGUAGE plpgsql AS
$$
DECLARE
utilizador int;
data_acesso date;
r record;
BEGIN
BEGIN -- begin transaction
select 2 ,4 into data_acesso,utilizador;
IF EXISTS(select 2) then -- condition
BEGIN
-- for each select result
FOR r IN SELECT 1,2,3
LOOP
-- do something
IF r = 1 THEN
SELECT 3;
ELSE
SELECT 4;
END IF;
END LOOP;
END;
COMMIT; -- commit transaction
END
$$
I am working with PostgreSQL 13.2.
The error is:
ERROR: syntax error at end of input LINE 30: $$
I have an intersection table consisting of two columns, Training Program and Course. My boss has asked me to structure it so that people will be blocked from adding more than 50 courses to a training program.
I thought I should do this with a trigger.
I was trying to follow along with this...Postgres insert or update trigger WHEN condition (old)
CREATE OR REPLACE FUNCTION fn_count_tracks_per_map()
RETURNS TRIGGER AS
$BODY$
BEGIN
DECLARE
val INTEGER;
BEGIN
SELECT COUNT(*) INTO val FROM ntnx_track_in_map m WHERE m.map_id = new.map_id;
IF val > 49
THEN
RAISE EXCEPTION 'INSERT failed, maximum tracks in map reached'
END IF;
RETURN new;
END
$BODY$
LANGUAGE plpgsql VOLATILE
The error I'm getting is this -- I've tried a couple things syntax wise but it hasn't helped yet, adding and removing ; mostly
Issues with your code:
the RAISE EXCEPTION statement must be terminated by a semicolon
there are two BEGINs, while there should be only one, after the declaration of the variables
Code:
CREATE OR REPLACE FUNCTION fn_count_tracks_per_map()
RETURNS TRIGGER AS
$BODY$
DECLARE val INTEGER;
BEGIN
SELECT COUNT(*) INTO val FROM ntnx_track_in_map m WHERE m.map_id = new.map_id;
IF val > 49 THEN
RAISE EXCEPTION 'INSERT failed, maximum tracks in map reached';
END IF;
RETURN new;
END
$BODY$
LANGUAGE plpgsql VOLATILE;
Demo on DB Fiddle
I'm busy trying to rewrite an Informix stored procedure for a PostgreSQL
database and I am stuck on something that is probably quite obvious to
everyone who know PostgreSQL.
I have my sql script as follows
-- ensure type and function get created
drop type if exists tp_users cascade;
drop function if exists sp_cmplist();
-- create type
create type tp_users as (
us_id char(30),
us_status char(1)
);
create function sp_cmplist()
returns tp_users as $$
declare
lr_users tp_users;
begin
for lr_users in
select users.us_id, users.us_status
from users
loop
return lr_users;
end loop;
end
$$ language 'plpgsql';
select sp_cmplist();
this is just a dummy script to select from an imaginary users table but how would I use this script with a cursor or loop to make sure all results are returned?
This code works:
CREATE TABLE foo(a int);
INSERT INTO foo VALUES(10),(20);
CREATE OR REPLACE FUNCTION retfoo()
RETURNS SETOF foo AS $$
BEGIN
RETURN QUERY SELECT * FROM foo;
RETURN;
END;
$$ LANGUAGE plpgsql;
postgres=# SELECT * FROM retfoo();
┌────┐
│ a │
├────┤
│ 10 │
│ 20 │
└────┘
(2 rows)
Time: 1.143 ms
I may have answered my own question with the following
drop type if exists tp_users cascade;
drop function if exists sp_cmplist();
create type tp_users as (
us_id text,
us_status text,
lv_nothing text,
lv_cnt int
);
create function sp_cmplist()
returns setof tp_users as $$
declare
lr_users tp_users;
lv_cnt int;
begin
lv_cnt := 0;
for lr_users in
select users.us_id, users.us_status
from users
loop
-- increment this counter for testing purposes
lv_cnt := lv_cnt + 1;
lr_users.lv_nothing := 'yupy';
lr_users.lv_cnt := lv_cnt;
return next lr_users;
end loop;
return;
end
$$ language 'plpgsql';
select * from sp_cmplist();
this seems to work perfectly
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