2010-04-16 33 views
30

En MySQL, puede insertar varias filas a una tabla en una consulta para n> 0:¿Cómo realizo una inserción de lote en Django?

INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9), ..., (n-2, n-1, n); 

¿Hay una manera de lograr lo anterior con métodos de QuerySet Django? Aquí hay un ejemplo:

values = [(1, 2, 3), (4, 5, 6), ...] 

for value in values: 
    SomeModel.objects.create(first=value[0], second=value[1], third=value[2]) 

Creo que lo anterior llama a una consulta de inserción para cada iteración del ciclo for. Estoy buscando una sola consulta, ¿es posible en Django?

+2

Actualización: la versión de desarrollo de Django lanzará un método 'bulk_create': https://docs.djangoproject.com/en/dev/ref/models/querysets/ # bulk-create –

Respuesta

12

Recientemente busqué algo así (inspirado en QuerySet.update(), como me imagino que tú también). Que yo sepa, no existe creación masiva en el marco de producción actual (1.1.1 a partir de hoy). Terminamos creando un administrador personalizado para el modelo que necesitaba bulk-create, y creamos una función en ese administrador para construir una declaración SQL apropiada con la secuencia de parámetros VALUES.

Algo así como (disculpas si esto no funciona ... es de esperar que he adaptado este runnably de nuestro código):

from django.db import models, connection 

class MyManager(models.Manager): 

    def create_in_bulk(self, values): 
     base_sql = "INSERT INTO tbl_name (a,b,c) VALUES " 
     values_sql = [] 
     values_data = [] 

     for value_list in values: 
      placeholders = ['%s' for i in range(len(value_list))] 
      values_sql.append("(%s)" % ','.join(placeholders)) 
      values_data.extend(value_list) 

     sql = '%s%s' % (base_sql, ', '.join(values_sql)) 

     curs = connection.cursor() 
     curs.execute(sql, values_data) 

class MyObject(models.Model): 
    # model definition as usual... assume: 
    foo = models.CharField(max_length=128) 

    # custom manager 
    objects = MyManager() 

MyObject.objects.create_in_bulk([('hello',), ('bye',), ('c',)]) 

Este enfoque corre el riesgo de ser muy específico para una base de datos en particular. En nuestro caso, queríamos que la función devolviera los ID recién creados, por lo que teníamos una consulta específica de postgres en la función para generar el número requerido de ID de la secuencia de clave primaria para la tabla que representa el objeto. Dicho esto, tiene un rendimiento significativamente mejor en las pruebas en comparación con la iteración sobre los datos y la emisión de instrucciones QuerySet.create() por separado.

+2

Por cierto. Este enfoque podría generar un error de "Paquete demasiado grande" en mysql (y probablemente en otras bases de datos) si tiene muchos datos. Es mejor dividir el conjunto de datos en trozos más pequeños. –

-2

No, no es posible porque los modelos django son objetos en lugar de una tabla. entonces las acciones de la tabla no son aplicables a los modelos django. y django crea un objeto y luego inserta datos en la tabla, por lo que no puede crear múltiples objetos a la vez.

+3

Considerando las respuestas anteriores que realmente funcionan, decir que no es posible parece una locura. – boatcoder

9

Aquí está la manera de hacer inserciones por lotes que todavía pasan por el ORM de Django (y así conserva los muchos beneficios que proporciona el ORM). Este enfoque implica la subclasificación de la clase InsertQuery y la creación de un administrador personalizado que prepara las instancias del modelo para su inserción en la base de datos de la misma manera que lo hace el método save() de Django. La mayoría del código para la clase BatchInsertQuery a continuación es directamente de la clase InsertQuery, con solo unas pocas líneas clave agregadas o modificadas. Para usar el método batch_insert, pase un conjunto de instancias de modelo que desee insertar en la base de datos. Este enfoque libera el código en sus vistas de tener que preocuparse por traducir las instancias del modelo en valores de SQL válidos; la clase manager junto con la clase BatchInsertQuery maneja eso.

from django.db import models, connection 
from django.db.models.sql import InsertQuery 

class BatchInsertQuery(InsertQuery): 

    #################################################################### 

    def as_sql(self): 
     """ 
     Constructs a SQL statement for inserting all of the model instances 
     into the database. 

     Differences from base class method:   

     - The VALUES clause is constructed differently to account for the 
     grouping of the values (actually, placeholders) into 
     parenthetically-enclosed groups. I.e., VALUES (a,b,c),(d,e,f) 
     """ 
     qn = self.connection.ops.quote_name 
     opts = self.model._meta 
     result = ['INSERT INTO %s' % qn(opts.db_table)] 
     result.append('(%s)' % ', '.join([qn(c) for c in self.columns])) 
     result.append('VALUES %s' % ', '.join('(%s)' % ', '.join( 
      values_group) for values_group in self.values)) # This line is different 
     params = self.params 
     if self.return_id and self.connection.features.can_return_id_from_insert: 
      col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column)) 
      r_fmt, r_params = self.connection.ops.return_insert_id() 
      result.append(r_fmt % col) 
      params = params + r_params 
     return ' '.join(result), params 

    #################################################################### 

    def insert_values(self, insert_values): 
     """ 
     Adds the insert values to the instance. Can be called multiple times 
     for multiple instances of the same model class. 

     Differences from base class method: 

     -Clears self.columns so that self.columns won't be duplicated for each 
     set of inserted_values.   
     -appends the insert_values to self.values instead of extends so that 
     the values (actually the placeholders) remain grouped separately for 
     the VALUES clause of the SQL statement. I.e., VALUES (a,b,c),(d,e,f) 
     -Removes inapplicable code 
     """ 
     self.columns = [] # This line is new 

     placeholders, values = [], [] 
     for field, val in insert_values: 
      placeholders.append('%s') 

      self.columns.append(field.column) 
      values.append(val) 

     self.params += tuple(values) 
     self.values.append(placeholders) # This line is different 

######################################################################## 

class ManagerEx(models.Manager): 
    """ 
    Extended model manager class. 
    """ 
    def batch_insert(self, *instances): 
     """ 
     Issues a batch INSERT using the specified model instances. 
     """ 
     cls = instances[0].__class__ 
     query = BatchInsertQuery(cls, connection) 
     for instance in instances: 

      values = [ (f, f.get_db_prep_save(f.pre_save(instance, True))) \ 
       for f in cls._meta.local_fields ] 
      query.insert_values(values) 

     return query.execute_sql() 

######################################################################## 

class MyModel(models.Model): 
    myfield = models.CharField(max_length=255) 
    objects = ManagerEx() 

######################################################################## 

# USAGE: 
object1 = MyModel(myfield="foo") 
object2 = MyModel(myfield="bar") 
object3 = MyModel(myfield="bam") 
MyModels.objects.batch_insert(object1,object2,object3) 
61

Estas respuestas están obsoletas. bulk_create ha sido llevado en Django 1.4:

https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create

+4

Con algún inconveniente durante bulk_create, 'El método save() del modelo no será llamado, y las señales pre_save y post_save no serán enviadas. – Charlesliam

+0

Note en particular que" si la clave primaria del modelo es un AutoField, no recupera y establezca el atributo de clave principal, como lo hace save(), a menos que el backend de la base de datos lo soporte (actualmente PostgreSQL) ". – Ninjakannon

Cuestiones relacionadas