【问题标题】:Django ORM: django aggregate over filtered reverse relationDjango ORM:django聚合过滤反向关系
【发布时间】: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 支持。

标签: django django-queryset


【解决方案1】:

考虑 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 魔术查询对象...

【讨论】:

  • 是的,我已经像这样过滤了,请参阅结果查询集的第一部分。问题是聚合函数 Max 将考虑来自后向关系的所有记录。
  • 然后添加类似.order_by('-issuing_date')[0]
  • aaahm...不确定它是否有效...再次:我需要查询在某个日期有效的所有文件;如果每个患者有多个文件,那么我们应该只考虑最近的一个。您的子句将只检索一个文件(假设只针对一名患者)。
【解决方案2】:

您可以使用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_bydistinct 时需要注意:https://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.query.QuerySet.distinct

编辑:从第一个过滤器中删除单个患者的依赖性并将latest 更改为order_bydistinct 的组合

【讨论】:

  • aaahm...不确定它是否有效...再次:我需要查询在某个日期有效的所有文件;如果每个患者有多个文件,那么我们应该只考虑最近的一个。您的子句将仅检索一个文件(假设仅适用于一名患者)。不管病人是谁,所有的文件怎么办?
猜你喜欢
  • 1970-01-01
  • 2012-11-03
  • 2021-11-02
  • 2019-11-06
  • 2020-09-23
  • 1970-01-01
  • 2020-09-01
  • 2018-04-18
  • 2017-03-09
相关资源
最近更新 更多