Mysql
 sql >> Base de données >  >> RDS >> Mysql

Sous-requête simple avec OuterRef

L'un des problèmes avec votre exemple est que vous ne pouvez pas utiliser queryset.count() en tant que sous-requête, car .count() essaie d'évaluer le jeu de requêtes et renvoie le nombre.

On peut donc penser que la bonne approche serait d'utiliser Count() Au lieu. Peut-être quelque chose comme ça :

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

Cela ne fonctionnera pas pour deux raisons :

  1. La Tag queryset sélectionne tous les Tag champs, tandis que Count ne peut compter que sur un champ. Ainsi :Tag.objects.filter(post=OuterRef('pk')).only('pk') est nécessaire (sélectionner compter sur tag.pk ).

  2. Count elle-même n'est pas une Subquery classe, Count est un Aggregate . Donc l'expression générée par Count n'est pas reconnu comme une Subquery (OuterRef nécessite une sous-requête), nous pouvons résoudre ce problème en utilisant Subquery .

L'application des correctifs pour 1) et 2) produirait :

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

Cependant si vous inspectez la requête en cours de production :

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"

vous remarquerez un GROUP BY clause. C'est parce que COUNT est une fonction d'agrégation. À l'heure actuelle, cela n'affecte pas le résultat, mais dans d'autres cas, cela peut arriver. C'est pourquoi la documentation suggérer une approche différente, où l'agrégation est déplacée dans la Subquery via une combinaison spécifique 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')
    )
)

Cela produira finalement :

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"