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

Python 如何在Django中跨字段计算值?,python,django,django-queryset,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

型号

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'))
我以前创建过大型字典,并手动对值进行排序/计数,但现在这是无法管理的,而且很难看。

只需一次查询(针对有限数量的列) 通过一个查询,我们可以这样做:

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_1
txt_2
的每个组合都有元素的数量。我们希望将此“扁平化”到每个个人选择。例如,我们可以通过构造:

因此,对于该
QuerySet
的每一行,我们将数字(
cnt
)添加到两个键中。因此,这意味着我们计算一行,其中
txt_1
txt_2
都有值
'X'
两次

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

result_dict = dict(result)
从不选择的选项不会出现在字典中,因为查询集不包含这些选项,因此我们永远不会将它们添加到
计数器中。但是我们当然可以对字典进行后期处理,并为这些添加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})

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

假设有一行
(X,Y)
,这是否意味着我们将其同时计算为
X
Y
?如果它是
(X,X)
?这算不算1到X?或者两次?@WillemVanOnsem-我们会计算每个值,这样(X,Y)将是{'X':1,'Y':1},而(X,X)将是{'X':2,'Y':0}。谢谢你的快速回答,我现在就试试!再次感谢,单查询方法似乎工作得很好!
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})