2011-12-13 71 views
17

Uso de Python 2.7 yManejo de errores clave principal elegante en Python/psycopg2

En [150]: psycopg2. versión Fuera [150]: '2.4.2 (dt diciembre ext PQ3)'

tengo unas simples scripts de Python que el procesamiento de las transacciones y escribe datos en una base de datos. Ocasionalmente hay una inserción que infringe mi clave principal. Esto está bien, solo quiero que ignore ese registro y continúe felizmente. El problema que estoy teniendo es que el error de clave primaria psycopg2 está abortando todo el bloque de transacción y todas las inserciones después de que el error falla. Aquí hay un error de ejemplo

ERROR: duplicate key value violates unique constraint "encounter_id_pkey" 
DETAIL: Key (encounter_id)=(9012235) already exists. 

Esto se encuentra en la siguiente inserción. no es una violación

Inserting: 0163168~9024065 
ERROR: current transaction is aborted, commands ignored until end of transaction block 

El segundo error se repite para cada inserción. Aquí hay un bucle simplificado. Estoy recorriendo un marco de datos de pandas, pero podría ser cualquier ciclo.

conn = psycopg2.connect("dbname='XXXX' user='XXXXX' host='XXXX' password='XXXXX'") 

cur = conn.cursor() 

for i, val in df2.iteritems(): 
    try: 
     cur = conn.cursor() 
     cur.execute("""insert into encounter_id_table (
     encounter_id,current_date ) 
     values  
     (%(create_date)s, %(encounter_id)s) ;""", 
     'encounter_id':i.split('~')[1], 
     'create_date': datetime.date.today() })   
     cur.commit() 
     cur.close() 
    except Exception , e: 
     print 'ERROR:', e[0] 
     cur.close() 
conn.close() 

Una vez más, la idea básica es manejar con elegancia el Error. En el dictamen del almirante Nelson de la Royal Navy: "Maldición, las maniobras van directo hacia ellos". O en nuestro caso, maldita sea, los errores van directamente hacia ellos ". Pensé, al abrir un cursor en cada inserción, que reiniciaría el bloque de transacciones. No quiero tener que restablecer la conexión solo por un error de clave principal. hay algo que sólo estoy perdiendo?

Gracias de ante mano por su tiempo.

John

Respuesta

19

debe deshacer la transacción en caso de error.

he añadido una más try..except..else construcción en el código de abajo para mostrar el lugar exacto donde se producirá la excepción.

try: 
    cur = conn.cursor() 

    try: 
     cur.execute("""insert into encounter_id_table (
      encounter_id,current_date ) 
      values  
      (%(create_date)s, %(encounter_id)s) ;""", 
      'encounter_id':i.split('~')[1], 
      'create_date': datetime.date.today() }) 
    except psycopg2.IntegrityError: 
     conn.rollback() 
    else: 
     conn.commit() 

    cur.close() 
except Exception , e: 
    print 'ERROR:', e[0] 
+0

Esto es casi es la respuesta. Pero necesito llamar al método rollback() en el objeto de conexión (conn) aquí. El cursor solo tiene commit no rollback. Gracias – jdennison

+0

Más errores de método. Tienes que invocar commit en la conexión, no el cursor. – jdennison

+0

Me preguntaba si la confirmación y la reversión son métodos de conexión. Piensa que alguien debería editar mi código para corregirlo. Lamentablemente, no estoy muy familiarizado con el uso directo de 'psycopg' ya que estoy usando ORM o bases de datos no relacionales por lo general. – lig

2

En primer lugar: CURRENT_DATE es una palabra reservada en todos los estándares SQL así como también en PostgreSQL. No puede usarlo como identificador sin hacer una doble cotización. Recomiendo encarecidamente no usarlo en absoluto. Me cambió el nombre de la columna para curdate en mi ejemplo

A continuación, no soy un experto en la sintaxis de Python, pero parece que han invertido el orden de inserción de columnas:

(%(create_date)s, %(encounter_id)s) 

debe ser:

(%(encounter_id)s, %(create_date)s) 

a su pregunta principal: puede evitar el problema por completo mediante la comprobación de si la clave ya está en la mesa antes de utilizarlo en el comando de inserción:

INSERT INTO encounter_id_table (encounter_id, curdate) 
SELECT 1234, now()::date 
WHERE NOT EXISTS (SELECT * FROM encounter_id_table t 
        WHERE t.encounter_id = 1234); 

En la sintaxis de Python, que debe ser:

cur.execute("""INSERT INTO encounter_id_table (encounter_id, curdate) 
    SELECT %(encounter_id)s, %(create_date)s, 
    WHERE NOT EXISTS (
      SELECT * FROM encounter_id_table t 
      WHERE t.encounter_id = %(encounter_id)s);""", 
    {'encounter_id':i.split('~')[1], 
    'create_date': datetime.date.today()})  
+0

A menos que establezca específicamente el nivel de aislamiento en algo más estricto, el uso de la sintaxis WHERE NOT EXISTS creará una condición de carrera. – lusional

+1

@lusional: en Postgres 9.5 o posterior, es mejor utilizar 'INSERTAR .. EN CONFLICTO NO HACER NADA '. Compare: http://stackoverflow.com/questions/17267417/how-to-upsert-merge-insert-on-duplicate-update-in-postgresql. Cambiar el nivel de aislamiento a 'Serializable' sería mucho más caro. –

+0

De acuerdo, el uso de la sintaxis INSERT .. ON CONFLICT es mucho mejor siempre que sea posible. Las condiciones de carrera son desagradables cuando no se tratan adecuadamente. – lusional