Я пытаюсь использовать Django ORM для задачи, которая требует JOIN
в SQL. я
уже есть обходной путь, который выполняет ту же задачу с несколькими запросами
и некоторая обработка вне БД, но я не удовлетворен сложностью среды выполнения.
Во-первых, я хотел бы дать вам краткое введение в соответствующую часть моего
модель. После этого я объясню задачу на английском языке, SQL и (неэффективно) Django ORM.
Модель
В моей модели CMS сообщения многоязычны: для каждого сообщения и каждого языка может быть один экземпляр содержимого сообщения. Кроме того, при редактировании сообщений я не UPDATE
, а INSERT
их новые версии.
Итак, PostContent
уникален для post
, language
и version
. Вот класс:
class PostContent(models.Model):
""" contains all versions of a post, in all languages. """
language = models.ForeignKey(Language)
post = models.ForeignKey(Post) # the Post object itself only
version = models.IntegerField(default=0) # contains slug and id.
# further metadata and content left out
class Meta:
unique_together = (("resource", "language", "version"),)
Задача в SQL
И вот задача: Я хотел бы получить список самых последних версий всех сообщений на каждом языке, используя ORM. В SQL это переводится как JOIN
в подзапрос, который выполняет GROUP BY
и MAX
, чтобы получить максимум version
для каждой уникальной пары resource
и language
. Прекрасным ответом на этот вопрос был бы ряд вызовов ORM, которые производят следующий оператор SQL:
SELECT
id,
post_id,
version,
v
FROM
cms_postcontent,
(SELECT
post_id as p,
max(version) as v,
language_id as l
FROM
cms_postcontent
GROUP BY
post_id,
language_id
) as maxv
WHERE
post_id=p
AND version=v
AND language_id=l;
Решение в Джанго
Мое текущее решение, использующее Django ORM, не создает такой JOIN, а два отдельных SQL
запросы, и один из этих запросов может стать очень большим. Сначала я выполняю подзапрос (внутренний SELECT
сверху):
maxv = PostContent.objects.values('post','language').annotate(
max_version=Max('version'))
Теперь, вместо того, чтобы присоединиться к maxv
, я явно запрашиваю каждый пост в maxv
,
фильтрация PostContent.objects.all()
для каждого набора post, language, max_version
. Результирующий SQL выглядит как
SELECT * FROM PostContent WHERE
post=P1 and language=L1 and version=V1
OR post=P2 and language=L2 and version=V2
OR ...;
В Джанго:
from django.db.models import Q
conjunc = map(lambda pc: Q(version=pc['max_version']).__and__(
Q(post=pc['post']).__and__(
Q(language=pc['language']))), maxv)
result = PostContent.objects.filter(
reduce(lambda disjunc, x: disjunc.__or__(x), conjunc[1:], conjunc[0]))
Если maxv
достаточно мало, например, при получении одного сообщения, это может быть
хорошее решение, но размер запроса и время его создания растут линейно с
количество постов. Сложность разбора запроса также по крайней мере линейна.
Есть ли лучший способ сделать это, кроме использования необработанного SQL?