如何在 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_upvote
和 is_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_upvote
和is_downvote
,而是返回一个属性num_votes
,它是0
、1
或-1
,具体取决于用户的投票方式。这可以在 Python 中轻松转换为两个属性。
(我确信一定可以在数据库中生成两个属性,但我的第一次尝试失败了!)。
【讨论】:
太棒了,我之前在Case(When())
上玩过,但还没到那里。
但是,上面的查询还不能正常工作。它返回多个Content
对象的实例,每个相关的Vote
对象都有一个实例,似乎忽略了votes__user__pk
限制。
@C14L 我已经编辑了答案以采取不同的方法(我有理由确定)有效并且不提供重复项。以前的方法确实返回多行。以上是关于如何在 Django 1.9 中表达 sqlite LEFT OUTER JOIN?的主要内容,如果未能解决你的问题,请参考以下文章