Как аннотировать набор запросов Django с подсчетом связанных объектов

У меня есть 3 модели django; Machine, Component и Part.

Я пытаюсь аннотировать набор запросов машины, чтобы включить в него количество уникальных деталей, используемых в этой машине.

В приведенном ниже примере ps1 используется для создания f1. ps1 и ps2 используются для создания f2. f1 и f2 используются для создания r1. Таким образом, для создания r1 используются 2 уникальные части, и именно это значение я хочу использовать для аннотации r1. Пока что мой код выводит 3 в этом случае.

    # |------------+---------------------+----------------+--------------+--------------|
    # | machine    | component           | part           | expected     | result       |
    # |            |                     |                | parts        | parts        |
    # |------------+---------------------+----------------+--------------+--------------|
    # | r1         | f1                  | ps1            | 2            | 3            |
    # |            | f2                  | ps1            |              |              |
    # |            |                     | ps2            |              |              |
    # |------------+---------------------+----------------+--------------+--------------|
  • Part имеет ManyToManyField от Component.
  • Component имеет ForeignKey к Machine.

Вот функция, с которой я работаю, чтобы попытаться достичь этого.

def annotate_machine_query_with_num_parts(machine_query):
    count_subquery = (
        Parts.objects.filter(pk=OuterRef("pk"))
        .annotate(count=Count("id", distinct=True))
        .values("count")
    )

    sum_subquery = (
        Parts.objects.filter(
            Q(component__machine_id=OuterRef("pk"))
        )
        .annotate(count=Subquery(count_subquery))
        .values("count")
        .annotate(num_parts=Sum("count"))
        .values("num_parts")
    )

    return machine_query.annotate(num_parts=Coalesce(sum_subquery, 0))

Это работает во многих тестовых случаях, но не работает, когда деталь повторно используется несколькими компонентами, как в случае с ps1 здесь.

Я попробовал несколько способов, которые привели к ошибкам subquery must return a single row. Я уверен, что должен быть более простой способ добиться этого.

I think you are overcomplicating things. You can work with distinct=True [Django-doc]:

from django.db.models import Count

Machine.annotate(num_parts=Count('component__parts', distinct=True))

Note: The related_name=… [Django-doc] is the name of the manager to fetch the related objects in reverse. Therefore normally the related_name of a ForeignKey or ManyToManyField is plural, for example components instead of component.

Вернуться на верх