使用左外连接注释Django查询集?

Jam*_*ber 26 django

说我有一个模型:

class Foo(models.Model):
    ...
Run Code Online (Sandbox Code Playgroud)

和另一个基本上为每个用户提供以下信息的模型Foo:

class UserFoo(models.Model):
    user = models.ForeignKey(User)
    foo = models.ForeignKey(Foo)
    ...

    class Meta:
        unique_together = ("user", "foo")
Run Code Online (Sandbox Code Playgroud)

我想生成一个Foos的查询集,但注释与(可选)相关的UserFoo基于user=request.user.

所以它实际上是一个 LEFT OUTER JOIN on (foo.id = userfoo.foo_id AND userfoo.user_id = ...)

Bri*_*ner 16

raw可能看起来像的解决方案

foos = Foo.objects.raw("SELECT foo.* FROM foo LEFT OUTER JOIN userfoo ON (foo.id = userfoo.foo_id AND foo.user_id = %s)", [request.user.id])
Run Code Online (Sandbox Code Playgroud)

您需要修改SELECT以包含额外的字段,userfoo这些字段将注释到查询Foo集中的结果实例.

  • 以及如何选择所需的UserFoo字段? (2认同)

小智 9

注意:此方法在Django 1.6+中不起作用.正如下面tcarobruce的评论中所解释的那样,该promote论点被删除作为#19849票的一部分:ORM清理.


Django没有提供完全内置的方法来实现这一点,但是构建一个完全原始的查询并不是必需的.(此方法不用于选择工作*UserFoo,所以我使用.comment作为一个例子字段从以包括UserFoo).

QuerySet.extra() method允许我们添加条款,SELECT和WHERE我们的查询的WHERE子句.我们使用它来UserFoo在结果中包含表中的字段,并将UserFoo匹配限制为当前用户.

results = Foo.objects.extra(
    select={"user_comment": "UserFoo.comment"},
    where=["(UserFoo.user_id IS NULL OR UserFoo.user_id = %s)"],
    params=[request.user.id]
)
Run Code Online (Sandbox Code Playgroud)

此查询仍需要该UserFoo表.可以使用.extras(tables=...)获取隐式INNER JOIN,但对于OUTER JOIN,我们需要自己修改内部查询对象.

connection = (
    UserFoo._meta.db_table, User._meta.db_table,  # JOIN these tables
    "user_id",              "id",                 # on these fields
)

results.query.join(  # modify the query
    connection,      # with this table connection
    promote=True,    # as LEFT OUTER JOIN
)
Run Code Online (Sandbox Code Playgroud)

我们现在可以评估结果.每个实例都有一个.user_comment包含值的属性UserFoo,或者None如果它不存在.

print results[0].user_comment
Run Code Online (Sandbox Code Playgroud)

(感谢Colin Copeland撰写的这篇博客文章,向我展示了如何进行OUTER JOIN.)

  • 作为[ORM清理](https://code.djangoproject.com/ticket/19849)的一部分,删除了`promote`关键字.在Django 1.6中不再可用. (2认同)

Ram*_*ast 9

这个答案可能不是你正在寻找的,但是因为它是谷歌搜索"django annotate outer join"时的第一个结果,所以我将在这里发布.

注意:在Djang 1.7上测试过

假设您有以下型号

class User(models.Model):
    name = models.CharField()

class EarnedPoints(models.Model):
    points = models.PositiveIntegerField()
    user = models.ForgeinKey(User)
Run Code Online (Sandbox Code Playgroud)

要获得总用户点数,您可以执行类似的操作

 User.objects.annotate(points=Sum("earned_points__points"))
Run Code Online (Sandbox Code Playgroud)

这将工作,但它不会返回没有积分的用户,这里我们需要外连接没有任何直接黑客或原始SQL

你可以通过这样做来实现这一目标

 users_with_points = User.objects.annotate(points=Sum("earned_points__points"))
 result = users_with_points | User.objects.exclude(pk__in=users_with_points)
Run Code Online (Sandbox Code Playgroud)

这将被转换为OUTER LEFT JOIN,并且将返回所有用户.没有积分的用户将None在其points属性中拥有值.

希望有所帮助