sql >> Base de Datos >  >> RDS >> Mysql

Subconsulta simple con OuterRef

Uno de los problemas con su ejemplo es que no puede usar queryset.count() como una subconsulta, porque .count() intenta evaluar el conjunto de consultas y devolver el recuento.

Entonces uno puede pensar que el enfoque correcto sería usar Count() en cambio. Tal vez algo como esto:

Post.objects.annotate(
    count=Count(Tag.objects.filter(post=OuterRef('pk')))
)

Esto no funcionará por dos razones:

  1. La Tag queryset selecciona todas las Tag campos, mientras que Count sólo puede contar con un campo. Así:Tag.objects.filter(post=OuterRef('pk')).only('pk') es necesario (para seleccionar contar con tag.pk ).

  2. Count en sí mismo no es una Subquery clase, Count es un Aggregate . Entonces la expresión generada por Count no se reconoce como una Subquery (OuterRef requiere subconsulta), podemos arreglar eso usando Subquery .

La aplicación de correcciones para 1) y 2) produciría:

Post.objects.annotate(
    count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk')))
)

Sin embargo si inspecciona la consulta que se está produciendo:

SELECT 
    "tests_post"."id",
    "tests_post"."title",
    COUNT((SELECT U0."id" 
            FROM "tests_tag" U0 
            INNER JOIN "tests_post_tags" U1 ON (U0."id" = U1."tag_id") 
            WHERE U1."post_id" = ("tests_post"."id"))
    ) AS "count" 
FROM "tests_post" 
GROUP BY 
    "tests_post"."id",
    "tests_post"."title"

notará un GROUP BY cláusula. Esto se debe a que COUNT es una función agregada. Ahora mismo no afecta al resultado, pero en algunos otros casos puede que sí. Es por eso que los docs sugiera un enfoque diferente, donde la agregación se mueva a la subquery a través de una combinación específica de values + annotate + values :

Post.objects.annotate(
    count=Subquery(
        Tag.objects
            .filter(post=OuterRef('pk'))
            # The first .values call defines our GROUP BY clause
            # Its important to have a filtration on every field defined here
            # Otherwise you will have more than one group per row!!!
            # This will lead to subqueries to return more than one row!
            # But they are not allowed to do that!
            # In our example we group only by post
            # and we filter by post via OuterRef
            .values('post')
            # Here we say: count how many rows we have per group 
            .annotate(count=Count('pk'))
            # Here we say: return only the count
            .values('count')
    )
)

Finalmente esto producirá:

SELECT 
    "tests_post"."id",
    "tests_post"."title",
    (SELECT COUNT(U0."id") AS "count" 
            FROM "tests_tag" U0 
            INNER JOIN "tests_post_tags" U1 ON (U0."id" = U1."tag_id") 
            WHERE U1."post_id" = ("tests_post"."id") 
            GROUP BY U1."post_id"
    ) AS "count" 
FROM "tests_post"