Django ORM - Как оставить соединение на двух условиях со многими ко многим полям - PullRequest
2 голосов
/ 19 мая 2019

У меня есть две модели:

class Subscription(models.Model):
    name = models.CharField(max_length=100, unique=True)

class Email(models.Model):
    email = models.EmailField(unique=True)
    subscriptions = models.ManyToManyField(Subscription)
    is_confirmed = models.BooleanField(default=False)

Мне нужно смоделировать SQL-запрос через django orm

select * from newsletter_subscription ss
left join newsletter_mail_subscriptions ms
    on ss.id = ms.subscription_id and ms.mail_id = <mail_id>;

чтобы узнать, подписана ли почта на каждую подписку, получив полный список доступных подписок

мне нужно что-то вроде FilteredRelated:

q = Subscription.objects.annotate(
    is_subscribe=FilteredRelation(
        'mail_subscriptions', condition=Q(mail_subscriptions__mail_id=10)
    )
)

Но FilteredRelation не поддерживает условия, охватывающие реляционные поля

1 Ответ

1 голос
/ 19 мая 2019

Нет необходимости делать это с FilteredRelation, вы можете написать это следующим образом:

Subscription.objects.filter(
    <b>email__id=</b><i>mail_id</i>
)

с mail_id идентификатор объекта Email, который выхотите фильтровать.

Django создаст запрос, который выглядит следующим образом:

SELECT subscription.id, subscription.name
FROM subscription
INNER JOIN email_subscriptions
    ON subscription.id = email_subscriptions.subscription_id
WHERE email_subscriptions.email_id = <i>email_id</i>

В любом случае нет необходимости использовать LEFT OUTER JOIN, так как вы проверяете, является ли mail_idопределенный id, следовательно, INNER JOIN приведет к тому же набору.

Обратите внимание, что вы используете ManyToManyField, и что Django создает таблицу между двумя сущностями, но вы не можетедоступ к этой таблице возможен только при указании модели с параметром through [Django-doc] .

Вы также можете аннотировать Subscription s с помощьюis_subscribed, например:

from django.db.models import Exists, OuterRef

Subscription.objects.annotate(
    is_subscribed=Exists(
        Email.subscriptions.through.objects.filter(
            subscription_id=OuterRef('id'),
            mail_id=<i>email_id</i>
        )
    )
)

Это приводит к следующему запросу:

SELECT subscription.*,
    <b>EXISTS(</b>
        SELECT U0.id, U0.email_id, U0.subscription_id
        FROM email_subscriptions U0
        WHERE U0.subscription_id = subscription.id AND U0.mail_id = <i>email_id</i>
    <b>) AS is_subscribed</b>
FROM subscription
...