【问题标题】:Annotate queryset with whether matching related object exists用是否存在匹配的相关对象来注释查询集
【发布时间】:2017-03-28 17:39:41
【问题描述】:

我有两个具有显式多对多关系的模型:一个东西,auth.user,以及一个连接两者的“最喜欢”模型。我希望能够通过特定用户是否喜欢来订购我的“东西”。在 Sqlite3 中,我想出的最好的查询是(大致)这个:

select 
    *, max(u.name = "john cleese") as favorited 
    from thing as t 
        join favorite as f on f.thing_id = t.id 
        join user as u on f.user_id = u.id
    group by t.id
    order by favorited desc
    ;

在我的 sql-to-django 翻译中让我绊倒的是max(u.name = "john cleese") 位。据我所知,Django 支持算术但不支持相等。我能找到的最接近的是一个没有正确分组输出行的 case 语句:

Thing.objects.annotate(favorited=Case(
    When(favorites__user=john_cleese, then=Value(True)),
    default=Value(False),
    output_field=BooleanField()
))

我尝试过的另一个方向是使用RawSQL

Thing.objects.annotate(favorited=RawSQL('"auth_user"."username" = "%s"', ["john cleese"]))

但是,这行不通,因为(据我所知)无法显式加入我需要的 favoriteauth_user 表。

我有什么遗漏吗?

【问题讨论】:

    标签: django django-queryset


    【解决方案1】:

    这将实现您(或任何其他在此处搜索的人)想要做的事情:

    Thing.objects.annotate(
        favorited=Count(Case(
            When(
                favorites__user=john_cleese, 
                then=1
            ),
            default=0,
            output_field=BooleanField(),
        )),
    )
    

    【讨论】:

    • 我猜你是对的,但请注意,根据底层数据的大小,注释可能会非常慢。
    【解决方案2】:

    根据我在related ticket 中读到的内容,您可以使用带有Exists query expression 的子查询。

    Exists 是一个使用 SQL EXISTS 语句的子查询子类。在许多情况下,它会比子查询执行得更好,因为数据库能够在找到第一个匹配行时停止对子查询的评估。

    假设在您的 ManyToMany 案例中的中间模型称为 Favorite

    from django.db.models import Exists, OuterRef
    
    is_favorited_subquery = Favorite.objects.filter(
      thing_id = OuterRef('pk')
    )
    
    Thing.objects.annotate(favorited=Exists(is_favorited_subquery))
    

    然后你可以通过查询的favorited属性来排序。

    【讨论】:

      【解决方案3】:

      我不确定你想要达到什么目标,但我会这样开始。

      from django.db import models
      from django.contrib.auth.models import User
      
      class MyUser(models.Model):             
          person = models.OneToOneField(User)
      
      class Thing(models.Model):
          thingname = models.CharField(max_length=10)
          favorited_by = models.ManyToManyField(MyUser)
      

      在你看来:

      qs = MyUser.objects.get(id=pk_of_user_john_reese).thing_set.all()
      

      将为您提供给定用户的所有 Thing 对象。

      你应该看看Django Docs for ManyToMany

      我在几个更小甚至更大的项目中使用 Django 已有几年了,但我从未使用过 RawSQL 功能。大多数时候我都在想,我的模型设计有一个错误。

      【讨论】:

      • 我没有想过从用户开始并从那个方向解决问题,但是这种方法会过滤掉所有不喜欢的东西。我想将我所有的东西都放在一个查询集中,并能够按收藏/不收藏对它们进行排序。
      • 我仍然不确定你想要实现什么,但如果你只想获取所有事物,你可以拥有一个包含所有事物的查询集,然后可以访问收藏的对象。
      猜你喜欢
      • 1970-01-01
      • 2016-10-18
      • 2013-12-07
      • 2023-03-19
      • 1970-01-01
      • 2019-11-01
      • 1970-01-01
      • 2020-04-15
      • 2021-10-17
      相关资源
      最近更新 更多