{{{#!python
Model.objects.annotate(foo=F('column')).aggregate(foo=Sum(F('foo')))
}}}
Initially, I was running this query on SQLite and Django was producing a
result (i.e. 0).
When I switched to MySQL this query crushed with the following exception:
{{{
Traceback (most recent call last):
File "/dir/.env/lib/python3.8/site-
packages/django/db/backends/utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "/dir/.env/lib/python3.8/site-
packages/django/db/backends/mysql/base.py", line 74, in execute
return self.cursor.execute(query, args)
File "/dir/.env/lib/python3.8/site-packages/MySQLdb/cursors.py", line
209, in execute
res = self._query(query)
File "/dir/.env/lib/python3.8/site-packages/MySQLdb/cursors.py", line
315, in _query
db.query(q)
File "/dir/.env/lib/python3.8/site-packages/MySQLdb/connections.py",
line 239, in query
_mysql.connection.query(self, query)
MySQLdb._exceptions.OperationalError: (1054, "Unknown column 'foo' in
'field list'")
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "mysql.py", line 15, in <module>
ret2 =
Model.objects.using('mysql').annotate(foo=F('column')).aggregate(foo=Sum(F('foo')))
File "/dir/.env/lib/python3.8/site-packages/django/db/models/query.py",
line 384, in aggregate
return query.get_aggregation(self.db, kwargs)
File "/dir/.env/lib/python3.8/site-
packages/django/db/models/sql/query.py", line 502, in get_aggregation
result = compiler.execute_sql(SINGLE)
File "/dir/.env/lib/python3.8/site-
packages/django/db/models/sql/compiler.py", line 1151, in execute_sql
cursor.execute(sql, params)
File "/dir/.env/lib/python3.8/site-
packages/django/db/backends/utils.py", line 100, in execute
return super().execute(sql, params)
File "/dir/.env/lib/python3.8/site-
packages/django/db/backends/utils.py", line 68, in execute
return self._execute_with_wrappers(sql, params, many=False,
executor=self._execute)
File "/dir/.env/lib/python3.8/site-
packages/django/db/backends/utils.py", line 77, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/dir/.env/lib/python3.8/site-
packages/django/db/backends/utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "/dir/.env/lib/python3.8/site-packages/django/db/utils.py", line
90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/dir/.env/lib/python3.8/site-
packages/django/db/backends/utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "/dir/.env/lib/python3.8/site-
packages/django/db/backends/mysql/base.py", line 74, in execute
return self.cursor.execute(query, args)
File "/dir/.env/lib/python3.8/site-packages/MySQLdb/cursors.py", line
209, in execute
res = self._query(query)
File "/dir/.env/lib/python3.8/site-packages/MySQLdb/cursors.py", line
315, in _query
db.query(q)
File "/dir/.env/lib/python3.8/site-packages/MySQLdb/connections.py",
line 239, in query
_mysql.connection.query(self, query)
django.db.utils.OperationalError: (1054, "Unknown column 'foo' in 'field
list'")
}}}
After examining the SQL query produced by Django, I realized that the
query is not correct.
Specifically, Django produced the following SQL query:
{{{#!sql
SELECT SUM(`foo`) AS `foo` FROM `model`
}}}
Instead of
{{{#!sql
SELECT SUM(`foo`) FROM (SELECT `model`.`column` AS `foo` FROM `model`)
subquery
}}}
The issue appears when a keyword in aggregate function is the same with
one of the keywords in annotate function. I initially thought that Django
does not have any restriction on the keywords used in aggregate (i.e. a
keyword in aggregate can be the same with the name of a column from the
inspected model).
If you think that aggregate should not conflict with the annotate, then
Django should produce a warning as running this query in SQLite subtly
produces an incorrect result.
--
Ticket URL: <https://code.djangoproject.com/ticket/31679>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* type: Uncategorized => Bug
* component: Uncategorized => Database layer (models, ORM)
* stage: Unreviewed => Accepted
Comment:
Reproduced on PostgreSQL, Django 3.0.7. Not sure, should this work or
produce an error.
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:1>
* owner: nobody => Jacob Walls
* status: new => assigned
* easy: 0 => 1
Comment:
I would expect this to fail with a ValueError. aggregate() supplies an
annotation, and a Query’s annotations are keyed on aliases. Supporting
this would take a massive rewrite, it seems to me.
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:2>
* has_patch: 0 => 1
Comment:
[https://github.com/django/django/pull/13273 PR]
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:4>
* needs_better_patch: 0 => 1
* easy: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:5>
* owner: Jacob Walls => (none)
* status: assigned => new
* has_patch: 1 => 0
Comment:
Unassigning myself because the attached patch was too restrictive.
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:6>
Comment (by Herbert Poul):
Is this the same as https://code.djangoproject.com/ticket/31880 (which was
fixed by https://github.com/django/django/pull/13431 )?
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:7>
Comment (by Kristijan Mitrovic):
> {{{#!python
> Model.objects.annotate(foo=F('column')).aggregate(foo=Sum(F('foo')))
> }}}
So in the example above, where both annotated and aggregated kwargs are
named `foo`, the output should actually contain just the last one, right?
The first annotated `foo` column can just be renamed and then removed from
the final result (to avoid confusion). That would be accesptable solution
without large rewriting of the code I hope.
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:9>
* needs_better_patch: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:10>
* owner: (none) => Jonny Park
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:11>
* owner: Jonny Park => (none)
* status: assigned => new
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:12>
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:11>
* owner: (none) => Simon Charette
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:12>
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:13>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"1297c0d0d76a708017fe196b61a0ab324df76954" 1297c0d]:
{{{
#!CommitTicketReference repository=""
revision="1297c0d0d76a708017fe196b61a0ab324df76954"
Fixed #31679 -- Delayed annotating aggregations.
By avoiding to annotate aggregations meant to be possibly pushed to an
outer query until their references are resolved it is possible to
aggregate over a query with the same alias.
Even if #34176 is a convoluted case to support, this refactor seems
worth it given the reduction in complexity it brings with regards to
annotation removal when performing a subquery pushdown.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:14>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"2ee01747c32a7275a7a1a5f7862acba7db764921" 2ee0174]:
{{{
#!CommitTicketReference repository=""
revision="2ee01747c32a7275a7a1a5f7862acba7db764921"
Refs #34551 -- Fixed QuerySet.aggregate() crash on precending aggregation
reference.
Regression in 1297c0d0d76a708017fe196b61a0ab324df76954.
Refs #31679.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:15>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"57f499e412c7c28b4a1f1b740468bf6eabbdb695" 57f499e4]:
{{{
#!CommitTicketReference repository=""
revision="57f499e412c7c28b4a1f1b740468bf6eabbdb695"
[4.2.x] Refs #34551 -- Fixed QuerySet.aggregate() crash on precending
aggregation reference.
Regression in 1297c0d0d76a708017fe196b61a0ab324df76954.
Refs #31679.
Backport of 2ee01747c32a7275a7a1a5f7862acba7db764921 from main
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:16>
* status: closed => new
* has_patch: 1 => 0
* resolution: fixed =>
* stage: Ready for checkin => Accepted
Comment:
Reverted in 2ee01747c32a7275a7a1a5f7862acba7db764921.
--
Ticket URL: <https://code.djangoproject.com/ticket/31679#comment:17>