И запрос к таблице внешнего ключа в django ORM - PullRequest
0 голосов
/ 12 июня 2018

Дано:

class Video(models.Model):
  tags = models.ManyToManyField(Tag)

class Tag(models.Model):
  name = models.CharField(max_length=20)

Я знаю, что могу использовать Video.objects.filter(tags__name__in=['foo','bar']), чтобы найти все Videos, которые имеют либо foo ИЛИ bar теги, но для того, чтобынайти те, которые имеют foo И bar, мне пришлось бы дважды присоединиться к внешнему ключу (если бы я писал от руки SQL).Есть ли способ сделать это в Django?

Я уже пробовал .filter(Q(tag__name='foo') & Q(tag__name='bar')), но это просто создает невозможный для удовлетворения запрос, где один Tag имеет как foo, так и bar в качестве своегоимя.

1 Ответ

0 голосов
/ 12 июня 2018

Это не так прямолинейно, как может показаться.Более того, JOIN два раза использовать одну и ту же таблицу обычно не очень хорошая идея: представьте, что ваш список содержит десять элементов.Вы собираетесь JOIN десять раз?Это легко станет недостижимым.

Что мы можем сделать, так это подсчитать перекрытие.Поэтому, если нам дан список элементов, мы сначала удостоверимся, что эти элементы уникальны:

tag_list = ['foo', 'bar']
tag_set = <b>set(</b>tag_list<b>)</b>

Затем мы посчитаем количество тегов Video, которые фактически находятся в наборе, и затемпроверьте, совпадает ли это число с количеством элементов в нашем наборе, например:

from django.db.models import <b>Q</b>

Video.objects.filter(
    <b>Q(tag__name__in=tag_set) | Q(tag__isnull=True)</b>
).annotate(
    <b>overlap=Count('tag')</b>
).filter(
    overlap=<b>len(tag_set)</b>
)

Обратите внимание, что Q(tag__isnull-True) используется для включения Video s без тегов .Это может показаться ненужным, но если tag_list пусто, мы, таким образом, хотим получить все видео (поскольку у них нет общих тегов).

Мы также предполагаем, чтоимена Tag s уникальные , в противном случае некоторые теги могут быть подсчитаны дважды.

За занавесами мы выполним запрос, подобный:

SELECT `video`.*, COUNT(`video_tag`.`tag_id`) AS overlap
FROM `video`
  LEFT JOIN `video_tag` ON `video_tag`.`video_id` = `video`.`id`
  LEFT JOIN `tag` ON `tag`.`id` = `video_tag`.`tag_id`
WHERE `tag`.`name` IN ('foo', 'bar')
GROUP BY `video`.`id`
HAVING overlap = 2
...