#29551: My use-case of QuerySet.extra(): Join two models without explicit 
relation
--------------------------------+--------------------------------------
     Reporter:  Bowser          |                    Owner:  nobody
         Type:  Uncategorized   |                   Status:  closed
    Component:  Uncategorized   |                  Version:  2.0
     Severity:  Normal          |               Resolution:  invalid
     Keywords:  QuerySet.extra  |             Triage Stage:  Unreviewed
    Has patch:  0               |      Needs documentation:  0
  Needs tests:  0               |  Patch needs improvement:  0
Easy pickings:  0               |                    UI/UX:  0
--------------------------------+--------------------------------------

Comment (by jonathan-golorry):

 `ForeignObject` requires that you join on a unique combination of fields.
 I don't think there's a `ForeignObject` equivalent for a ManyToMany
 relationship. For filtering functionality, you can get pretty much
 whatever you need by annotating subqueries. I haven't looked into
 prefetching.

 In my case, I have customers and purchases. I want to see when a customer
 has cancelled a purchase without later making another purchase in the same
 category. Naively:
 {{{
 Purchase.objects.filter(cancelled=True).exclude(
     customer__purchases__category=F("category"),
     customer__purchases__id__gt=F("id"),
 )
 }}}
 This doesn't work because django creates separate subqueries for each
 exclude condition (this might be a django bug, since django normally
 combines conditions in the same exclude call).

 My working implementation:
 {{{
 later = Purchase.objects.filter(
     category=OuterRef("category"),
     customer_id=OuterRef("customer_id"),
     id__gt=OuterRef("id"),
 )
 Purchase.objects.annotate(latest=~Exists(later)).filter(latest=True,
 cancelled=True)
 }}}

 `ForeignObject` generates a correct sql query using an inner join instead
 of a subquery, but the system check fails. Customers very rarely purchase
 something in the same category multiple times, so the inner join on
 `customer_id` and `category` is around as fast as the subquery. I actually
 managed to get a faster sql query putting the ID comparison directly in a
 left join, but I'm not sure why that worked.
 {{{
 same_category = models.ForeignObject(
     "self",
     on_delete=models.PROTECT,
     from_fields=["customer", "category"],
     to_fields=["customer", "category"],
 )
 Purchase.objects.filter(cancelled=True).exclude(
     same_category__id__gt=F("id"),
 )
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/29551#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 unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/064.048d631b1d4b5ef3a34c34422d7ed3bd%40djangoproject.com.

Reply via email to