2015-09-06 61 views
0

我有以下型號在我的DB(瓶-SQLAlchemy中,聲明的方式,簡化):如何用SQLAlchemy求和* count *子查詢?

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

這不處理球員沒有比賽,但一般應工作。沒有遊戲的玩家可以用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分割該代碼失敗。我嘗試使用子查詢,但沒有任何成功。

我應該如何實現它?或者,也許我應該使用某種連接/任何? (DB計劃不能更改。)

+2

沒有實際看到或思考太辛苦一下:'won.scalar()/ count.scalar() '? 'Query.scalar()'產生一個標量子查詢,它是子句元素 – Eevee

+1

Query.scalar()返回數字,而不是子查詢。 – MarSoft

+1

啊,對不起,我的意思是'Query.as_scalar()':) – Eevee

回答

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()) 
+0

非常感謝!這工作。我唯一的改變是使用'db.select'而不是'select',但不應該是關鍵。 – MarSoft