【问题标题】:Django ORM: Joining QuerySetsDjango ORM:加入查询集
【发布时间】:2012-03-28 05:49:47
【问题描述】:

我正在尝试将 Django ORM 用于在 SQL 中需要 JOIN 的任务。一世 已经有一种解决方法可以通过多个查询完成相同的任务 和一些数据库外处理,但我对运行时的复杂性并不满意。

首先,我想简要介绍一下我的相关部分 模型。之后,我会用英语、SQL 和(低效的)Django ORM 来解释这个任务。

模型

在我的 CMS 模型中,帖子是多语言的:对于每个帖子和每种语言,帖子内容都可以有一个实例。另外,在编辑帖子时,我不会UPDATE,而是INSERT它们的新版本。

所以,PostContentpostlanguageversion 上是唯一的。这是课程:

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 BYMAX 的子查询上,以获取resourcelanguage 的每对唯一对的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


【解决方案1】:

您可以使用| 运算符连接(在联合意义上)查询集,只要查询集查询相同的模型。

但是,听起来您想要PostContent.objects.order_by('version').distinct('language') 之类的东西;由于您在 1.3.1 中无法完全做到这一点,请考虑将 valuesdistinct() 结合使用以获得您需要的效果。

【讨论】:

  • QuerySet.__or__ 将两个查询与 SQL OR 运算符组合在一起,结果是集合的并集,而不是连接。
  • 我想要类似的东西,但 Django 1.3.1 中的 QuerySet.distinct 只接受布尔值作为参数:-)
  • 我知道你在使用哪种解决方案,它特定于 Postgres:Django 1.4 确实实现了 QuerySet.distinct(self, *field_names),它被编译为 Postgres 理解的 SELECT DISTINCT ON(fields) 选项。如果不使用 Postgres 作为后端,则返回 NotImplementedError。我想使用 ORM 作为后端的抽象,因此DISTINCT ON 对我来说不是一个好的解决方案。常规 SELECT DISTINCT 选项的工作方式不同:它从结果中消除重复行。由于每个PostContent 都有一个唯一的私钥,DISTINCT 不会改变结果。
猜你喜欢
  • 2020-05-26
  • 2014-05-19
  • 2022-01-21
  • 2021-07-25
  • 1970-01-01
  • 2017-09-18
  • 1970-01-01
  • 2014-09-04
  • 2015-10-26
相关资源
最近更新 更多