【发布时间】:2012-03-28 05:49:47
【问题描述】:
我正在尝试将 Django ORM 用于在 SQL 中需要 JOIN 的任务。一世
已经有一种解决方法可以通过多个查询完成相同的任务
和一些数据库外处理,但我对运行时的复杂性并不满意。
首先,我想简要介绍一下我的相关部分 模型。之后,我会用英语、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 的子查询上,以获取resource 和language 的每对唯一对的version 的最大值。这个问题的完美答案是产生以下 SQL 语句的一些 ORM 调用:
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 中的解决方案
我当前使用 Django ORM 的解决方案不会产生这样的 JOIN,而是两个单独的 SQL
查询,其中一个查询可能会变得非常大。我首先执行子查询(上面的内部SELECT):
maxv = PostContent.objects.values('post','language').annotate(
max_version=Max('version'))
现在,我没有加入maxv,而是明确要求maxv 中的每一个帖子,由
为post, language, max_version 的每个元组过滤PostContent.objects.all()。生成的 SQL 看起来像
SELECT * FROM PostContent WHERE
post=P1 and language=L1 and version=V1
OR post=P2 and language=L2 and version=V2
OR ...;
在 Django 中:
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 之外,还有更好的方法吗?
【问题讨论】:
-
这很长,而且不是特别清楚。请把它提炼下来。
-
我不能让它更短,但我希望它现在更清楚了。
标签: python django orm django-models