#django #many-to-many #manytomanyfield
#django #многие ко многим #manytomanyfield
Вопрос:
Прочитав документы django 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
они ведут себя по-разному для двух наборов запросов.
Комментарии:
1. Если вы заказываете by , вы создаете
JOIN
, forq1
, это, таким образом, присоединится к onPizzaToppings
. Для последнего набор запросов уже объединен из-за фильтрации, и, таким образом, вы исключаете определенные строки в СОЕДИНЕНИИ.
Ответ №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
Если вы затем выполните a .order_by('pizzas_with_topping')
, то вы, таким образом, создаете a JOIN
в таблице PizzaToppings
модели и, таким образом, упорядочиваете по первичному ключу этой таблицы. Таким образом, для первого набора запросов это выглядит следующим образом:
-- 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
таким образом, это означает, что если одна и та же начинка используется для нескольких пицц, for q1
, она будет появляться каждый раз для каждой пиццы, тогда как for q2
мы уже отфильтровали для пиццы и, таким образом, извлекаем каждую Topping
для этой пиццы, а не для других.
Комментарии:
1. Большое вам спасибо за очень подробное объяснение 🙂 Имеет смысл