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


76

有没有一种方法可以获取使用django 1.4+中的bulk_create功能创建的项目的主键?


我也很想知道人们如何解决这个问题。我想您必须执行一些操作,例如锁定表,运行bulk_create,查询所有新记录然后解锁表?从文档中似乎很明显,bulk_create不返回auto_increment键,因此解决此问题的唯一方法是进行复杂的处理。我想的另一种方法是拥有另一个表,该表用于按顺序跟踪使用的主键,因此您事先分配了一个ID块,然后运行bulk_create,并且应该知道预期的主键。我
对这


1
哦耶!看来我大约4年的旧建议刚刚融入了股票Django 1.10中,让我们所有人都可以享用。:-)我现在只猜测postgres的作品。
塔特尔

现在可以在Django 1.10和PostgreSQl中使用:docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create
Maxime R.

希望可能也有对mysql的支持
Roel

Answers:


66

2016年

从Django 1.10开始-现在受支持(仅在Postgres上),这是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()创建的对象上设置主键


9
欢迎来到未来
Trinh Hoang Nhu

2
可悲的是一个mysql用户
Roel

4
如果在mysql中怎么办?bulk_create创建的条目在数据库中是否有一个id值?
Mohammed Shareef C

1
@MohammedShareefC它将在数据库中获取一个主键,但是该bulk_create方法返回的列表与您提供的列表相同,并且本地对象(该列表的成员)未设置它,因为pyriku在他的答案中演示了这一点
Yushin Washio

在支持它的数据库上(除PostgreSQL <9.5和Oracle以外的所有数据库),将ignore_conflicts参数设置为True会告诉数据库忽略插入任何失败约束的行的失败,例如重复的唯一值。启用此参数将禁用在每个模型实例上设置主键(如果数据库正常支持的话)。
尤金

30

根据文档,您无法执行此操作:https : //docs.djangoproject.com/en/dev/ref/models/querysets/#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)。如果出于某些原因想要pk,则需要以经典方式保存对象。


18
我认为这就是问题的重点,或者至少是我的解释方式,即:人们使用什么技术来克服的限制bulk_create,以便可靠地检索创建的ID?
DanH 2013年

3
有一个开放的PR可以添加对从bulk_create 返回ID的支持:github.com/django/django/pull/5166值得注意的是Postgres支持返回ID,因此有一种方法可以通过原始sql操作立即获取ID。
gordonc

27

我可以想到两种方法:

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)

这有一个限制,即必须有一个字段来存储创建对象的时间。


2
您知道,我已经有了一个date_created字段,因此尽管可以轻松添加一个字段,但是这仍然可以工作。我唯一关心的是,多个查询可能同时命中数据库,因此我想我需要bulk_createcreated_at查询之前和之后实施某种锁定机制。
DanH 2013年

是的,可以使用原子事务来确保避免竞争状况。
karthikr 2013年

关于第一种方法,在Django 1.10中,values_list('id',flat = True)返回一个查询集,该查询集似乎在调用bulk_create之后进行了评估-在list()中包装category_ids以强制数据库查询,这很有帮助。
乔治

甚至我都觉得这select max(id) is better

1
@ deathangel908不要这样做max(id),我尝试了它并遇到了问题。MariaDB文档明确声明,除了唯一性之外,不要承担与PK有关的任何其他事情。
帕特里克

13

实际上,我的同事已经提出了以下解决方案,现在看来非常明显。添加一个名为的新列bulk_ref,您将在其中填充一个唯一值并为每一行插入。然后,只需使用bulk_ref预先设置的查询表,瞧,就可以检索您插入的记录。例如:

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)

16
将额外的字段添加到模型中以解决查询问题不是一个好习惯。
最多

1
尽管这是事实,但无论如何,散装刀片都应被视为一种优化,这可能会损害设计。在“不够快”和“不够完美的设计”之间存在一种张力,需要在此处进行权衡。在Django PR 5166发行之前,这对于需要优化批量插入的团队来说可能是一个合理的折衷方案。
Scott A

如果在应用程序中的不同时间多次调用了批量创建,那么我们需要每次都更新一次bulk_ref,这需要一个
statis


1
@DanH似乎是避免查询的合理选择,为此添加一个额外的字段实际上可能非常有帮助。
varun

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)

1

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

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


0

Django文档目前正在限制规定:

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

但是,有个好消息。有几张关于bulk_create记忆的话题。上面列出票证最有可能会很快实施解决方案,但是显然不能保证准时或是否会成功。

因此,有两种可能的解决方案,

  1. 等待,看看此补丁是否可以投入生产。您可以通过测试规定的解决方案并让django社区了解您的想法/问题来为您提供帮助。https://code.djangoproject.com/attachment/ticket/19527/bulk_create_and_create_schema_django_v1.5.1.patch

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


0

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



0

bulk_create与结合使用时,@ Or Duan建议的方法适用于PostgreSQL ignore_conflicts=False。当ignore_conflicts=True设置,那么你没有得到的值AutoField(通常是ID)在返回的对象。


-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>]

1
问题是,是否可以使用bulk_create取回主键。bulk_create()不会在创建的对象上设置主键!
kissgyorgy 2013年

在打印出的对象中,缺少主键。
弗罗斯特(Frost)2015年

1
可以肯定,这现在可以在Postgres中使用。
TankorSmash
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.