Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Einfache Unterabfrage mit OuterRef

Eines der Probleme mit Ihrem Beispiel ist, dass Sie queryset.count() nicht verwenden können als Unterabfrage, weil .count() versucht, den Abfragesatz auszuwerten und die Anzahl zurückzugeben.

Man könnte also denken, dass die Verwendung von Count() der richtige Ansatz wäre stattdessen. Vielleicht so etwas:

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

Dies wird aus zwei Gründen nicht funktionieren:

  1. Das Tag queryset wählt alle Tag aus Felder, während Count kann nur auf einem Feld rechnen. Also:Tag.objects.filter(post=OuterRef('pk')).only('pk') wird benötigt (um das Zählen auf tag.pk auszuwählen ).

  2. Count selbst ist keine Subquery Klasse, Count ist ein Aggregate . Also der von Count generierte Ausdruck wird nicht als Subquery erkannt (OuterRef erfordert eine Unterabfrage), können wir das beheben, indem wir Subquery verwenden .

Das Anwenden von Korrekturen für 1) und 2) würde Folgendes erzeugen:

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

Allerdings wenn Sie die erstellte Abfrage überprüfen:

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"

Sie werden ein GROUP BY bemerken Klausel. Das liegt daran, dass COUNT ist eine Aggregatfunktion. Im Moment hat es keinen Einfluss auf das Ergebnis, aber in einigen anderen Fällen kann es sein. Deshalb die docs schlagen einen anderen Ansatz vor, bei dem die Aggregation in die subquery verschoben wird über eine bestimmte Kombination von 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')
    )
)

Schließlich wird dies produzieren:

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"