【问题标题】:How to sum *count* subqueries with SQLAlchemy?如何用 SQLAlchemy 对 *count* 子查询求和?
【发布时间】:2015-09-06 02:26:36
【问题描述】:

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

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,也被认为是获胜;
  • 该玩家参与的其他游戏被视为输掉的游戏。

所以我的算法如下:

@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

当我想确定特定玩家的赢率时,这种方法很有效;但是当我想按胜率对玩家进行排序时它失败了。 我尝试用 SQL 重写它,得到了这样的结果:

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

这不适用于没有游戏的玩家,但应该可以正常工作。没有游戏的玩家大概可以用 MySQLCASE 语句处理。

但问题是我不知道如何使用 SQLAlchemy 对该 SQL 进行编码。 这是我尝试使用的(简化的)代码:

@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 不能除以Query 时,此代码失败。我尝试使用子查询,但没有任何成功。

我应该如何实现它?或者也许我应该使用某种连接/什么? (数据库方案无法更改。)

【问题讨论】:

  • 实际上并没有仔细考虑或思考这个问题:won.scalar() / count.scalar()? Query.scalar() 产生一个标量子查询,它是一个子句元素
  • Query.scalar() 返回数字,而不是子查询。
  • 啊,对不起,我的意思是Query.as_scalar() :)

标签: python mysql count sqlalchemy subquery


【解决方案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())

【讨论】:

  • 非常感谢!那行得通。我唯一的改变是使用db.select 而不仅仅是select,但不应该是关键的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 2019-07-03
  • 1970-01-01
  • 2022-09-18
相关资源
最近更新 更多