3表内连接django

时间:2012-03-23 11:20:19

标签: python sql django django-queryset

我正在创建一份报告,按日期和医生列出患者死亡人数。在整个研究过程中将有数千人死亡,数百名医生,我希望我的报告能够快速运行(页面加载时间不到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?

3 个答案:

答案 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>"