如何使用SQLAlchemy对* count *子查询求和?

时间:2015-09-06 02:26:37

标签: python mysql count sqlalchemy subquery

我的数据库中有以下模型(Flask-SQLALchemy,声明式方法,简化):

UIView

每场比赛可能会赢,输或者以平局结束。 我需要让玩家按照" win rate" - 即:

  • 如果玩家创建游戏并且该游戏的获胜者为class Player(db.Model): id = db.Column(db.Integer, primary_key = True) ... class Game(db.Model): id = db.Column(db.Integer, primary_key = True) creator_id = db.Column(db.Integer, db.ForeignKey('player.id')) creator = db.relationship(Player, foreign_keys='Game.creator_id') opponent_id = db.Column(db.Integer, db.ForeignKey('player.id')) opponent = db.relationship(Player, foreign_keys='Game.opponent_id') winner = db.Column(db.Enum('creator', 'opponent')) ,则视为获胜;
  • 如果玩家被邀请参加游戏作为对手而游戏的获胜者是creator,那么它也被视为胜利;
  • 此玩家参与的其他游戏被视为迷失游戏。

所以我的算法如下:

opponent

当我想确定特定玩家的赢率时,此方法有效;但是当我想按胜率对球员进行排序时,它失败了。 我试图在SQL中重写它并得到这样的东西:

@hybrid_property
def winrate(self):
    games = Game.query.filter(or_(
        Game.creator_id == self.id,
        Game.opponent_id == self.id,
    ))
    count = 0
    wins = 0
    for game in games:
        count += 1
        if game.creator_id == self.id and game.winner == 'creator':
            wins += 1
        elif game.opponent_id == self.id and game.winner == 'opponent':
            wins += 1
    if count == 0:
        return 0
    return wins / count

这不会在没有游戏的情况下处理玩家但是应该可以正常工作。没有游戏的玩家可能会使用MySQL SELECT * FROM player ORDER BY ((SELECT count(g1.id) FROM game g1 WHERE g1.creator_id = player.id AND g1.winner = 'creator' ) + (SELECT count(g2.id) FROM game g2 WHERE g2.opponent_id = player.id AND g2.winner = 'opponent' )) / (SELECT count(g3.id) FROM game g3 WHERE player.id IN (g3.creator_id, g3.opponent_id) ) 声明进行处理。

但问题是我无法想象如何使用SQLAlchemy对此SQL进行编码。 这是我尝试使用的(简化)代码:

CASE

@winrate.expression def winrate(cls): cnt = Game.query.filter( cls.id.in_(Game.creator_id, Game.opponent_id) ).with_entities(func.count(Game.id)) won = Game.query.filter( or_( and_( Game.creator_id == cls.id, Game.winner == 'creator', ), and_( Game.opponent_id == cls.id, Game.winner == 'opponent', ), ) ) return case([ (count == 0, 0), ], else_ = ( won / count )) 行告诉我won / count无法被Query分割时,此代码失败。我尝试使用子查询但没有任何成功。

我该如何实施?或者也许我应该使用某种连接/什么? (DB方案不能更改。)

1 个答案:

答案 0 :(得分:1)

尝试使用核心表达式而不是orm查询:

class Player(..):
    # ...
    @winrate.expression
    def _winrate(cls):
        cnt = (
            select([db.func.count(Game.id)])
            .where(
                db.or_(
                    Game.creator_id == cls.id,
                    Game.opponent_id == cls.id,
                ))
            .label("cnt")
        )
        won = (
            select([db.func.count(Game.id)])
            .where(
                db.or_(
                    db.and_(Game.creator_id == cls.id,
                            Game.winner == 'creator'),
                    db.and_(Game.opponent_id == cls.id,
                            Game.winner == 'opponent'),
                ))
            .label("cnt")
        )

        return db.case(
            [(cnt == 0, 0)],
            else_ = db.cast(won, db.Numeric) / cnt
        )
# ...
q = session.query(Player).order_by(Player.winrate.desc())