Django ORM: фильтрация по массиву содержит OuterRef в подзапросе, дает нулевые результаты - PullRequest
1 голос
/ 11 марта 2020

Вот изолированный запрос ORM:

Purpose.objects.annotate(
    conversation_count=SubqueryCount(
        Conversation.objects.filter(goal_slugs__contains=[OuterRef("slug")]).values("id")
    )
)

Где SubqueryCount:

from django.db.models import IntegerField
from django.db.models.expressions import Subquery


class SubqueryCount(Subquery):
    template = "(SELECT count(*) FROM (%(subquery)s) _count)"
    output_field = IntegerField()

Когда этот запрос выполняется, используется следующий sql (через объяснение djdt):

SELECT
    "taxonomy_purpose"."id",
    "taxonomy_purpose"."slug",
    (
        SELECT count(*)
        FROM (
            SELECT U0."id"
            FROM "conversations_conversation" U0
            WHERE U0."goal_slugs" @> ARRAY['ResolvedOuterRef(slug)']::varchar(100)[]
        ) _count
    ) AS "conversation_count"
FROM "taxonomy_purpose"

Обратите внимание на ResolvedOuterRef(slug), введенный в поиск ARRAY в виде строки. Я делаю что-то не так, или я должен сообщить об этом как об ошибке? Если это ошибка, есть ли известный обходной путь (возможно, создание собственного класса OuterRef)?

1 Ответ

1 голос
/ 11 марта 2020

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

Я предполагаю, что вы используете Postgres и Django 2.2

Основная проблема в django - проблемы с приведением [Anything], когда Anything не простая строка. Он не работает ни с OuterRef, ни с выражениями F (это то, что я использовал для воспроизведения с более простым запросом)

Итак, я бы использовал функцию для преобразования его в массив непосредственно в Postgres:

from django.db.models import Func
Purpose.objects.annotate(
    conversation_count=SubqueryCount(
        Conversation.objects.filter(
            goal_slugs__contains=Func(
                OuterRef("slug"),
                function="ARRAY",
                template="%(function)s[%(expressions)s]",
            )
        ).values("id")
    )
)

В качестве альтернативы, поскольку вы просто проверяете, что значение принадлежит массиву, я бы использовал ANY в Postgres. Для этого вы можете определить пользовательский поиск:

from django.db.models import Lookup
from django.db.models.fields import Field


class EqualAny(Lookup):
    lookup_name = "any"

    def as_sql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        params = lhs_params + rhs_params

        # Notice I reversed right and left here for your example
        return "%s = ANY (%s)" % (rhs, lhs), params




# We need to register the lookup here to make sure it happens during the app setup
Field.register_lookup(EqualAny)
Purpose.objects.annotate(
    conversation_count=SubqueryCount(
        Conversation.objects.filter(goal_slugs__any=OuterRef("slug")).values("id")
    )
)

Если вы предоставите модели, я мог бы проверить сгенерированный SQL, чтобы убедиться, что он работает

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...