[英]How to sum *count* subqueries with SQLAlchemy?
I have the following models in my DB (Flask-SQLALchemy, declarative approach, simplified): 我的数据库中有以下模型(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'))
Each game may be either won, lost or ended in draw. 每场比赛可能赢,输或平局。 I need to get players sorting them by "win rate" - ie: 我需要让玩家按“获胜率”对它们进行排序-即:
creator
, it is considered win; 如果玩家创建了一个游戏,而该游戏的赢家是creator
,则视为获胜; opponent
, it is also considered win; 如果邀请玩家作为对手参加比赛,而游戏的获胜者为opponent
,则也视为获胜; So my algorithm is as follows: 所以我的算法如下:
@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
This approach works when I want to determine win rate for particular player; 当我要确定特定玩家的获胜率时,这种方法有效。 but it fails when I want to sort players by win rate. 但是当我想按获胜率对玩家进行排序时,它失败了。 I tried to rewrite it in SQL and got something like this: 我试图用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)
)
This doesn't handle players without games but should work in general. 这不能处理没有游戏的玩家,但应该可以正常工作。 Players without games can be probably handled with MySQL CASE
statement. 没有游戏的玩家可能可以使用MySQL CASE
语句来处理。
But the problem is that I cannot figure how do I encode this SQL using SQLAlchemy. 但是问题是我无法弄清楚如何使用SQLAlchemy对该SQL进行编码。 Here is a (simplified) code I try to use: 这是我尝试使用的(简化)代码:
@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
))
This code fails when it comes to won / count
line telling me that Query
cannot be divided by Query
. 当涉及won / count
行时,此代码失败,告诉我Query
不能被Query
。 I tried using subqueries but without any success. 我尝试使用子查询,但没有成功。
How should I implement it? 我应该如何实施? Or maybe I should use some kind of joins/whatever? 还是我应该使用某种联接/其他方式? (DB scheme cannot be changed.) (无法更改数据库方案。)
Try working with core expressions instead of orm queries: 尝试使用核心表达式而不是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())
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.