使用数据库元数据¶
随着引擎和 SQL 执行的关闭,我们准备开始一些 Alchemy。SQLAlchemy Core 和 ORM 的核心元素都是 SQL 表达式语言,它允许流畅、可组合的 SQL 查询构造。这些查询的基础是表示数据库概念(如表和列)的 Python 对象。这些对象统称为数据库元数据。
SQLAlchemy 中数据库元数据最常见的基础对象称为 MetaData
、Table
和 Column
。以下部分将说明如何在面向 Core 的样式和面向 ORM 的样式中使用这些对象。
ORM 读者们,请继续关注我们!
与其他部分一样,Core 用户可以跳过 ORM 部分,但 ORM 用户最好从这两个角度熟悉这些对象。在使用 ORM 时,这里讨论的 Table
对象以更间接(也是完全 Python 类型)的方式声明的,但是在 ORM 的配置中仍然有一个 Table
对象。
使用 Table 对象设置 MetaData¶
当我们使用关系数据库时,我们从中查询的数据库中的基本数据保存结构称为表。在 SQLAlchemy 中,数据库 “table” 最终由一个类似名称的 Python 对象表示。
要开始使用 SQLAlchemy 表达式语言,我们希望拥有
Table
对象,这些对象表示我们感兴趣的所有数据库表。Table
是
以编程方式构造,或者直接使用
Table
构造函数,或者通过使用 ORM Mapped 类间接地进行介绍(稍后在使用 ORM 声明式形式定义表元数据中介绍)。还可以选择从现有数据库加载部分或全部表信息,称为反射。
无论使用哪种方法,我们总是从一个集合开始,该集合将是我们放置表格的位置,称为 MetaData
对象。 这个对象本质上是 Python 字典周围的门面,它存储了一系列 Table
对象,这些对象与它们的字符串名称有键。虽然 ORM 提供了一些关于从哪里获取这个集合的选项,但我们总是可以选择直接创建一个,如下所示:
>>> from sqlalchemy import MetaData
>>> metadata_obj = MetaData()
一旦我们有了 MetaData
对象,我们就可以声明一些
Table
对象。本教程将从经典的 SQLAlchemy 教程模型开始,该模型具有一个名为 user_account
的表,用于存储网站的用户等,以及一个相关的表 address
,用于存储与user_account
中的行关联的电子邮件地址
桌子。当完全不使用 ORM 声明式模型时,我们会构建每个
Table
对象直接分配,通常将每个对象分配给一个变量,该变量将是我们在应用程序代码中引用 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
table 中,我们将使用 user_table
Python 变量来引用它。
Table
的组成部分¶
我们可以观察到,用 Python 编写的 Table
结构与 SQL CREATE TABLE 语句相似;从 Table Name(表名)开始,然后列出每一列,其中每列都有一个 Name (名称) 和一个 DataType(数据类型)。我们在上面使用的对象是:
Column
- 表示数据库表中的列,并将自身分配给Table
对象。专栏
通常包括 String Name 和 Type Object。 的集合父
Table
的 Column 对象 通常通过位于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
的参考和 API 文档
Table
and Column
位于 Describes databases with MetaData 中。数据类型的参考文档位于 SQL 数据类型对象中。
在接下来的部分中,我们将说明 Table
的基本功能之一,即在特定的数据库连接上生成 DDL。但首先,我们将声明第二个 Table
。
声明 Simple Constraints¶
示例 user_table
中的第一个 Column
包括
Column.primary_key
参数,该参数是一种简写技术,用于指示此 Column
应是此表的主键的一部分。主键本身通常是隐式声明的,由 PrimaryKeyConstraint
结构表示,我们可以在Table.primary_key
属性:
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
通常显式声明的约束是
与数据库对应的 ForeignKeyConstraint
对象
外键约束。当我们声明彼此相关的表时,SQLAlchemy 使用这些外键约束声明的存在,不仅可以将它们在 CREATE 语句中发送到数据库,还可以帮助构建 SQL 表达式。
只涉及目标表上的单个列的 ForeignKeyConstraint
通常通过 ForeignKey
对象使用列级速记表示法进行声明。 下面我们声明第二个表
address
的地址,该地址将具有引用用户的
外键约束
桌子:
>>> 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
表示
参数。
在下一节中,我们将为用户
发出完成的 DDL,并且
address
表以查看完成的结果。
向数据库发出 DDL¶
我们构建了一个对象结构,它表示数据库中的两个数据库表,从根 MetaData
开始
object,然后转换为两个 Table
对象,每个对象都包含 Column
和 Constraint
的集合
对象。 此对象结构将是大多数作的中心
我们未来会同时使用 Core 和 ORM。
我们可以用这个结构做的第一件有用的事情是将 CREATE TABLE 语句或 DDL 发送到我们的 SQLite 数据库,以便我们可以插入
并从中查询数据。 我们已经拥有实现这一目标所需的所有工具,例如
调用
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 过程还负责以正确的 Sequences 发出 CREATE 语句;上面,FOREIGN KEY 约束依赖于现有的 user
表,因此第二个创建 address
表。在更复杂的依赖关系场景中,FOREIGN KEY 约束也可以在事后使用 ALTER 应用于 table。
MetaData
对象还具有
MetaData.drop_all()
方法,该方法将以与发出 CREATE 相反的顺序发出 DROP 语句以删除架构元素。
使用 ORM 声明式表单定义表元数据¶
当使用 ORM 时,我们声明 Table
元数据的过程通常与声明 Map 类的过程相结合。
映射的类是我们想要创建的任何 Python 类,然后它将
具有将链接到数据库表中列的属性。
虽然有几种方法可以实现这一点,但最常见的是
style 称为
declarative,并允许我们同时声明用户定义的类和 Table
元数据。
建立声明式基¶
使用 ORM 时,MetaData
集合仍然存在,但它本身与通常称为声明性基的仅限 ORM 的结构相关联。获取新的 Declarative Base 的最权宜之计是创建一个新类,该类是 SQLAlchemy DeclarativeBase
类的子类:
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
... pass
在上面,Base
类就是我们所说的 Declarative Base。当我们创建作为 Base
的子类的新类时,与
适当的类级指令,它们都将分别建立为一个新的
ORM 在类创建时映射类,每个类通常(但不限于)引用特定的 Table
对象。
Declarative Base 是指自动为我们创建的 MetaData
集合,假设我们没有从外部提供一个集合。此 MetaData
集合可通过
DeclarativeBase.metadata
类级属性。当我们创建新的映射类时,它们都将在此
MetaData
集合:
>>> Base.metadata
MetaData()
Declarative Base 还引用了一个名为 registry
的集合,它是 SQLAlchemy ORM 中的中心“映射器配置”单元。虽然很少直接访问,但此对象是 mapper 配置过程的核心,因为一组 ORM 映射类将通过此注册表相互协调。与 MetaData
的情况一样,我们的 Declarative Base 也为我们创建了一个注册表
(同样具有传递我们自己的注册表
的选项),我们可以通过 DeclarativeBase.registry
类变量访问它:
>>> Base.registry
<sqlalchemy.orm.decl_api.registry object at 0x...>
声明映射类¶
建立基
类后,user_account
我们现在可以根据新类 User
和
地址
。我们在下面说明了最现代的 Declare 形式,它是从使用特殊类型的 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__
属性获取。
如前所述,这种形式称为 Declarative Table Configuration。几种替代声明样式之一会让我们直接构建Table
对象,并将其直接分配给DeclarativeBase.__table__
。这种风格称为 Declarative with Imperative Table。
为了指示Table
中的列,我们使用mapped_column()
结构,并与基于Mapped
类型键入注释结合使用。此对象将生成应用于Table
构造的Column
对象。
对于数据类型简单且没有其他选项的列,我们可以指示 单独使用映射
类型注释,使用简单的 Python 类型,如int
和str
表示Integer
和String
。 自定义 Python 类型在 Declarative 中的解释方式 映射过程非常开放;查看各部分 使用带注释的声明性表(mapped_column() 的类型带注释的形式)和 自定义 type Map for background。
可以根据Optional[<typ>]
类型注释(或其等效项<typ> | 无
或联合[<typ>, None]
). 这mapped_column.nullable
参数也可以显式使用(并且不必与 Comments 的可选性匹配)。
使用显式键入注释是完全的 可选。我们也可以使用mapped_column()
而不带注解。 当使用这种形式时,我们会使用更显式的类型对象,例如Integer
和String
以及nullable=False
根据需要在每个mapped_column()
构造中。
两个附加属性User.addresses
和Address.user
定义了一种称为relationship()
的不同类型的属性,该属性 具有如图所示的 Annotation Aware 配置样式。 这relationship()
结构在 使用 ORM 相关对象。
如果我们没有声明自己的__init__()
方法,则会自动为类提供一个方法。此方法的默认形式接受所有属性名称作为可选的关键字参数:>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
要自动生成一个功能齐全的__init__()
方法,该 提供位置参数以及带有 default 关键字的参数 values 中,DataClasses 功能在 可以使用声明性 Dataclass Mapping。当然,使用显式__init__()
方法也始终是一个选项。
添加了__repr__()
方法,以便我们获得可读的字符串输出;这里不需要这些方法。与__init__()
一样,__repr__()
方法 可以使用 DataClasses 功能。
另请参阅
ORM 映射样式 - 不同 ORM 配置样式的完整背景。
Declarative Mapping - Declarative 类映射概述
带有 mapped_column() 的声明式表 - 有关如何使用的详细信息
mapped_column()
和 Mapped
来定义 Table
中使用 Declare 时要映射的列。
从 ORM 映射向数据库发出 DDL¶
由于我们的 ORM 映射类引用 MetaData
集合中包含的 Table
对象,因此在给定
Declarative Base 使用与前面在
将 DDL 发送到数据库。在我们的例子中,我们已经生成了
user
和 address
表。如果我们还没有这样做,我们可以自由地使用 MetaData
与我们的 ORM 声明式基类相关联,为此,通过访问
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 Reflection (表反射)¶
为了完善有关使用表元数据的部分,我们将说明本节开头提到的另一个作,即表反射。Table 反射是指通过读取当前
数据库的状态。 而在前面的部分中,我们一直在声明
Table
对象,然后我们可以选择将 DDL 发送到数据库以生成这样的架构,反射过程将相反地执行这两个步骤,从现有数据库开始并生成 Python 内数据结构来表示该数据库中的架构。
提示
不要求必须使用反射才能将 SQLAlchemy 与预先存在的数据库一起使用。SQLAlchemy 应用程序在 Python 中显式声明所有元数据是完全典型的,因此其结构对应于现有数据库。元数据结构也不需要包括表、列或预先存在的数据库中本地应用程序运行不需要的其他约束和构造。
作为反射的示例,我们将创建一个新的 Table
object 表示我们在其中手动创建的some_table
对象
本文档的前面部分。 还有一些种类
如何执行此作,但最基本的是构造一个
Table
对象,给定表的名称和
MetaData
集合,而不是指示单个 Column
和
Constraint
对象,将目标 Engine
传递给它
使用 Table.autoload_with
参数:
>>> 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 数据库,其中包含两个表,以及 Core 和 ORM 面向表的构造,我们可以使用它们通过 Connection
和/或 ORM 与这些表进行交互
会话
。在以下部分中,我们将说明如何使用这些结构创建、作和选择数据。