2010-03-03 14 views
8

Estoy haciendo una inserción masiva de registros en una base de datos desde un archivo de registro. Ocasionalmente (~ 1 fila de cada mil) una de las filas viola la clave principal y hace que la transacción falle. Actualmente, el usuario tiene que pasar manualmente por el archivo que causó la falla y eliminar la fila ofensiva antes de intentar volver a importar. Dado que hay cientos de estos archivos para importar, no es práctico.Continuación de una transacción después del error de violación de clave primaria

Mi pregunta: ¿Cómo puedo omitir la inserción de registros que se viole la restricción de clave primaria, sin tener que hacer una declaración SELECT antes de cada fila para ver si ya existe?

Nota: Conozco la pregunta muy similar #1054695, pero parece ser una respuesta específica de SQL Server y estoy usando PostgreSQL (importando a través de Python/psycopg2).

Respuesta

12

También puede usar SAVEPOINTs en una transacción.

Pythonish pseudocódigo es ilustrar desde el lado de la aplicación:

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

Edición: He aquí un ejemplo real de esto en acción en psql basado en una ligera variación del ejemplo en la documentación (sentencias SQL con el prefijo "> "):

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

Tenga en cuenta que el valor 3 se insertó después del error, pero aún dentro de la misma transacción.

La documentación para SAVEPOINT está en http://www.postgresql.org/docs/8.4/static/sql-savepoint.html.

+0

Eso no va a funcionar, cuando ocurre un error, la transacción se cancela y se revierte. Necesita un manejador de excepciones dentro de la base de datos. Consulta fallida: ERROR: la transacción actual se cancela, los comandos se ignoran hasta el final del bloque de transacción –

+0

Sí lo hará. Ese es el objetivo de SAVEPOINTs. He editado mi respuesta para dar un ejemplo concreto. –

+1

---- editar ---- Lo siento, estaba equivocado ... lástima de mí;) Funciona bien, tienes razón. –

4

Utilizaría un procedimiento almacenado para detectar las excepciones en sus violaciones únicas. Ejemplo:

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'); 
+0

Perfecto, gracias. – John

+0

Siempre es mejor registrar sus excepciones ... puede modificar el blog de excepción para iniciar sesión y continuar. – Guru

+0

Puede dejar que la función registre las excepciones, no hay problema. –

0

O puede usar SSIS y hacer que las filas con errores tomen una ruta distinta a las exitosas.

Una vez que está utilizando una base de datos diferente, ¿puede insertar los archivos a granel en una tabla de etapas y luego usar el código SQL para seleccionar solo los registros que no tienen una identificación de exisitng?

+0

¿Puede profundizar en lo que quiere decir con SSIS? – John

+0

SSIS es la herramienta de importación de datos que viene con SQL Server. No entendí que estás usando postgre. Todavía puede hacer el trabajo para Postgre, pero no estoy seguro de cómo lo obtendría, ya que no creo que venga con la versión gratuita de SQL Server. – HLGEM

1

se puede hacer un rollback a la transacción o una reversión a un punto de guardado justo antes del código que plantea la excepción (cr es el cursor):

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

Este código asume que no se está ejecutando la transacción, de lo contrario no recibirías ese mensaje de error.

Django postgresql backend creates cursors directamente desde psycopg. Quizás en el futuro hagan una clase de proxy para el cursor de Django, similar al cursor of odoo. Se extienden el cursor con el following code (auto es el cursor):

@contextmanager 
@check 
def 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) 

De esa manera el contexto hace que su código sea más fácil, será:

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

y el código es más legible, debido a que la las cosas de la transacción no están allí.

Cuestiones relacionadas