如何获取使用django bulk_create创建的对象的主键

时间:2013-04-10 18:33:40

标签: django django-models django-views django-queryset

有没有办法在django 1.4 +中使用bulk_create功能获取您创建的项目的主键?

12 个答案:

答案 0 :(得分:46)

2016

由于Django 1.10 - 现在支持(仅限Postgres),这里是link to the doc

>>> list_of_objects = Entry.objects.bulk_create([
...     Entry(headline="Django 2.0 Released"),
...     Entry(headline="Django 2.1 Announced"),
...     Entry(headline="Breaking: Django is awesome")
... ])
>>> list_of_objects[0].id
1

从更改日志中

  

在Django 1.10中更改:   添加了

时,支持在使用PostgreSQL时使用bulk_create()创建的对象上设置主键

答案 1 :(得分:27)

根据文档,您无法执行此操作:https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create

bulk-create就是为了这个:以有效的方式创建大量对象,节省了大量的查询。但这意味着你得到的反应有点不完整。如果你这样做:

>>> categories = Category.objects.bulk_create([
    Category(titel="Python", user=user),
    Category(titel="Django", user=user),
    Category(titel="HTML5", user=user),
])

>>> [x.pk for x in categories]
[None, None, None]

这并不意味着您的类别没有pk,只是查询没有检索它们(如果密钥是AutoField)。如果由于某种原因需要pks,则需要以经典方式保存对象。

答案 2 :(得分:23)

我能想到的两种方法:

a)你可以做到

category_ids = Category.objects.values_list('id', flat=True)
categories = Category.objects.bulk_create([
    Category(title="title1", user=user, created_at=now),
    Category(title="title2", user=user, created_at=now),
    Category(title="title3", user=user, created_at=now),
])
new_categories_ids = Category.objects.exclude(id__in=category_ids).values_list('id', flat=True)

如果查询集非常庞大,这可能会有点贵。

b)如果模型有created_at字段,

now = datetime.datetime.now()
categories = Category.objects.bulk_create([
    Category(title="title1", user=user, created_at=now),
    Category(title="title2", user=user, created_at=now),
    Category(title="title3", user=user, created_at=now),
])

new_cats = Category.objects.filter(created_at >= now).values_list('id', flat=True)

这有一个限制,即在创建对象时存储一个字段。

答案 3 :(得分:11)

实际上,我的同事提出了以下解决方案,现在看来显而易见。添加一个名为bulk_ref的新列,您可以使用唯一值填充该列,并为每一行插入。然后,只需事先用bulk_ref设置查询表,然后检索插入的记录。 e.g:

cars = [Car(
    model="Ford",
    color="Blue",
    price="5000",
    bulk_ref=5,
),Car(
    model="Honda",
    color="Silver",
    price="6000",
    bulk_ref=5,
)]
Car.objects.bulk_create(cars)
qs = Car.objects.filter(bulk_ref=5)

答案 4 :(得分:3)

我将与您分享 AUTO_INCREMENT InnoDB 中的 (MySQL) 处理以及 bulk_create (Django) 时获取主键的方法

根据bulk_create doc If the model’s primary key is an AutoField it does not retrieve and set the primary key attribute, as save() does, unless the database backend supports it (currently PostgreSQL).所以我们需要在Django或者MySQL中找出问题的原因,然后再寻找解决方案。

Django 中的 AUTO FIELD 实际上是 MySQL 中的 AUTO_INCREMENT。它用于为新行生成唯一标识 (ref)

您想要 bulk_create 对象 (Django) 意味着 insert multiple rows in a single SQL query。但是如何检索最近自动生成的 PK(主键)?感谢LAST_INSERT_IDIt returns first value automatically generated of the most recently executed INSERT statement...This value cannot be affected by other clients, even if they generate AUTO_INCREMENT values of their own. This behavior ensures that each client can retrieve its own ID without concern for the activity of other clients, and without the need for locks or transactions.

我鼓励您阅读 AUTO_INCREMENT Handling in InnoDB 并阅读 Django 代码 django.db.models.query.QuerySet.bulk_create 以了解为什么 Django 尚不支持 MySQl。这真有趣。请回到这里并评论您的想法。

接下来,我将向您展示示例代码:

from django.db import connections, models, transaction
from django.db.models import AutoField, sql

def dict_fetch_all(cursor):
    """Return all rows from a cursor as a dict"""
    columns = [col[0] for col in cursor.description]
    return [
        dict(zip(columns, row))
        for row in cursor.fetchall()
    ]

class BulkQueryManager(models.Manager):
    def bulk_create_return_with_id(self, objs, batch_size=2000):
        self._for_write = True
        fields = [f for f in self.model._meta.concrete_fields if not isinstance(f, AutoField)]
        created_objs = []
        with transaction.atomic(using=self.db):
            with connections[self.db].cursor() as cursor:
                for item in [objs[i:i + batch_size] for i in range(0, len(objs), batch_size)]:
                    query = sql.InsertQuery(self.model)
                    query.insert_values(fields, item)
                    for raw_sql, params in query.get_compiler(using=self.db).as_sql():
                        cursor.execute(raw_sql, params)
                    raw = "SELECT * FROM %s WHERE id >= %s ORDER BY id DESC LIMIT %s" % (
                        self.model._meta.db_table, cursor.lastrowid, cursor.rowcount
                    )
                    cursor.execute(raw)
                    created_objs.extend(dict_fetch_all(cursor))

        return created_objs

class BookTab(models.Model):
    name = models.CharField(max_length=128)
    bulk_query_manager = BulkQueryManager()

    class Meta:
        db_table = 'book_tab'


def test():
    x = [BookTab(name="1"), BookTab(name="2")]
    create_books = BookTab.bulk_query_manager.bulk_create_return_with_id(x)
    print(create_books)  # [{'id': 2, 'name': '2'}, {'id': 1, 'name': '1'}]

这个想法是使用 cursor 执行 raw insert sql,然后返回 created_records。根据{{​​1}},它确保不会有任何记录中断您的AUTO_INCREMENT handling in InnoDB PK objs (cursor.lastrowid)。

奖励:它正在我的公司进行生产。但是你需要关心 cursor.lastrowid - len(objs) + 1 to cursor.lastrowid 为什么 Django 不支持它。

答案 5 :(得分:1)

# datatime.py
# my datatime function
def getTimeStamp(needFormat=0, formatMS=True):
    if needFormat != 0:
        return datetime.datetime.now().strftime(f'%Y-%m-%d %H:%M:%S{r".%f" if formatMS else ""}')
    else:
        ft = time.time()
        return (ft if formatMS else int(ft))


def getTimeStampString():
    return str(getTimeStamp()).replace('.', '')


# model
    bulk_marker = models.CharField(max_length=32, blank=True, null=True, verbose_name='bulk_marker', help_text='ONLYFOR_bulkCreate')



# views
import .........getTimeStampString

data_list(
Category(title="title1", bulk_marker=getTimeStampString()),
...
)
# bulk_create
Category.objects.bulk_create(data_list)
# Get primary Key id
Category.objects.filter(bulk_marker=bulk_marker).values_list('id', flat=True)

答案 6 :(得分:1)

我尝试了许多策略来解决MariaDB / MySQL的这一局限性。最后,我想到的唯一可靠的解决方案是在应用程序中生成主键。不要自己生成INT AUTO_INCREMENT PK字段,即使在隔离级别为serializable的事务中也不起作用,因为MariaDB中的PK计数器不受事务锁定的保护。

解决方案是向模型添加唯一的UUID字段,在模型类中生成其值,然后将其用作标识符。当您将一堆模型保存到数据库中时,仍然不会取回它们的实际PK,但这很好,因为在随后的查询中,您可以使用其UUID唯一地标识它们。

答案 7 :(得分:0)

django documentation目前在限制条件下声明:

  

如果模型的主键是AutoField,则它不会检索和   设置主键属性,如save()所做。

但是,有好消息。有几张票从内存中谈到bulk_createticket listed above最有可能获得解决方案,很快就会实施,但显然没有时间保证,或者它是否会成功。

所以有两种可能的解决方案,

  1. 等一下,看看这个补丁是否能投入生产。您可以通过测试所述解决方案来帮助解决这个问题,并让django社区了解您的想法/问题。 https://code.djangoproject.com/attachment/ticket/19527/bulk_create_and_create_schema_django_v1.5.1.patch

  2. 覆盖/编写自己的批量插入解决方案。

答案 8 :(得分:0)

可能最简单的解决方法是手动分配主键。它取决于特定情况,但有时它足以从表中的max(id)+1开始并指定在每个对象上递增的数字。但是,如果多个客户端可能同时插入记录,则可能需要一些锁定。

答案 9 :(得分:0)

这在Django库存中不起作用,但有一个patch in the Django bug tracker使bulk_create设置了创建对象的主键。

答案 10 :(得分:0)

bulk_createignore_conflicts=False结合使用时,@ Or Duan建议的方法适用于PostgreSQL。设置ignore_conflicts=True后,您将无法在返回的对象中获得AutoField(通常是ID)的值。

答案 11 :(得分:-7)

这应该有用。

categories = Category.objects.bulk_create([
    Category(titel="Python", user=user),
    Category(titel="Django", user=user),
    Category(titel="HTML5", user=user),
])


>>> categories[0]
[<Category: Python>]
>>> categories[1]
[<Category: Django>]