如何在 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_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 执行此操作 - 请参阅 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
而是 returns 一个 属性 num_votes
是 0
, [=16] =] 或 -1
取决于用户的投票方式。这可以简单地转换为 Python.
中的两个属性
(我确信一定可以在数据库中生成两个属性,但我的第一次尝试失败了!)。
我正在尝试 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_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 执行此操作 - 请参阅 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
而是 returns 一个 属性 num_votes
是 0
, [=16] =] 或 -1
取决于用户的投票方式。这可以简单地转换为 Python.
(我确信一定可以在数据库中生成两个属性,但我的第一次尝试失败了!)。