50

how can i have a subquery in django's queryset? for example if i have:

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

this is what i have done yet.

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

but it not working because it returns two output...

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
gadss
  • 21,687
  • 41
  • 104
  • 154
  • 3
    Your example is not very good. You don't need a subquery for this: `select name, age from person, employee where person.id = employee.id and employee.company = 'Private'` – ypercubeᵀᴹ Dec 19 '11 at 02:18

6 Answers6

69

as mentioned by ypercube your use case doesn't require subquery.

but anyway since many people land into this page to learn how to do sub-query here is how its done.

from django.db.models import Subquery

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

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

Just a nice guy
  • 549
  • 3
  • 19
Ramast
  • 7,157
  • 3
  • 32
  • 32
  • 1
    Mentioned on docs: https://docs.djangoproject.com/en/1.9/ref/models/querysets/#in " can also use a queryset to dynamically evaluate the list of values instead of providing a list of literal values:" and "This queryset will be evaluated as subselect statement". – Ciro Santilli OurBigBook.com Jun 08 '16 at 21:19
  • 3
    In django 3.2 .only('id') is .values_list('id', flat=True) – Hippolyte BRINGER Dec 17 '21 at 20:52
53
ids = Employee.objects.filter(company='Private').values_list('id', flat=True)
Person.objects.filter(id__in=ids).values('name', 'age')
Jan Pöschko
  • 5,412
  • 1
  • 28
  • 28
  • 24
    `values_list` returns a `ValuesQuerySet` and those two lines will actually translate into a single query with a subquery. – BBT Mar 20 '17 at 17:07
  • 10
    This will in fact generate **only one** query, meaning the answer is correct. Additionally, 1. `.values('id')` also works just the same and 2. the use of the `pk` field instead is recommended. – emyller Nov 21 '17 at 18:37
  • I think the behaviour of `values_list` depends on django version. This was asked 7 years ago, and I guess some pre-py3-compatible version of django returns `list`, while the later versions return `ValuesQuerySet` and thus constructing subquery. – smido Jul 25 '19 at 12:19
  • 3
    @Antoine Actually, **this is a subquery**, And `.values_list()` **returns a queryset!** If you don't believe me, try [logging the sql](https://stackoverflow.com/questions/4375784/log-all-sql-queries) for this answer. – Lord Elrond Jan 10 '20 at 01:41
  • 5
    I feel obligated to upvote this since 12 people clearly don't know how querysets work. – Lord Elrond Jan 10 '20 at 01:41
  • 7
    Indeed, I don't know what made me think and say that... Maybe at the time the repr of a ValuesListQuerySet looked like a list and/or it did not translate into a subquery back in 2016 but I have no evidence to defend myself. – Antoine Pinsard Jan 10 '20 at 15:53
18

The correct answer on your question is here https://docs.djangoproject.com/en/2.1/ref/models/expressions/#subquery-expressions

As an example:

>>> 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]))
Dmitry Anch
  • 424
  • 3
  • 4
12

You can create subqueries in Django by using an unevaluated queryset to filter your main queryset. In your case, it would look something like this:

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

I'm assuming that you have a reverse relationship from Person to Employee named employee. I found it helpful to look at the SQL query generated by a queryset when I was trying to understand how the filters work.

print people.query

As others have said, you don't really need a subquery for your example. You could just join to the employee table:

people2 = Person.objects.filter(employee__company='Private')
Don Kirkby
  • 53,582
  • 27
  • 205
  • 286
1
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]))

the generated 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"

For more details, see this article.

geisterfurz007
  • 5,292
  • 5
  • 33
  • 54
vikas thakur
  • 132
  • 1
  • 4
0

Take good care with onlyif your subqueries don't select the primary key.

Example:

class Customer:
    pass

class Order:
    customer: Customer
    pass

class OrderItem:
    order: Order
    is_recalled: bool
  • Customer has Orders
  • Order has OrderItems

Now we are trying to find all customers with at least one recalled order-item.(1)

This will not work properly

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)

The code above looks very fine, but it produces the following query:


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

Instead one should use 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) Note: in a real case we might consider 'WHERE EXISTS'

reto
  • 16,189
  • 7
  • 53
  • 67