Django аннотирует Multiple Sum с одним полем Many to Many

Возьмем эти модели:

class Computer(models.Model):
    name = models.CharField(max_length=100, null=False, blank=False)
    cpu = models.PositiveIntegerField(null=False, blank=False)
    ram = models.PositiveBigIntegerField(null=False, blank=False)
    customer = models.ForeignKey(
    Customer,
    related_name='Computers',
    on_delete=models.SET_NULL,
    null=True,
    blank=True
    )


class Disk(models.Model):
    computer = models.ForeignKey(
        Computer,
        related_name='Disks',
        on_delete=models.CASCADE,
        null=False,
        blank=False
    )
    size = models.PositiveBigIntegerField(null=False, blank=False)


class Customer(models.Model):
    name = models.CharField(max_length=255,
                            blank=True, null=True)
    creation_date = models.DateTimeField(auto_now_add=True)
    enabled = models.BooleanField(
        blank=False, null=False, default=False)

Я хочу получить список по клиентам, общее количество CPU, RAM и размер диска (с указанием количества компьютеров для каждого клиента).

примерно так:

{'customer': ACME_Corp., 'cpu__sum': 72, 'ram__sum': 10737418240, 'computer__sum': 10, 'disks__sum': 10000000000000}

Я пробовал нечто подобное:

Computer.values('customer').annotate(Sum('cpu'), Sum('ram'), Sum('Disks__size'),computer__sum=Count('computer_id'))

Проблема заключается в следующем: Если компьютер имеет несколько дисков, я собираюсь добавить его процессор (оперативную память и счетчик) столько раз, сколько у него дисков

например, если у нас есть 2 компьютера с 1 процессором на каждом, но к одному из них подключены два диска, то общее количество процессоров будет не 2, а 3...

Любая помощь будет оценена по достоинству, спасибо!!!

Мы можем работать с подзапросом для определения общего размера диска, и таким образом предотвратить то, что Django делает лишние JOIN:

from django.db.models import OuterRef, Subquery, Sum

Customer.objects.annotate(
    total_cpu=Sum('Computers__cpu'),
    total_ram=Sum('Computers__ram'),
    total_disk=Subquery(
        Disk.objects.filter(computer__customer=OuterRef('pk')).values(
            'computer__customer'
        ).values(
            total_disk=Sum('size')
        ).order_by('computer__customer')[:1]
    )
)

В результате будет получен запрос, который выглядит следующим образом:

SELECT customer.*,
       SUM(computer.cpu) AS total_cpu,
       SUM(computer.ram) AS total_ram,
       (
           SELECT SUM(U0.size) AS total_disk
           FROM disk U0
           INNER JOIN computer U1 ON U0.computer_id = U1.id
           WHERE U1.customer_id = customer.id
           GROUP BY U1.customer_id
           ORDER BY U1.customer_id ASC
           LIMIT 1
       ) AS total_disk
FROM customer
LEFT OUTER JOIN computer ON customer.id = computer.customer_id
GROUP BY customer.id
Вернуться на верх