Opened 6 months ago
Closed 5 months ago
#36407 closed Bug (fixed)
Query compiler optimizes CASE..WHEN into a programming error on Postgres 16
| Reported by: | deceze | Owned by: | ontowhee |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Ready for checkin | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Boiled down to its simplest form:
MyModel.objects.order_by(
Case(
When(pk__in=some_list, then=Value(1)),
default=Value(0)
output_field=IntegerField()
).desc()
)
If some_list here is empty, the query compiler seems to reduce the entire expression to just 0, yielding:
... ORDER BY 0
Which Postgres 16+ (maybe 15+, not sure) doesn't like:
django.db.utils.ProgrammingError: ORDER BY position 0 is not in select list
I've had an alternate version before using:
MyModel.objects.annotate(
is_in_list=Case(
When(pk__in=some_list, then=Value(True)),
default=Value(False)
output_field=BooleanField()
)
).order_by(
F('is_in_list').desc()
)
This instead produced the plain query:
... ORDER BY false
which yielded:
psycopg2.errors.SyntaxError: non-integer constant in ORDER BY
I appreciate the compiler trying to optimize the query, but in this case that's a liability. Postgres would accept the condition if the constant was explicitly annotated like:
... ORDER BY false::boolean
Change History (9)
comment:1 by , 6 months ago
| Triage Stage: | Unreviewed → Accepted |
|---|---|
| Version: | 3.2 → dev |
comment:2 by , 6 months ago
This relates to #26192 (Cannot order query by constant value on PostgreSQL).
The problem here is effectively that the Case.as_sql's not case_parts should consider isinstance(self.default, Value) and use Cast like it was done in f6075fb333bae29ee213b050e91eaadef75496dd if it's the case.
comment:3 by , 6 months ago
| Has patch: | set |
|---|---|
| Owner: | set to |
| Status: | new → assigned |
comment:4 by , 6 months ago
I've opened a PR for this, where it uses Cast, except for Oracle. I think this should address the issue, but I may be missing some points.
With postgres, ORDER BY 0 is erroring out because the 0 is referencing a column position in the select list, and the column positions uses 1-indexing.
Interestingly, Oracle does not sort as expected when casting. The sql it generates using the test provided by Sarah is,
SELECT "ORDERING_ARTICLE"."ID", "ORDERING_ARTICLE"."AUTHOR_ID", "ORDERING_ARTICLE"."SECOND_AUTHOR_ID", "ORDERING_ARTICLE"."HEADLINE", "ORDERING_ARTICLE"."PUB_DATE" FROM "ORDERING_ARTICLE" ORDER BY CAST(0 AS NUMBER(11)) DESC
Without casting, the query is,
SELECT "ORDERING_ARTICLE"."ID", "ORDERING_ARTICLE"."AUTHOR_ID", "ORDERING_ARTICLE"."SECOND_AUTHOR_ID", "ORDERING_ARTICLE"."HEADLINE", "ORDERING_ARTICLE"."PUB_DATE" FROM "ORDERING_ARTICLE" ORDER BY 0 DESC
I'm not sure how it is sorting with ORDER BY 0 if a number is referencing the column position. I wonder if it uses a default column that is different from the default column in Django. Running a small SQL example directly on the database gives an error as expected
SELECT id, first_name, dob FROM person ORDER BY 0
*
ERROR at line 1:
ORA-01785: ORDER BY item must be the number of a SELECT-list expression
but it raising an error in Django.
comment:5 by , 6 months ago
Interestingly, Oracle does not sort as expected when casting.
This is because the order is ambiguous, ordering by a constant is the same as not ordering by anything which means the database is allowed to return results in whatever orders it wants.
This is not an issue with Oracle but with the test itself. It should order by pk as well to make sure it matches the expected sequence.
comment:6 by , 6 months ago
| Needs tests: | set |
|---|
comment:7 by , 5 months ago
| Needs tests: | unset |
|---|
comment:8 by , 5 months ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Thank you for the report! Replicated on main
Possible regression test
tests/ordering/tests.py
This errors when testing against with postgres
django.db.utils.ProgrammingError: ORDER BY position 0 is not in select list LINE 1: ..._article"."pub_date" FROM "ordering_article" ORDER BY 0 DESC ^