Преобразование необработанного sql self join в код Django orm (без внутреннего внешнего ключа)

У меня есть модели Article и Tag, имеющие отношение многие ко многим через другую модель ArticleTag. Я хочу найти для данного тега "Здоровье", сколько раз он встречался одновременно с другими тегами в статьях. (например, на 4 статьи с тегом "Ковид", на 0 статей с тегом "Инфекция" и т.д.)

Я могу выполнить самоприсоединение на ArticleTag с некоторыми условиями Where и пунктом Group By и получить желаемый результат:

SELECT tag.title, COUNT(*) as co_occurrences
FROM app_articletag as t0
INNER JOIN app_articletag t1 on (t0.article_id = t1.article_id)
INNER JOIN app_tag tag on (tag.id = t1.tag_id)
WHERE t0.tag_id = 43 and t1.tag_id != 43
GROUP BY t1.tag_id, tag.title

Однако я хочу держаться как можно дальше от необработанных запросов и работать с API Django QuerySet.

Я видел другие темы о self join, но все их модели имеют внешний ключ к самому себе.

Вот мои модели Django:

class Tag(Model):
    ...

class Article(Model):
    tags = models.ManyToManyField(Tag, through=ArticleTag, through_fields=('article', 'tag'))

class ArticleTag(Model):
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)) 
    article = models.ForeignKey(Article, on_delete=models.CASCADE))

Один из подходов заключается в следующем, учитывая, что t является тегом Health:

ArticleTag.objects.values(
    "tag__name"
).annotate(
    articles_with_health=Count(
        "pk", filter=Q(article__articletag__tag=t)
    )
).exclude(tag=t)

Это должно вернуть результат типа:

[
    {'tag__name': 'Infection', 'articles_with_health': 0}, 
    {'tag__name': 'Covid', 'articles_with_health': 4}
]
Вернуться на верх