【问题标题】:Django models - how to filter number of ForeignKey objectsDjango 模型 - 如何过滤 ForeignKey 对象的数量
【发布时间】:2010-09-20 11:21:57
【问题描述】:

我有一个模型AB,是这样的:

class A(models.Model):
  title = models.CharField(max_length=20)
  (...)

class B(models.Model):
  date = models.DateTimeField(auto_now_add=True)
  (...)
  a = models.ForeignKey(A)

现在我有一些 AB 对象,我想获得一个查询,该查询选择所有 A 对象少于 2 个 B 指向它们。

A 类似于池,用户(B)加入池。如果只有 1 或 0 加入,则根本不应该显示池。

这样的模型设计是否可行?还是我应该稍微修改一下?

【问题讨论】:

    标签: python django database-design


    【解决方案1】:

    问题和选择的答案来自 2008 年,从那时起,此功能已集成到 django 框架中。由于这是“django 过滤器外键计数”的热门谷歌点击,我想使用Aggregation 为最近的 django 版本添加一个更简单的解决方案。

    from django.db.models import Count
    cats = A.objects.annotate(num_b=Count('b')).filter(num_b__lt=2)
    

    就我而言,我不得不将这个概念更进一步。我的“B”对象有一个名为 is_available 的布尔字段,我只想返回具有 0 个以上 B 对象且 is_available 设置为 True 的 A 对象。

    A.objects.filter(B__is_available=True).annotate(num_b=Count('b')).filter(num_b__gt=0).order_by('-num_items')
    

    【讨论】:

    • 我试过这样做,但它真的很慢(就像在答案 1 中使用“额外”几乎是即时的,但这样每次输入都需要超过 5 秒)。有没有更快的方法而不陷入原始 SQL 中?
    • 添加额外的原始 SQL “打破”了使用 ORM 的想法
    • @llazzaro - 不,它没有。 Django 文档从来没有说过“你不应该使用 SQL,否则你会破坏抽象”。如果他们有,他们不会让你这样做。当然你总是避免它,但有时你不得不这样做。
    • @llazzaro 很好,但问题是关于 Django 的。你评论的答案是关于 Django。
    • 这只是我在抱怨和质疑 ORM DB 抽象东西的有用性。就个人而言,生成像SELECT * FROM yourapp_a WHERE id IN (SELECT a_id FROM yourapp_b GROUP BY a_id HAVING COUNT(*) < 2) 这样的 SQL 查询比破译每个单独框架的愚蠢语法来处理应该是一个简单的数据库事务要容易得多。我真的认为这更像是对学习/了解 SQL 的厌恶,这是不幸的,因为它在许多数据库平台上都非常符合。只是我的两分钱。
    【解决方案2】:

    听起来像是extra 的工作。

    A.objects.extra(
        select={
            'b_count': 'SELECT COUNT(*) FROM yourapp_b WHERE yourapp_b.a_id = yourapp_a.id',
        },
        where=['b_count < 2']
    )
    

    如果 B 计数是您经常需要作为过滤或排序标准的东西,或者需要显示在列表视图中,您可以考虑通过在 A 模型中添加 b_count 字段并使用信号在 B被添加或删除:

    from django.db import connection, transaction
    from django.db.models.signals import post_delete, post_save
    
    def update_b_count(instance, **kwargs):
        """
        Updates the B count for the A related to the given B.
        """
        if not kwargs.get('created', True) or kwargs.get('raw', False):
            return
        cursor = connection.cursor()
        cursor.execute(
            'UPDATE yourapp_a SET b_count = ('
                'SELECT COUNT(*) FROM yourapp_b '
                'WHERE yourapp_b.a_id = yourapp_a.id'
            ') '
            'WHERE id = %s', [instance.a_id])
        transaction.commit_unless_managed()
    
    post_save.connect(update_b_count, sender=B)
    post_delete.connect(update_b_count, sender=B)
    

    另一种解决方案是在添加或删除相关 B 时管理 A 对象上的状态标志。

    B.objects.create(a=some_a)
    if some_a.hidden and some_a.b_set.count() > 1:
        A.objects.filter(id=some_a.id).update(hidden=False)
    
    ...
    
    some_a = b.a
    some_b.delete()
    if not some_a.hidden and some_a.b_set.count() < 2:
        A.objects.filter(id=some_a.id).update(hidden=True)
    

    【讨论】:

    • 解决方案太复杂,而Django有注解功能。
    • 不正确标记为正确解决方案。请参阅下面的 gravitron 解决方案。
    【解决方案3】:

    我建议修改您的设计以在 A 上包含一些状态字段。

    问题是“为什么?”之一。为什么 A 有 = 2 个 B。是因为用户没有输入什么吗?或者是因为他们尝试过并且他们的输入有错误。还是因为

    使用存在或不存在外键将含义限制为 - 嗯 - 存在或不存在。你没有办法表达“为什么?”

    此外,您还有以下选择

    [ a for a in A.objects.all() if a.b_set.count() < 2 ]
    

    这可能会很昂贵,因为它会获取所有 A,而不是强制数据库完成这项工作。


    编辑:来自评论“需要我注意用户加入/用户离开池事件”。

    您不会“观看”任何东西——您提供的 API 可以满足您的需求。这是 Django 模型的核心优势。这是一种方法,在 A 类中使用显式方法。

    class A( models.Model ):
        ....
        def addB( self, b ):
            self.b_set.add( b )
            self.changeFlags()
        def removeB( self, b ):
            self.b_set.remove( b )
            self.changeFlags()
        def changeFlags( self ):
            if self.b_set.count() < 2: self.show= NotYet
            else: self.show= ShowNow
    

    您还可以为此定义一个特殊的Manager,并将默认的b_set 经理替换为您的经理,该经理计算引用和更新A

    【讨论】:

    • A 类似于池的东西,用户(B)加入池。如果只有 1 或 0 加入,则根本不应该显示池......这就是为什么我不想包含这种状态 - 需要我注意用户加入/用户离开池事件。但是,也许就是这样……
    【解决方案4】:

    我认为加入或离开池可能不像列出(显示)池那样频繁。我也相信用户加入/离开操作来更新池显示状态会更有效。这样,列出和显示池将需要更少的时间,因为您只需对池对象的 SHOW_STATUS 运行单个查询。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-19
      • 1970-01-01
      • 1970-01-01
      • 2013-03-16
      • 1970-01-01
      • 2018-01-10
      • 2020-07-02
      相关资源
      最近更新 更多