#sql #django
#django
Вопрос:
В 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?
Комментарии:
1. Обновление: в версии для разработки django будет выпущен
bulk_create
метод: docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create
Ответ №1:
Эти ответы устарели. bulk_create
было добавлено в Django 1.4:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create
Комментарии:
1. С некоторым недостатком во время bulk_create,
The model’s save() method will not be called, and the pre_save and post_save signals will not be sent.
2. Обратите особое внимание, что «если первичный ключ модели является автозаполем, он не извлекает и не устанавливает атрибут первичного ключа, как это делает save(), если только серверная часть базы данных не поддерживает его (в настоящее время PostgreSQL)».
Ответ №2:
Недавно я сам искал такую вещь (вдохновленный QuerySet.update(), как я полагаю, и вы тоже). Насколько мне известно, в текущей рабочей среде (1.1.1 на сегодняшний день) не существует массового создания. В итоге мы создали пользовательский менеджер для модели, для которой требовалось массовое создание, и создали функцию в этом менеджере для создания соответствующего оператора SQL с параметрами последовательности ЗНАЧЕНИЙ.
Что-то вроде (прошу прощения, если это не сработает … надеюсь, я адаптировал это для выполнения из нашего кода):
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() .
Комментарии:
1. Кстати. Такой подход может привести к ошибке «Пакет слишком большой» в mysql (и, возможно, в других базах данных), если у вас очень много данных. Лучше разбить ваш набор данных на более мелкие фрагменты.
Ответ №3:
Вот способ выполнения пакетных вставок, который по-прежнему проходит через ORM Django (и, таким образом, сохраняет многие преимущества, предоставляемые ORM). Этот подход включает в себя создание подкласса класса InsertQuery, а также создание пользовательского менеджера, который подготавливает экземпляры модели для вставки в базу данных во многом таким же образом, который использует метод save() Django. Большая часть кода для класса BatchInsertQuery, приведенного ниже, взята непосредственно из класса InsertQuery, с добавлением или изменением всего нескольких ключевых строк. Чтобы использовать метод batch_insert, передайте набор экземпляров модели, которые вы хотите вставить в базу данных. Такой подход освобождает код в ваших представлениях от необходимости беспокоиться о переводе экземпляров модели в допустимые значения SQL; класс manager совместно с классом 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:
Вы могли бы добиться необходимой производительности, выполняя транзакции вручную. Что это позволит вам сделать, так это создать все вставки в одной транзакции, а затем зафиксировать транзакцию всю сразу. Надеюсь, это поможет вам: http://docs.djangoproject.com/en/dev/topics/db/transactions /
Ответ №5:
Нет, это невозможно, потому что модели django являются объектами, а не таблицей. таким образом, табличные действия неприменимы к моделям django. и django создает объект, а затем вставляет данные в таблицу, поэтому вы не можете создать несколько объектов за один раз.
Комментарии:
1. Учитывая приведенные выше ответы, которые действительно работают, утверждение, что это невозможно, кажется безумием.