Фильтр для каждого элемента в списке, чтобы проверить, есть ли он в списке идентификаторов

1

Я хочу получить только результаты, где для каждой категории игр эта категория находится в списке идентификаторов, делая следующее, не работает. Game.categories - это свойство hybrid_property, которое дает мне категории игр.

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

games = Game.query \
  .filter([category.id for category in Game.categories].in_([1, 2, 3])) \
  .paginate(page, current_app.config['POSTS_PER_PAGE'], False)

Это мои модели.py. Обратите внимание, что каждая категория игр зависит от того, какая категория была определена пользователем.

class CategoryUpvote(db.Model):
  id = db.Column(db.Integer, primary_key=True)
  user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
  category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
  game_id = db.Column(db.Integer, db.ForeignKey('game.id'))

class Category(db.Model):
  id = db.Column(db.Integer, primary_key=True)
  name = db.Column(db.String(32))

class Game(db.Model):
  id = db.Column(db.Integer, primary_key=True)
  title = db.Column(db.String(255), index=True, unique=True)

  category_upvotes = db.relationship('CategoryUpvote', backref='category', lazy='dynamic', cascade="save-update, merge, delete")

  def add_upvote(self, category, user):
    if not self.is_upvoted(category, user):
      upvote = CategoryUpvote(category_id=category.id, game_id=self.id, user_id=user.id)
      db.session.add(upvote)
    else:
      upvote = self.category_upvotes.filter(CategoryUpvote.category_id == category.id, CategoryUpvote.user_id == user.id, CategoryUpvote.game_id == self.id).first()
      db.session.delete(upvote)

  def is_upvoted(self, category, user):
    return self.category_upvotes.filter(CategoryUpvote.category_id == category.id, CategoryUpvote.user_id == user.id, CategoryUpvote.game_id == self.id).count() > 0

  @hybrid_property
  def categories(self):
    return Category.query \
      .filter(CategoryUpvote.game_id == self.id, CategoryUpvote.category_id == Category.id) \
      .group_by(Category.id) \
      .order_by(db.func.count(CategoryUpvote.game_id).desc()) \
      .limit(5) \
      .all()

Я получаю эту ошибку

'list' object has no attribute 'in_'

Как я могу каким-то образом запустить фильтр для каждой категории игры? например

.filter(Game.categories[0].in_([1, 2, 3]), Game.categories[1].in_([1, 2, 3]), Game.categories[2].in_([1, 2, 3])) \

Кроме того, если в игре есть каждая категория в качестве внешнего ключа, я мог бы сделать что-то вроде этого

Game.query.join(Category).filter(Category.id.in_([1, 2, 3]))

Как сделать что-то подобное с hybrid_property?

Теги:
flask
sqlalchemy
flask-sqlalchemy

2 ответа

3
Лучший ответ

Атрибут Game.categories, хотя и подразумевается как hybrid_property, не работает в контексте SQL. Когда используется для создания объектов выражения SQL на уровне класса, он по-прежнему выполняет фактический запрос к базе данных, возвращая вместо этого список объектов Category. Из-за этого большинство ваших ошибок.

Кроме того, он по существу выполняет соединение (используя стиль pre-SQL-92) между Game и CategoryUpvote, возвращая 5 лучших проголосовавших категорий из всех игр. Это сделало бы свойство намного более полезным в целом, если бы оно возвращало Query без материализации в список:

class Game(db.Model):

  @hybrid_property
  def categories(self):
    return Category.query \
      .join(CategoryUpvote) \
      .filter(CategoryUpvote.game_id == self.id) \
      .group_by(Category.id) \
      .order_by(db.func.count(CategoryUpvote.game_id).desc()) \
      .limit(5)

Затем вы получите доступ к списку категорий экземпляра Game используя

game.categories.all()

или просто перебирать его напрямую. Если гибридное свойство возвращает запрос, вы можете использовать его, например, для создания коррелированных подзапросов. Затем вы можете повернуть свою попытку фильтровать категории на основе списка идентификаторов в предикате "разница между заданными идентификаторами и идентификаторами категорий игры, идентифицированными идентификатором id пуста", или, другими словами, все указанные идентификаторы содержатся в наборе из топ-5 категорий игры:

# A portable way to build a (derived) table of ids. Use VALUES
# instead, if supported.
ids = union(*[select([literal(i).label('id')]) for i in [1, 2, 3]])

Game.query.filter(~exists(
    ids.select()
        .except_(Game.categories
            .correlate(Game)
            .with_entities(Category.id)
            .subquery()
            .select())))

Обратите внимание, что

Я хочу получить только результаты, где для каждой категории игр эта категория находится в списке идентификаторов

а также

Это запрос, который я ожидаю получить список всех игр, в которых есть хотя бы одна категория в списке идентификатора, который я указываю

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

Чтобы получить последнее, вы будете запрашивать с использованием предиката "пересечение заданных идентификаторов и идентификаторов категорий игры, идентифицированной идентификатором id, не является пустой", поэтому вы должны поменять значение except_() на intersect() и удалить оператор NOT ~ перед exists() - потому что, если что-то не пустое, существует строка или строки.

1

Проблема заключается в том, что вы пытаетесь запустить запрос с использованием генератора

category.id.in_([1,2,3]) for category in Game.categories

Приведем небольшой пример. Когда вы запускаете следующий код, вы получаете нужный результат от 0 до 5 в списке.

>>> print([a for a in range(5)])
[0, 1, 2, 3, 4]

Теперь, если вы удалите квадратную скобку, тогда

>>> print(a for a in range(5))
<generator object <genexpr> at 0x102829f10>

Вы можете видеть, что выход является выражением генератора. То же самое происходит с вашим кодом, так как вы не оцениваете, python отправляет его как объект генератора

У меня нет опыта работы с sqlalchemy, но я чувствую, что вы пытаетесь достичь, вы можете сделать что-то вроде этого:

for category in Game.categories
    result.append(category.id.in_([1, 2, 3]))

games = Game.query \
  .filter(Game.id.in_(result)) \
  .paginate(page, current_app.config['POSTS_PER_PAGE'], False)
  • 0
    Спасибо за ваш ответ, но проблема все еще сохраняется. Я пытаюсь запустить фильтр для каждой категории каждой игры, и отредактировал мой код выше для уточнения.
  • 0
    @ Психодрама Я обновил код. Я понятия не имею о sqlalchemy, поэтому вам может понадобиться проверить синтаксис. Кроме того, цикл for, который я написал, не рекомендуется. Я не мог понять, чего вы пытаетесь достичь, поэтому я написал цикл. Я бы предпочел сделать это с помощью базы данных, используя соединение

Ещё вопросы

Сообщество Overcoder
Наверх
Меню