对于以下所有内容,我使用的是Django 1.9。
我有一个看起来像这样的Django模型:
class Project(models.Model):
name = models.CharField()
task_count = models.FloatField()
requesters = models.ManyToManyField('Requester', through='ProjectRequester')
class ProjectRequester(models.Model):
project = models.ForeignKey('Project')
requester = models.ForeignKey('Requester')
class Requester(models.Model):
username = models.CharField()
我有一个表单,允许用户获取所有项目的总任务数或通过名称或请求者对任务计数进行分组。如果按名称分组,这完全正常。您将获得一个类似于以下内容的查询集:
qs = Project.objects.select_related().values('name')
qs = qs.annotate(task_count=models.Sum('task_count'))
生成的SQL完全符合您的预期,例如:
SELECT SUM(Project.task_count) FROM Project GROUP BY Project.Name;
但是,由于请求者的多对多连接,当您通过请求者进行数据转换时,任何具有多个请求者的项目都会将其任务重复计算。这样:
qs = Project.objects.select_related().values('requesters__username')
qs = qs.annotate(task_count=models.Sum('task_count'))
导致这一点:
SELECT SUM(Project.task_count) FROM Project
LEFT OUTER JOIN ProjectRequester ON ProjectRequester.ProjectId=Project.Id
LEFT OUTER JOIN Requester ON Requester.Id=ProjectRequester.RequesterId
GROUP BY Requester.Username;
出于很多原因,我绝对不想使用原始sql。所以我最初的想法是将请求者的数量存储为项目模型(requester_count = models.FloatField()
)上的一个字段,当项目加载到表中时,该模型会自动填充。这样,我可以将上面的查询集创建的第二行更改为:
task_count = models.Sum(models.ExpressionWrapper(
models.F('task_count') / models.F('requester_count')
qs = qs.annotate(task_count=task_count)
这会产生所需的结果:
SELECT SUM(Project.task_count / Project.requester_count) FROM Project
LEFT OUTER JOIN ProjectRequester ON ProjectRequester.ProjectId=Project.Id
LEFT OUTER JOIN Requester ON Requester.Id=ProjectRequester.RequesterId
GROUP BY Requester.Username;
耶!然而,实际上还有许多其他的东西在这里(大约20种其他方式你可以实际转动,加上能够根据某些字段的任何组合进行过滤)所以它并不像只需添加一个if pivot == 'name', use regular sum annotation, else use sum divided by requester count
(因为如果LEFT OUTER JOIN没有发生,使用第二个注释将不会产生正确数量的任务)。
因此,这引出了我的问题:是否有某种方法可以强制Django始终使用ProjectRequester表添加LEFT OUTER JOIN,即使该表没有过滤器或组?这样,我总是可以简单地除以requester_count,无论使用pivot还是filter,数学总是可以解决。
提前致谢。