SQLAlchemy 2.0 文档
使用数据库元数据¶
有了引擎和 SQL 执行,我们准备开始一些 Alchemy 了。SQLAlchemy Core 和 ORM 的核心要素是 SQL 表达式语言,它允许流畅、可组合地构建 SQL 查询。这些查询的基础是代表数据库概念(如表和列)的 Python 对象。这些对象统称为 数据库元数据。
SQLAlchemy 中数据库元数据最常见的基石对象被称为 MetaData
、 Table
和 Column
。以下章节将说明如何在面向 Core 的风格和面向 ORM 的风格中使用这些对象。
ORM 阅读者,请留步!
与其他章节一样,Core 用户可以跳过 ORM 章节,但 ORM 用户最好熟悉这两种视角的对象。此处讨论的 Table
对象在使用 ORM 时以更间接(且完全 Python 类型化)的方式声明,但 ORM 的配置中仍然存在 Table
对象。
使用 Table 对象设置 MetaData¶
当我们使用关系数据库时,数据库中我们从中查询数据的基本数据持有结构被称为表。在 SQLAlchemy 中,数据库“表”最终由一个类似命名的 Python 对象 Table
表示。
要开始使用 SQLAlchemy 表达式语言,我们将需要构造 Table
对象,以表示我们感兴趣的所有数据库表。Table
是以编程方式构造的,可以直接使用 Table
构造函数,也可以间接使用 ORM 映射类(稍后在 使用 ORM 声明形式定义表元数据 中描述)。还可以选择从现有数据库加载部分或全部表信息,称为 反射。
无论使用哪种方法,我们总是从一个集合开始,该集合将是我们放置表的 MetaData
对象。此对象本质上是 Python 字典周围的 外观,该字典存储一系列 Table
对象,这些对象以其字符串名称为键。虽然 ORM 提供了一些选项来获取此集合,但我们始终可以选择直接创建一个,如下所示
>>> from sqlalchemy import MetaData
>>> metadata_obj = MetaData()
一旦我们有了 MetaData
对象,我们就可以声明一些 Table
对象。本教程将从经典的 SQLAlchemy 教程模型开始,该模型有一个名为 user_account
的表,用于存储网站的用户,以及一个相关的表 address
,用于存储与 user_account
表中的行关联的电子邮件地址。当完全不使用 ORM 声明模型时,我们直接构造每个 Table
对象,通常将每个对象分配给一个变量,这将是我们如何在应用程序代码中引用表的方式
>>> from sqlalchemy import Table, Column, Integer, String
>>> user_table = Table(
... "user_account",
... metadata_obj,
... Column("id", Integer, primary_key=True),
... Column("name", String(30)),
... Column("fullname", String),
... )
在上面的示例中,当我们希望编写代码来引用数据库中的 user_account
表时,我们将使用 user_table
Python 变量来引用它。
Table
的组件¶
我们可以观察到,以 Python 编写的 Table
构造类似于 SQL CREATE TABLE 语句;从表名开始,然后列出每个列,其中每个列都有一个名称和数据类型。我们在上面使用的对象是
Column
- 表示数据库表中的列,并将自身分配给Table
对象。Column
通常包括字符串名称和类型对象。Column
对象相对于父Table
的集合通常通过位于Table.c
的关联数组访问>>> user_table.c.name Column('name', String(length=30), table=<user_account>) >>> user_table.c.keys() ['id', 'name', 'fullname']
Integer
,String
- 这些类表示 SQL 数据类型,可以传递给Column
,无论是否需要实例化。在上面,我们希望为“name”列提供长度“30”,因此我们实例化了String(30)
。但是对于“id”和“fullname”,我们没有指定这些,因此我们可以发送类本身。
另请参阅
MetaData
、 Table
和 Column
的参考和 API 文档位于 使用 MetaData 描述数据库。数据类型的参考文档位于 SQL 数据类型对象。
在接下来的章节中,我们将说明 Table
的基本功能之一,即在特定的数据库连接上生成 DDL。但首先我们将声明第二个 Table
。
声明简单约束¶
示例 user_table
中的第一个 Column
包括 Column.primary_key
参数,这是一种简写技术,用于指示此 Column
应该是此表的主键的一部分。主键本身通常是隐式声明的,由 PrimaryKeyConstraint
构造表示,我们可以在 Table.primary_key
属性上看到 Table
对象
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
最常显式声明的约束是 ForeignKeyConstraint
对象,它对应于数据库 外键约束。当我们声明彼此相关的表时,SQLAlchemy 使用这些外键约束声明的存在,不仅是为了在数据库的 CREATE 语句中发出它们,而且还为了帮助构造 SQL 表达式。
仅涉及目标表上单个列的 ForeignKeyConstraint
通常使用列级简写符号通过 ForeignKey
对象声明。下面我们声明第二个表 address
,它将具有引用 user
表的外键约束
>>> from sqlalchemy import ForeignKey
>>> address_table = Table(
... "address",
... metadata_obj,
... Column("id", Integer, primary_key=True),
... Column("user_id", ForeignKey("user_account.id"), nullable=False),
... Column("email_address", String, nullable=False),
... )
上面的表还具有第三种约束,在 SQL 中是“NOT NULL”约束,上面使用 Column.nullable
参数指示。
提示
当在 Column
定义中使用 ForeignKey
对象时,我们可以省略该 Column
的数据类型;它是从相关列的数据类型自动推断的,在上面的示例中,是 user_account.id
列的 Integer
数据类型。
在下一节中,我们将发出 user
和 address
表的完整 DDL,以查看完成的结果。
向数据库发出 DDL¶
我们已经构建了一个对象结构,该结构表示数据库中的两个数据库表,从根 MetaData
对象开始,然后进入两个 Table
对象,每个对象都保存着 Column
和 Constraint
对象的集合。此对象结构将成为我们使用 Core 和 ORM 执行的大多数操作的中心。
我们可以使用此结构做的第一个有用的事情是向我们的 SQLite 数据库发出 CREATE TABLE 语句或 DDL,以便我们可以从中插入和查询数据。我们已经拥有了执行此操作所需的所有工具,只需在我们的 MetaData
上调用 MetaData.create_all()
方法,并将引用目标数据库的 Engine
发送给它
>>> metadata_obj.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
CREATE TABLE user_account (
id INTEGER NOT NULL,
name VARCHAR(30),
fullname VARCHAR,
PRIMARY KEY (id)
)
...
CREATE TABLE address (
id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
email_address VARCHAR NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user_account (id)
)
...
COMMIT
上面的 DDL 创建过程包括一些特定于 SQLite 的 PRAGMA 语句,这些语句在发出 CREATE 之前测试每个表的是否存在。完整的一系列步骤也包含在 BEGIN/COMMIT 对中,以适应事务性 DDL。
创建过程还负责以正确的顺序发出 CREATE 语句;在上面,FOREIGN KEY 约束依赖于 user
表的存在,因此 address
表是第二个创建的。在更复杂的依赖场景中,FOREIGN KEY 约束也可以在使用 ALTER 之后应用于表。
MetaData
对象还具有 MetaData.drop_all()
方法,该方法将以与发出 CREATE 时相反的顺序发出 DROP 语句,以便删除架构元素。
使用 ORM 声明形式定义表元数据¶
当使用 ORM 时,我们声明 Table
元数据的过程通常与声明 映射 类的过程相结合。映射类是我们想要创建的任何 Python 类,它将具有与其关联的属性,这些属性将链接到数据库表中的列。虽然实现方式有几种,但最常见的样式称为 声明式,它允许我们一次性声明用户定义的类和 Table
元数据。
建立声明基类¶
当使用 ORM 时,MetaData
集合仍然存在,但它本身与通常称为声明基类的仅 ORM 构造关联。获取新的声明基类最便捷的方法是创建一个新的类,该类继承 SQLAlchemy DeclarativeBase
类
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
... pass
上面,Base
类是我们将调用的声明基类。当我们创建 Base
的子类的新类时,结合适当的类级指令,它们将在类创建时各自建立为新的 ORM 映射类,每个类通常(但不完全是)引用特定的 Table
对象。
声明基类引用为我们自动创建的 MetaData
集合,假设我们没有从外部提供一个。此 MetaData
集合可通过 DeclarativeBase.metadata
类级属性访问。当我们创建新的映射类时,它们每个都将引用此 MetaData
集合中的 Table
>>> Base.metadata
MetaData()
声明式基类也指一个名为 registry
的集合,它是 SQLAlchemy ORM 中的中心“映射器配置”单元。虽然很少直接访问,但此对象对于映射器配置过程至关重要,因为一组 ORM 映射类将通过此注册表相互协调。与 MetaData
的情况一样,我们的声明式基类也为我们创建了一个 registry
(同样可以使用选项传递我们自己的 registry
),我们可以通过 DeclarativeBase.registry
类变量访问它
>>> Base.registry
<sqlalchemy.orm.decl_api.registry object at 0x...>
声明映射类¶
建立 Base
类后,我们现在可以根据新类 User
和 Address
为 user_account
和 address
表定义 ORM 映射类。我们在下面说明了声明式的最现代形式,它由 PEP 484 类型注解驱动,使用特殊的类型 Mapped
,它指示要映射为特定类型的属性
>>> from typing import List
>>> from typing import Optional
>>> from sqlalchemy.orm import Mapped
>>> from sqlalchemy.orm import mapped_column
>>> from sqlalchemy.orm import relationship
>>> class User(Base):
... __tablename__ = "user_account"
...
... id: Mapped[int] = mapped_column(primary_key=True)
... name: Mapped[str] = mapped_column(String(30))
... fullname: Mapped[Optional[str]]
...
... addresses: Mapped[List["Address"]] = relationship(back_populates="user")
...
... def __repr__(self) -> str:
... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
>>> class Address(Base):
... __tablename__ = "address"
...
... id: Mapped[int] = mapped_column(primary_key=True)
... email_address: Mapped[str]
... user_id = mapped_column(ForeignKey("user_account.id"))
...
... user: Mapped[User] = relationship(back_populates="addresses")
...
... def __repr__(self) -> str:
... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
上面的两个类 User
和 Address
现在被称为 ORM 映射类,可用于 ORM 持久性和查询操作,这将在后面描述。关于这些类的详细信息包括
每个类都引用一个
Table
对象,该对象是在声明式映射过程中生成的,通过将字符串分配给DeclarativeBase.__tablename__
属性来命名。创建类后,此生成的Table
可从DeclarativeBase.__table__
属性获得。如前所述,这种形式被称为 声明式表配置。几种替代声明样式之一将让我们直接构建
Table
对象,并将其直接分配给DeclarativeBase.__table__
。这种样式被称为 带有命令式表的声明式。为了指示
Table
中的列,我们使用mapped_column()
构造,并结合基于Mapped
类型的类型注解。此对象将生成应用于Table
构造的Column
对象。对于具有简单数据类型且没有其他选项的列,我们可以仅指示
Mapped
类型注解,使用简单的 Python 类型(如int
和str
)来表示Integer
和String
。在声明式映射过程中,Python 类型如何解释具有非常开放的结尾;有关背景信息,请参阅 使用带注解的声明式表(mapped_column() 的类型注解形式) 和 自定义类型映射 部分。可以根据
Optional[<typ>]
类型注解(或其等效项<typ> | None
或Union[<typ>, None]
)的存在将列声明为“可为空”或“非空”。也可以显式使用mapped_column.nullable
参数(并且不必与注解的可选性匹配)。显式类型注解的使用是完全可选的。我们也可以在没有注解的情况下使用
mapped_column()
。当使用这种形式时,我们将使用更显式的类型对象,如Integer
和String
,以及根据需要在每个mapped_column()
构造中使用nullable=False
。另外两个属性
User.addresses
和Address.user
定义了一种不同类型的属性,称为relationship()
,它具有类似的注解感知配置样式,如所示。relationship()
构造在 使用 ORM 相关对象 中进行了更全面的讨论。如果我们不声明自己的
__init__()
方法,则会自动为类提供一个__init__()
方法。此方法的默认形式接受所有属性名称作为可选关键字参数>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
为了自动生成功能齐全的
__init__()
方法,该方法提供位置参数以及带有默认关键字值的参数,可以使用 声明式数据类映射 中介绍的数据类功能。当然,始终可以选择使用显式的__init__()
方法。添加了
__repr__()
方法,以便我们获得可读的字符串输出;这些方法不是必需的。与__init__()
的情况一样,可以使用 数据类 功能自动生成__repr__()
方法。
另请参阅
ORM 映射风格 - 关于不同 ORM 配置风格的完整背景信息。
声明式映射 - 声明式类映射概述
带有 mapped_column() 的声明式表 - 详细介绍如何使用 mapped_column()
和 Mapped
来定义在使用声明式时要映射的 Table
中的列。
从 ORM 映射向数据库发出 DDL¶
由于我们的 ORM 映射类引用包含在 MetaData
集合中的 Table
对象,因此给定声明式基类发出 DDL 使用与之前在 向数据库发出 DDL 中描述的过程相同的过程。在我们的例子中,我们已经在 SQLite 数据库中生成了 user
和 address
表。如果我们尚未这样做,我们可以自由地使用与我们的 ORM 声明式基类关联的 MetaData
来执行此操作,方法是从 DeclarativeBase.metadata
属性访问集合,然后像以前一样使用 MetaData.create_all()
。在这种情况下,运行了 PRAGMA 语句,但由于发现表已存在,因此未生成新表
>>> Base.metadata.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
COMMIT
表反射¶
为了总结关于使用表元数据的章节,我们将说明本节开头提到的另一个操作,即 表反射。表反射是指通过读取数据库的当前状态来生成 Table
和相关对象的过程。在前面的章节中,我们一直在 Python 中声明 Table
对象,然后可以选择向数据库发出 DDL 以生成这样的模式,而反射过程反向执行这两个步骤,从现有数据库开始,并生成 Python 中的数据结构来表示该数据库中的模式。
提示
为了将 SQLAlchemy 与预先存在的数据库一起使用,不需要必须使用反射。典型的做法是 SQLAlchemy 应用程序在 Python 中显式声明所有元数据,使其结构与现有数据库的结构相对应。元数据结构也不需要包含预先存在的数据库中本地应用程序不需要的表、列或其他约束和构造。
作为反射的示例,我们将创建一个新的 Table
对象,该对象表示我们在本文档前面部分手动创建的 some_table
对象。执行此操作的方法再次有一些变体,但最基本的方法是构造一个 Table
对象,给定表的名称和它将所属的 MetaData
集合,然后不是指示单个 Column
和 Constraint
对象,而是使用 Table.autoload_with
参数将其传递给目标 Engine
>>> some_table = Table("some_table", metadata_obj, autoload_with=engine)
BEGIN (implicit)
PRAGMA main.table_...info("some_table")
[raw sql] ()
SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type in ('table', 'view')
[raw sql] ('some_table',)
PRAGMA main.foreign_key_list("some_table")
...
PRAGMA main.index_list("some_table")
...
ROLLBACK
在该过程结束时,some_table
对象现在包含有关表中存在的 Column
对象的信息,并且该对象的使用方式与我们显式声明的 Table
完全相同
>>> some_table
Table('some_table', MetaData(),
Column('x', INTEGER(), table=<some_table>),
Column('y', INTEGER(), table=<some_table>),
schema=None)
下一步¶
我们现在有一个 SQLite 数据库已准备就绪,其中包含两个表,以及核心和 ORM 面向表的构造,我们可以使用它们通过 Connection
和/或 ORM Session
与这些表进行交互。在以下各节中,我们将说明如何使用这些结构创建、操作和选择数据。
flambé! 龙和 炼金术士 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:2025 年 3 月 11 日星期二下午 02:40:17 EDT