Условный фильтр MongoEngine с ReferenceField()

#mongodb #mongoengine

#mongodb #mongoengine

Вопрос:

Предположим, что существует simple User и Post model.

 class User(Document):
    user_id = StringField(primary_key=True)
    gender = StringField(default='M')


class Post(Document):
    user = ReferenceField(User)
    body = StringField()


if __name__ == '__main__':
    hide = User(user_id='hide', gender='M').save()
    john = User(user_id='john', gender='M').save()
    test = User(user_id='test', gender='W').save()
    admin = User(user_id='admin', gender='W').save()

    Post(user=hide, body='hide post').save()
    Post(user=john, body='john post').save()
    Post(user=test, body='test post').save()
    Post(user=admin, body='admin post').save()

    hide = User.objects(user_id='hide').first()

    posts = Post.objects(user__ne=hide)
    for post in posts:
        print(post.body)
  

Результатом является

джон пост тестовый пост, сообщение администратора

Я запустил условие user__ne = скрыть, поэтому печатаются все сообщения, кроме сообщения hide.

В этом случае, как я могу добавить больше лайков условию, gender =’W’?

Приведенный ниже код является результатом моих попыток.

posts = Post.objects(user__ne=hide, user__gender__ne='M')

и

 from mongoengine.queryset.visitor import Q

posts = Post.objects(Q(user__ne=hide) amp; Q(user__gender__ne='M'))
  

Но оба кода выдают ошибки -> mongoengine.errors.InvalidQueryError: Cannot perform join in mongoDB: user__gender

Я знаю, что это может быть реализовано с помощью этого.

 gender = User.objects(gender__ne='M')
posts = Post.objects(Q(user__ne=hide) amp; Q(user__nin=gender))
  

Но если у пользователей слишком много строк, возможно, возникает проблема с памятью.

Вопрос

  1. Возможно ли выполнить запрос с условием сразу?

  2. Действительно ли .objects() запрашивает базу данных?

Ответ №1:

Как подробно описано в родственном тикете на github

1 — В mongodb нет объединений, поэтому нет другого варианта, кроме того, который вы предложили. Одна простая вещь, которую вы можете сделать для повышения производительности и уменьшения занимаемой памяти, — это извлекать только идентификаторы пользователей, см. Ниже:

 male_ids = User.objects(gender__ne='M').scalar('id')   # Only fetch the user ids, i.o loading full object data into User model
posts = Post.objects(Q(user__ne=hide) amp; Q(user__nin=male_ids))
  

Примечание: В mongoengine есть CachedReferenceField, которое может помочь вам достичь желаемого (оно в основном дублирует значение gender рядом со ссылкой на пользователя в коллекции Post) и синхронизировать его, но CachedReferenceField страдает от некоторых ошибок (и проблемы с производительностью для их синхронизации), поэтому, возможно, оно могло бы подойти для простых вариантов использования, но я бы не советовал его использовать.

2 — .objects() возвращает набор запросов, запрос запускается только при выполнении итерации по набору запросов (или печати набора запросов). Смотрите ниже:

 user_qs = User.objects()
print(type(user_qs))    # <class mongoengine.queryset.queryset.QuerySet>, query not fired yet
for user in qs_user:    # fires the actual query and load data in User instances
        pass