Как выполнить пакетную вставку в Django? - PullRequest
34 голосов
/ 16 апреля 2010

В mysql вы можете вставить несколько строк в таблицу в одном запросе для n> 0:

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

Есть ли способ достичь вышеуказанного с помощью методов набора запросов Django? Вот пример:

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

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

Я полагаю, что приведенное выше вызывает запрос вставки для каждой итерации цикла for. Я ищу один запрос, это возможно в Django?

Ответы [ 5 ]

68 голосов
/ 03 февраля 2013

Эти ответы устарели. bulk_create было внесено в Django 1.4:

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

12 голосов
/ 17 апреля 2010

Я недавно искал такую ​​вещь сам (вдохновленный QuerySet.update () , как я полагаю, вы тоже). Насколько мне известно, в текущей производственной среде массового создания не существует (1.1.1 на сегодняшний день). В итоге мы создали собственный менеджер для модели, которая требовала массового создания, и создали функцию этого менеджера для построения соответствующего оператора SQL с последовательностью параметров VALUES.

Что-то вроде (извините, если это не работает ... надеюсь, я адаптировал это из нашего кода):

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', )] )

Этот подход рискует быть очень специфичным для конкретной базы данных. В нашем случае мы хотели, чтобы функция возвращала только что созданные идентификаторы, поэтому у нас был специфический для postgres запрос для генерации необходимого количества идентификаторов из последовательности первичных ключей для таблицы, которая представляет объект. Тем не менее, он работает значительно лучше в тестах по сравнению с итерациями по данным и выдачей отдельных операторов QuerySet.create ().

9 голосов
/ 11 августа 2010

Вот способ сделать пакетную вставку, которая все еще проходит через ORM Django (и, таким образом, сохраняет много преимуществ, которые обеспечивает ORM). Этот подход включает создание подкласса класса InsertQuery, а также создание настраиваемого менеджера, который подготавливает экземпляры модели для вставки в базу данных практически так же, как метод save () Django. Большая часть кода для класса BatchInsertQuery ниже взята из класса InsertQuery, с добавлением или изменением всего нескольких ключевых строк. Чтобы использовать метод batch_insert, передайте набор экземпляров модели, которые вы хотите вставить в базу данных. Такой подход освобождает код в ваших представлениях от необходимости беспокоиться о переводе экземпляров модели в допустимые значения SQL; класс менеджера в сочетании с классом BatchInsertQuery обрабатывает это.

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)
4 голосов
/ 17 апреля 2010

Вы можете получить необходимую производительность, выполняя ручные транзакции. Это позволит вам создать все вставки в одной транзакции и затем зафиксировать транзакцию сразу Надеюсь, это поможет вам: http://docs.djangoproject.com/en/dev/topics/db/transactions/

0 голосов
/ 17 апреля 2010

Нет, это невозможно, потому что модели django являются объектами, а не таблицей.поэтому действия с таблицами не применимы к моделям Django.и django создает объект, затем вставляет данные в таблицу, поэтому вы не можете создать несколько объектов за один раз.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...