Django模型 - 如何过滤ForeignKey对象的数量

时间:2008-11-03 10:38:55

标签: python django database-design

我有一个模型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加入,则不应显示池。

这种模型设计有可能吗?或者我应该稍微修改一下?

5 个答案:

答案 0 :(得分:118)

问题和选择的答案来自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.objects.filter(B__is_available=True).annotate(num_b=Count('b')).filter(num_b__gt=0).order_by('-num_items')

答案 1 :(得分:7)

听起来像是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)

答案 2 :(得分:3)

我建议您修改您的设计,在A上添加一些状态字段。

问题是“为什么?”为什么A有&lt; 2 B和为什么A有&gt; = 2 B'。是因为用户没有输入什么东西吗?或者是因为他们尝试过,他们的输入有错误。或者是因为&lt;在这种情况下,2规则不适用。

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

此外,您还有以下选项

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

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


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

您不会“观察”任何内容 - 您提供的API可以满足您的需求。这是Django模型的核心优势。这是一种方法,使用A类中的explict方法。

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

答案 3 :(得分:1)

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

答案 4 :(得分:-1)

这样做怎么样?

queryset = A.objects.filter(b__gt=1).distinct()