#33929 closed Bug (duplicate)
Field Reference in FilteredRelation Does Not Recognize Previously Defined FilteredRelation
Reported by: | Matt | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 4.1 |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
I suspect this may be the same root cause as https://code.djangoproject.com/ticket/33766, but the use-case here is different enough I thought I'd log a new ticket.
All this is using Django 4.0 or 4.1, on PostgreSQL. I confess that I have not checked if other DB layers might generate correct SQL.
It appears that I cannot reference one FilteredRelation from another's condition without jumping through some hoops. Starting with the following example models:
from django.db import models class A(models.Model): ... class B(models.Model): a = models.ForeignKey("A", on_delete=models.CASCADE) complete = models.BooleanField(default=False) class C(models.Model): a = models.ForeignKey("A", on_delete=models.CASCADE) b = models.OneToOneField("B", blank=True, null=True, on_delete=models.CASCADE) complete = models.BooleanField(default=False)
Now suppose that I want a count of incomplete B, and also incomplete C, but only when related to an incomplete B.
If I were writing SQL myself, I’d write this as:
SELECT COUNT(b.id) as b_count, COUNT(c.id) as c_count FROM a LEFT JOIN b ON b.a_id = a.id AND NOT b.complete LEFT JOIN c ON c.a_id = a.id AND c.b_id = b.id AND NOT c.complete
Now, the below queryset very nearly works:
A.objects.annotate( binc=FilteredRelation("b", condition=Q(b__complete=False)), cinc=FilteredRelation("c", condition=Q(c__b=F("binc__pk"), c__complete=False)), b_count=Count("binc"), c_count=Count("cinc"), )
Unfortunately this uses an incorrect table alias into the cinc
FilteredRelation, where I tried to reference F("binc__pk")
. If I try to execute it, I get
django.db.utils.ProgrammingError: missing FROM-clause entry for table "t4" LINE 1: ...("a"."id" = cinc."a_id" AND ((cinc."b_id" = (T4."id") A…
There is a workaround: I can force the correct identifier using RawSQL, and use this, which provides correct results:
A.objects.annotate( binc=FilteredRelation("b", condition=Q(b__complete=False)), cinc=FilteredRelation("c", condition=Q(c__b=RawSQL("binc.id", ()), c__complete=False)), b_count=Count("binc"), c_count=Count("cinc"), )
Change History (3)
comment:1 by , 2 years ago
comment:2 by , 2 years ago
Resolution: | → duplicate |
---|---|
Status: | new → closed |
Thanks for the report! Agree, this is a duplicate of #33929.
comment:3 by , 2 years ago
I agree with Mariusz and I think this has the exact same root cause as #33766 and detailed in this comment.
The gist of it is that filter relations conditions are resolved very late in the process of SQL compilation (basically when the FROM
clause is generated) and thus the resolving of references to filtered relation induced joins alias can be off.
I hope to add some more context after looking this over. It appears as though Matt is correct regarding being related to https://code.djangoproject.com/ticket/33766. The fields are not being properly aliased.
App name in my tests is
interface
which I left references of in the console output, but I removed theinterface_
prefix from the formatted SQL queries for readability.Invoking the query compiler adds the naive alias
T4
to the query which also later shows up in theQuery.alias_map
.Notice the incrementing
Tn
on theQuery.alias_map
but only the last one is referenced in the resulting query:The following works but selects and groups by one additional field
binc_pk
. Postgres Query Planner output included. On a larger queryset this could get expensive.Not ideal.
Instinctively I looked into the implementation of
OuterRef
for ideas, but the implementation is quite contrasting to FilteredRelation.