如何在 Django 1.9 中表达 sqlite LEFT OUTER JOIN?

How to express a sqlite LEFT OUTER JOIN in Django 1.9?

我正在尝试 select 多个 Content 对象及其相关的 Vote 特定用户的值,或者如果 Content 对象没有,则为“0” ' 没有该用户的相关 Vote 值。 (简化的)模型看起来像这样

class Content(models.Model):
    content_type = models.CharField(max_length=1, choices=choices, null=False)
    points = models.PositiveIntegerField(null=False, default=0)

class Vote(models.Model):
    user = models.ForeignKey(User, related_name='votes', null=False)
    content = models.ForeignKey(Content, related_name='votes', null=False)
    value = models.SmallIntegerField(null=False)  # either 1 or -1

这是 SQLite 中的一个简单的 LEFT OUTER JOIN,它将字段 is_upvoteis_downvote 附加到 Content 对象,具体取决于它找到的 Vote 值。

SELECT c.id, c.points, 
    COALESCE((v.value == 1), 0) AS is_upvote, 
    COALESCE((v.value == -1), 0) AS is_downvote 
FROM pgm4app_content c 
LEFT OUTER JOIN pgm4app_vote v ON v.content_id = c.id AND v.user_id=1 
WHERE c.content_type='q';

有没有办法在 Django 中表达这个?我发现 this answer from 3 years ago 建议进行两次单独的查找,然后将其加入 Python。从那时起 Django ORM 有变化吗?

加入 Python 看起来有点像这样

c = Content.objects.filter(content_type='q')
v = {obj.user_id: obj.value for obj in Vote.objects.filter(user=1, content__in=c)}
for obj in c:
    obj.is_upvote = v.get(obj.id, 0) == 1
    obj.is_downvote = v.get(obj.id, 0) == -1

是的!您可以在较新版本的 Django 中通过 ORM 执行此操作 - 请参阅 documentation on conditional expressions:

from django.db.models import IntegerField, Case, When, Sum, Q

Content.objects.all().annotate(
    num_votes=Sum(Case(
            When(Q(votes__user__pk=1) & Q(votes__value=1), then=1),
            When(Q(votes__user__pk=1) & Q(votes__value=-1), then=-1),
            default=0,
            output_field=IntegerField()
            )
        )
    )

这生成的 SQL 查询类似于:

SELECT "myapp_content"."id", "myapp_content"."points", 
SUM(CASE WHEN ("myapp_vote"."user_id" = 1 AND "myapp_vote"."value" = 1) THEN 1 
WHEN ("myapp_vote"."user_id" = 1 AND "myapp_vote"."value" = -1) THEN -1 ELSE 0 END) 
AS "num_votes" FROM "myapp_content" 
LEFT OUTER JOIN "myapp_vote" ON ("myapp_content"."id" = "myapp_vote"."content_id") 
GROUP BY "myapp_content"."id", "myapp_content"."points"

而不是两个属性 is_upvoteis_downvote 而是 returns 一个 属性 num_votes0, [=16] =] 或 -1 取决于用户的投票方式。这可以简单地转换为 Python.

中的两个属性

(我确信一定可以在数据库中生成两个属性,但我的第一次尝试失败了!)。