0

抱歉,我想不出一个更好的通用标题。我正在使用金字塔和 sqlalchemy 构建照片投票应用程序。完整的 schema 和 orm 在最后,但最相关的信息是

  1. photo桌子
  2. vote带有photo_id和的表user_id,其中后者标识对照片投赞成票或反对票的用户。
  3. category包含照片类别列表的表格
  4. photocategory表链接photocategory

在应用程序的主照片提要中,我想按以下顺序显示照片

  1. 用户未投票的照片在他/她投票的照片之前
  2. 在上面的每个组中(未投票然后投票),按照片现有投票数的降序排列

该应用程序还可以灵活地仅显示特定类别的照片

我现在实施的方式是......

  • 只获取所需类别的照片

IE

photos = DBSession.query(Photos).join(photocategory, Photo.id==photocategory.c.photo_id).filter(photocategory.c.category_id==__CATEGORY_ID_HERE__).all()
  • photos按票数排序

IE

photos = sorted(photos, key=lambda photo: len(photo.votes), reverse=True)
  • 遍历photos以查看用户是否已经投票,并将照片附加到 avotedunvoted列表/数组

但是,这太低效了,因为我必须搜索用户之前为每张照片投票的所有照片photos,所以我想知道这样做的正确和有效方法是什么......谢谢

架构

  photo_table = schema.Table('photo', metadata,
  schema.Column('id', types.Integer,
    schema.Sequence('photo_seq_id'), primary_key=True),
  schema.Column('caption', types.UnicodeText(), nullable=True),
  schema.Column('timestamp', types.TIMESTAMP(), default=datetime.now()),
  schema.Column('last_updated', types.TIMESTAMP(), default=datetime.now(),),
  schema.Column('spam', types.Boolean, default=0),
  schema.Column('trash', types.Boolean, default=0),
  schema.Column('image_path', types.Unicode(255), nullable=False),
  schema.Column('user_id', types.Integer, schema.ForeignKey('user.id', ondelete='CASCADE')),
  mysql_engine='InnoDB'
)

category_table = schema.Table('category', metadata,
  schema.Column('id', types.Integer,
    schema.Sequence('category_seq_id'), primary_key=True),
  schema.Column('name', types.Unicode(255), nullable=False, unique=True),
  mysql_engine='InnoDB'
)

photocategory_table = schema.Table('photocategory', metadata,
  schema.Column('photo_id', types.Integer, schema.ForeignKey('photo.id', ondelete='CASCADE'), primary_key=True),
  schema.Column('category_id', types.Integer, schema.ForeignKey('category.id', ondelete='CASCADE'), primary_key=True),
  mysql_engine='InnoDB'
)

vote_table = schema.Table('vote', metadata,
  schema.Column('id', types.Integer,
    schema.Sequence('vote_seq_id'), primary_key=True),
  schema.Column('timestamp', types.TIMESTAMP(), default=datetime.now()),
  schema.Column('upvote', types.Boolean, nullable=False),
  schema.Column('photo_id', types.Integer, schema.ForeignKey('photo.id', ondelete='CASCADE')),
  schema.Column('user_id', types.Integer, schema.ForeignKey('user.id', ondelete='CASCADE')),
  mysql_engine='InnoDB'
)

ORM 映射器

orm.mapper(Photo, photo_table, properties={
  'votes': orm.relation(Vote, backref='photo', lazy='dynamic'),
  'categories': orm.relation(Category, secondary=photocategory_table, backref='photos'),
})

orm.mapper(User, user_table, properties={
  'photos': orm.relation(Photo, backref='owner'),
  'votes': orm.relation(Vote, backref='voter', lazy='dynamic'),
})
4

1 回答 1

0

也许使用 SQL 本身来做这件事是一个更好的主意。以下内容未经测试,可能无法正常工作,但您可以对其进行调整以满足您的需要

您可以通过以下方式选择类别的照片

cat_photos = session.query(PhotoCategory.photo_id.label('pid')).options(lazyload('*')).filter(PhotoCategory.category_id==SOMEID).subquery()

加入用户和照片表以获取用户可以投票的照片列表。假设用户不能对他自己的照片投票,你会得到

elig_photos = session.query(User).join(Photos,Photos.user_id!=User.id).options(lazyload('*')).with_entities(Photos.id.label('pid'),User.id.label('uid').subquery()

现在您可以将其加入投票表和您的子查询,以获取用户尚未投票的特定类别的照片

not_voted = session.query(elig_photos).outerjoin(Votes,and_(Votes.photo_id==elig_photos.c.pid,Votes.user_id==elig_photos.c.uid)).join(cat_photos,cat_photos.c.pid==elig_photos.c.pid).with_entities(elig_photos.c.pid.label('photo_id'),Votes.upvote.label('upvote')).options(lazyload('*')).filter(User.id==SOMEUSER).filter(Votes.upvote==None).all()

获取按票数排序的此类照片。我们将不得不进行左连接,因为可能有些照片还没有人投票

sorted_votes = session.query(cat_photos).outerjoin(Votes,cat_photos.c.pid==Votes.photo_id).with_entities(cat_photos.c.pid.label('pid'),func.coalesce(func.sum(Votes.upvote),0).label('sum_votes')).options(lazyload('*')).group_by(cat_photos.pid).order_by(desc('sum_votes')).all()

注意 - 我options(lazyload('*'))为每个查询添加了一个选项以使关系延迟加载

现在你有两个列表 -

  • sorted_votes其中按票数降序排列了该类别的照片(0票为没有被任何人投票的照片)
  • not_voted其中列出了用户未投票的该类别的照片

请注意,用户上传的该类别的照片将成为第一个列表的一部分

您可以在 python 中处理这两个列表以首先根据它们在第一个列表中的索引显示 not_voted,然后按原样显示第一个列表的其余部分

not_voted_sorted = sorted(not_voted,key=lambda x:sorted_votes.index(x))
rest_of_the_photos = [y for y in sorted_votes if y not in not_voted]

如果您只从这些查询中获取 photo_ids,那将是一个好主意 (IMO)。整个 Photo 对象列表可以从 DB 中单独获取,并保存在以 photo_id 为键的字典中。因此,如果您知道要显示的 photo_id,则可以在模板中显示整个照片详细信息。

最后注意:如果某些事情不起作用,请尝试为您尝试执行的操作编写一个普通 SQL,然后编写 SQLAlchemy 等效项并打印该语句以比较您的手写 SQL 和 SQLAlchemy 生成的 SQL 是否相同

于 2013-03-27T07:11:08.597 回答