Django ORM-如何在多对多字段的两个条件下保持联接

时间:2019-05-19 15:15:37

标签: python django django-orm

我有两个模型:

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)

我需要通过Django Orm模拟SQL查询

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 个答案:

答案 0 :(得分:1)

根本不需要使用FilteredRelation来执行此操作,您可以这样写:

Subscription.objects.filter(
    email__id=mail_id
)

使用 mail_id 您要过滤的Email对象的ID。

Django将构建如下查询:

SELECT subscription.id, subscription.name
FROM subscription
INNER JOIN email_subscriptions
    ON subscription.id = email_subscriptions.subscription_id
WHERE email_subscriptions.email_id = email_id

无论如何都不需要使用LEFT OUTER JOIN,因为您检查mail_id是否是特定的id,因此INNER JOIN将得到相同的集合。

请注意,您使用的是ManyToManyField,并且Django在两个实体之间创建了一个表,但是您不能访问该表,除非您使用参数through [Django-doc]指定一个模型。

您还可以用Subscription来注释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=email_id
        )
    )
)

这将导致如下查询:

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