Как выбрать из промежуточной модели «многие ко многим» в Django? - PullRequest
2 голосов
/ 29 декабря 2011

У меня есть модели книг и людей:

from django.db import models

class Book(models.Model):
    author = models.ManyToManyField('Person')

class Person(models.Model):
    name = models.CharField(max_length=16)

Я немного упростил их здесь. Как мне составить запрос на django, чтобы получить всех авторов книг? С SQL я бы сделал выборку в промежуточной таблице и соединил ее с таблицей людей, чтобы получить имя, но я не уверен, как сделать что-то подобное здесь ... Конечно, в таблице Person есть люди, которые не авторы книг, или я мог бы просто получить Person.objects.all ().

Ответы [ 4 ]

3 голосов
/ 29 декабря 2011

Самый простой способ:

Person.objects.filter(book__isnull=False)

Это выберет всех людей, с которыми связана хотя бы одна книга.

3 голосов
/ 29 декабря 2011

Так же просто, как 1,2,3 с Фильтрация аннотаций :

from django.db.models import Count

Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)

Для любопытства я сгенерировал SQL для каждого из способов, предложенных в этой теме:*

In [9]: Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name", COUNT("testapp_book_author"."book_id") AS "count_book" FROM "testapp_person" LEFT OUTER JOIN "testapp_book_author" ON ("testapp_person"."id" = "testapp_book_author"."person_id") GROUP BY "testapp_person"."id", "testapp_person"."name", "testapp_person"."id", "testapp_person"."name" HAVING COUNT("testapp_book_author"."book_id") > 0  LIMIT 21; args=(0,)
Out[9]: [<Person: Person object>]

In [10]: Person.objects.exclude(book=None)
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name" FROM "testapp_person" WHERE NOT (("testapp_person"."id" IN (SELECT U0."id" FROM "testapp_person" U0 LEFT OUTER JOIN "testapp_book_author" U1 ON (U0."id" = U1."person_id") LEFT OUTER JOIN "testapp_book" U2 ON (U1."book_id" = U2."id") WHERE (U2."id" IS NULL AND U0."id" IS NOT NULL)) AND "testapp_person"."id" IS NOT NULL)) LIMIT 21; args=()
Out[10]: [<Person: Person object>]

In [11]: Person.objects.filter(pk__in=Book.objects.values_list('author').distinct())
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name" FROM "testapp_person" WHERE "testapp_person"."id" IN (SELECT DISTINCT U1."person_id" FROM "testapp_book" U0 LEFT OUTER JOIN "testapp_book_author" U1 ON (U0."id" = U1."book_id")) LIMIT 21; args=()
Out[11]: [<Person: Person object>]

Может быть, это поможет вам выбрать.

Персонально, я предпочитаю версию Криса, потому что она самая короткая.С другой стороны, я не знаю наверняка о влиянии наличия подзапросов, которое имеет место для двух других способов.Тем не менее, они демонстрируют интересные концепции QuerySet:

  1. Annonation , агрегирование по значению набора запросов.Если вы используете агрегат (Count ('book')), то вы получите общее количество книг.Если вы используете annotate (Count ('book')), то вы получите общее количество книг на значение набора запросов (на человека).Кроме того, у каждого человека есть атрибут count_book , что довольно круто: Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)[0].count_book

  2. Подзапросы , что очень полезно для создания сложных запросов.или оптимизировать запросы (например, объединить наборы запросов, например, предварительную выборку общих отношений).

1 голос
/ 29 декабря 2011

Вы можете легко получить все идентификаторы авторов, используя Book.objects.all().values_list('author', flat=True).distinct()

Как указывает jpic ниже

Person.objects.filter(pk__in=Book.objects.values_list('author').distinct()), вы получите все объекты персонажа, а не только их идентификаторы.

0 голосов
/ 29 декабря 2011

Эта глава книги django отвечает всем потребностям вашей модели с примерами, очень похожими на ваши, включая отношение ManyToMany. http://www.djangobook.com/en/2.0/chapter10/

...