Django ORM:按数组过滤包含子查询中的 OuterRef 产生零结果

Django ORM: Filtering by array contains OuterRef within a subquery yields zero results

这是一个单独的 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 class)?

我没有你的模型或任何值,所以我无法检查我所说的是否有效。

我假设您使用的是 Postgres 和 Django 2.2

主要问题是当 Anything 不是简单字符串时,django 在转换 [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")
    )
)

或者,由于您只是检查一个值是否属于一个数组,我会在 Postgres 中使用 ANY。为此,您可以定义自定义查找:

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 以检查它是否有效