Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Subconsulta simples com OuterRef


Um dos problemas com seu exemplo é que você não pode usar queryset.count() como uma subconsulta, porque .count() tenta avaliar o conjunto de consultas e retornar a contagem.

Então, pode-se pensar que a abordagem correta seria usar Count() em vez de. Talvez algo assim:
Post.objects.annotate(
    count=Count(Tag.objects.filter(post=OuterRef('pk')))
)

Isso não funcionará por dois motivos:

  1. A Tag queryset seleciona todas as Tag campos, enquanto Count só pode contar com um campo. Assim:Tag.objects.filter(post=OuterRef('pk')).only('pk') énecessário (para seleccionar a contagem em tag.pk ).

  2. Count em si não é uma Subquery classe, Count é um Aggregate . Portanto, a expressão gerada por Count não é reconhecido como uma Subquery (OuterRef requer subconsulta), podemos corrigir isso usando Subquery .

A aplicação de correções para 1) e 2) produziria:
Post.objects.annotate(
    count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk')))
)

No entanto se você inspecionar a consulta que está sendo produzida:
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"

você notará um GROUP BY cláusula. Isso ocorre porque COUNT é uma função agregada. No momento, isso não afeta o resultado, mas em alguns outros casos pode. É por isso que os documentos sugerir uma abordagem diferente, onde a agregação é movida para a subquery por meio de uma combinação 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, isso produzirá:
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"