2010-01-06 3 views
0

Lorsque j'utilise extra d'une certaine manière sur un Django queryset (appeler qs), le résultat de qs.count() est différent de celui len(qs.all()). Pour reproduire:`la count` de queryset est erroné après` extra`

Faire un projet vide Django et l'application, puis ajouter un modèle trivial:

class Baz(models.Model): 
    pass 

maintenant faire quelques objets:

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

En utilisant la méthode extra pour sélectionner seulement quelques-uns des les produit le nombre prévu:

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

Mais ajouter un select clause extra et y faire référence dans la clause where, et le compte est soudainement mal, même si le résultat de all() est correcte:

>>> 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 

Je pense que le problème a à voir avec django.db.models.sql.query.BaseQuery.get_count(). Il vérifie si les attributs select ou aggregate_select de BaseQuery ont été définis; Si c'est le cas, il utilise une sous-requête. Mais django.db.models.sql.query.BaseQuery.add_extra ajoute seulement à l'attribut extra de BaseQuery, pas select ou aggregate_select.

Comment puis-je résoudre le problème? Je sais que je pourrais juste utiliser len(qs.all()), mais ce serait bien de pouvoir passer le jeu de requête extra à d'autres parties du code, et ces parties peuvent appeler count() sans savoir qu'il est cassé.

+0

Vérifié que le problème est toujours présent dans Django 1.2.1. –

Répondre

0

Redéfinir get_count() et monkeypatching semble résoudre le problème:

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 

Test:

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

Mise à jour travailler avec 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 

Je ne suis pas Assurez-vous que cette correction aura d'autres conséquences imprévues.