Django: Как "объединить" два набора запросов с помощью Prefetch Object?
Context
Я совсем новичок в Django и пытаюсь написать сложный запрос, который, как мне кажется, можно было бы легко записать на SQL, но для которого я не могу использовать ORM.
Модели
 У меня есть несколько моделей с именами SignalValue, SignalCategory, SignalSubcategory, SignalType, SignalSubtype, которые имеют одинаковую структуру, подобную следующей модели:
class MyModel(models.Model):
    id = models.BigAutoField(primary_key=True)
    name = models.CharField()
    fullname = models.CharField()
 У меня также есть явные модели, которые представляют отношения между моделью SignalValue и другими моделями SignalCategory, SignalSubcategory, SignalType, SignalSubtype. Каждое из этих отношений называется SignalValueCategory, SignalValueSubcategory, SignalValueType, SignalValueSubtype соответственно. Ниже в качестве примера приведена модель SignalValueCategory:
class SignalValueCategory(models.Model):
    signal_value = models.OneToOneField(SignalValue)
    signal_category = models.ForeignKey(SignalCategory)
 Наконец, у меня также есть две следующие модели. ResultSignal хранит все сигналы, относящиеся к модели Result:
class Result(models.Model):
    pass
class ResultSignal(models.Model):
    id = models.BigAutoField(primary_key=True)
    result = models.ForeignKey(
        Result
    )
    signal_value = models.ForeignKey(
        SignalValue
    )
Query
 Я пытаюсь достичь следующего.
Для данного Result я хочу получить все ResultSignal, принадлежащие ему, отфильтровать их, чтобы сохранить интересующие меня, и аннотировать их двумя полями, которые мы назовем filter_group_id и filter_group_name. Значения двух полей определяются SignalValue данного ResultSignal.
 С моей точки зрения, самый простой способ достичь этого - сначала аннотировать SignalValue с соответствующими filter_group_name и filter_group_id, а затем соединить получившиеся QuerySet с ResultSignal. Однако, я думаю, что в Django невозможно соединить два QuerySet вместе. Следовательно, я подумал, что мы могли бы использовать объекты Prefetch для достижения того, что я пытаюсь сделать, но похоже, что я не могу заставить это работать должным образом.
Code
Теперь я опишу текущее состояние моих запросов.
 Сначала аннотируем SignalValue с соответствующими filter_group_name и filter_group_id. Обратите внимание, что filter_aggregator в следующем коде - это просто сложный фильтр, который позволяет мне выбрать только нужные SignalValue. group_filter - это тот же фильтр, но в виде списка подфильтров. Кроме того, filter_name_case - это условное выражение (конструкция Case()):
# Attribute a group_filter_id and group_filter_name for each signal
signal_filters = SignalValue.objects.filter(
    filter_aggregator
).annotate(
    filter_group_id=Window(
        expression=DenseRank(),
        order_by=group_filters
    ),
    filter_group_name=filter_name_case
)
 Затем, пытаясь присоединиться/аннотировать SignalResults:
prefetch_object = Prefetch(
    lookup="signal_value",
    queryset=signal_filters,
    to_attr="test"
 )
result_signals: QuerySet = (
    last_interview_result
        .resultsignal_set
        .filter(signal_value__in=signal_values_of_interest)
        .select_related(
            'signal_value__signalvaluecategory__signal_category', 
            'signal_value__signalvaluesubcategory__signal_subcategory',
            'signal_value__signalvaluetype__signal_type',
            'signal_value__signalvaluesubtype__signal_subtype',
        )
        .prefetch_related(
            prefetch_object
        )
        .values(
            "signal_value",
            "test",
            category=F('signal_value__signalvaluecategory__signal_category__name'), 
            subcategory=F('signal_value__signalvaluesubcategory__signal_subcategory__name'),
            type=F('signal_value__signalvaluetype__signal_type__name'),
            subtype=F('signal_value__signalvaluesubtype__signal_subtype__name'),
        )
)
 Обычно, насколько я понимаю, в результирующем QuerySet должно быть поле "test", которое сейчас доступно, которое будет содержать поля signal_filter, первого QuerySet. Однако Django жалуется, что "test" не найден при вызове .values(...) в последней части моего кода: Cannot resolve keyword 'test' into field. Choices are: [...]. Как будто параметр to_attr объекта Prefetch вообще не был принят во внимание.
Вопросы
- Did I missunderstand the functioning of annotate()andprefetch_related()functions? If not, what am I doing wrong in my code for the specified parameterto_attrto not exist in my resultingQuerySet?
- Is there a better way to join two QuerySets in Django or am I better off using RawSQL? An alternative way would be to switch to Pandas to make the join in-memory, but it is very often more efficient to do such transformations on the SQL side with well-designed queries.
Вы на правильном пути, но просто не знаете, что делает prefetch.
- Ваши аннотации верны, но "тестовая" предварительная выборка на самом деле не является атрибутом. Вы пакетно обрабатываете запросы - SELECT * FROM signal_value, чтобы не выполнять select для каждой строки. Просто отбросьте аннотацию "test", и все будет в порядке. https://docs.djangoproject.com/en/3.2/ref/models/querysets/#prefetch-related
- Пожалуйста, не используйте pandas, это определенно не нужно и является тонной накладных расходов. Как вы сами говорите, эффективнее выполнять преобразования на стороне sql .
 Из документации по prefetch_related:
Помните, что, как и всегда с QuerySets, любые последующие цепочки методов, которые подразумевают другой запрос к базе данных, будут игнорировать ранее кэшированные результаты и получать данные, используя свежий запрос к базе данных.
 Не очевидно, но вызов values() является частью этих цепочек методов, которые подразумевают другой запрос, и на самом деле  отменяет  prefetch_related. Это должно работать, если вы удалите его.