Nano Hash - криптовалюты, майнинг, программирование

Фильтрация Django QuerySet на основе существования отношения ManyToMany

Модели

Вот основные настройки моделей, которые у нас есть.

В списке много элементов, а элемент может быть во многих списках. Для данного элемента, если какой-либо из его списков хороший (т. е. list.bad == False), то этот элемент является хорошим. Если элемент не отображается ни в одном из хороших списков, значит, он плохой.

У нас есть собственный QuerySet для элементов с методом возврата только хороших элементов и методом возврата только плохих элементов.

class Item(models.Model):
    objects = ItemQuerySet.as_manager()
    name = models.CharField(max_length=255, unique=True)

class List(models.Model):
    name = models.CharField(max_length=255, unique=True)
    bad = models.BooleanField(default=True)
    items = models.ManyToManyField(Item, related_name='lists')

class ItemQuerySet(models.QuerySet):
    def bad(self):
        return self.exclude(lists__bad=False)

    def good(self):
         return self.filter(lists__bad=False)

Сценарий

Вот пример сценария, с которым у нас возникли проблемы: один плохой список, один хороший список и два элемента.

BadList:    GoodList:
- Item1     - Item1
- Item2

Поскольку Item1 появляется по крайней мере в одном хорошем списке, он должен появиться в Item.objects.good(), а не в Item.objects.bad().

Поскольку Item2 не появляется ни в одном хорошем списке, он должен появиться в Item.objects.bad(), а не в Item.objects.good().

Мы можем настроить сценарий следующим образом:

# Create the two lists.
>>> goodlist = List.objects.create(name='goodlist', bad=False)
>>> badlist = List.objects.create(name='badlist', bad=True)

# Create the two items.
>>> item1 = Item.objects.create(name='item1')
>>> item2 = Item.objects.create(name='item2')

# Item1 goes in both lists
>>> goodlist.items.add(item1)
>>> badlist.items.add(item1)

# Item2 only in badlist
>>> badlist.items.add(item2)

И действительно, Item.objects.good() и Item.objects.bad() работают так, как мы ожидаем:

>>> Item.objects.bad() # This returns what we want! Good!
<QuerySet [<Item: item2>]>

>>> Item.objects.good() # This returns what we want! Good!
<QuerySet [<Item: item1>]>

Эта проблема

Спасибо, что терпели меня. Вот где наш пользовательский QuerySet идет не так. Если мы получим доступ к good() и bad() пользовательским методам QuerySet через один элемент списка, мы получим неправильные результаты.

>>> badlist.items.bad() # WRONG! We want to ONLY see item2 here!
<QuerySet [<Item: item1>, <Item: item2>]

>>> badlist.items.good() # WRONG! We want to see item1 here!
<QuerySet []>

Кажется, когда мы делаем badlist.items.bad(), запрос только учитывает badlist при определении того, являются ли элементы плохими, вместо того, чтобы рассматривать все списки, в которых находятся элементы. быть дело.

Я думаю, что в методе ItemQuerySet.bad мне нужно что-то вроде self.exclude(any__lists__bad=False), а не просто self.exclude(lists__bad=False). Но, конечно, этого ключевого слова any__ на самом деле не существует, и я не уверен, как правильно выразить эту логику в Django QuerySet. Кажется, что использование объектов Q может быть шагом вперед, но я все еще не совсем уверен, как выразить подобный запрос с объектами Q.

В нашей фактической базе данных меньше 100 списков, но миллионы элементов. Таким образом, по соображениям производительности идеально делать это с помощью одного запроса, а не свойства или нескольких запросов.

Ваше здоровье!


Ответы:


1

Если вы распечатаете запрос, созданный badlist.items.bad(), вы увидите проблему: он будет использовать предложение WHERE в сквозной таблице, таким образом ограничивая списки только плохим списком. Вам нужно начать с уровня Item, если вы хотите правильно применить bad и good, а затем отфильтровать элементы в списке.

item_ids = list(badlist.items.values_list('id'), flat=True)

Item.objects.bad().filter(id__in=item_ids)

Item.objects.good().filter(id__in=item_ids)

Изменить: я не могу проверить это без схемы, но я думаю, что вы можете использовать аннотации для подсчета количества списков, а затем фильтровать по этому

def annotate_good(self);
    return self.annotate(good=Count(Case(When(lists__bad=False, then=1), default=0)))

def good(self):
    return self.annotate_good().exclude(good=0)

def bad(self):
    return self.annotate_good().filter(good=0)

В противном случае, если производительность действительно является проблемой, я бы добавил хорошее или плохое поле в модель Item и обновил его при сохранении, чтобы запросы стали предельно простыми.

13.06.2017
  • Спасибо за ответ! Это работает правильно, однако я обеспокоен тем, что другие люди, работающие с кодом, попытаются использовать маршрут badlist.items.good(), не зная ничего лучшего. Я не хочу, чтобы люди были введены в заблуждение таким образом. Кроме того, я хотел упомянуть об этом в исходном сообщении: в нашей фактической базе данных менее 100 списков, но миллионы элементов. Таким образом, по соображениям производительности идеально делать это с помощью одного запроса, а не свойства или нескольких запросов. 13.06.2017
  • Ах, попался. Я думаю, вы можете сделать это, используя аннотацию набора запросов. Я отредактировал свой ответ выше, указав решение, которое, по моему мнению, должно работать. В противном случае, чтобы повысить производительность, я бы просто добавил плохой/хороший столбец в элемент и обновил его, что значительно упростило запрос. 13.06.2017
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..