SQLAlchemy 2.0 基础增删改查
1 - 核心概念
1.1 SQLAlchemy 2.0 与 1.x 的区别
- 废弃 Query 对象,改用基于
select()的统一查询方式 - 声明式模型的更新:
Mapped和mapped_column- 使用类型注解(Type Hints)和
mapped_column明确定义字段属性。 - 类型注解(如
Mapped[int])提供更好的 IDE 支持和代码可读性。 mapped_column可以更灵活地配置字段属性(如默认值、索引等)。
- 使用类型注解(Type Hints)和
- 推荐使用 context manager (
with语句) 管理连接
1.2 Core 与 ORM
SQLAlchemy 提供了两个不同的 API,一个建立在另一个之上。这些 API 分别被称为 Core 和 ORM。
- SQLAlchemy Core 是 SQLAlchemy 作为“database toolkit”的基础架构。该库提供了管理数据库连接、与数据库查询和结果交互以及程序化构造 SQL 语句的工具。
- SQLAlchemy ORM 基于 SQLAlchemy Core,提供了 “object relational mapping” 能力。包含:
- 一个配置层,可将 Python 类和对象映射到数据库表。
- Session: 一种对象持久化机制
2 - 连接数据库
2.1 创建 Engine 实例
Engine: sqlalchemy.engine.Engine
from sqlalchemy import create_engine
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)Lazy Connecting 特性:
- 创建 Engine 实例的时候不会立即连接数据库,而是在需要的时候才连接。
3 - 事务
3.1 Connection 模式 vs Session 模式
当使用 Core API 时,通过 Connection 对象操作数据库,返回 Result 对象。
当使用 ORM 时,Engine 则由 Session 管理。
- 现代 SQLAlchemy 的
Session强调事务,其 SQL 执行模式与Connection模式大致相同 - 实际上,
Session是Connection的封装,最终是调用Connection来执行 SQL - 当传入非 ORM 的 SQL 语句时,
Session跟Connection没什么区别
3.2 Connection 模式
3.2.1 获取 Connection 对象
用 Engine.connect() 方法获取 Connection 对象。
执行 SQL statement: 用 text() 构造 textual SQL, 用 conn.execute() 执行。
from sqlalchemy import text
with engine.connect() as conn:
result = conn.execute(text("select 'hello world'"))
print(result.all())进入 with 语句块时,自动开启事务。
当退出 with 语句块时,事务结束,自动执行 ROLLBACK。
3.2.2 Commit Changes
教程中会更多地使用 "commit as you go" 风格,而我们实际使用中可能更多地使用 "begin once" 风格。
"commit as you go" 风格
如果有修改,在退出前,应显式地使用 Connection.commit() 提交事务
# "commit as you go"
with engine.connect() as conn:
conn.execute(text("CREATE TABLE some_table (x int, y int)"))
conn.execute(
text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
[{"x": 1, "y": 1}, {"x": 2, "y": 4}],
)
conn.commit()特殊情况下,如果希望 autocommit,可参考如下文档进行配置:
"begin once" 风格
用 Connection.begin() 显式地开启事务,如果正常退出,自动 COMMIT;如果抛出了异常,自动 ROLLBACK。
# "begin once"
with engine.begin() as conn:
conn.execute(
text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
[{"x": 6, "y": 8}, {"x": 9, "y": 10}],
)3.3 Session 模式
在现代 SQLAlchemy 中,当使用 ORM 时,通过 Session 对象进行事务和数据库交互。
3.3.1 获取 Session 对象
同样的,推荐使用 context manager (with 语句) 管理 Session 对象。
from sqlalchemy.orm import Session
stmt = text("SELECT x, y FROM some_table WHERE y > :y ORDER BY x, y")
with Session(engine) as session:
result = session.execute(stmt, {"y": 6})
for row in result:
print(f"x: {row.x} y: {row.y}")这种情况下,也是 "commit as you go" 风格,需要在退出 with 语句块时,显式地使用 Session.commit() 提交事务。
更多 Session 用法,参考文档: Basics of Using a Session
4 - Core API 增删改查
Core API 通过 execute() 方法接收 sql statement 作为参数,实现对数据库的增删改查。
4.1 查询结果
执行 SELECT 语句,查询结果返回 Result 对象实例。
4.1.1 Result 对象
Result 对象支持迭代,每次迭代返回一个 Row 对象实例。
with engine.connect() as conn:
result = conn.execute(text("SELECT x, y FROM some_table"))
for row in result:
print(f"x: {row.x} y: {row.y}")4.1.2 Row 对象
Row 对象使用方法类似于 python 的 namedtuple,可以用以下四种方式访问值:
(假设: result = conn.execute(text("select x, y from some_table")))
(1) Tuple Assignment
for x, y in result:
...(2) Integer Index
for row in result:
x = row[0]
y = row[1](3) Attribute Name
大部分情况下,Row 对象的属性名与表的列名相同,也可以通过 SELECT 语句显式指定。但个别情况下,名字会受数据库特性影响。
for row in result:
x = row.x
y = row.y(4) Mapping Access
通过 Result.mappings() 方法,将 Result 对象转为 MappingResult 对象。迭代时返回 RowMapping 对象 (read-only)。
文档:
for dict_row in result.mappings():
x = dict_row["x"]
y = dict_row["y"]4.2 传参
execute() 接受 bound parameters 作为参数。
SQL 语句中使用 :param_name 表示参数,在 execute() 中传入 {param_name: value} 形式的字典。
with engine.connect() as conn:
result = conn.execute(text("SELECT x, y FROM some_table WHERE y > :y"), {"y": 2})
for row in result:
print(f"x: {row.x} y: {row.y}")4.2.1 executemany
当执行 DML 语句(如 INSERT, UPDATE, DELETE)时,可以传入包含多个参数的列表。
语句会被执行多次,依次从参数列表中取值。
这种执行风格叫做 executemany。
with engine.connect() as conn:
result = conn.execute(
text("SELECT x, y FROM some_table WHERE y > :y"),
[{"y": 2}, {"y": 3}],
)execute vs executemany
- executemany 方式下,执行过程会得到优化,实现更高的性能
- executemany 方式不支持返回结果 (唯一例外: 使用
insert()构建的语句)
5 - ORM 增删改查
(Data Manipulation with the ORM)(https://docs.sqlalchemy.org/en/20/tutorial/orm_data_manipulation.html)
当使用 textual SQL 时,Session 与 Connection 的执行模式大致相同。
本节主要介绍对 ORM 的增删改查。
5.1 查询
SQLAlchemy 的 ORM 提供了一系列 SQL constructs 来构建查询语句。
5.1.1 增
(1) insert() SQL Expression Construct
使用 insert() 构造 Insert 对象,它表示一条 INSERT 语句。
from sqlalchemy import insert
stmt = insert(user_table).values(name="spongebob", fullname="Spongebob Squarepants")通常情况下,不需要显式调用 .values() 方法。通过在执行时传入参数,也可实现相同效果。
高级用法:
在用到 scalar subquery (子查询) 时, 同时使用
.values()和执行时传入参数。
(2) SQL Statement
大部分 SQL 语句都支持直接 print 出来。
用 stmt.compile() 可以获取编译后的 SQL 语句 (Compiled 对象)。
compiled = stmt.compile()(3) 执行
with engine.connect() as conn:
result = conn.execute(stmt)
conn.commit()执行 INSERT 语句不会返回 rows。
当只插入一条记录时,通常支持返回在执行 INSERT 时自动生成的默认值(例如,primary key)。
result.inserted_primary_key
# (1,)支持 "executemany" 风格,实现批量插入:
with engine.connect() as conn:
result = conn.execute(
insert(user_table),
[
{"name": "sandy", "fullname": "Sandy Cheeks"},
{"name": "patrick", "fullname": "Patrick Star"},
],
)
conn.commit()(4) INSERT...RETURNING
对于支持 RETURNING 的数据库,可以使用 Insert.returning() 方法,显式要求返回插入的值。
insert_stmt = insert(address_table).returning(
address_table.c.id, address_table.c.email_address
)
print(insert_stmt)
# INSERT INTO address (id, user_id, email_address)
# VALUES (:id, :user_id, :email_address)
# RETURNING address.id, address.email_address也可以和 Insert.from_select() 一起使用:
select_stmt = select(user_table.c.id, user_table.c.name + "@aol.com")
insert_stmt = insert(address_table).from_select(
["user_id", "email_address"], select_stmt
)
print(insert_stmt.returning(address_table.c.id, address_table.c.email_address))
# INSERT INTO address (user_id, email_address)
# SELECT user_account.id, user_account.name || :name_1 AS anon_1
# FROM user_account RETURNING address.id, address.email_address5.1.2 查询
使用 select() construct 构造 Select 对象,表示 SELECT 语句。
print(select(User))
# SELECT user_account.id, user_account.name, user_account.fullname
# FROM user_account(1) 查询整个 ORM 对象
当使用 Session.execute() 查询 ORM 映射对象时,返回的数据中,每一项都是一个被查询的 ORM 映射对象。
(用 Connection.execute() 执行查询时,返回的数据是普通的 Row 对象)
stmt = select(User).where(User.name == "spongebob")
with Session(engine) as session:
for row in session.execute(stmt):
print(row)
# BEGIN (implicit)
# SELECT user_account.id, user_account.name, user_account.fullname
# FROM user_account
# WHERE user_account.name = ?
# [...] ('spongebob',)
# (User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
# ROLLBACK在结果上调用 .first() 方法,返回只有一个元素的 Result 对象。
row = session.execute(select(User)).first()
row
# (User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)推荐用 Session.scalars() 方法,返回一个 ScalarResult 对象,再调用 .first() 方法可直接得到结果对象。
user = session.scalars(select(User)).first()
user
# User(id=1, name='spongebob', fullname='Spongebob Squarepants')(2) 查询特定列
用 select() 的参数指定要查询的列。
row = session.execute(select(User.name, User.fullname)).first()
row
# ('spongebob', 'Spongebob Squarepants')(3) 列与 ORM 对象混合查询
session.execute(
select(User.name, Address).where(User.id == Address.user_id).order_by(Address.id)
).all()(4) Labeled SQL Expressions
用 ColumnElement.label() 方法为列指定别名。
from sqlalchemy import func, cast
stmt = select(
("Username: " + user_table.c.name).label("username"),
).order_by(user_table.c.name)
with engine.connect() as conn:
for row in conn.execute(stmt):
print(f"{row.username}")
# Username: spongebob(5) Where 子句
Select.where() 方法支持将包含 Column 对象的 ==, !=, <, >=, >, <= 等表达式转为 where 子句。
stmt = select(User).where(User.name == "spongebob")“AND” 和 “OR” 连接符分别使用 and_() 和 or_() 函数构造。
from sqlalchemy import and_, or_
print(
select(Address.email_address).where(
and_(
or_(User.name == "squidward", User.name == "sandy"),
Address.user_id == User.id,
)
)
)对于简单相等关系,也可以用 Select.filter_by() 方法构造。
stmt = select(User).filter_by(name="spongebob")(6) 显式的 FROM 和 JOIN
对于两表之间有外键关系的情况,SQLAlchemy 会自动生成 JOIN 语句。
Select.join_from() 方法支持显式指定 FROM 子句。需要传入 JOIN 关系中的左表和右表。
print(
select(user_table.c.name, address_table.c.email_address).join_from(
user_table, address_table
)
)
# SELECT user_account.name, address.email_address
# FROM user_account JOIN address ON user_account.id = address.user_idSelect.join() 方法只需要传入 JOIN 关系中的右表。
print(select(user_table.c.name, address_table.c.email_address).join(address_table))
# SELECT user_account.name, address.email_address
# FROM user_account JOIN address ON user_account.id = address.user_idSelect.select_from() 方法支持指定子查询的 FROM 子句。
from sqlalchemy import func
print(select(func.count("*")).select_from(user_table))
# SELECT count(:count_2) AS count_1
# FROM user_accountON 子句
Select.join() 和 Select.join_from() 方法支持指定 ON 子句。
print(
select(address_table.c.email_address)
.select_from(user_table)
.join(address_table, user_table.c.id == address_table.c.user_id)
)OUTER和FULL
print(select(user_table).join(address_table, isouter=True))
# SELECT user_account.id, user_account.name, user_account.fullname
# FROM user_account LEFT OUTER JOIN address ON user_account.id = address.user_id
print(select(user_table).join(address_table, full=True))
# SELECT user_account.id, user_account.name, user_account.fullname
# FROM user_account FULL OUTER JOIN address ON user_account.id = address.user_id