簡體   English   中英

如何用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'))

每場比賽可能贏,輸或平局。 我需要讓玩家按“獲勝率”對它們進行排序-即:

  • 如果玩家創建了一個游戲,而該游戲的贏家是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 我嘗試使用子查詢,但沒有成功。

我應該如何實施? 還是我應該使用某種聯接/其他方式? (無法更改數據庫方案。)

嘗試使用核心表達式而不是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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM