我试图在一个基于Django的网站上解决性能问题,而对Django和Python语法的了解却很少。我似乎已经正确识别了问题。我似乎也知道下一步该怎么做,但是我无法掌握使一切正常运行的Python / Django语法。
class Company(models.Model):
name = models.CharField(max_length=100)
bic = models.CharField(max_length=100, blank=True)
def get_order_count(self):
return self.orders.count()
def get_order_sum(self):
total_sum = 0
for contact in self.contacts.all():
for order in contact.orders.all():
total_sum += order.total
return total_sum
class Contact(models.Model):
company = models.ForeignKey(
Company, related_name="contacts", on_delete=models.DO_NOTHING)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True)
def get_order_count(self):
return self.orders.count()
class Order(models.Model):
order_number = models.CharField(max_length=100)
company = models.ForeignKey(Company, related_name="orders", on_delete=models.DO_NOTHING)
contact = models.ForeignKey(Contact, related_name="orders", on_delete=models.DO_NOTHING)
total = models.DecimalField(max_digits=18, decimal_places=9)
order_date = models.DateTimeField(null=True, blank=True)
def __str__(self):
return "%s" % self.order_number
我的直觉是性能问题是由get_order_sum
中的嵌套循环引起的。而且我的解决方案非常清楚:嵌套的“ fors”应替换为一个简单的命令,该命令使用聚合并利用数据库自身有效的内部SQL功能。因此,在我看来,解决方案应如下所示:
return self.contacts.all().orders.all().aggregate(Sum('total'))
问题是我无法弄清楚如何正确编写我想让Django / Python做的事情。请帮帮我!
还是我弄错了,是我的查看代码中的问题(或其一部分)吗?
<table>
<tr>
<th>Name</th>
<th>Order Count</th>
<th>Order Sum</th>
<th>Select</th>
</tr>
{% for company in company_list|slice:":100" %}
<tr>
<td>{{ company.name }}</td>
<td>{{ company.orders.count }}</td>
<td>{{ company.get_order_sum|floatformat:2 }}</td>
<td><input type="checkbox" name="select{{company.pk}}" id=""></td>
</tr>
{% for contact in company.contacts.all %}
<tr>
<td>- {{ contact.first_name }} {{ contact.last_name }}</td>
<td>Orders: {{ contact.orders.count }}</td>
<td> </td>
<td> </td>
</tr>
{% endfor %}
{% endfor %}
</table>
对于如何进一步改进此代码(尤其是从性能POV),我也将不胜感激。
答案 0 :(得分:1)
您猜到,您的性能问题很可能是由 get_order_sum 方法引起的。您正在运行查询以获取公司的所有污染物,然后对于每个联系人,您正在运行查询以获取该联系人的命令。您可以通过单个查询找到该总和,例如在Django中:
from django.db.models import Sum
def get_order_sum(self):
return self.contacts.aggregate(order_sum=Sum('orders__total')).get('order_sum')
请注意,聚合函数以以下格式返回字典:
{
'order_sum': 123
}
答案 1 :(得分:1)
关键是不要有过多的数据库查询(这与尽可能少地查询不一样),并且正如您所提到的,让数据库正常工作是很擅长的。
实际上,a)您需要的所有数据都应在到达模板之前company_list
中,这样您就不会在模板的循环内发送数据库查询了; b) company_list
应该以有效的方式填充数据。
from django.db.models import Prefetch, Sum, Count
contacts_with_orders = Prefetch(
'contacts',
queryset=Contact.objects.annotate(order_count=Count('orders'))
)
company_list = (Company.objects
.prefetch_related(contacts_with_orders)
.annotate(order_sum=Sum('orders__total'),
order_count=Count('orders'))
)
现在,您可以进行循环并访问所有必需的数据,而无需任何进一步的查询:
for company in company_list:
company.name
company.order_sum
company.order_count
for contact in company.contacts.all():
contact.first_name
contact.order_count
虽然这应该比以前快几个数量级,但仍然很麻烦。还有更多的优化空间:如果需要,您可以通过仅查询所需的列(而不是完整的行)以及返回字典(而不是对象)来节省更多时间。请参见“预取”中的only(),values() and values_list()和to_attr
参数。