【问题标题】:Get object from list of objects without extra database calls - Django从对象列表中获取对象而无需额外的数据库调用 - Django
【发布时间】:2013-12-12 12:35:32
【问题描述】:

我有一个对象的导入,我想检查数据库是否已经在之前导入过,如果有,我会更新它,如果没有,我会创建一个新的。但是最好的方法是什么。

现在我有这个:

old_books = Book.objects.filter(foreign_source="import")
for book in new_books:
    try:
        old_book = old_books.get(id=book.id):
        #update book
    except:
        #create book

但这会为 new_books 中的每本书创建一个数据库调用。所以我正在寻找一种方法,它只会对数据库进行一次调用,然后从该查询集中获取对象。

Ps:不要寻找 get_or_create 之类的东西,因为更新和创建函数比这更复杂:)

--- 编辑---

我想我的解释不够好,因为答案并不能反映问题所在。所以为了更清楚(我希望):

我想根据该对象的 id 从查询集中挑选出一个对象。我想要完整的对象,所以我可以更新它并使用它更改的值保存它。所以假设我有一个包含 3 个对象 A 和 B 和 C 的查询集。然后我想要一种方法来询问查询集是否有对象 B 以及是否有然后获取它,而无需额外的数据库调用。

【问题讨论】:

    标签: django


    【解决方案1】:

    假设new_booksBook 的另一个查询集,您可以尝试过滤它的id

    old_books = Book.objects.filter(foreign_source="import").filter(id__in=[b.id for b in new_books])
    

    有了这个old_books 已经创建了书籍。

    【讨论】:

    • 这只会限制 old_books 中的对象数量,但如果有新书的旧版本,我仍然会遇到同样的问题 - 无需进行另一个数据库调用
    【解决方案2】:

    您可以使用values_list('id', flat=True) 在单个数据库调用中获取所有 ID(比查询集快得多)。然后你可以使用集合来找到交叉点。

    new_book_ids = new_books.values_list('id', flat=True)
    old_book_ids = Book.objects.filter(foreign_source="import") \
                               .values_list('id', flat=True)
    to_update_ids = set(new_book_ids) & set(old_book_ids)
    to_create_ids = set(new_book_ids) - to_update_ids
    

    -- 编辑(包括更新的部分)--

    我猜你面临的问题是批量更新而不是批量获取。

    如果更新很简单,那么这样的事情可能会起作用:

    old_book_ids = Book.objects.filter(foreign_source="import") \
                               .values_list('id', flat=True)
    to_update = []
    to_create = []
    for book in new_books:
        if book.id in old_book_ids:
            # list of books to update
            # to_update.append(book.id)
        else:
            # create a book object
            # Book(**details)
    # Update books
    Book.objects.filter(id__in=to_update).update(field='new_value')
    Book.objects.bulk_create(to_create)
    

    但是如果更新比较复杂(更新字段依赖于相关字段),那么你可以检查 MySQL 中的insert... on duplicated key update 选项和its custom manager for Django

    如果以上内容完全偏离轨道,请发表评论。

    【讨论】:

      【解决方案3】:

      您必须执行多个查询。你需要两组对象,你不能像那样同时获取它们任意拆分它们。没有 bulk_get_or_create 方法。

      但是,您提供的示例代码将对 每个对象 进行查询,这确实不是很有效(或 djangoic)。相反,使用__in 子句创建智能子查询,然后您可以将数据库命中限制为仅两个查询:

      old_to_update = Book.objects.filter(foreign_source="import", pk__in=new_books)
      old_to_create = Book.objects.filter(foreign_source="import").exclude(pk__in=new_books)
      

      Django 足够聪明,知道如何在该上下文中使用 new_books 查询集(它也可以是常规的 id 列表)

      更新

      Queryset 对象只是一种对象列表。所以你现在需要做的就是遍历对象:

      for book in old_to_update:
          #update book
      
      for book in old_to_create:
          #create book
      

      此时,当它从 QuerySet 中获取书籍时,不是从数据库中,这比为每一本书都使用 .get() 要高效得多 - 你会得到同样的结果。每次迭代您都可以使用一个对象,就像您从直接的 .get() 调用中获得它一样。

      【讨论】:

      • 你好尤维。我不明白为什么我不断得到不回答问题的答案。您的回答只会给我 2 个查询集,但我不是在寻找查询集,因为我必须对对象而不是查询集执行操作。所以我想做的是从我的查询集中挑选每个对象,这样我就可以单独对它们执行操作。这就是问题所在 -> 如何从查询集中挑选“获取”对象 -> 如何在不进行数据库调用的情况下查询查询集以获取其中的一个对象。
      • 获得查询集后,您可以循环对象并像普通列表一样操作它们。那时,您将不再访问数据库(更新对象时除外)。 在你自己的帖子中这样做的方式,使用get 方法将为每个对象进行数据库命中,这是非常低效的。一般来说,除非您正在寻找一个特定对象,否则没有理由使用get
      • 但我是。我不是在问如何遍历查询集。我在问如何根据该对象的 id 从查询集中挑选对象。这是我在其他任何地方都找不到好的答案的问题
      • 你可以只做Book.objects.filter(...).get(...),但是如果你对一堆对象这样做,效率非常低(它是对每个对象的数据库查询)。 QuerySet 是一个对象列表。对不起,我只是不明白你有什么困惑
      • 确实它效率低下,而且正是我在问题中所描述的问题。所以我正在寻找一种有效的方法
      【解决方案4】:

      我发现的最佳解决方案是使用 python next() 函数。

      首先将查询集评估为一个集合,然后选择您需要的书与下一个:

      old_books = set(Book.objects.filter(foreign_source="import"))
      old_book = next((book for book in existing_books if book.id == new_book.id), None )
      

      这样,每次您需要从查询集中获取特定书籍时,都不会查询数据库。然后你可以这样做:

      if old_book:
          #update book
          old_book.save()
      else:
          #create new book
      

      Django 1.7 中有一个 update_or_create() 方法可以更好地解决这个问题:https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.update_or_create

      【讨论】:

        猜你喜欢
        • 2022-08-05
        • 1970-01-01
        • 2017-12-17
        • 2018-12-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-10
        • 2014-09-17
        相关资源
        最近更新 更多