#37258 closed Bug (fixed)
Combining values() querysets on a model with Meta.ordering crashes when the order fields are not included
| Reported by: | Adam Johnson | Owned by: | Sarah Boyce |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 6.1 |
| Severity: | Release blocker | Keywords: | |
| Cc: | Shai Berger, Simon Charette, Lily Acorn, Lily | 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
Combining querysets restricted with values()/values_list() (e.g. via union()) on a model with Meta.ordering crashes at SQL compilation on Django 6.1 when the default ordering columns are not part of the selected columns:
django.db.utils.DatabaseError: ORDER BY term does not match any column in the result set.
Minimal reproduction (crashes on PostgreSQL, MySQL, and Oracle — any backend with supports_slicing_ordering_in_compound=True; the same code works on Django 6.0):
class Book(models.Model): name = models.CharField(max_length=10) rank = models.IntegerField() class Meta: ordering = ["rank"] Book.objects.values("name").union(Book.objects.values("name")) # crashes on iteration
Same for values_list(), and for intersection()/difference(). qs1.union(qs2).values("name") doesn't crash but silently adds a spurious __orderbycol2 column to the compiled query and result set.
Regression in 087bb9e8f3478d53f12b1737af865992af17c5f2 (Refs #36644 -- Applied default ordering after union()), which sets default_ordering = True on the combined query so that Meta.ordering is applied after the combination. Root cause: for combined queries with a limited list of selected fields, SQLCompiler.get_order_by() cannot inject the implicit ordering columns into the values() parts (has_select_fields guard in django/db/models/sql/compiler.py), so it raises DatabaseError.
Change History (13)
comment:1 by , 3 weeks ago
| Summary: | Combining `values()` querysets on a model with `Meta.ordering` crashes → Combining values() querysets on a model with Meta.ordering crashes when the order fields are not included |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
comment:2 by , 2 weeks ago
comment:3 by , 2 weeks ago
| Patch needs improvement: | set |
|---|
Left a question about whether we can avoid mocking the feature flag in the test.
comment:4 by , 2 weeks ago
| Cc: | added |
|---|
comment:5 by , 2 weeks ago
| Cc: | added |
|---|
comment:6 by , 13 days ago
| Cc: | added |
|---|
comment:7 by , 13 days ago
I agree with Jacob that this query be invalid and that it worked before feels accidental.
If it is feasible to catch this and have a FieldError raised, and add a note in the breaking changes release notes - I think I would prefer that over a deprecation
comment:8 by , 12 days ago
Alternative PR: https://github.com/django/django/pull/21798
Concluded we don't need to change the error in a back port
comment:9 by , 12 days ago
| Owner: | changed from to |
|---|---|
| Patch needs improvement: | unset |
| Triage Stage: | Accepted → Ready for checkin |
I'd like some input on whether the use is invalid, or perhaps should become invalid after a deprecation.
This query always crashed (user error):
The question is whether the default ordering should imply the same query (and thus imply user error):
The correct version would be:
I don't think that's so much to ask, as in some cases we expect default ordering to be cleared via
order_by()before a union...https://github.com/django/django/pull/21277#issuecomment-4453655100
... and now it might be necessary after a union in order to make the effect of
Meta.orderingeasier to reason about.Currently, the only time
Meta.orderingis ignored is in group by queries. Making the query from this bug report continue to work would be introducing a second, partial exception depending on the specific columns involved. I'm not sure that's good for the long-term.A python exception would be nice instead of a DatabaseError. We could detect the mismatch, and issue a deprecation warning.
How we ended up here:
first()no longer injectorder_by("pk")if ordering had ever been force-cleared byorder_by().union()caused the resulting combined query to appear as if the order had been force-cleared, making it impossible to distinguish whether the user eventually wanted an ordering viafirst()or not.Meta.ordering. In retrospect, we could have added a minor incompatible change as we did for thefirst()change (7cf1c22d4dfdd46f2082cfc55b714b68c4fd2de3).