Continuing a transaction after primary key violation error Continuing a transaction after primary key violation error sql sql

Continuing a transaction after primary key violation error

You can also use SAVEPOINTs in a transaction.

Pythonish pseudocode is illustrate from the application side:

database.execute("BEGIN")foreach data_row in input_data_dictionary:    database.execute("SAVEPOINT bulk_savepoint")    try:        database.execute("INSERT", table, data_row)    except:        database.execute("ROLLBACK TO SAVEPOINT bulk_savepoint")        log_error(data_row)        error_count = error_count + 1    else:        database.execute("RELEASE SAVEPOINT bulk_savepoint")if error_count > error_threshold:    database.execute("ROLLBACK")else:    database.execute("COMMIT")

Edit: Here's an actual example of this in action in psql based on a slight variation of the example in the documentation (SQL statements prefixed by ">"):

> CREATE TABLE table1 (test_field INTEGER NOT NULL PRIMARY KEY);NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "table1_pkey" for table "table1"CREATE TABLE> BEGIN;BEGIN> INSERT INTO table1 VALUES (1);INSERT 0 1> SAVEPOINT my_savepoint;SAVEPOINT> INSERT INTO table1 VALUES (1);ERROR:  duplicate key value violates unique constraint "table1_pkey"> ROLLBACK TO SAVEPOINT my_savepoint;ROLLBACK> INSERT INTO table1 VALUES (3);INSERT 0 1> COMMIT;COMMIT> SELECT * FROM table1;   test_field ------------          1          3(2 rows)

Note that the value 3 was inserted after the error, but still inside the same transaction!

The documentation for SAVEPOINT is at

I would use a stored procedure to catch the exceptions on your unique violations. Example:

CREATE OR REPLACE FUNCTION my_insert(i_foo text, i_bar text)  RETURNS boolean LANGUAGE plpgsql AS$BODY$begin       insert into foo(x, y) values(i_foo, i_bar);    exception        when unique_violation THEN -- nothing    return true;end;$BODY$;SELECT my_insert('value 1','another value');

You can do a rollback to the transaction or a rollback to a save point just before the code that raises the exception (cr is the cursor):

name = uuid.uuid1().hexcr.execute('SAVEPOINT "%s"' % name)try:    # your failing query goes hereexcept Exception:    cr.execute('ROLLBACK TO SAVEPOINT "%s"' % name)    # your alternative code goes here else:    cr.execute('RELEASE SAVEPOINT "%s"' % name)

This code assumes there is running transaction, otherwise you would not receive that error message.

Django postgresql backend creates cursors directly from psycopg. Maybe in the future they make a proxy class for the Django cursor, similar to the cursor of odoo. They extend the cursor with the following code (self is the cursor):

@contextmanager@checkdef savepoint(self):    """context manager entering in a new savepoint"""    name = uuid.uuid1().hex    self.execute('SAVEPOINT "%s"' % name)    try:        yield    except Exception:        self.execute('ROLLBACK TO SAVEPOINT "%s"' % name)        raise    else:        self.execute('RELEASE SAVEPOINT "%s"' % name)

That way the context makes your code easier, it will be:

try:    with cr.savepoint():        # your failing query goes hereexcept Exception:    # your alternative code goes here 

and the code is more readable, because the transaction stuff is not there.