了解 order_by 多值字段 (Django)

Understanding order_by of multi-valued fields (Django)

阅读 django docs on order_by 后,note/warning 说(如果我理解正确的话):

我尝试用一​​个基本示例对此进行测试:

最小可重现示例

class Pizza(models.Model):
    name = models.CharField(max_length=100)
    toppings = models.ManyToManyField('Topping', through='PizzaToppings')

class PizzaToppings(models.Model):
    pizza = models.ForeignKey('Pizza', on_delete=models.CASCADE, related_name="pizza_toppings")
    topping = models.ForeignKey('Topping', on_delete=models.CASCADE, related_name="pizzas_with_topping")
    amount = models.IntegerField()

    class Meta:
        ordering = ["amount",]

class Topping(models.Model):
    ingredient = models.CharField(max_length=100)

然后

>>> p1 = Pizza.objects.create(name="Cheese and Tomato")
>>> p2 = Pizza.objects.create(name="Pepperoni")
>>> cheese = Topping.objects.create(ingredient="Cheese")
>>> tomato = Topping.objects.create(ingredient="Tomato puree")
>>> p1.toppings.add(cheese, through_defaults={"amount":4})
>>> p1.toppings.add(tomato, through_defaults={"amount":3})
>>> p2.toppings.add(cheese, through_defaults={"amount":2})
>>> p2.toppings.add(tomato, through_defaults={"amount":1})

到目前为止,一切正常。但这就是事情变得混乱的地方:

>>> q1 = Topping.objects.all()
<QuerySet [<Topping: Topping object (1)>, <Topping: Topping object (2)>]>
>>> q2 = p1.toppings.all()
<QuerySet [<Topping: Topping object (1)>, <Topping: Topping object (2)>]>
>>> q1.order_by("pizzas_with_topping")
<QuerySet [<Topping: Topping object (2)>, <Topping: Topping object (1)>, <Topping: Topping object (2)>, <Topping: Topping object (1)>]>
>>> q2.order_by("pizzas_with_topping")
<QuerySet [<Topping: Topping object (2)>, <Topping: Topping object (1)>]>

问题

从上面可以看出,查询集在它们包含的元素方面是相同的。但是当一个 q1 被订购时,我们得到了文档中描述的行为。在 q2 中,我们没有这种行为。大概这是因为 django 正在做一些聪明的事情,因为查询集与 p1.

相关的配料有关

问题

'under the hood' 执行此行为的实际情况是什么?查询集是相同的(如果我理解正确的话),那么为什么 order_by 对两个查询集的行为不同。

两个查询集不同。第一个查询集表示如下查询:

-- q1
SELECT *
FROM topping

q2 表示的查询如下所示:

-- q2
SELECT *
FROM topping
<b>INNER JOIN</b> pizzatoppings ON pizzatoppings.topping_id = topping.id
WHERE pizzatoppings.pizza_id = <i>id-of-pizza</i>

如果你然后执行 .order_by('pizzas_with_topping'),那么你就在 PizzaToppings 模型的 table 上创建了 JOIN,因此你按主键排序其中 table。对于第一个查询集,这看起来像:

-- q1.order_by('pizzas_with_topping')
SELECT *
FROM topping
<b>LEFT OUTER JOIN</b> pizzatoppings ON pizzatoppings.topping_id = topping.id
ORDER BY <b>pizzatoppings.id</b>

对于后者,您过滤已存在的连接:

-- q2.order_by('pizzas_with_topping')
SELECT *
FROM topping
INNER JOIN pizzatoppings ON pizzatoppings.topping_id = topping.id
WHERE pizzatoppings.pizza_id = <i>id-of-pizza</i>
ORDER BY <b>pizzatoppings.id</b>

这意味着如果相同的配料用于多个比萨饼,对于 q1,每个比萨饼每次都会出现,而对于 q2 我们已经在比萨饼上进行了过滤,并且因此检索该披萨的每个 Topping,而不是其他披萨。