Я хотел бы уменьшить количество подобных запросов. Вот мои модели:
class Skill(models.Model):
name = models.TextField()
class Employee(models.Model):
firstname = models.TextField()
skills = models.ManyToManyField(Skill, through='SkillStatus')
def skills_percentage(self):
completed = 0
total = 0
for skill in self.skills.all().prefetch_related("skillstatus_set__employee"):
for item in skill.skillstatus_set.all():
if item.employee.firstname == self.firstname:
total += 1
if item.status:
completed += 1
try:
percentage = round((completed / total * 100), 2)
except ZeroDivisionError:
percentage = 0.0
return f"{percentage} %"
class SkillStatus(models.Model):
employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
status = models.BooleanField(default=False)
Моя основная проблема связана с методом skills_percentage, я делаю слишком много запросов при вычислении указанного значения. Я уже немного улучшил ситуацию с prefetch_related, но в Django Debug Toolbar все еще есть лишние запросы. Что еще здесь можно сделать?
Я пробовал играть с различными комбинациями select_related и prefetch_related. Я думал о других вариантах для расчета skills_percentage, но они также требовались для многих запросов...
Заранее спасибо.
Вы можете попробовать так:
from django.db.models import Count, When, Case, Cast, FloatField
employees = Employee.objects.annotate(
total=Count('skills',distinct=True),
completed = Count('skills', filter=Q(skillstatus__status=True),distinct=True)
).annotate(
percentage= Case(
When(total=0, then=0.0),
default=(Cast(
F('completed')*100 / F('total'),
output_field=FloatField()
)
)
)
)
# usage
for employee in employees:
print(employee.percentage)
# or
employees.values('firstname', 'percentage')
Здесь я считаю навыки дважды, один раз без каких-либо условий для общего количества и второй раз с условием фильтра. Затем я аннотирую разделение завершенного/общего значения исходным набором запросов, преобразовывая его как FloatField.
вы можете получить доступ к этим значениям через цикл или набор запросов значений. Я добавлю несколько примеров в ответ
Здравствуйте, большое спасибо за поправку. Это избавило от лишних запросов, но, к сожалению, не у всех моих сотрудников результат верный. Похоже процент рассчитывается неправильно (может быть 1 или 0). Проблема где-то в "завершенном" значении... Я уже изменил "filter=Q(skills__status=True)" на "filter=Q(skills__skillstatus__status=True)", но что-то еще есть
Исправлено с небольшой модификацией)employees = Employee.objects.annotate( total=Count('skillstatus', Different=True), Completed=Count('skillstatus', filter=Q(skillstatus__status=True), Different=True), ) .annotate (процент = регистр (когда (всего = 0, затем = 0,0), по умолчанию = (приведение (F («завершено») * 100 / F («всего»), output_field = FloatField () ) )) )
Вы можете использовать функции агрегации, предоставляемые Django ORM. Это может помочь сократить количество запросов, необходимых для подсчета количества выполненных и общих навыков для сотрудника.
Использование выражений F() и annotate() позволяет нам выполнять вычисления в одном запросе без необходимости отдельного цикла для связанных объектов Skill.
from django.db.models import Count, F
class Employee(models.Model):
...
def skills_percentage(self):
counts = self.skills.annotate(
completed_count=Count('skillstatus', filter=Q(skillstatus__status=True, skillstatus__employee=self)),
total_count=Count('skillstatus', filter=Q(skillstatus__employee=self)),
).aggregate(
completed_count_sum=Sum('completed_count'),
total_count_sum=Sum('total_count'),
)
completed = counts['completed_count_sum'] or 0
total = counts['total_count_sum'] or 0
try:
percentage = round((completed / total * 100), 2)
except ZeroDivisionError:
percentage = 0.0
return f"{percentage} %"
Это определенно улучшило ситуацию, спасибо большое :) Теперь осталось всего 6 похожих запросов (было 9 на 3 сотрудников). Значит, вообще не нужно использовать prefetch_related?
Вы можете использовать prefetch_related, чтобы еще больше сократить количество запросов, если они связаны с доступом к объектам Skill, связанным с экземплярами Employee. prefetch_related можно использовать для извлечения всех связанных объектов в одном запросе, что может быть более эффективным, чем извлечение их по одному в цикле. Однако имейте в виду, что prefetch_related может быть дорогостоящим с точки зрения использования памяти, поскольку он одновременно извлекает все связанные объекты в памяти. Поэтому вы должны использовать его разумно и только тогда, когда это имеет смысл делать в зависимости от конкретного варианта использования.
И есть ли способ объединить расчет «завершено» и «всего» в 1 набор запросов? Я пытался сделать это с помощью объекта F, но это не мой первый большой проект Django :)
Да, можно объединить вычисление «завершено» и «всего» в один набор запросов, используя выражения Django F().
Большое спасибо. Это немного уменьшило количество запросов, но «сотрудники» сами по себе являются набором запросов (а не числом). Или я что-то не так делаю?