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

Posted

技术标签:

【中文标题】如何在 Django 1.9 中表达 sqlite LEFT OUTER JOIN?【英文标题】:How to express a sqlite LEFT OUTER JOIN in Django 1.9? 【发布时间】:2016-05-04 10:24:32 【问题描述】:

我正在尝试为特定用户选择多个Content 对象及其相关的Vote 值,如果Content 对象没有该用户的相关Vote 值,则为“0” . (简化的)模型如下所示

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 中表达这一点,看看是否有可能。 我正在考虑在获取所有记录后应用过滤器。这也可以在模板和视图中完成。 既然您已经有了查询,只需发送到 raw。 【参考方案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,而是返回一个属性num_votes,它是01-1,具体取决于用户的投票方式。这可以在 Python 中轻松转换为两个属性。

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

【讨论】:

太棒了,我之前在Case(When()) 上玩过,但还没到那里。 但是,上面的查询还不能正常工作。它返回多个Content 对象的实例,每个相关的Vote 对象都有一个实例,似乎忽略了votes__user__pk 限制。 @C14L 我已经编辑了答案以采取不同的方法(我有理由确定)有效并且不提供重复项。以前的方法确实返回多行。

以上是关于如何在 Django 1.9 中表达 sqlite LEFT OUTER JOIN?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django 1.9 中删除 DB (sqlite3) 以从头开始?

如何在 GraphQL 中表达输入验证

我将如何在 Scala 中表达链式赋值?

如何在 Eclipse PDE 中表达项目间依赖关系

让SQLite数据库中表名支持重命名的方法

如何在 EF Core 和 LINQ 中表达 GetDate() 和 DateAdd() 方法?