Django ORM:django聚合过滤后的反向关系

时间:2016-01-18 08:25:07

标签: django django-queryset

通过进一步限制生成的查询集,该问题与Django ORM: filter primary model based on chronological fields from related model远程相关。

模型

假设我们有以下型号:

class Patient(models.Model)
    name = models.CharField()
    # other fields following

class MedicalFile(model.Model)
    patient = models.ForeignKey(Patient, related_name='files')
    issuing_date = models.DateField()
    expiring_date = models.DateField()
    diagnostic = models.CharField()

查询

我需要选择在指定日期有效的所有文件,最有可能是过去的文件。我在这里遇到的问题是,对于每个患者,将会有一个小的重叠期,患者将有2个有效文件。如果我们要查询那个小时间段的日期,我只需要选择最新的文件。

更重要的是:考虑患者John Doe。他将拥有从2012年开始的一系列“不间断”文件,如下所示:

+---+------------+-------------+
|ID |issuing_date|expiring_date|
+---+------------+-------------+
|1  |2012-03-06  |2013-03-06   |
+---+------------+-------------+
|2  |2013-03-04  |2014-03-04   |
+---+------------+-------------+
|3  |2014-03-04  |2015-03-04   |
+---+------------+-------------+

正如人们可以很容易地观察到的那样,这些文件的有效性存在几天的重叠。例如,在2013-03-05,文件1和2是有效的,但我们只考虑文件2(作为最新文件)。我猜这个用例并不特别:这是管理订阅的情况,为了获得连续订阅,您需要提前续订。

现在,在我的应用程序中,我需要查询历史数据,例如给我所有在2013-03-05有效的文件,只考虑“最近的”文件。我能够通过使用RawSQL来解决这个问题,但我希望有一个没有原始SQL的解决方案。在上一个问题中,我们能够通过反向关系聚合过滤“最新”文件,如:

qs = MedicalFile.objects.annotate(latest_file_date=Max('patient__files__issuing_date'))
qs = qs.filter(issuing_date=F('latest_file_date')).select_related('patient')

问题是我们需要通过对2013-03-05进行过滤来限制计算latest_file_date的范围。但是聚合函数不会在筛选的查询集上运行...

“差”解决方案

我目前正在通过额外的queryset子句(用您的具体应用程序替换“app”)来执行此操作:

reference_date = datetime.date(year=2013, month=3, day=5)
annotation_latest_issuing_date = {
    'latest_issuing_date': RawSQL('SELECT max(file.issuing_date) '
                                  'FROM <app>_medicalfile file '
                                  'WHERE file.person_id = <app>_medicalfile.person_id '
                                  '  AND file.issuing_date <= %s', (reference_date, ))
}
qs = MedicalFile.objects.filter(expiring_date__gt=reference_date, issuing_date__lte=reference_date)
qs = qs.extra(**annotation_latest_issuing_date).filter(issuing_date=F('latest_issuing_date'))

这样写,查询集返回正确的记录数。

问题:如果没有RaWSQL和(已经隐含)具有相同的性能水平,怎么能实现?

2 个答案:

答案 0 :(得分:0)

考虑p是一个Patient类实例。

我认为你可以这样做:

p.files.filter(issue_date__lt='some_date', expiring_date__gt='some_date')

请参阅https://docs.djangoproject.com/en/1.9/topics/db/queries/#backwards-related-objects

或者使用Q魔术查询对象...

答案 1 :(得分:0)

您可以使用id__in并提供嵌套的过滤查询集(就像在给定日期有效的所有文件一样)。

qs = MedicalFile.objects
.filter(id__in=self.filter(expiring_date__gt=reference_date, issuing_date__lte=reference_date))
.order_by('patient__pk', '-issuing_date')
.distinct('patient__pk')  # field_name parameter only supported by Postgres

order_by按病人分组文件,最先发布日期。然后distinct检索每个患者的第一个文件。但是,在合并order_bydistincthttps://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.query.QuerySet.distinct

时,需要一般谨慎

修改:从第一次过滤删除了单个患者依赖,并将latest更改为order_bydistinct的组合