【发布时间】:2016-04-23 08:25:43
【问题描述】:
通过进一步限制生成的查询集,该问题与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 年 3 月 5 日,文件 1 和 2 是有效的,但我们只考虑文件 2(作为最新的文件)。我猜这个用例并不特殊:这是管理订阅的情况,为了获得持续订阅,您需要提前续订。
现在,在我的应用程序中,我需要查询历史数据,例如给我所有在 2013 年 3 月 5 日有效的文件,只考虑“最新”的文件。我可以通过使用 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 的范围。但是聚合函数不会在过滤后的查询集上运行...
“糟糕”的解决方案
我目前正在通过一个额外的查询集子句来执行此操作(用您的具体应用程序替换“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 并且(已经暗示)具有相同的性能水平,如何实现它?
【问题讨论】:
-
在某些地方,django 允许嵌套过滤,其行为方式符合人们的预期:
qs = MedicalFile.objects.filter(id__in=self.filter(patient=john_doe, expiring_date__gt=reference_date, issuing_date__lte=reference_date)).latest('issuing_date') -
latest 返回表中的最新记录。我们需要患者的最新文件,但在包含所有可能患者的结果集中。
-
这就是你使用
id__in“预过滤”的原因 -
好的,对于一名患者来说,这可能有效。但是最初的问题呢:检索在某个日期有效的所有文件? (无论有多少患者)。
-
您可以尝试从嵌套过滤器中删除患者并使用
order_by('patient__pk', '-issuing_date').distinct('patient__pk')而不是latest('issuing_date')。但是distinct带有字段名称,我认为只有 Postgres 支持。