#17502: Filter on field from base class 2 levels up hierarchy causes extra join
--------------------------------------+------------------------------------
     Reporter:  dbenamy@…             |                    Owner:  lrekucki
         Type:  Cleanup/optimization  |                   Status:  assigned
    Component:  Uncategorized         |                  Version:  1.3
     Severity:  Normal                |               Resolution:
     Keywords:                        |             Triage Stage:  Accepted
    Has patch:  0                     |      Needs documentation:  0
  Needs tests:  0                     |  Patch needs improvement:  0
Easy pickings:  0                     |                    UI/UX:  0
--------------------------------------+------------------------------------
Changes (by lrekucki):

 * status:  new => assigned
 * stage:  Unreviewed => Accepted


Comment:

 Going step by step:

    1. We start with table "C".
    2. {{{Query.setup_inherited_model()}}} adds {{{INNER JOIN}}} for all
 parent models. C is joined with B [{{{ ON (C.b_ptr_id = B.a_ptr_id) - B's
 parent pointer is it's own primary key). Then C is joined with A and
 because of B's PK is the same as B's parent pointer, Django optimizes? by
 doing (C.b_ptr_id [= B.a_ptr_id] = A.id).
    3. Then the {{{Query.add_filter()}}} lookups the {{{alice}}} field and
 setups neccesary joins with {{{Query.setup_joins()}}}. As the field
 resides on the base model, the following code is executed:
 {{{#!python
                 for int_model in opts.get_base_chain(model):
                     if int_model is proxied_model:
                         opts = int_model._meta
                     else:
                         lhs_col = opts.parents[int_model].column
                         dedupe = lhs_col in opts.duplicate_targets
                         if dedupe:
                             exclusions.update(self.dupe_avoidance.get(
                                     (id(opts), lhs_col), ()))
                             dupe_set.add((opts, lhs_col))
                         opts = int_model._meta
                         alias = self.join((alias, opts.db_table, lhs_col,
                                 opts.pk.column), exclusions=exclusions)
                         joins.append(alias)
                         exclusions.add(alias)
                         for (dupe_opts, dupe_col) in dupe_set:
                             self.update_dupe_avoidance(dupe_opts,
 dupe_col,
                                     alias)
 }}}

 This takes a different approach by joining C with B (alias lookup
 succeeds) and then B with A (alias lookup fails, because we previously
 used a different connection). This is where the additional join comes
 from.

 Possible solutions:

   a. make the strategy in 2. and 3. consistent. Chaining the joins step by
 step instead of skipping should work, but if we select only values from C
 and A, we should be able to skip joining B. This would suggest changing
 {{{setup_joins to}}} try to reuse the optimized alias first.

   b. When doing the {{{setup_inherited_models()}}}, if we did join to B
 (i.e. there are selected fields to B), add a fake entry to {{{alias_map}}}
 for "B INNER JOIN A", that {{{setup_joins()}}} could reuse later.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/17502#comment:2>
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 post to this group, send email to django-updates@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to