不确定以前是否有人问过这个问题,但在 SQLAlchemy docs 中,他们谈论引入
select()
作为 ORM 的新 2.0 style
的一部分。
之前(1.x style
),使用query()
方法来获取数据。
这两者有什么区别?
例如,要查询
Users
表中包含电子邮件和姓名的用户,我们可以在查询 API 中执行以下操作:
session.query(Users).filter_by(name='name', email='[email protected]').first()
在 Select API 中,同样会导致更多代码:
from sqlalchemy import select
query = select(Users).filter_by(name='name', email='[email protected]')
user = session.execute(query).fetchone()
与其他相比,使用其中一种是否有任何显着优势,例如性能提升? 2.0 API 仍在积极开发中,但似乎他们的文档更倾向于选择 API,而不是“传统”查询 API。这只是试图弥合 ORM 和核心功能之间的差距吗?
最大的区别在于
select
语句的构造方式。新方法创建一个 select
对象,它更加动态,因为它可以从其他 select 语句构造,无需显式子查询定义:
# select from a subqeuery styled query
q = select(Users).filter_by(name='name', email='[email protected]')
q = select(Users.name, Users.email).select_from(q)
结果是根据最新的可选 API,更加“原生 SQL”的查询构造。可以在各种功能的语句中定义和传递查询,例如 where 子句、having、select_from、intersect、union 等。
性能方面,可能在 python 运行时(编译查询)方面有一些轻微的好处,但与网络延迟 + 数据库工作相比可以忽略不计。
💡顺便说一句,好问题!我的回答是根据我使用 select API 的经验得出的。我很好奇听听其他人怎么说。
从 1.4 开始,SQLAlchemy 内部已经通过 select() API 实现了 query(),所以在性能方面应该差别很小。
在 1.4 版本中,所有 Core 和 ORM SELECT 语句均从 直接选择对象;当使用 Query 对象时,at 语句 调用时,它将其状态复制到 Select,然后调用 内部使用 2.0 风格执行。
https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#change-5159
从历史上看,query() 和 select() 之间的区别是 query() 用于 ORM,而 select() 用于 Core。 2.0 版本消除了 ORM 和 Core 之间的许多差异,并使它们的使用更加统一。比较 select() 和 query() 已经没有意义了。
虽然有一些向后兼容性,并且您不必立即采用 2.0 风格,但我认为在 1.4 和 2.0 中开始采用它是明智的。我这样做已经有一段时间了,发现它很容易习惯,而且与 1.x 风格相比很快就更直观了。但我现在只使用 SQLAlchemy 大约一年,并且拥有多年的原生 SQL 经验。
这个问题的“愚蠢”答案是
select().where()
方法很难使用query().filter_by()
方法很容易使用让我通过一个例子来解释一下:
'''
This demonstrates the "old" way of querying
'''
#!/usr/bin/env python3
from sqlalchemy import Engine
from sqlalchemy import create_engine
from sqlalchemy import select
from sqlalchemy.orm import Session
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class SimpleTable(Base):
__tablename__ = 'simple_table'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
number: Mapped[int]
def main():
url = 'postgresql://postgres:password@ipaddress/postgres'
engine = create_engine(url)
create_tables(engine)
with Session(engine) as session:
simple_table_rows = session \
.query(SimpleTable) \
.filter_by(name='name1') \
.all()
for simple_table_row in simple_table_rows:
print(f'name: {simple_table_row.name}, number: {simple_table_row.number}')
if __name__ == '__main__':
main()
将此与新方法进行对比,新方法中我们需要额外的
simple_table_row = simple_table_row[0]
语句来将仅包含单个元素的元组转换为我们实际想要的类型。 (这是SimpleTableRow
。)
'''
This demonstrates the "new" way of querying
'''
with Session(engine) as session:
simple_table_rows = session.execute(
select(SimpleTable)
.where(SimpleTable.name=='name1')
).all()
for simple_table_row in simple_table_rows:
simple_table_row = simple_table_row[0]
print(f'name: {simple_table_row.name}, number: {simple_table_row.number}')
我们可以检查返回的“thing”确实是一个元组。
for simple_table_row in simple_table_rows:
print(type(simple_table_row))
print(f'')
for d in dir(simple_table_row):
if not d.startswith('__'):
print(d)
打印此输出:
<class 'sqlalchemy.engine.row.Row'>
_abc_impl
_asdict
_data
_fields
_filter_on_values
_get_by_key_impl_mapping
_is_protocol
_key_to_index
_mapping
_op
_parent
_special_name_accessor
_t
_to_tuple_instance
_tuple
_values_impl
count
index
t
tuple
我并不是 100% 明白为什么需要额外的“获取元组的 0 索引元素”。
我猜测新的
select()
语法允许对数据库进行更复杂的查询,例如通过一次选择多个对象,也许作为连接或其他内容的一部分。
然而,对于大多数简单的用例来说,它是不必要的,只是因为必须记住拨打
row = row[0]
而带来额外的小不便。
我建议您在大多数情况下使用“旧”样式。
SQL Alchemy 的一个主要问题是它作为一个库有多复杂。
编写代码时通常有不止一种方法来完成某些任务。然而,通常只有一种“明显”的好方法。
使用 SQL ALchemy,有多种不同的方法可以做完全相同的事情,并且文档并没有真正解释哪种方法是最明智的方法。
它告诉我们有一种“旧”方法和一种“新”方法,还有一种“较旧的旧方法”,仅适用于版本 1.4 等...
文档很长,但我不确定它的长篇大论是否能帮助我们理解如何执行常见的操作,这应该是相对简单的。