Postgres multiple SQL-Statements do not stop when an error occurs - postgresql

I have multiple Insert Statementes:
INSERT INTO table1(item_name) VALUES ('item1');
INSERT INTO table1(item_name) VALUES ('item2');
INSERT INTO table1(item_name) VALUES ('item3');
the first value already exists in the table. So i get an error: ERROR: duplicate key value violates unique constraint when the first statement is executed and the execution of the other statements stops.
How can i force Postgres to execute the other statements despite the error? I know that INSERT ON CONFLICT in this case would be a solution, my question is more general: How to continue with the following statements if an error occurs.

If you send several SQL statements, separated by semicolon, to the PostgreSQL server in a single query, they are executed in a single transaction. So if one of them fails, all are undone and processing terminates.
You have to execute each statement separately for that.

If you use psql CLI instead of pgadmin, you can use ON_ERROR_ROLLBACK setting.
When set to on, if a statement in a transaction block generates an
error, the error is ignored and the transaction continues. When set to
interactive, such errors are only ignored in interactive sessions, and
not when reading script files. When set to off (the default), a
statement in a transaction block that generates an error aborts the
entire transaction. The error rollback mode works by issuing an
implicit SAVEPOINT for you, just before each command that is in a
transaction block, and then rolling back to the savepoint if the
command fails.
Example:
select * from t;
x
---
1
(1 row)
\set ON_ERROR_ROLLBACK on
begin;
BEGIN
insert into t values(1);
ERROR: duplicate key value violates unique constraint "t_pkey"
DETAIL: Key (x)=(1) already exists.
insert into t values(2);
INSERT 0 1
insert into t values(3);
INSERT 0 1
commit;
COMMIT
select * from t;
x
---
1
2
3
(3 rows)

Related

How to roll back a transaction on error in PostgreSQL?

I'm writing a script for PostgreSQL and since I want it to be executed atomically, I'm wrapping it inside a transaction.
I expected the script to look something like this:
BEGIN
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END; -- A.k.a. COMMIT;
However, in this case pgAdmin warns me about a syntax error right after the initial BEGIN. If I terminate the command there by appending a semicolon like so: BEGIN; it instead informs me about error near EXCEPTION.
I realize that perhaps I'm mixing up syntax for control structures and transactions, however I couldn't find any mention of how to roll back a failed transaction in the docs (nor in SO for that matter).
I also considered that perhaps the transaction is rolled back automatically on error, but it doesn't seem to be the case since the following script:
BEGIN;
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
COMMIT;
warns me that: ERROR: current transaction is aborted, commands ignored until end of transaction block and I have to then manually ROLLBACK; the transaction.
It seems I'm missing something fundamental here, but what?
EDIT:
I tried using DO as well like so:
DO $$
BEGIN
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END; $$
pgAdmin hits me back with a: ERROR: cannot begin/end transactions in PL/pgSQL. HINT: Use a BEGIN block with an EXCEPTION clause instead. which confuses me to no end, because that is exactly what I am (I think) doing.
POST-ACCEPT EDIT:
Regarding Laurenz's comment: "Your SQL script would contain a COMMIT. That ends the transaction and rolls it back." - this is not the behavior that I observe. Please consider the following example (which is just a concrete version of an example I already provided in my original question):
BEGIN;
-- Just a simple, self-referencing table.
CREATE TABLE "Dummy" (
"Id" INT GENERATED ALWAYS AS IDENTITY,
"ParentId" INT NULL,
CONSTRAINT "PK_Dummy" PRIMARY KEY ("Id"),
CONSTRAINT "FK_Dummy_Dummy" FOREIGN KEY ("ParentId") REFERENCES "Dummy" ("Id")
);
-- Foreign key violation terminates the transaction.
INSERT INTO "Dummy" ("ParentId")
VALUES (99);
COMMIT;
When I execute the script above, I'm greeted with: ERROR: insert or update on table "Dummy" violates foreign key constraint "FK_Dummy_Dummy". DETAIL: Key (ParentId)=(99) is not present in table "Dummy". which is as expected.
However, if I then try to check whether my Dummy table was created or rolled back like so:
SELECT EXISTS (
SELECT FROM information_schema."tables"
WHERE "table_name" = 'Dummy');
instead of a simple false, I get the same error that I already mentioned twice: ERROR: current transaction is aborted, commands ignored until end of transaction block. Then I have to manually terminate the transaction via issuing ROLLBACK;.
So to me it seems that either the comment mentioned above is false or at least I'm heavily misinterpreting something here.
You cannot use ROLLBACK in PL/pgSQL, except in certain limited cases inside procedures.
You don't need to explicitly roll back in your PL/pgSQL code. Just let the exception propagate out of the PL/pgSQL code, and it will cause an error, which will cause the whole transaction to be rolled back.
Your comments suggest that this code is called from an SQL script. Then the solution would be to have a COMMIT in that SQL script at some place after the PL/pgSQL code. That would end the transaction and roll it back.
I think you must be using an older version, as the exact code from your question works without error for me:
(The above is with PostgreSQL 13.1, and pgAdmin 4.28.)
It also works fine for me, without the exception block:
As per this comment, you can remove the exception block within a function, and if an error occurs, the transaction run within it will automatically be rolled back. That appears to be the case, from my limited testing.

How to return values from dynamically generated "insert" command?

I have a stored procedure that performs inserts and updates in the tables. The need to create it was to try to centralize all the scan functions before inserting or updating records. Today the need arose to return the value of the field ID of the table so that my application can locate the registry and perform other stored procedures.
Stored procedure
SET TERM ^ ;
CREATE OR ALTER procedure sp_insupd (
iaction varchar(3),
iusuario varchar(20),
iip varchar(15),
imodulo varchar(30),
ifieldsvalues varchar(2000),
iwhere varchar(1000),
idesclogs varchar(200))
returns (
oid integer)
as
declare variable vdesc varchar(10000);
begin
if (iaction = 'ins') then
begin
vdesc = idesclogs;
/*** the error is on the line below ***/
execute statement 'insert into '||:imodulo||' '||:ifieldsvalues||' returning ID into '||:oid||';';
end else
if (iaction = 'upd') then
begin
execute statement 'select '||:idesclogs||' from '||:imodulo||' where '||:iwhere into :vdesc;
execute statement 'execute procedure SP_CREATE_AUDIT('''||:imodulo||''');';
execute statement 'update '||:imodulo||' set '||:ifieldsvalues||' where '||:iwhere||';';
end
insert into LOGS(USUARIO, IP, MODULO, TIPO, DESCRICAO) values (
:iusuario, :iip, :imodulo, (case :iaction when 'ins' then 1 when 'upd' then 2 end), :vdesc);
end^
SET TERM ; ^
The error in the above line is occurring due to syntax error. The procedure is compiled normally, that is, the error does not happen in the compilation, since the line in question is executed through the "execute statement". When there was no need to return the value of the ID field, the procedure worked normally with the line like this:
...
execute statement 'insert into '||:imodulo||' '||:ifieldsvalues||';';
...
What would be the correct way for the value of the ID field to be stored in the OID variable?
What is REAL VALUE in ifieldsvalues ?
you can not have BOTH
'insert into '||:imodulo||' '||:ifieldsvalues
'update '||:imodulo||' set '||:ifieldsvalues
because methods to specify column names and column values in INSERT and UPDATE statements is fundamentally different!!! You either would have broken update-stmt or broken insert-stmt!
The error in the above line is occurring due to syntax error
This is not enough. Show the real error text, all of it.
It includes the actual command you generate and it seems you had generated it really wrong way.
all the scan functions before inserting or updating records
Move those functions out of the SQL server and into your application server.
Then you would not have to make insert/update in that "strings splicing" way, which is VERY fragile and "SQL injection" friendly. You stepped into the road to hell here.
the error does not happen in the compilation
Exactly. And that is only for starters. You are removing all the safety checks that should had helped you in applications development.
http://searchsoftwarequality.techtarget.com/definition/3-tier-application
https://en.wikipedia.org/wiki/Multitier_architecture#Three-tier_architecture
http://bobby-tables.com
On modern Firebird versions EXECUTE STATEMENT command can have the same INTO clause as PSQL SELECT command.
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql-coding.html#fblangref25-psql-execstmt
Use http://translate.ru to read http://www.firebirdsql.su/doku.php?id=execute_statement
Or just see SQL examples there. Notice, however, those examples all use SELECT dynamic command, not INSERT. So I am not sure it would work that way.
This works in Firebird 2.5 (but not in Firebird 2.1) PSQL blocks.
execute statement 'insert into Z(payload) values(2) returning id' into :i;
To run it from IBExpert/FlameRobin/iSQL interactive shell add that obvious boilerplate:
execute block returns (i integer) as
begin
execute statement 'insert into Z(payload) values(2) returning id' into :i;
suspend;
end

Unexpected end of command - line 1, column 532676549 (with execute statement in firebird 2.5)

I have a problem with execeute statement in a stored procedure and i cant figure it out, i try the text of the query in a variable, show it in a exception to see if was well, copy and execute it and worket, but inside the execute statement i get the same error "Unexpected end of command - line 1, column 532676549"
execute statement 'insert into rep_balancediario(id_rep_balancediario,id_plancuenta,id_saldocontable,nivel,codigoreducido,
SALDOANTERIOR,CREDITO,DEBITO,SALDODIA, cuentacontable,imputable,id_moneda,contracuenta,saldocontabledes,ID_SUCURSALAGENCIA)
values(gen_id(id_rep_balancediario,1),'||:v_id_PlanCuenta||','||:V_NROSALDOCONTABLE||','||:v_Nivel||',"'||:v_CodigoReducido||'",'||
:V_SALDOANTERIORMN||','||:V_CREDITOMN||','||:V_DEBITOMN||','||:v_resultadosaldoactual||',"'||cast(:v_CuentaContable as VARCHAR(11))||'","S",'
||:v_nromoneda||',
"'||cast(:v_contracuenta as VARCHAR (11))||'","'||cast(:V_DESSALDOCONTABLE as VARCHAR(255))||' >'||cast(:v_des_suc as varchar(100))||'",'
||:VE_IDSUCURSALAGENCIA||')'
--:V_str_sql
with AUTONOMOUS TRANSACTION;
does some one know what could by the problem?
Unexpected end of command usually means that the parser reached the end of the statement while it was still expecting tokens, or reached a parser state where it should be complete but there are still tokens left. The fact you are concatenating values into the query is a red flag. Also the "column 532676549" could be an indication of problem, as that exceeds query length limits (64k in Firebird 2.5 and earlier), and also suggests you have literals that are larger than supported (32k).
Execute statement supports parametrized queries: use it. I'd suggest that you rewrite your query to a parametrized query and see if that works, as an example:
execute statement ('insert into aTable (column1, column2) values (?, ?)') (aVariable, 'literal');
or
execute statement ('insert into aTable (column1, column2) values (:var1, :var2)') (var1 := aVariable, var2 := 'literal');
I also see that your query contains double quotes where I'd expect single quotes, which suggests that you use the deprecated dialect 1, which brings yet another set of potential problems.

PL/pgSQL query in PostgreSQL returns result for new, empty table

I am learning to use triggers in PostgreSQL but run into an issue with this code:
CREATE OR REPLACE FUNCTION checkAdressen() RETURNS TRIGGER AS $$
DECLARE
adrCnt int = 0;
BEGIN
SELECT INTO adrCnt count(*) FROM Adresse
WHERE gehoert_zu = NEW.kundenId;
IF adrCnt < 1 OR adrCnt > 3 THEN
RAISE EXCEPTION 'Customer must have 1 to 3 addresses.';
ELSE
RAISE EXCEPTION 'No exception';
END IF;
END;
$$ LANGUAGE plpgsql;
I create a trigger with this procedure after freshly creating all my tables so they are all empty. However the count(*) function in the above code returns 1.
When I run SELECT count(*) FROM adresse; outside of PL/pgSQL, I get 0.
I tried using the FOUND variable but it is always true.
Even more strangely, when I insert some values into my tables and then delete them again so that they are empty again, the code works as intended and count(*) returns 0.
Also if I leave out the WHERE gehoert_zu = NEW.kundenId, count(*) returns 0 which means I get more results with the WHERE clause than without.
--Edit:
Here is an example of how I use the procedure:
CREATE TABLE kunde (
kundenId int PRIMARY KEY
);
CREATE TABLE adresse (
id int PRIMARY KEY,
gehoert_zu int REFERENCES kunde
);
CREATE CONSTRAINT TRIGGER adressenKonsistenzTrigger AFTER INSERT ON Kunde
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE checkAdressen();
INSERT INTO kunde VALUES (1);
INSERT INTO adresse VALUES (1,1);
It looks like I am getting the DEFERRABLE INITIALLY DEFERRED part wrong. I assumed the trigger would be executed after the first INSERT statement but it happens after the second one, although the inserts are not inside a BEGIN; - COMMIT; - Block.
According to the PostgreSQL Documentation inserts are commited automatically every time if not inside such a block and thus there shouldn't be an entry in adresse when the first INSERT statement is commited.
Can anyone point out my mistake?
--Edit:
The trigger and DEFERRABLE INITIALLY DEFERRED seem to be working all right.
My mistake was to assume that since I am not using a BEGIN-COMMIT-Block each insert would be executed in an own transaction with the trigger being executed afterwards every time.
However even without the BEGIN-COMMIT all inserts get bundled into one transaction and the trigger is executed afterwards.
Given this behaviour, what is the point in using BEGIN-COMMIT?
You need a transaction plus the "DEFERRABLE INITIALLY DEFERRED" because of the chicken and egg problem.
starting with two empty tables:
you cannot insert a single row into the person table, because the it needs at least one address.
you cannot insert a single row into the address table, because the FK constraint needs a corresponding row on the person table to exist
This is why you need to bundle the two inserts into one operation: the transaction. You need the BEGIN+ COMMIT, and the DEFERRABLE allows transient forbidden database states to exists: it causes the check to be evaluated at commit time.
This may seem a bit silly, but the answer is you need to stop deferring the trigger and run it BEFORE the insert. If you run it after the insert, of course there is data in the table.
As far as I can tell this is working as expected.
One further note, you probably dont mean:
RAISE EXCEPTION 'No Exception';
You probably want
RAISE INFO 'No Exception';
Then you can change your settings and run queries in transactions to test that the trigger does what you want it to do. As it is, every insert is going to fail and you have no way to move this into production without editing your procedure.

incrementing on errors

How do I suppress the 'id' in this table from incrementing when an error occurs?
db=> CREATE TABLE test (id serial primary key, info text, UNIQUE(info));
NOTICE: CREATE TABLE will create implicit sequence "test_id_seq" for serial column "test.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "test_pkey" for table "test"
NOTICE: CREATE TABLE / UNIQUE will create implicit index "test_info_key" for table "test"
CREATE TABLE
db=> INSERT INTO test (info) VALUES ('hello') ;
INSERT 0 1
db=> INSERT INTO test (info) VALUES ('hello') ;
ERROR: duplicate key violates unique constraint "test_info_key"
db=> INSERT INTO test (info) VALUES ('hello') ;
ERROR: duplicate key violates unique constraint "test_info_key"
db=> INSERT INTO test (info) VALUES ('goodbye') ;
INSERT 0 1
db=> SELECT * from test; SELECT last_value from test_id_seq;
id | info
----+---------
1 | hello
4 | goodbye
(2 rows)
last_value
------------
4
(1 row)
You cannot suppress this - and there is nothing wrong with having gaps in your ID values.
The primary key is a totally meaningless value that is only used to uniquely identify one row in a table.
You cannot rely on the ID to never have any gaps - just think what happens if you delete a row.
Simply ignore it - nothing is wrong
Edit
Just wanted to mention that this behaviour is also clearly stated in the manual:
To avoid blocking concurrent transactions that obtain numbers from the same sequence, a nextval operation is never rolled back
http://www.postgresql.org/docs/current/static/functions-sequence.html
(Scroll to the bottom)
Your question boils down to this: "Can I rollback the next value from a PostgreSQL sequence?"
And the answer is, "You can't." PostgreSQL documentation says
To avoid blocking concurrent transactions that obtain numbers from the same sequence, a nextval operation is never rolled back . . .
Imagine two different transactions go to insert. Transaction A gets id=1 Transaction B gets id=2. Transaction B commits. transaction A rolls back. Now what do we do? How could we roll back the sequence for A without affecting B or later transactions?
I figured it out.
I needed to write a wrapper function around my INSERT statement.
The database will normally have one user at a time so the 'race to the next id' condition is rare. What I was concerned about was when my (unmentioned) 'pull rows from remote database table' function would try to reinsert the growing remote database table into the master database table. I am displaying the row ids and I didn't want the users to see the gap in numbering as missing data.
Anyways here is my solution:
CREATE FUNCTION testInsert (test.info%TYPE) RETURNS void AS '
BEGIN
PERFORM info FROM test WHERE info=$1;
IF NOT FOUND THEN
INSERT INTO test (info) VALUES ($1);
END IF;
END;' LANGUAGE plpgsql;