如何在Django中跨字段计算值?

时间:2018-07-27 12:03:48

标签: python django django-queryset

模型

class ModelA(models.Model):
    name = models.CharField()

class ModelB(models.Model):
    MY_CHOICES = (
        ('X', 'X'),
        ('Y', 'Y'),
        ('Z', 'Z'),
    )
    modela = models.ForeignKey(ModelA, on_delete=models.CASCADE)
    txt_1 = models.CharField(choices=MY_CHOICES)
    txt_2 = models.CharField(choices=MY_CHOICES)

考虑到上面的简化示例,鉴于有两个字段需要计数,我如何计算每个选择值被记录了多少次?

理想情况下,结果将类似于以下内容:

{'X': 15, 'Y': 27, 'Z': 89}

我尝试了以下方法,但是在我的真实模型中,我有大约20个字段需要计数,而这并没有给出我希望得到的结果:

ModelA.objects.values('modelb__txt1', 'modelb__txt2').annotate(Count('modelb__txt1', 'modelb__txt2'))

我以前已经创建了庞大的字典,并手动对值进行了排序/计数,但是现在这很难处理且很丑陋。

1 个答案:

答案 0 :(得分:1)

使用一个查询(用于有限的列数)

通过一个查询,我们可以这样做:

from django.db.models import Count

qs = ModelB.objects.values('txt_1', 'txt_2').annotate(
    cnt=Count('id')
).order_by('txt_1', 'txt_2')

但是现在我们还不存在,因为现在txt_1txt_2的每种组合都有元素数量。我们希望将其“扁平化”到每个单独的选择。例如,我们可以通过构造一个Counter [Python-doc]来做到这一点:

from collections import Counter

result = Counter()
for row in qs:
    result[row['txt_1']] += row['cnt']
    result[row['txt_2']] += row['cnt']

因此,对于此QuerySet的每一行,我们将数字(cnt)加到两个键上。因此,这意味着我们对txt_1txt_2两次都具有值'X'的行进行计数。

Counter是字典的子类,但是如果要将其强制转换为dict字典,则可以稍后编写:

result_dict = dict(result)

从不选择 的选项将出现在字典中,因为查询集将不包含这些选项,因此我们将从不将它们添加到Counter中。但是我们当然可以对字典进行后处理,并为它们添加0。

使用 n 条查询(使用 n 列数)

以上通常会很好地工作。但是,如果选择的数量很大,则在Python方面的处理将更多,通常比较慢。然后,我们可以进行线性化,并处理两个查询:

from collections import Counter
from django.db.models import Count

result = Counter()
for col in ['txt_1', 'txt_2']:
    qs = ModelB.objects.values(col).annotate(cnt=Count('id')).order_by(col)
    result.update({q[col]: q['cnt'] for q in qs})

这将减少两个查询。但是在这种情况下,每个查询(最多)将返回三行。而另一种方法将导致一个查询返回(最多)九行。对于少量的行,这不是问题。但是,案例数可以很容易地按列数呈指数增长。