2012-05-14 10 views
6

Estoy insertando varios registros de 10k en una base de datos con reglas de integridad REF. Algunas de las filas de datos lamentablemente están duplicadas (en el sentido de que ya existen en la base de datos). Sería demasiado costoso verificar la existencia de cada fila en la base de datos antes de insertarla, así que pretendo proceder manejando las excepciones IntegrityError lanzadas por SQLAlchemy, registrando el error y luego continuando.SQLAlchemy IntegrityError e importaciones de datos masivos

Mi código se verá algo como esto:

# establish connection to db etc. 

tbl = obtain_binding_to_sqlalchemy_orm() 
datarows = load_rows_to_import() 

try: 
    conn.execute(tbl.insert(), datarows) 
except IntegrityError as ie: 
    # eat error and keep going 
except Exception as e: 
    # do something else 

El (implícito) que estoy haciendo anterior es que no se está produciendo SQLAlchemy las inserciones múltiples en una sola transacción. Si mi suposición es incorrecta, significa que si se produce un IntegrityError, el resto de la inserción se cancela. ¿Alguien puede confirmar si el "patrón" de pseudocódigo anterior funcionará como se esperaba, o terminaré perdiendo datos como resultado de las excepciones arrojadas por IntegrityError?

Además, si alguien tiene una mejor idea de hacer esto, me interesará escucharlo.

Respuesta

1

puede funcionar de esta manera, si no inició ninguna transacción anteriormente, ya que en este caso se activará el autocommit feature de sqlalchemy, pero debe establecerlo explícitamente como se describe en el enlace.

0

También encontré este problema cuando estaba analizando archivos de datos ASCII para importar los datos en una tabla. El problema es que, instintiva e intuitivamente, quería que SQLAlchemy saltara las filas duplicadas mientras permitía los datos únicos. O podría ser el caso de que se genere un error aleatorio con una fila, debido al motor SQL actual, como cadenas de caracteres unicode no permitidas.

Sin embargo, este comportamiento está fuera del alcance de la definición de la interfaz SQL. Las API de SQL y, por lo tanto, SQLAlchemy solo comprenden las transacciones y confirmaciones, y no tienen en cuenta este comportamiento selectivo. Además, parece peligroso depender de la función de confirmación automática, ya que la inserción se detiene después de la excepción, dejando el resto de los datos.

Mi solución (que no estoy seguro si es la más elegante) es procesar cada línea en un bucle, capturar y registrar excepciones, y confirmar los cambios al final.

Suponiendo que de alguna manera adquirió datos en una lista de listas, es decir, una lista de filas que son listas de valores de columna. Luego lee cada fila en un bucle:

# Python 3.5 
from sqlalchemy import Table, create_engine 
import logging 

# Create the engine 
# Create the table 
# Parse the data file and save data in `rows` 

conn = engine.connect() 
trans = conn.begin() # Disables autocommit 

exceptions = {} 
totalRows = 0 
importedRows = 0 

ins = table.insert() 

for currentRowIdx, cols in enumerate(rows): 
    try: 
     conn.execute(ins.values(cols)) # try to insert the column values 
     importedRows += 1 

    except Exception as e: 
     exc_name = type(e).__name__ # save the exception name 
     if not exc_name in exceptions: 
      exceptions[exc_name] = [] 
     exceptions[exc_name].append(currentRowIdx) 

    totalRows += 1 

for key, val in exceptions.items(): 
    logging.warning("%d out of %d lines were not imported due to %s."%(len(val), totalRows, key)) 

logging.info("%d rows were imported."%(importedRows)) 

trans.commit() # Commit at the very end 
conn.close() 

Para maximizar la velocidad en esta operación, debe desactivar la confirmación automática. Estoy usando este código con SQLite y todavía es 3-5 veces más lento que mi versión anterior usando solo sqlite3, incluso con autocommit desactivado. (La razón por la que porté a SQLAlchemy fue para poder usarlo con MySQL.)

No es la solución más elegante en el sentido de que no es tan rápida como una interfaz directa a SQLite. Si perfilo el código y encuentro el cuello de botella en un futuro próximo, actualizaré esta respuesta con la solución.

Cuestiones relacionadas