我正在创建一份报告,按日期和医生列出患者死亡人数。在整个研究过程中将有数千人死亡,数百名医生,我希望我的报告能够快速运行(页面加载时间不到1秒)。
我想创建一个可用于创建表的queryset对象。我会为桌子做这样的事情 -
for doc in doctors:
html += "<tr><td>" + str(doc) + "<td>"
for period in time_periods:
count = my_new_queryset.filter(gp = doc)
.filter(date__gte=period['start_date'])
.filter(date__lte=period['end_date'])
html += "<td>" + str(count) + "</td>"
html += "</tr>"
在sql中查询看起来像这样 -
SELECT patient.name,
death.date,
patient_gp_link.gp
FROM patient
INNER JOIN death
ON patient.id = death.patient
INNER JOIN patient_gp_link
ON patient.id = patient_gp_link.patient
WHERE patient_gp_link.is_main = true;
(简化)模型看起来像 -
class GP(models.Model):
#....
class Patient(models.Model):
#....
class Death(models.Model):
patient = models.ForeignKey(Patient)
date = models.DateField()
class PatientGPLink(models.Model):
gp = models.ForeignKey(GP)
patient = models.ForeignKey(Patient)
is_main = models.BooleanField(default = False)
我只是看不到如何创建与此sql对应的queryset对象。 django可以这样做,还是应该使用原始sql?
答案 0 :(得分:2)
扩展我对你的回答的评论,我试着编写一个可以做你想要的查询集,但根据你的数据模型似乎不可能。
您的数据模型实际上并未描述特定医生治疗特定患者的日期范围。无论您如何编写查询,您正在编写的查询将返回错误的结果。您目前所说的内容如下:
“如果患者死亡,那么治疗该患者is_main=True
的任何医生都将被标记为负责任”。 (责任可能不是正确的词,但你应该明白这一点。)
现在,如果你只是将一个GP作为is_main
分配给特定患者,那么上述情况就好了。但是你的数据模型没有强制执行此操作,并且可能容易出错。特别是如果患者死亡后is_main
发生变化。我将数据模型构建为以下之一:
class GP(models.Model):
name = models.CharField(max_length=64)
class Patient(models.Model):
name = models.CharField(max_length=64)
class Death(models.Model):
current_gp = models.ForeignKey(GP)
patient = models.ForeignKey(Patient)
date = models.DateField()
class Consultation(models.Model):
gp = models.ForeignKey(GP)
patient = models.ForeignKey(Patient)
start_date = models.DateField()
end_date = models.DateField(blank=True, null=True)
或..
class GP(models.Model):
name = models.CharField(max_length=64)
class Patient(models.Model):
name = models.CharField(max_length=64)
class Death(models.Model):
patient = models.ForeignKey(Patient)
date = models.DateField()
class Consultation(models.Model):
gp = models.ForeignKey(GP)
patient = models.ForeignKey(Patient)
start_date = models.DateField()
end_date = models.DateField(blank=True, null=True)
第一种结构的好处是可以提供极其高效的查询,但需要在患者死亡时输入额外的信息。但是,Consultation
(以前称为PatientGPLink)模型将具有推断此信息所需的所有信息。您还可以将Death.current_gp设置为ManyToManyField,以支持多个GP负责患者。
第二个结构可以收集相同的信息,但需要日期时间过滤,这将加入另一个表,并使查询更慢,更复杂。
如果您非常有意识地维护is_main
字段,那么所有这些都是无关紧要的,因为数据是正确的。但是,让我向您展示如何从您的视图中以(可能)更有效的方式查询您想要的信息:
def my_view(request):
doctors = GP.objects.all()
periods = get_time_periods() # however it is you do this...
smallest_date = get_smallest_date(time_periods)
largest_date = get_largest_date(time_periods)
deaths = Death.objects.select_related(depth=1).filter(date__range=(smallest_date, largest_date))
# build the results table with initial count of 0 to account for all doctors
# {period: {doctor: count}}
results = dict((period,{doctor: 0}) for doctor in doctors for period in periods)
for death in deaths:
for period in time_periods: # I'm assuming this is a small range of values
if death.date > period['start_date'] and death.date < period['end_date']:
results[period][death.current_gp] += 1 # add a death to the count
然后,在您的模板中,您拥有包含所有预先计算信息的results
表格:
<table>
{% for period, lookup in results.items %}
{% for doctor, deaths in lookup.items %}
<tr>
<td>{{ period }}</td>
<td>{{ doctor }}</td>
<td>{{ deaths }}</td>
</tr>
{% endfor %}
{% endfor %}
</table>
总共2个SQL查询。这种方式有更多的手动处理,但计算结果应该比查询数据库num_doctors * num_timeperiods + 1倍更快,这正是您目前所做的。
编辑:
为了使它适用于您当前的模型结构(如果您真的无法更改模型......),您可以将我的答案与我的答案结合起来,并最终获得与我编写的原始视图非常相似的视图。我正在忽略所有评论,因为它们将与上述相同。我已将评论放在我改变原始视图的位置。
def my_view(request):
doctors = GP.objects.all()
periods = get_time_periods()
smallest_date = get_smallest_date(time_periods)
largest_date = get_largest_date(time_periods)
# we make depth=3 so it spans via the PatientGPLink over to GP
deaths = Death.objects.select_related(depth=3).filter(date__range=(smallest_date, largest_date)).filter(patient__patientgplink__ismain=True)
results = dict((period,{doctor: 0}) for doctor in doctors for period in periods)
for death in deaths:
for period in time_periods:
if death.date > period['start_date'] and death.date < period['end_date']:
# and we change how we access the GP
results[period][death.patient.patientgplink.gp] += 1
仍然只有两个查询,尽管这些查询较大(跨越多个表)。
答案 1 :(得分:1)
QuerySet可以与.select_related()建立联接。您还需要阅读lookups that span relationships。 my_new_queryset看起来像:
Patient.objects.filter(patientgplink__is_main=True).select_related('death')
您还可以尝试让医生按{3}计算每位医生的数量。
答案 2 :(得分:0)
感谢jpic提供有关查询的信息,这些查询跨越了我的关系(特别是'向后'功能)+1。我已经包含了我的更新代码,以防万一其他人来看。
for doc in GP.objects.all():
html += "<tr><td>" + str(doc) + "<td>"
patients = (Patient.objects.filter(patientgplink__gp=doc)
.filter(patientgplink__is_main=True)
.select_related(depth=1))
for period in time_periods:
count = (patients.filter(death__date__gte=period['start_date'])
.filter(death__date__lte=period['end_date'])
.count())
html += "<td>" + str(count) + "</td>"
html += "</tr>"