Hen*_*nyH 10 python sqlalchemy
我有一个简单的模型类,代表两个角色之间的战斗:
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)
Run Code Online (Sandbox Code Playgroud)
我有一种构造CTE的方法,该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")
Run Code Online (Sandbox Code Playgroud)
然后,我有了一个查询,该查询利用此视图来确定战斗最多的角色:
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()
Run Code Online (Sandbox Code Playgroud)
这将生成以下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
)
Run Code Online (Sandbox Code Playgroud)
当对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)
Run Code Online (Sandbox Code Playgroud)
此时有几件事要注意:
<alias>.<column>在主选择语句中使用来解决此问题-在其他选择中有关于在子选择上使用别名的Postgres的详细记录。我的第一个问题是,尽管没有明确指示(据我所知),尽管SQLalchemy决定引入它,但我将如何对该子选择进行别名呢?
我发现该问题的解决方案是添加.alias("foo")到查询中:
query = query\
...\
.alias("foo")
Run Code Online (Sandbox Code Playgroud)
导致以下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)
Run Code Online (Sandbox Code Playgroud)
我的第二个问题是,为什么没有加入别名防止子选择创建和 为什么不使用别名!该"foo"别名是看似忽略但对生成的查询产生重大影响。
答案
尽管没有明确指示,SQLalchemy 还是决定引入它
它不是。您在调用时就告诉它使用子查询db.sesion.query(query)(尽管您可能没有意识到)。db.session.execute(query)代替使用。
为什么添加别名会阻止创建子选择以及为什么不使用别名!“foo”别名看似被忽略,但却对生成的查询产生了重大影响。
它没有并且被使用。
解释-介绍
SQLAlchemy 只是欺骗了你。我猜你一直习惯于print(query)窥视引擎盖下并了解问题所在 - 这次运气不好,它没有告诉你全部真相。
要查看生成的真实 SQL,请在引擎中打开回显功能。完成后,您会发现实际上 sqlalchemy 生成了以下查询:
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 foo.name AS foo_name, foo.wins AS foo_wins, foo.losses AS foo_losses
FROM (
SELECT
battle_appearence.name AS name,
sum(battle_appearence.was_winner) AS wins,
sum(battle_appearence.was_loser) AS losses
FROM battle_appearence
GROUP BY battle_appearence.name
ORDER BY count(*) DESC
LIMIT ?
)
AS foo
Run Code Online (Sandbox Code Playgroud)
两个查询都正常工作(我声称真正使用过的查询 - 上面 - 以及您在答案末尾给出的查询)。让我们首先深入探讨这一点 - 为什么这些不同?
如何调试查询以及为什么您看到的内容不同
您看到的查询(我们将其称为S作为select over alias)是查询的字符串表示形式或 的结果str(query.compile())。您可以调整它以使用 postgres 方言:
dialect = postgresql.dialect()
str(query.compile(dialect=dialect))
Run Code Online (Sandbox Code Playgroud)
并得到略有不同的结果,但仍然没有子查询。很有趣,不是吗?仅供将来参考,query.compile(简化后)与调用相同dialect.statement_compiler(dialect, query, bind=None)
第二个查询(我们将其称为A作为别名)是在调用 时生成的db.session.query(query).all()。如果您只输入str(db.session.query(query)),您将看到我们得到一个不同的查询(与N相比) - 带有子query.compile()查询和别名。
和会议有关系吗?否 - 您可以通过将查询转换为Query对象来检查这一点,忽略会话信息:
from sqlalchemy.orm.query import Query
str(Query(query))
Run Code Online (Sandbox Code Playgroud)
查看实现细节 ( ),我们可以看到AQuery.__str__发生的事情是:
context = Query(query)._compile_context()
str(context.statement.compile(bind=None))
Run Code Online (Sandbox Code Playgroud)
将尝试选择一种方言(在我们的例子中正确识别 Postgres),然后以与Scontext.statement.compile变体相同的方式执行语句:
dialect.statement_compiler(dialect, context.statement, bind=None)
Run Code Online (Sandbox Code Playgroud)
提醒我们自己,S源自:
dialect = postgresql.dialect()
str(dialect.statement_compiler(dialect, query, bind=None))
Run Code Online (Sandbox Code Playgroud)
这暗示我们在上下文中有些东西会改变语句编译器的行为。其作用是什么dialect.statement_compiler?它是 的子类的构造函数SQLCompiler,专门从事继承过程以满足您的方言需求;对于 Postgres 来说应该是PGCompiler。
注意:我们可以走A的捷径:
dialect.statement_compiler(dialect, Query(query).statement, bind=None)
Run Code Online (Sandbox Code Playgroud)
让我们比较一下编译对象的状态。__dict__这可以通过访问编译器的属性轻松完成:
with_subquery = dialect.statement_compiler(dialect, context.statement, bind=None)
no_subquery = dialect.statement_compiler(dialect, query, bind=None)
from deepdiff import DeepDiff
DeepDiff(sub.__dict__, nosub.__dict__, ignore_order=True)
Run Code Online (Sandbox Code Playgroud)
重要的是,陈述的类型已经改变。这并不意外,因为在第一个实例中context.statement是一个sqlalchemy.sql.selectable.Select对象,而在后者中query是sqlalchemy.sql.selectable.Alias一个对象。
这凸显了这样一个事实:Query使用db.session.query(), 将查询转换为对象会导致编译器根据语句的更改类型采取不同的路线。我们可以看到S实际上是一个包含在 select 中的别名,使用:
>>> context.statement._froms
[<sqlalchemy.sql.selectable.Alias at 0x7f7e2f4f7160; foo>]
Run Code Online (Sandbox Code Playgroud)
事实上,别名在包含在 select 语句 ( S ) 中时呈现,创建子查询在某种程度上与将 Alias 描述为在 SELECT 语句中使用(但不作为查询的根)的文档一致:
当从 Table 对象创建 Alias 时,这会导致该表在 SELECT 语句中呈现为 tablename AS aliasname。
为什么首先要有一个子选择?
让我们将查询命名.alias('foo')为N(无别名),并在下面的伪代码中将其表示为n_query。sqlalchemy.sql.selectable.Select因为它是您调用时的类型,所以db.session.query(n_query)它创建了一个子查询,其方式与使用别名的情况大致相同。您可以使用以下命令验证我们是否在另一个选择中进行了选择:
>>> Query(nquery).statement._froms
[<sqlalchemy.sql.selectable.Select at 0x7f7e1e26e668; Select object>]
Run Code Online (Sandbox Code Playgroud)
您现在应该很容易看到,在选择中包含选择意味着在使用 查询数据库时始终会创建子选择db.session.query(n_query)。
我不确定为什么您显示的第一个查询有一个可见的子查询 - 您是否有可能使用过 echo (或者str(db.session(n_query))当时?
我可以改变这种行为吗?
当然!只需使用以下命令执行您的查询:
db.session.execute(n_query)
Run Code Online (Sandbox Code Playgroud)
然后(如果您按照上面的说明启用了 echo)您将看到发出相同的查询(正如您在最后发布的那样)。
这与执行别名查询完全相同:
db.session.execute(n_query.alias('foo'))
Run Code Online (Sandbox Code Playgroud)
因为如果没有连续选择,别名就没有任何用处!
| 归档时间: |
|
| 查看次数: |
339 次 |
| 最近记录: |