Django ORM: присоединение к QuerySets - PullRequest
4 голосов
/ 11 марта 2012

Я пытаюсь использовать 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?

1 Ответ

0 голосов
/ 11 марта 2012

Вы можете объединять (в смысле объединения) наборы запросов с помощью оператора |, если наборы запросов запрашивают одну и ту же модель.

Однако, похоже, вы хотите что-то вроде PostContent.objects.order_by('version').distinct('language');поскольку вы не можете сделать это в 1.3.1, рассмотрите возможность использования values в сочетании с distinct(), чтобы получить нужный эффект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...