【发布时间】:2019-09-26 03:50:56
【问题描述】:
我有一个简单的模型类,代表两个角色之间的战斗:
class WaifuPickBattle(db.Model):
"""Table which represents a where one girl is chosen as a waifu."""
__tablename__ = "waifu_battles"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
date = db.Column(db.DateTime, nullable=False)
winner_name = db.Column(db.String, nullable=False)
loser_name = db.Column(db.String, nullable=False)
我有一种方法可以构建一个 CTE,它将战斗投射到一系列外观中(每场战斗都有两个外观 - 胜利者和失败者):
def get_battle_appearences_cte():
"""Create a sqlalchemy subquery of the battle appearences."""
wins = select([
WaifuPickBattle.date,
WaifuPickBattle.winner_name.label("name"),
expression.literal_column("1").label("was_winner"),
expression.literal_column("0").label("was_loser")
])
losses = select([
WaifuPickBattle.date,
WaifuPickBattle.loser_name.label("name"),
expression.literal_column("0").label("was_winner"),
expression.literal_column("1").label("was_loser")
])
return wins.union_all(losses).cte("battle_appearence")
然后我有一个查询,它利用这个视图来确定参加过最多战斗的角色:
def query_most_battled_waifus():
"""Find the waifus with the most battles in a given date range."""
appearence_cte = get_battle_appearences_cte()
query = \
select([
appearence_cte.c.name,
func.sum(appearence_cte.c.was_winner).label("wins"),
func.sum(appearence_cte.c.was_loser).label("losses"),
])\
.group_by(appearence_cte.c.name)\
.order_by(func.count().desc())\
.limit(limit)
return db.session.query(query).all()
这会生成以下 SQL:
WITH battle_appearence AS
(
SELECT
waifu_battles.date AS date,
waifu_battles.winner_name AS name,
1 AS was_winner,
0 AS was_loser
FROM waifu_battles
UNION ALL
SELECT
waifu_battles.date AS date,
waifu_battles.loser_name AS name,
0 AS was_winner,
1 AS was_loser
FROM waifu_battles
)
SELECT
name AS name,
wins AS wins,
losses AS losses
FROM
(
SELECT
battle_appearence.name AS name,
sum(battle_appearence.was_winner) AS wins,
sum(battle_appearence.was_winner) AS losses
FROM battle_appearence
GROUP BY battle_appearence.name
ORDER BY count(*) DESC
)
这在对 SQLite 数据库执行时非常有效,但在对 Postgres SQL 数据库执行时会出现以下错误:
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.SyntaxError) subquery in FROM must have an alias
LINE 6: FROM (SELECT battle_appearence.name AS name, count(battle_ap... ^ HINT: For example, FROM (SELECT ...) [AS] foo.
[SQL: WITH battle_appearence AS (SELECT waifu_battles.date AS date, waifu_battles.winner_name AS name, 1 AS was_winner, 0 AS was_loser FROM waifu_battles UNION ALL SELECT waifu_battles.date AS date, waifu_battles.loser_name AS name, 0 AS was_winner, 1 AS was_loser FROM waifu_battles) SELECT name AS name, wins AS wins, losses AS losses FROM (SELECT battle_appearence.name AS name, count(battle_appearence.was_winner) AS wins, count(battle_appearence.was_winner) AS losses FROM battle_appearence GROUP BY battle_appearence.name ORDER BY count(*) DESC)] (Background on this error at: http://sqlalche.me/e/f405)
此时有几点需要注意:
- 子选择是多余的,我们应该简单地使用子选择作为主选择语句。
- 您可以通过为子选择设置别名并在主选择语句中使用
<alias>.<column>来解决此问题 - 要求子选择别名的 Postgres 已在其他地方详细记录。
我的第一个问题是如何为这个子选择设置别名,因为 SQLalchemy 决定引入它,尽管没有明确指示(据我所知)?
我发现问题的解决方案是在查询中添加.alias("foo"):
query = query\
...\
.alias("foo")
这会导致生成以下 SQL(奇怪地解决了整个冗余子选择问题!):
WITH battle_appearence AS
(
SELECT
waifu_battles.date AS date,
waifu_battles.winner_name AS name,
1 AS was_winner,
0 AS was_loser
FROM waifu_battles
UNION ALL
SELECT
waifu_battles.date AS date,
waifu_battles.loser_name AS name,
0 AS was_winner,
1 AS was_loser
FROM waifu_battles
)
SELECT
battle_appearence.name,
sum(battle_appearence.was_winner) AS wins,
sum(battle_appearence.was_winner) AS losses
FROM battle_appearence
GROUP BY battle_appearence.name
ORDER BY count(*) DESC
我的第二个问题是为什么添加别名会阻止创建子选择和 为什么没有使用别名! "foo" 别名看似被忽略,但对生成的查询产生了重大影响。
【问题讨论】:
-
只是猜测你第二个问题的后半部分 - 你的 alias() 是你查询的最后一个语句。根据文档, select() 对象上的别名创建了一个命名子查询 '(select ...) AS aliasname' 但在这种情况下 - 作为最后的语句 - 它被优化了,因为在这个查询“之外”没有任何东西甚至理论上引用它。如果您在 .alias() 之后添加一些引用“foo”的内容,它会出现吗?
-
如果没有别名,您应该将核心
select()语句传递给Session.execute(),而不是Session.query()。前者执行,而后者构造新查询(在 ORM 级别)。
标签: python sqlalchemy