Как сопоставить объекты на основе выбранного manytomany в Django orm

#django #django-orm

#django #django-orm

Вопрос:

Я пытаюсь получить объекты на основе выбранных / добавленных элементов в поле множества.

Мои модели:

 class Location(models.Model):
    name = models.CharField(max_length=45)

class BenefitLocation(models.Model):
  benefit = models.ForeignKey(Benefit)
  location = models.ForeignKey(Location)

class Benefit(models.Model):
    locations = models.ManyToManyField(Location, through='BenefitLocation')
  

Вот что я пытаюсь сделать в orm:

 selected_locations = Location.objects.filter(id__in[1,2]) #Getting IDs from api request

matching_benefits = Benefit.objects.filter(locations=selected_locations)
  

В matching_benefits мне нужны только те, у которых есть именно эти выбранные местоположения. Когда я пробую свой код, я получаю эту ошибку: django.db.utils.OperationalError: (1242, 'Subquery returns more than 1 row')

Как я могу получить matching_benefits?

Редактировать:

Используя код @Willem Van Onsem, подобный этому:

 location_ids = [1]
location_ids = set(location_ids)

matching_benefits = Benefit.objects.annotate(
  nloc_count=Count('locations'),
  nloc_filter=Count('locations', filter=Q(locations__in=location_ids))
  ).filter(
  nloc_count=len(location_ids),
  nloc_filter=len(location_ids)
)

for item in matching_benefits:
  print(item.locations.values('id'))
  
#output
#<QuerySet [{'id': 1}]>
#<QuerySet [{'id': 2}]>
  

Но запрос по-прежнему дает два преимущества (с разными местоположениями)

Ответ №1:

Вы должны использовать __in :

 selected_locations = Location.objects.filter(id__in=[1,2])
matching_benefits = Benefit.objects.filter(locations__in=selected_locations)  

но здесь чище использовать список идентификаторов напрямую:

 matching_benefits = Benefit.objects.filter(locations__in=[1,2])  

Если вы ищете Benefit s, которые имеют по крайней мере все эти местоположения, вы фильтруете с помощью:

 from django.db.models import Sum

location_ids = [1, 2]
location_ids = set(location_ids)

matching_benefits = Benefit.objects.filter(
    locations__in=location_ids
).annotate(nloc=Count('locations')).filter(nloc=len(location_ids))  

Для Benefit s, которые имеют точно все эти местоположения, вы фильтруете с помощью:

 from django.db.models import Sum, Q

location_ids = [1,2]
location_ids = set(location_ids)

matching_benefits = Benefit.objects.annotate(
    nloc=Count('locations'),
    nloc_filter=Count('locations', filter=Q(locations__in=location_ids))
).filter(
    nloc=len(location_ids),
    nloc_filter=len(location_ids)
)  

Комментарии:

1. __in Похоже, что использование фильтра не дает особых преимуществ с точным location_id в locations. Фильтр получает преимущества только с одним из location_id в списке.

2. @TomasJacobsen: Я не совсем понимаю, что вы говорите. Для извлечения объектов будет использоваться IN ... предложение. В случае, если вы используете selected_locations , он будет использовать подзапрос. Для баз данных MySQL это часто является проблемой, поскольку MySQL не анализирует подзапрос, чтобы увидеть, что он » статический «, и, таким образом, запускает подзапрос для каждого элемента.

3. Допустим, есть два местоположения New York и London , и есть три преимущества. Один с just New York , один с London и третий с обоими местоположениями. Если я __in проверю, какое преимущество имеет местоположение » London amp;amp; New York «, я получу все три преимущества, но я хотел получить только то, где присутствовали оба местоположения.

4. @TomasJacobsen: если есть преимущество с New York , London и Paris , это тоже должно быть включено?

5. Нет, если фильтр запрашивает New York и London , он должен извлекать только те преимущества, которые имеют точно выбранные.