Opened 3 months ago
Closed 3 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 , 3 months ago
Triage Stage: | Unreviewed → Accepted |
---|---|
Version: | 3.2 → dev |
comment:2 by , 3 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 , 3 months ago
Has patch: | set |
---|---|
Owner: | set to |
Status: | new → assigned |
comment:4 by , 3 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 , 3 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 , 3 months ago
Needs tests: | set |
---|
comment:7 by , 3 months ago
Needs tests: | unset |
---|
comment:8 by , 3 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