【问题标题】:Understanding order_by of multi-valued fields (Django)了解多值字段的 order_by (Django)
【发布时间】:2020-12-08 19:33:18
【问题描述】:

阅读django docs on order_by 后,有一条注释/警告(如果我理解正确的话)说:

  • 如果您使用多值字段对查询集进行排序,则该查询集中具有多个相关项的每个元素都将多次添加到由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 相关的浇头。

问题

实际上在“幕后”发生了什么来强制执行这种行为?查询集是相同的(如果我理解正确的话),那么为什么 order_by 对两个查询集的行为不同。

【问题讨论】:

  • 如果您按顺序订购,则为q1 创建一个JOIN,这将因此加入PizzaToppings。对于后者,querset is 由于过滤已经加入,因此您消除了 JOIN 中的某些行。

标签: django many-to-many manytomanyfield


【解决方案1】:

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

-- q1
SELECT *
FROM topping

q2 表示的查询如下所示:

-- q2
SELECT *
FROM topping
INNER JOIN pizzatoppings ON pizzatoppings.topping_id = topping.id
WHERE pizzatoppings.pizza_id = id-of-pizza

如果您随后执行.order_by('pizzas_with_topping'),那么您将在PizzaToppings 模型的表上创建一个JOIN,并因此按该表的主键排序。对于第一个查询集,这看起来像:

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

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

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

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

【讨论】:

  • 非常感谢您的详尽解释 :) 有道理
猜你喜欢
  • 2011-03-10
  • 1970-01-01
  • 2017-11-24
  • 1970-01-01
  • 2017-06-09
  • 2015-01-26
  • 2014-06-20
  • 2014-05-18
  • 2016-05-15
相关资源
最近更新 更多