#django #orm #django-models #many-to-many #django-queryset
#django #orm #django-модели #»многие ко многим» #django-queryset
Вопрос:
У меня есть следующая структура модели:
class Container(models.Model):
pass
class Generic(models.Model):
name = models.CharacterField(unique=True)
cont = models.ManyToManyField(Container, null=True)
# It is possible to have a Generic object not associated with any container,
# thats why null=True
class Specific1(Generic):
...
class Specific2(Generic):
...
...
class SpecificN(Generic):
...
Скажем, мне нужно получить все модели Specific
типа, которые имеют отношение к определенному контейнеру.
SQL для этого более или менее тривиален, но вопрос не в этом. К сожалению, я не очень опытен в работе с ORM (в частности, с ORM Django), поэтому, возможно, я упускаю шаблон здесь.
При выполнении методом перебора, —
c = Container.objects.get(name='somename') # this gets me the container
items = c.generic_set.all()
# this gets me all Generic objects, that are related to the container
# Now what? I need to get to the actual Specific objects, so I need to somehow
# get the type of the underlying Specific object and get it
for item in items:
spec = getattr(item, item.get_my_specific_type())
это приводит к тонне обращений к базе данных (по одному для каждой общей записи, относящейся к контейнеру), так что это, очевидно, не лучший способ сделать это. Теперь, возможно, это можно было бы сделать, получив конкретные объекты напрямую:
s = Specific1.objects.filter(cont__name='somename')
# This gets me all Specific1 objects for the specified container
...
# do it for every Specific type
таким образом, база данных будет обрабатываться один раз для каждого конкретного типа (приемлемо, я полагаю).
Я знаю, что .функция select_related() не работает с отношениями m2m, поэтому здесь она мало чем поможет.
Повторяю, конечным результатом должна быть коллекция конкретных объектов (не универсальных).
Комментарии:
1. Оглядываясь назад, вопрос кажется мне немного бессмысленным, поскольку я уже предоставил единственно возможный ответ. В конце концов, нет способа получить объединенный результирующий набор из нескольких таблиц с произвольными полями. Хорошо, очевидно, что есть способ, но он уродлив и включает динамический sql и / или «select *». Я думаю.
2. Ваш вопрос здесь на самом деле не имеет ничего общего с оптимизацией отношения ManyToMany, а все, что связано с оптимизацией запросов против многостолового наследования. Что действительно является сложной проблемой.
3. Теперь, когда я думаю об этом, то, что привело меня к мысли, что эта проблема связана с отношениями m2m, на самом деле заключается в том факте, что select_related не пересекает отношения «многие ко многим».
Ответ №1:
Я думаю, вы уже обрисовали в общих чертах две простые возможности. Либо вы выполняете один запрос фильтра по отношению к Generic, а затем приводите каждый элемент к его определенному подтипу (в результате получается n 1 запросов, где n — количество возвращенных элементов), либо вы выполняете отдельный запрос к каждой конкретной таблице (в результате получается k запросов, где k — количество определенных типов).
На самом деле стоит провести сравнительный анализ, чтобы увидеть, какой из них в действительности быстрее. Второй вариант кажется лучше, потому что в нем (вероятно) меньше запросов, но каждый из этих запросов должен выполнять объединение с промежуточной таблицей m2m. В первом случае вы выполняете только один запрос объединения, а затем множество простых. Некоторые серверные части базы данных лучше работают с большим количеством небольших запросов, чем с меньшим количеством более сложных.
Если второй вариант действительно значительно быстрее для вашего варианта использования, и вы готовы проделать дополнительную работу по очистке вашего кода, должна быть возможность написать пользовательский метод менеджера для универсальной модели, который «предварительно извлекает» все данные подтипа из соответствующих конкретных таблиц для данного набора запросов, используя только один запрос для таблицы подтипа; аналогично тому, как этот фрагмент оптимизирует универсальные внешние ключи с помощью массовой предварительной выборки. Это дало бы вам те же запросы, что и ваш второй вариант, с более сухим синтаксисом вашего первого варианта.
Комментарии:
1. Не знаю, кто проголосовал против этого. И да, я рассмотрю возможность написания пользовательского менеджера, однако, как я уже говорил, мой опыт работы с ORM очень ограничен (хотя у меня нет проблем с SQL), поэтому внутренности по-прежнему остаются для меня чем-то вроде черного ящика. В любом случае, я пойду и посмотрю, что я могу сделать.
Ответ №2:
Не полный ответ, но вы можете избежать большого количества обращений, выполнив это
items= list(items)
for item in items:
spec = getattr(item, item.get_my_specific_type())
вместо этого :
for item in items:
spec = getattr(item, item.get_my_specific_type())
Действительно, принудительно преобразуя список python, вы заставляете django orm загружать все элементы в ваш набор запросов. Затем он выполняет это в одном запросе.
Комментарии:
1. Это хороший намек (упоминается в документации, которую я прочитал :). И к тому же не слишком очевидное.
2. Хм, это просто неправда. Приведение к списку в данном случае не имеет значения. В обеих версиях выполняется только один запрос к общей таблице, и в обеих версиях один запрос выполняется к конкретной таблице для каждого элемента. Одинаковое количество запросов для обоих.
Ответ №3:
Я случайно наткнулся на следующий пост, который в значительной степени отвечает на ваш вопрос :
http://lazypython.blogspot.com/2008/11/timeline-view-in-django.html