Django中的从属子查询

时间:2016-07-26 17:19:17

标签: python sql django-queryset

我一直在靠墙试图使用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的最新永久性决策,因为在应用程序中重复调用此查询,迭代通过更简单的QuerySet(如Decision.filter(user_id=user.pk, project_id=project.pk))来获取最终结果不是一个选择。到目前为止,我使用像这样的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。提前谢谢。

1 个答案:

答案 0 :(得分:0)

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

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

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