了解 order_by 多值字段 (Django)
Understanding order_by of multi-valued fields (Django)
阅读 django docs on order_by
后,note/warning 说(如果我理解正确的话):
- 如果您使用多值字段对查询集进行排序,则该查询集中具有多个相关项的每个元素都将多次添加到由
order_by
创建的结果查询集中。
我尝试用一个基本示例对此进行测试:
最小可重现示例
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
,而不是其他披萨。
阅读 django docs on order_by
后,note/warning 说(如果我理解正确的话):
- 如果您使用多值字段对查询集进行排序,则该查询集中具有多个相关项的每个元素都将多次添加到由
order_by
创建的结果查询集中。
我尝试用一个基本示例对此进行测试:
最小可重现示例
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
,而不是其他披萨。