【问题标题】:Dependant subqueries in DjangoDjango中的依赖子查询
【发布时间】:2016-11-30 11:22:52
【问题描述】:

我一直在努力尝试使用 Django ORM 进行此查询,我到处搜索但找不到答案。 这是我的模型。

class Decision(models.Model):
    ACTION_CHOICES = [('include', _('Include')), ('exclude', _('Exclude'))]
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    date_taken = models.DateTimeField(_('date taken'), default=timezone.now)
    action = models.CharField(max_length=7, choices=ACTION_CHOICES)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    project = models.ForeignKey('projects.Project', on_delete=models.CASCADE)
    is_permanent = models.BooleanField(_('is permanent'), default=False)

我要查询的是用户在项目中所做的每个 content_object 的最新永久决策,因为此查询在应用程序中被重复调用,通过像 Decision.filter(user_id=user.pk, project_id=project.pk) 这样更简单的 QuerySet 进行迭代以获得最终结果这不是一个选择。到目前为止,我使用像这样的 RawQuerySet 解决了它。

Decision.objects.raw("SELECT decision.id, decision.content_type_id, decision.object_id "
                     "FROM canvasblocks_decision AS decision "
                     "WHERE decision.date_taken = (SELECT max(last_decision.date_taken) "
                     "                             FROM canvasblocks_decision AS last_decision "
                     "                             WHERE last_decision.object_id = decision.object_id AND "
                     "                                   last_decision.content_type_id = decision.content_type_id AND "
                     "                                   last_decision.project_id = %s AND decision.user_id = %s) ",
                     [project.pk, user.pk])

但是我不喜欢这个解决方案,因为我失去了所有 Django ORM 的能力,而且我必须再次过滤这个查询来检查操作,失去这个能力是很痛苦的,因为我必须在一个子查询上使用所有随之而来的复杂性。小伙伴们,你们知道更好的方法吗?顺便说一句,我使用的是 Django 1.9.4。提前致谢。

【问题讨论】:

    标签: python sql django-queryset


    【解决方案1】:

    如果数据集很小,您可以进行多次传递,但这不是一个好主意。

    几个选项,具体取决于您使用的数据库。

    1) 您可以将sql更改为使用rank或dense_rank函数以使查询更简单。

    select decision.id, decision.content_type_id, decision.object_id,
           first_value (last_decision.date_taken) 
            over (partition by ecision.id, decision.content_type_id, decision.object_id
                  order by last_decision.date_taken desc
                  )
    from   canvasblocks_decision AS decision
    ...
    

    2) 您可以将相同的逻辑放在注释中,以获得排名。这样,您就拥有了 django 对象提供的所有内容,并获得了这个额外的列。

    from django.db.models.expressions import RawSQL
    Decision.objects.filter().annotate(rank=RawSQL("RANK() OVER (partition by id, content_type, object_id
                                       (ORDER BY date_taken DESC)", [])   
                                      )
    

    ..

    这可能会有所帮助:https://stackoverflow.com/a/35948419/237939

    【讨论】:

    • 谢谢@Rajesh Chamarthi。我必须将我的数据库迁移到 postgres!这么多惊人的功能!我不完全理解你给我的第一个选项,你的答案的postrgres window functions 对我来说是全新的,我想first_value(...) 占据分区中排名最高的行,但我没有看到last_decision.date_taken 来自哪里,在第二个选项中,我将不得不做一个.filter(rank=1) 以获得最终结果?
    • 是的,您需要过滤 rank=1。我还编辑了添加分区子句的答案。抱歉,目前尚未测试,因为我无法使用 Django 访问实例。顺便说一句,Postgres 是个不错的选择!
    • 别担心,我稍后会测试,我想我终于明白第一个选项是什么意思了,this video 有帮助,OVER (PARTITION BY ...) 的行为就像我的子查询是 @987654330 @ 但更简单,在这种情况下,您添加到最近元素的每行 decision.date_taken 并比较 WHERE 中的这两个日期以选择匹配项,对吗?如果我错了,请纠正我,我认为它只是缺少OVER (PARTITION BY ...) 的别名,而是decision.date_taken 而不是last_decision.date_taken?再次感谢。
    • 分区应该只有 table-name.column_name。
    猜你喜欢
    • 1970-01-01
    • 2014-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多