Opened 7 years ago
Closed 5 years ago
#28289 closed Bug (fixed)
QuerySet.count() does not with work raw sql annotations on inherited model fields
Reported by: | Karolis Ryselis | Owned by: | Can Sarıgöl |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Accepted | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Consider these models
class BaseItem(models.Model): title = models.CharField(max_length=32) class Item(BaseItem): pass
If I use a RawSQL
annotation of Item
's queryset that includes one of the fields defined in BaseItem
and call .count()
on annotated queryset, it fails with the error:
django.db.utils.OperationalError: (1054, "Unknown column 'title' in 'field list'")
(MySQL 5.7)
code to reproduce the bug with given models:
queryset = Item.objects.all() queryset = queryset.annotate(title2=RawSQL("title", ())) queryset.count() # crashes
I have tracked down what causes this bug. Query.get_aggregation
method drops INNER JOIN
required to select the title
field. Specifically, this code drops it:
if not inner_query.distinct: # If the inner query uses default select and it has some # aggregate annotations, then we must make sure the inner # query is grouped by the main model's primary key. However, # clearing the select clause can alter results if distinct is # used. if inner_query.default_cols and has_existing_annotations: inner_query.group_by = [self.model._meta.pk.get_col(inner_query.get_initial_alias())] inner_query.default_cols = False
Code is taken from Django 1.8 but 1.11 looks the same.
default_cols
is set to False
and the INNER JOIN
is dropped. Quick fix is to add a condition for setting default_cols
to False
:
if not inner_query.distinct: # If the inner query uses default select and it has some # aggregate annotations, then we must make sure the inner # query is grouped by the main model's primary key. However, # clearing the select clause can alter results if distinct is # used. if inner_query.default_cols and has_existing_annotations: inner_query.group_by = [self.model._meta.pk.get_col(inner_query.get_initial_alias())] if not has_existing_annotations: inner_query.default_cols = False
I don't know if it could be done in a nicer way. I was able to reproduce this in 1.8.18 and 1.11.0
Attachments (1)
Change History (8)
comment:1 by , 7 years ago
Summary: | Queryset.count does not with work raw sql annotations on inherited model fields → QuerySet.count() does not with work raw sql annotations on inherited model fields |
---|---|
Triage Stage: | Unreviewed → Accepted |
Type: | Uncategorized → Bug |
by , 7 years ago
Attachment: | 28289-test.diff added |
---|
comment:2 by , 6 years ago
Has patch: | set |
---|---|
Owner: | changed from | to
Status: | new → assigned |
Version: | 1.11 → master |
Hi, PR
To finding the inherited columns, thought two approaches:
a- try to parse the columns from raw sql with using a convert function which returns a list from sql string. (with regex pattern maybe)
b- if the query contains a column that comes from the inherited model, take this field name with get_fields() - get_fields(include_parents=False)
I chose b
.
I hoped to find a property that contains whether the model is inherited. Is this possible?
Because of that, it performs every time.
comment:3 by , 5 years ago
Patch needs improvement: | set |
---|
comment:4 by , 5 years ago
Patch needs improvement: | unset |
---|
comment:5 by , 5 years ago
Patch needs improvement: | set |
---|
comment:6 by , 5 years ago
Patch needs improvement: | unset |
---|
I'm attaching a test for Django's test suite that I wrote quickly to reproduce this.