Skip to content

SQLAlchemy 2.0 基础增删改查

官方文档: SQLAlchemy Unified Tutorial

1 - 核心概念

1.1 SQLAlchemy 2.0 与 1.x 的区别

  1. 废弃 Query 对象,改用基于 select() 的统一查询方式
  2. 声明式模型的更新: Mappedmapped_column
    1. 使用类型注解(Type Hints)和 mapped_column 明确定义字段属性。
    2. 类型注解(如 Mapped[int])提供更好的 IDE 支持和代码可读性。
    3. mapped_column 可以更灵活地配置字段属性(如默认值、索引等)。
  3. 推荐使用 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 - 连接数据库

Establishing Connectivity - the Engine

2.1 创建 Engine 实例

Engine: sqlalchemy.engine.Engine

python
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 模式大致相同
  • 实际上,SessionConnection 的封装,最终是调用 Connection 来执行 SQL
  • 当传入非 ORM 的 SQL 语句时,SessionConnection 没什么区别

3.2 Connection 模式

3.2.1 获取 Connection 对象

文档: class sqlalchemy.engine.Connection

Engine.connect() 方法获取 Connection 对象。

执行 SQL statement: 用 text() 构造 textual SQL, 用 conn.execute() 执行。

python
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() 提交事务

python
# "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,可参考如下文档进行配置:

https://docs.sqlalchemy.org/en/20/core/connections.html#setting-transaction-isolation-levels-including-dbapi-autocommit

"begin once" 风格

Connection.begin() 显式地开启事务,如果正常退出,自动 COMMIT;如果抛出了异常,自动 ROLLBACK。

python
# "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 对象进行事务和数据库交互。

文档: class sqlalchemy.orm.Session

3.3.1 获取 Session 对象

同样的,推荐使用 context manager (with 语句) 管理 Session 对象。

python
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 对象

文档: class sqlalchemy.engine.Result

Result 对象支持迭代,每次迭代返回一个 Row 对象实例。

python
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 对象

文档: class sqlalchemy.engine.Row

Row 对象使用方法类似于 python 的 namedtuple,可以用以下四种方式访问值:

(假设: result = conn.execute(text("select x, y from some_table"))

(1) Tuple Assignment
python
for x, y in result:
    ...
(2) Integer Index
python
for row in result:
    x = row[0]
    y = row[1]
(3) Attribute Name

大部分情况下,Row 对象的属性名与表的列名相同,也可以通过 SELECT 语句显式指定。但个别情况下,名字会受数据库特性影响。

python
for row in result:
    x = row.x
    y = row.y
(4) Mapping Access

通过 Result.mappings() 方法,将 Result 对象转为 MappingResult 对象。迭代时返回 RowMapping 对象 (read-only)。

文档:

python
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} 形式的字典。

python
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

python
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 查询

文档: Selecting ORM Entities and Columns

SQLAlchemy 的 ORM 提供了一系列 SQL constructs 来构建查询语句。

5.1.1 增

(1) insert() SQL Expression Construct

使用 insert() 构造 Insert 对象,它表示一条 INSERT 语句。

python
from sqlalchemy import insert
stmt = insert(user_table).values(name="spongebob", fullname="Spongebob Squarepants")

通常情况下,不需要显式调用 .values() 方法。通过在执行时传入参数,也可实现相同效果。

高级用法:

在用到 scalar subquery (子查询) 时, 同时使用 .values() 和执行时传入参数。

参考 文档的 "Deep Alchemy" 部分

(2) SQL Statement

大部分 SQL 语句都支持直接 print 出来。

stmt.compile() 可以获取编译后的 SQL 语句 (Compiled 对象)。

文档: class sqlalchemy.engine.Compiled

python
compiled = stmt.compile()
(3) 执行
python
with engine.connect() as conn:
    result = conn.execute(stmt)
    conn.commit()

执行 INSERT 语句不会返回 rows。

当只插入一条记录时,通常支持返回在执行 INSERT 时自动生成的默认值(例如,primary key)。

python
result.inserted_primary_key
# (1,)

支持 "executemany" 风格,实现批量插入:

python
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() 方法,显式要求返回插入的值。

python
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() 一起使用:

python
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_address

5.1.2 查询

文档: ORM Querying Guide

使用 select() construct 构造 Select 对象,表示 SELECT 语句。

文档: function sqlalchemy.sql.expression.select

python
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 对象)

python
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 对象。

python
row = session.execute(select(User)).first()
row
# (User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)

推荐用 Session.scalars() 方法,返回一个 ScalarResult 对象,再调用 .first() 方法可直接得到结果对象。

python
user = session.scalars(select(User)).first()
user
# User(id=1, name='spongebob', fullname='Spongebob Squarepants')
(2) 查询特定列

select() 的参数指定要查询的列。

python
row = session.execute(select(User.name, User.fullname)).first()
row
# ('spongebob', 'Spongebob Squarepants')
(3) 列与 ORM 对象混合查询
python
session.execute(
    select(User.name, Address).where(User.id == Address.user_id).order_by(Address.id)
).all()
(4) Labeled SQL Expressions

ColumnElement.label() 方法为列指定别名。

python
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 子句。

python
stmt = select(User).where(User.name == "spongebob")

“AND” 和 “OR” 连接符分别使用 and_()or_() 函数构造。

python
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() 方法构造。

python
stmt = select(User).filter_by(name="spongebob")
(6) 显式的 FROM 和 JOIN

对于两表之间有外键关系的情况,SQLAlchemy 会自动生成 JOIN 语句。

Select.join_from() 方法支持显式指定 FROM 子句。需要传入 JOIN 关系中的左表和右表。

python
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_id

Select.join() 方法只需要传入 JOIN 关系中的右表。

python
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_id

Select.select_from() 方法支持指定子查询的 FROM 子句。

python
from sqlalchemy import func
print(select(func.count("*")).select_from(user_table))
# SELECT count(:count_2) AS count_1
# FROM user_account

ON 子句

Select.join()Select.join_from() 方法支持指定 ON 子句。

python
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

python
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
(7) ORDER BY, GROUP BY, HAVING

文档: ORDER BY, GROUP BY, HAVING

Released under the MIT License.