如何使用 SQLAlchemy 对 *count* 个子查询求和?
How to sum *count* subqueries with SQLAlchemy?
我的数据库中有以下模型(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'))
每场比赛可能赢、输或以平局结束。
我需要让玩家按 "win rate" 对它们进行排序 - 即:
- 如果玩家创建了一个游戏并且该游戏的获胜者是
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)
)
这不适用于没有游戏的玩家,但一般情况下应该可以。没有游戏的玩家可能可以用 MySQL CASE
语句来处理。
但问题是我不知道如何使用 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
除。我尝试使用子查询但没有成功。
我该如何实施?或者我应该使用某种 joins/whatever? (无法更改数据库方案。)
尝试使用核心表达式而不是 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())
我的数据库中有以下模型(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'))
每场比赛可能赢、输或以平局结束。 我需要让玩家按 "win rate" 对它们进行排序 - 即:
- 如果玩家创建了一个游戏并且该游戏的获胜者是
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)
)
这不适用于没有游戏的玩家,但一般情况下应该可以。没有游戏的玩家可能可以用 MySQL CASE
语句来处理。
但问题是我不知道如何使用 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
除。我尝试使用子查询但没有成功。
我该如何实施?或者我应该使用某种 joins/whatever? (无法更改数据库方案。)
尝试使用核心表达式而不是 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())