#33929: Field Reference in FilteredRelation Does Not Recognize Previously 
Defined
FilteredRelation
-------------------------------------+-------------------------------------
     Reporter:  Matt                 |                    Owner:  nobody
         Type:  Bug                  |                   Status:  new
    Component:  Database layer       |                  Version:  4.1
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------

Comment (by Nick Vellios):

 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 the `interface_` prefix from the formatted
 SQL queries for readability.

 {{{
 >>> qs = 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"),
 ... )
 >>> qs.query.alias_map
 {'interface_a': <django.db.models.sql.datastructures.BaseTable object at
 0x11095fa60>, 'binc': <django.db.models.sql.datastructures.Join object at
 0x11095c6a0>, 'cinc': <django.db.models.sql.datastructures.Join object at
 0x11095de10>}
 }}}

 Invoking the query compiler adds the naive alias `T4` to the query which
 also later shows up in the `Query.alias_map`.
 {{{
 >>> str(qs.query)

 SELECT
   "a"."id",
   COUNT(binc."id") AS "b_count",
   COUNT(cinc."id") AS "c_count"
 FROM "a"
 LEFT OUTER JOIN "b" binc
   ON ("a"."id" = binc."a_id"
   AND (NOT binc."complete"))
 LEFT OUTER JOIN "c" cinc
   ON ("a"."id" = cinc."a_id"
   AND ((cinc."b_id" = (T4."id")
   AND NOT cinc."complete")))
 GROUP BY "a"."id"
 }}}

 Notice the incrementing `Tn` on the `Query.alias_map` but only the last
 one is referenced in the resulting query:

 {{{
 >>> qs.query.alias_map
 {'interface_a': <django.db.models.sql.datastructures.BaseTable object at
 0x11095fa60>, 'binc': <django.db.models.sql.datastructures.Join object at
 0x11095c6a0>, 'cinc': <django.db.models.sql.datastructures.Join object at
 0x11095de10>, 'T4': <django.db.models.sql.dat
 astructures.Join object at 0x1077991b0>, 'T5':
 <django.db.models.sql.datastructures.Join object at 0x10778b400>}

 >>> str(qs.query)
 'SELECT "interface_a"."id", COUNT(binc."id") AS "b_count",
 COUNT(cinc."id") AS "c_count" FROM "interface_a" LEFT OUTER JOIN
 "interface_b" binc ON ("interface_a"."id" = binc."a_id" AND (NOT
 binc."complete")) LEFT OUTER JOIN "interface_c" cinc ON
 ("interface_a"."id" =
  cinc."a_id" AND ((cinc."b_id" = (T6."id") AND NOT cinc."complete")))
 GROUP BY "interface_a"."id"'

 >>> str(qs.query)
 'SELECT "interface_a"."id", COUNT(binc."id") AS "b_count",
 COUNT(cinc."id") AS "c_count" FROM "interface_a" LEFT OUTER JOIN
 "interface_b" binc ON ("interface_a"."id" = binc."a_id" AND (NOT
 binc."complete")) LEFT OUTER JOIN "interface_c" cinc ON
 ("interface_a"."id" =
  cinc."a_id" AND ((cinc."b_id" = (T8."id") AND NOT cinc."complete")))
 GROUP BY "interface_a"."id"'

 >>> qs.query.alias_map
 {'interface_a': <django.db.models.sql.datastructures.BaseTable object at
 0x11095fa60>, 'binc': <django.db.models.sql.datastructures.Join object at
 0x11095c6a0>, 'cinc': <django.db.models.sql.datastructures.Join object at
 0x11095de10>, 'T4': <django.db.models.sql.dat
 astructures.Join object at 0x1077991b0>, 'T5':
 <django.db.models.sql.datastructures.Join object at 0x10778b400>, 'T6':
 <django.db.models.sql.datastructures.Join object at 0x11099b640>, 'T7':
 <django.db.models.sql.datastructures.Join object at 0x110834580>, 'T8':
 <dj
 ango.db.models.sql.datastructures.Join object at 0x110ad0610>, 'T9':
 <django.db.models.sql.datastructures.Join object at 0x110ad03a0>}


 >>> str(qs.query)

 SELECT
   "a"."id",
   COUNT(binc."id") AS "b_count",
   COUNT(cinc."id") AS "c_count"
 FROM "a"
 LEFT OUTER JOIN "b" binc
   ON ("a"."id" = binc."a_id"
   AND (NOT binc."complete"))
 LEFT OUTER JOIN "c" cinc
   ON ("a"."id" = cinc."a_id"
   AND ((cinc."b_id" = (T9"id")
   AND NOT cinc."complete")))
 GROUP BY "a"."id"

 }}}

 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.

 {{{
 A.objects.annotate(
     binc=FilteredRelation("b", condition=Q(b__complete=False)),
     binc_pk=F('binc__pk'),  # <-- Allows elimination of relying on raw
 SQL, however...
     cinc=FilteredRelation("c", condition=Q(c__b=F('binc_pk'),
 c__complete=False)),
     b_count=Count("binc"),
     c_count=Count("cinc"),
 )
 }}}

 {{{
 SELECT
   "a"."id",
   binc."id" AS "binc_pk",  /* <-- Ugly */
   COUNT(binc."id") AS "b_count",
   COUNT(cinc."id") AS "c_count"
 FROM "a"
 LEFT OUTER JOIN "b" binc
   ON ("a"."id" = binc."a_id"
   AND (NOT binc."complete"))
 LEFT OUTER JOIN "c" cinc
   ON ("a"."id" = cinc."a_id"
   AND ((cinc."b_id" = (binc."id")
   AND NOT cinc."complete")))
 GROUP BY "a"."id",
   binc."id"  /* <-- Ugly */
 }}}

 Not ideal.

 {{{
 ➜  ~ psql -d dj_issue_33929
 psql (14.4)
 Type "help" for help.

 dj_issue_33929=# EXPLAIN SELECT
 dj_issue_33929-#   "interface_a"."id",
 dj_issue_33929-#   binc."id" AS "binc_pk",
 dj_issue_33929-#   COUNT(binc."id") AS "b_count",
 dj_issue_33929-#   COUNT(cinc."id") AS "c_count"
 dj_issue_33929-# FROM "interface_a"
 dj_issue_33929-# LEFT OUTER JOIN "interface_b" binc
 dj_issue_33929-#   ON ("interface_a"."id" = binc."a_id"
 dj_issue_33929(#   AND (NOT binc."complete"))
 dj_issue_33929-# LEFT OUTER JOIN "interface_c" cinc
 dj_issue_33929-#   ON ("interface_a"."id" = cinc."a_id"
 dj_issue_33929(#   AND ((cinc."b_id" = (binc."id")
 dj_issue_33929(#   AND NOT cinc."complete")))
 dj_issue_33929-# GROUP BY "interface_a"."id",
 dj_issue_33929-#          binc."id";
                                       QUERY PLAN
 
---------------------------------------------------------------------------------------
  HashAggregate  (cost=162.74..185.34 rows=2260 width=32)
    Group Key: interface_a.id, binc.id
    ->  Hash Left Join  (cost=97.80..140.14 rows=2260 width=24)
          Hash Cond: ((interface_a.id = cinc.a_id) AND (binc.id =
 cinc.b_id))
          ->  Hash Right Join  (cost=60.85..91.33 rows=2260 width=16)
                Hash Cond: (binc.a_id = interface_a.id)
                ->  Seq Scan on interface_b binc  (cost=0.00..28.10
 rows=905 width=16)
                      Filter: (NOT complete)
                ->  Hash  (cost=32.60..32.60 rows=2260 width=8)
                      ->  Seq Scan on interface_a  (cost=0.00..32.60
 rows=2260 width=8)
          ->  Hash  (cost=25.40..25.40 rows=770 width=24)
                ->  Seq Scan on interface_c cinc  (cost=0.00..25.40
 rows=770 width=24)
                      Filter: (NOT complete)
 (13 rows)

 dj_issue_33929=# EXPLAIN SELECT
 dj_issue_33929-#   "interface_a"."id",
 dj_issue_33929-#   COUNT(binc."id") AS "b_count",
 dj_issue_33929-#   COUNT(cinc."id") AS "c_count"
 dj_issue_33929-# FROM "interface_a"
 dj_issue_33929-# LEFT OUTER JOIN "interface_b" binc
 dj_issue_33929-#   ON ("interface_a"."id" = binc."a_id"
 dj_issue_33929(#   AND (NOT binc."complete"))
 dj_issue_33929-# LEFT OUTER JOIN "interface_c" cinc
 dj_issue_33929-#   ON ("interface_a"."id" = cinc."a_id"
 dj_issue_33929(#   AND ((cinc."b_id" = (binc.id)
 dj_issue_33929(#   AND NOT cinc."complete")))
 dj_issue_33929-# GROUP BY "interface_a"."id";
                                       QUERY PLAN
 
---------------------------------------------------------------------------------------
  HashAggregate  (cost=157.09..179.69 rows=2260 width=24)
    Group Key: interface_a.id
    ->  Hash Left Join  (cost=97.80..140.14 rows=2260 width=24)
          Hash Cond: ((interface_a.id = cinc.a_id) AND (binc.id =
 cinc.b_id))
          ->  Hash Right Join  (cost=60.85..91.33 rows=2260 width=16)
                Hash Cond: (binc.a_id = interface_a.id)
                ->  Seq Scan on interface_b binc  (cost=0.00..28.10
 rows=905 width=16)
                      Filter: (NOT complete)
                ->  Hash  (cost=32.60..32.60 rows=2260 width=8)
                      ->  Seq Scan on interface_a  (cost=0.00..32.60
 rows=2260 width=8)
          ->  Hash  (cost=25.40..25.40 rows=770 width=24)
                ->  Seq Scan on interface_c cinc  (cost=0.00..25.40
 rows=770 width=24)
                      Filter: (NOT complete)
 (13 rows)
 }}}

 Instinctively I looked into the implementation of `OuterRef` for ideas,
 but the implementation is quite contrasting to FilteredRelation.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33929#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/01070182a994190d-66690d69-0473-4010-8ff0-6b8cec4c8383-000000%40eu-central-1.amazonses.com.

Reply via email to