【问题标题】:how to subquery in queryset in django?如何在 django 的查询集中进行子查询?
【发布时间】:2012-01-23 06:52:03
【问题描述】:

如何在 django 的查询集中有一个子查询?例如,如果我有:

select name, age from person, employee where person.id = employee.id and
employee.id in (select id from employee where employee.company = 'Private')

这就是我所做的。

Person.objects.value('name', 'age')
Employee.objects.filter(company='Private')

但它不起作用,因为它返回两个输出...

【问题讨论】:

  • 你的例子不是很好。您不需要子查询:select name, age from person, employee where person.id = employee.id and employee.company = 'Private'

标签: python django


【解决方案1】:

如果您的子查询没有选择主键,请注意only

例子:

class Customer:
    pass

class Order:
    customer: Customer
    pass

class OrderItem:
    order: Order
    is_recalled: bool
  • 客户有订单
  • Order 有 OrderItems

现在我们正试图找到至少有一件召回的订单商品的所有客户。(1)

这将无法正常工作

order_ids = OrderItem.objects \
    .filter(is_recalled=True) \
    .only("order_id")

customer_ids = OrderItem.objects \
    .filter(id__in=order_ids) \
    .only('customer_id')


# BROKEN! BROKEN
customers = Customer.objects.filter(id__in=customer_ids)

上面的代码看起来很好,但它会产生以下查询:


select * from customer where id in (
    select id  -- should be customer_id
    from orders 
    where id in (
        select id -- should be order_id
        from order_items 
        where is_recalled = true))

应该使用select


order_ids = OrderItem.objects \
    .filter(is_recalled=True) \
    .select("order_id")

customer_ids = OrderItem.objects \
    .filter(id__in=order_ids) \
    .select('customer_id')


customers = Customer.objects.filter(id__in=customer_ids)

(1) 注意:在实际情况下,我们可能会考虑 'WHERE EXISTS'

【讨论】:

    【解决方案2】:
    hero_qs = Hero.objects.filter(category=OuterRef("pk")).order_by("-benevolence_factor")
    Category.objects.all().annotate(most_benevolent_hero=Subquery(hero_qs.values('name')[:1]))
    

    生成的sql

    SELECT "entities_category"."id",
           "entities_category"."name",
      (SELECT U0."name"
       FROM "entities_hero" U0
       WHERE U0."category_id" = ("entities_category"."id")
       ORDER BY U0."benevolence_factor" DESC
       LIMIT 1) AS "most_benevolent_hero"
    FROM "entities_category"
    

    更多详情请见this article

    【讨论】:

    • 这应该是评论而不是回答
    【解决方案3】:

    你的问题的正确答案在这里https://docs.djangoproject.com/en/2.1/ref/models/expressions/#subquery-expressions

    举个例子:

    >>> from django.db.models import OuterRef, Subquery
    >>> newest = Comment.objects.filter(post=OuterRef('pk')).order_by('-created_at')
    >>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values('email')[:1]))
    

    【讨论】:

      【解决方案4】:

      您可以在 Django 中创建子查询,方法是使用 unevaluated queryset 过滤您的主查询集。在您的情况下,它看起来像这样:

      employee_query = Employee.objects.filter(company='Private')
      people = Person.objects.filter(employee__in=employee_query)
      

      我假设您有一个从 PersonEmployee 的反向关系,名为 employee。当我试图了解过滤器的工作原理时,我发现查看查询集生成的 SQL 查询很有帮助。

      print people.query
      

      正如其他人所说,您的示例实际上并不需要子查询。你可以加入员工表:

      people2 = Person.objects.filter(employee__company='Private')
      

      【讨论】:

        【解决方案5】:

        正如 ypercube 所述,您的用例不需要子查询。

        但无论如何,由于很多人登陆此页面来学习如何进行子查询,这里是如何完成的。

        employee_query = Employee.objects.filter(company='Private').only('id').all()
        Person.objects.value('name', 'age').filter(id__in=employee_query)
        

        来源: http://mattrobenolt.com/the-django-orm-and-subqueries/

        【讨论】:

        • 在文档中提到:docs.djangoproject.com/en/1.9/ref/models/querysets/#in“也可以使用查询集来动态评估值列表,而不是提供文字值列表:”和“这个查询集将被评估为子选择语句”。
        • 在 django 3.2 中 .only('id') 是 .values_list('id', flat=True)
        【解决方案6】:
        ids = Employee.objects.filter(company='Private').values_list('id', flat=True)
        Person.objects.filter(id__in=ids).values('name', 'age')
        

        【讨论】:

        • values_list 返回一个ValuesQuerySet,这两行实际上将转换为带有子查询的单个查询。
        • 这实际上会生成只有一个查询,这意味着答案是正确的。此外,1. .values('id') 也同样有效,2. 建议使用 pk 字段。
        • @Antoine 实际上,这是一个子查询,而.values_list() 返回一个查询集!如果你不相信我,试试@987654321 @这个答案。
        • 我觉得有义务对此表示赞同,因为 12 个人显然不知道查询集是如何工作的。
        • 确实,我不知道是什么让我这么想和这么说...也许当时 ValuesListQuerySet 的 repr 看起来像一个列表和/或它没有转化为子查询2016 年,但我没有证据为自己辩护。
        猜你喜欢
        • 2018-11-18
        • 2013-03-16
        • 2022-01-14
        • 1970-01-01
        • 1970-01-01
        • 2021-12-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多