在`extra`之后,queryset的`count`是错误的

时间:2010-01-06 01:02:59

标签: django

当我以某种方式在Django查询集上使用extra(称之为qs)时,qs.count()的结果与len(qs.all())不同。重现:

创建一个空的Django项目和应用程序,然后添加一个简单的模型:

class Baz(models.Model):
    pass

现在制作一些对象:

>>> Baz(id=1).save()
>>> Baz(id=2).save()
>>> Baz(id=3).save()
>>> Baz(id=4).save()

使用extra方法仅选择其中一些产生预期的计数:

>>> Baz.objects.extra(where=['id > 2']).count()
2
>>> Baz.objects.extra(where=['-id < -2']).count()
2

但是向select添加extra子句并在where子句中引用它,并且计数突然错误,即使all()的结果是正确的:

>>> Baz.objects.extra(select={'negid': '0 - id'}, where=['"negid" < -2']).all()
[<Baz: Baz object>, <Baz: Baz object>]   # As expected
>>> Baz.objects.extra(select={'negid': '0 - id'}, where=['"negid" < -2']).count()
0   # Should be 2

我认为问题与django.db.models.sql.query.BaseQuery.get_count()有关。它检查BaseQuery的selectaggregate_select属性是否已设置;如果是这样,它使用子查询。但django.db.models.sql.query.BaseQuery.add_extra仅添加到BaseQuery的extra属性,而不是selectaggregate_select

我该如何解决这个问题?我知道我可以使用len(qs.all()),但能够将extra'ed查询集传递给代码的其他部分会很好,这些部分可能会在不知道的情况下调用count()它已经坏了。

1 个答案:

答案 0 :(得分:0)

重新定义get_count()和monkeypatching似乎可以解决问题:

def get_count(self):
    """
    Performs a COUNT() query using the current filter constraints.
    """
    obj = self.clone()
    if len(self.select) > 1 or self.aggregate_select or self.extra:
        # If a select clause exists, then the query has already started to
        # specify the columns that are to be returned.
        # In this case, we need to use a subquery to evaluate the count.
        from django.db.models.sql.subqueries import AggregateQuery
        subquery = obj
        subquery.clear_ordering(True)
        subquery.clear_limits()

        obj = AggregateQuery(obj.model, obj.connection)
        obj.add_subquery(subquery)

    obj.add_count_column()
    number = obj.get_aggregation()[None]

    # Apply offset and limit constraints manually, since using LIMIT/OFFSET
    # in SQL (in variants that provide them) doesn't change the COUNT
    # output.
    number = max(0, number - self.low_mark)
    if self.high_mark is not None:
        number = min(number, self.high_mark - self.low_mark)

    return number

django.db.models.sql.query.BaseQuery.get_count = quuux.get_count

测试:

>>> Baz.objects.extra(select={'negid': '0 - id'}, where=['"negid" < -2']).count()
2

更新以使用Django 1.2.1:

def basequery_get_count(self, using):
    """
    Performs a COUNT() query using the current filter constraints.
    """
    obj = self.clone()
    if len(self.select) > 1 or self.aggregate_select or self.extra:
        # If a select clause exists, then the query has already started to
        # specify the columns that are to be returned.
        # In this case, we need to use a subquery to evaluate the count.
        from django.db.models.sql.subqueries import AggregateQuery
        subquery = obj
        subquery.clear_ordering(True)
        subquery.clear_limits()

        obj = AggregateQuery(obj.model)
        obj.add_subquery(subquery, using=using)

    obj.add_count_column()
    number = obj.get_aggregation(using=using)[None]

    # Apply offset and limit constraints manually, since using LIMIT/OFFSET
    # in SQL (in variants that provide them) doesn't change the COUNT
    # output.
    number = max(0, number - self.low_mark)
    if self.high_mark is not None:
        number = min(number, self.high_mark - self.low_mark)

    return number
models.sql.query.Query.get_count = basequery_get_count

然而,我不确定此修复是否会产生其他意外后果。

相关问题