Фильтр для каждого элемента в списке, чтобы проверить, есть ли он в списке идентификаторов - PullRequest
0 голосов
/ 27 августа 2018

Я хочу получить результаты, только если для каждой категории игры эта категория находится в списке идентификаторов, выполнение следующих действий не работает.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)

Мой models.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_'

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

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

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

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

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

Ответы [ 2 ]

0 голосов
/ 27 августа 2018

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

Кроме того, он, по сути, выполняет объединение (используя стиль, предшествующий 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() - потому что, если что-то не пусто, существует строка или строки .

0 голосов
/ 27 августа 2018

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

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)
...