#31657: self referencing foreign key join and order_by issues
-----------------------------------------+------------------------
               Reporter:  Jack Delany    |          Owner:  nobody
                   Type:  Uncategorized  |         Status:  new
              Component:  Uncategorized  |        Version:  3.0
               Severity:  Normal         |       Keywords:
           Triage Stage:  Unreviewed     |      Has patch:  0
    Needs documentation:  0              |    Needs tests:  0
Patch needs improvement:  0              |  Easy pickings:  0
                  UI/UX:  0              |
-----------------------------------------+------------------------
 Initially discovered on 2.2.10 but verified still happens on 3.0.6.  Given
 the following models:
 {{{
 class OneModel(models.Model):
     class Meta:
         ordering = ("-id",)

     id = models.BigAutoField(primary_key=True)
     root = models.ForeignKey("OneModel", on_delete=models.CASCADE,
 null=True)
     oneval = models.BigIntegerField(null=True)

 class TwoModel(models.Model):
     id = models.BigAutoField(primary_key=True)
     record = models.ForeignKey(OneModel, on_delete=models.CASCADE)
     twoval = models.BigIntegerField(null=True)
 }}}
 The following queryset gives unexpected results and appears to be an
 incorrect SQL query:
 {{{
 qs = TwoModel.objects.filter(record__oneval__in=[1,2,3])
 qs = qs.order_by("record__root_id")
 print(qs.query)
 }}}
 {{{
 SELECT "orion_twomodel"."id", "orion_twomodel"."record_id",
 "orion_twomodel"."twoval" FROM "orion_twomodel" INNER JOIN
 "orion_onemodel" ON ("orion_twomodel"."record_id" = "orion_onemodel"."id")
 LEFT OUTER JOIN "orion_onemodel" T3 ON ("orion_onemodel"."root_id" =
 T3."id") WHERE "orion_onemodel"."oneval" IN (1, 2, 3) ORDER BY T3."id"
 DESC
 }}}
 The query has an unexpected DESCENDING sort.  That appears to come from
 the default sort order on the OneModel class, but I would expect the
 order_by() to take prececence.  The the query has two JOINS, which is
 unnecessary.  It appears that, since OneModel.root is a foreign key to
 itself, that is causing it to do the unnecessary extra join.  In fact,
 testing a model where root is a foreign key to a third model doesn't show
 the problem behavior.

 Note also that the queryset with `order_by("record__root")` gives the
 exact same SQL.

 This queryset gives correct results and what looks like a pretty optimal
 SQL:
 {{{
 qs = TwoModel.objects.filter(record__oneval__in=[1,2,3])
 qs = qs.order_by("record__root__id")
 print(qs.query)
 }}}
 {{{
 SELECT "orion_twomodel"."id", "orion_twomodel"."record_id",
 "orion_twomodel"."twoval" FROM "orion_twomodel" INNER JOIN
 "orion_onemodel" ON ("orion_twomodel"."record_id" = "orion_onemodel"."id")
 WHERE "orion_onemodel"."oneval" IN (1, 2, 3) ORDER BY
 "orion_onemodel"."root_id" ASC
 }}}

 So is this a potential bug or a misunderstanding on my part?

 Another queryset that works around the issue and gives a reasonable SQL
 query and expected results:
 {{{
 qs = TwoModel.objects.filter(record__oneval__in=[1,2,3])
 qs = qs.annotate(root_id=F("record__root_id"))
 qs = qs.order_by("root_id")
 print(qs.query)
 }}}
 {{{
 SELECT "orion_twomodel"."id", "orion_twomodel"."record_id",
 "orion_twomodel"."twoval" FROM "orion_twomodel" INNER JOIN
 "orion_onemodel" ON ("orion_twomodel"."record_id" = "orion_onemodel"."id")
 WHERE "orion_onemodel"."oneval" IN (1, 2, 3) ORDER BY
 "orion_onemodel"."zero_id" ASC
 }}}
 ASCENDING sort, and a single INNER JOIN, as I'd expect.  That actually
 works for my use because I need that output column anyway.

 One final oddity; with the original queryset but the inverted sort
 order_by():
 {{{
 qs = TwoModel.objects.filter(record__oneval__in=[1,2,3])
 qs = qs.order_by("-record__root_id")
 print(qs.query)
 }}}
 {{{
 SELECT "orion_twomodel"."id", "orion_twomodel"."record_id",
 "orion_twomodel"."twoval" FROM "orion_twomodel" INNER JOIN
 "orion_onemodel" ON ("orion_twomodel"."record_id" = "orion_onemodel"."id")
 LEFT OUTER JOIN "orion_onemodel" T3 ON ("orion_onemodel"."root_id" =
 T3."id") WHERE "orion_onemodel"."oneval" IN (1, 2, 3) ORDER BY T3."id" ASC
 }}}
 One gets the query with the two JOINs but an ASCENDING sort order.  I was
 not under the impression that sort orders are somehow relative to the
 class level sort order, eg: does specifing `order_by("-record__root_id")`
 invert the class sort order?  Testing that on a simple case doesn't show
 that behavior at all.

 Thanks for any assistance and clarification.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/31657>
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/051.229ac4da8468f93df7b7380e956644aa%40djangoproject.com.

Reply via email to