2012-09-16 10 views
16

Estoy usando bulk_create para cargar miles o filas en una base de datos postgresql. Desafortunadamente, algunas de las filas están causando IntegrityError y deteniendo el proceso bulk_create. Me preguntaba si había alguna manera de decirle a django que ignorara esas filas y ahorrara la mayor cantidad de lotes posible.Django bulk_create con ignorar las filas que causan IntegrityError?

+0

que puede no ser posible debido a PostgreSQL aborta la transacción en el primer error. Django necesitaría (a) crear un SAVEPOINT antes de cada inserción, lo que ralentiza las cosas y cuesta recursos; o (b) Use un procedimiento o consulta para insertar solo si la fila aún no existe. Personalmente, insertaría de forma masiva en una nueva tabla separada, probablemente 'DESBLOQUEADO' o 'TEMPORAL', luego 'INSERTAR EN SELECCIONABLE' * SITIO DONDE NO EXISTE (SELECCIONAR 1 FROM realtable DONDE temptable.id = realtable.id) ' o similar. –

Respuesta

6

(Nota: Yo no uso de Django, lo que puede haber respuestas-marco específico más adecuados)

No es posible que Django para hacer esto simplemente ignorando INSERT fracasos porque PostgreSQL aborta la transacción completa en el primer error

Django necesitaría uno de estos enfoques:

  1. INSERT cada fila de una transacción separada e ignorar errores (muy lento);
  2. Crea un SAVEPOINT antes de cada inserción (puede tener problemas de escala);
  3. Utilice un procedimiento o consulta para insertar solo si la fila no existe (complicada y lenta); o
  4. Bulk-insert o (mejor) COPY los datos en una tabla TEMPORARY, luego fusione eso en la tabla principal del servidor.

El enfoque tipo upsert (3) parece una buena idea, pero upsert and insert-if-not-exists are surprisingly complicated.

En lo personal, me gustaría tener (4): Me-mayor inserción en una nueva tabla por separado, probablemente UNLOGGED o TEMPORARY, entonces me gustaría correr algún manual para SQL:

LOCK TABLE realtable IN EXCLUSIVE MODE; 

INSERT INTO realtable 
SELECT * FROM temptable WHERE NOT EXISTS (
    SELECT 1 FROM realtable WHERE temptable.id = realtable.id 
); 

El LOCK TABLE ... IN EXCLUSIVE MODE impide que una inserción concurrente que crea una fila provoque un conflicto con una inserción realizada por la declaración anterior y que falla. Sí no previene SELECT concurrentes, solo SELECT ... FOR UPDATE, INSERT, UPDATE y DELETE, por lo que las lecturas de la tabla continúan normalmente.

Si no puede permitirse el lujo de bloquear las escrituras simultáneas durante demasiado tiempo, podría utilizar un CTE escribible para copiar rangos de filas de temptable en realtable, reintentando cada bloque si fallara.

+0

Gracias @ craig-ringer Terminé limpiando mi lista de objetos python antes de insertarlos en el DB, algo similar al enfoque # 3 de ustedes, pero en python puro. – Meitham

+0

Hay un ejemplo detallado de (4) en https://rodmtech.net/docs/django/django-bulk_create-without-integrityerror-rollback/ – eugene

1

O 5. divide y vencerás

no he probado o punto de referencia de este fondo, pero se lleva a cabo bastante bien para mí. YMMV, dependiendo en particular de cuántos errores espera obtener en una operación masiva.

def psql_copy(records): 
    count = len(records) 
    if count < 1: 
     return True 
    try: 
     pg.copy_bin_values(records) 
     return True 
    except IntegrityError: 
     if count == 1: 
      # found culprit! 
      msg = "Integrity error copying record:\n%r" 
      logger.error(msg % records[0], exc_info=True) 
      return False 
    finally: 
     connection.commit() 

    # There was an integrity error but we had more than one record. 
    # Divide and conquer. 
    mid = count/2 
    return psql_copy(records[:mid]) and psql_copy(records[mid:]) 
    # or just return False 
1

Una solución rápida y sucia para esto que no involucra SQL manual y tablas temporales es simplemente intentar insertar de forma masiva los datos. Si falla, vuelva a la inserción en serie.

objs = [(Event), (Event), (Event)...] 

try: 
    Event.objects.bulk_create(objs) 

except IntegrityError: 
    for obj in objs: 
     try: 
      obj.save() 
     except IntegrityError: 
      continue 

Si usted tiene montones y montones de errores esto puede no ser tan eficiente (que va a pasar más tiempo en serie inserción que hacerlo a granel), pero estoy trabajando a través de un conjunto de datos de alta cardinalidad con pocas se duplica así que esto resuelve la mayoría de mis problemas.

0

Incluso en Django 1.11 no hay forma de hacer esto.Encontré una mejor opción que usar Raw SQL. Lo usa djnago-query-builder. Tiene un método upsert

from querybuilder.query import Query 
q = Query().from_table(YourModel) 
# replace with your real objects 
rows = [YourModel() for i in range(10)] 
q.upsert(rows, ['unique_fld1', 'unique_fld2'], ['fld1_to_update', 'fld2_to_update']) 

Nota: La biblioteca sólo el soporte de PostgreSQL

Cuestiones relacionadas