前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SqlAlchemy 2.0 中文文档(一)

SqlAlchemy 2.0 中文文档(一)

作者头像
ApacheCN_飞龙
发布2024-06-26 14:18:53
1100
发布2024-06-26 14:18:53
举报
文章被收录于专栏:信数据得永生信数据得永生

原文:docs.sqlalchemy.org/en/20/contents.html

概述

原文:docs.sqlalchemy.org/en/20/intro.html

SQLAlchemy SQL 工具包和对象关系映射器是一套全面的用于处理数据库和 Python 的工具集。它有几个不同的功能区域,可以单独使用或组合在一起。其主要组件如下图所示,组件依赖关系组织成层次结构:

上面,SQLAlchemy 最重要的两个面向用户的部分是对象关系映射器(ORM)和核心(Core)

核心部分包含了 SQLAlchemy 的 SQL 和数据库集成以及描述服务,其中最突出的部分是SQL 表达式语言

SQL 表达式语言是一个独立于 ORM 包的工具包,它提供了一种构建 SQL 表达式的系统,这些表达式由可组合的对象表示,然后可以在特定事务范围内“执行”到目标数据库中,返回一个结果集。通过传递表示这些语句的 SQL 表达式对象以及表示要与每个语句一起使用的参数的字典,可以实现插入、更新和删除(即 DML)。

ORM 在 Core 的基础上构建了一种用于处理映射到数据库模式的域对象模型的手段。当使用 ORM 时,SQL 语句的构造方式与使用 Core 时基本相同,但 DML 任务(在这里指的是将业务对象持久化到数据库中)是使用一种称为工作单元的模式自动化的,该模式将可变对象的状态变化转换为 INSERT、UPDATE 和 DELETE 构造,并以这些对象的术语调用这些构造。SELECT 语句也通过 ORM 特定的自动化和对象中心的查询功能进行增强。

而使用 Core 和 SQL 表达式语言工作呈现了数据库的模式中心视图,以及以不可变性为导向的编程范式,ORM 在此基础上构建了一个更加明确面向对象的数据库领域视图,具有更多显式面向对象的编程范式,并依赖于可变性。由于关系型数据库本身是一个可变的服务,区别在于 Core/SQL 表达式语言是命令导向的,而 ORM 是状态导向的。

文档概览

文档分为四个部分:

  • SQLAlchemy 统一教程 - 这个全新的教程适用于 1.4/2.0 系列的 SQLAlchemy,全面介绍了整个库,从 Core 的描述开始,逐渐深入到 ORM 特定的概念。新用户以及从 SQLAlchemy 1.x 系列转来的用户应该从这里开始。
  • SQLAlchemy ORM - 在这一部分中,提供了 ORM 的参考文档。
  • SQLAlchemy 核心 - 这里提供了 Core 中的其他所有内容的参考文档。SQLAlchemy 引擎、连接和池服务也在这里描述。
  • 方言 - 提供所有 方言 实现的参考文档,包括 DBAPI 具体内容。

代码示例

SQLAlchemy 分发包含可用于大多数 ORM 的工作代码示例。所有包含的示例应用程序的描述位于 ORM 示例。

在 wiki 上还有各种各样的示例,涉及到核心 SQLAlchemy 结构以及 ORM。请参阅 Theatrum Chemicum

安装指南

支持的平台

SQLAlchemy 支持以下平台:

  • cPython 3.7 及更高版本
  • 兼容 Python-3 的 PyPy 版本

从 2.0 版本开始更改:SQLAlchemy 现在的目标是 Python 3.7 及以上版本。

异步 IO 支持

SQLAlchemy 的 asyncio 支持依赖于 greenlet 项目。这个依赖关系将默认安装在常见的机器平台上,但不支持每个架构,并且在不常见的架构上也可能不会默认安装。请参阅 异步 IO 平台安装说明(包括 Apple M1) 部分,了解确保存在 asyncio 支持的额外详细信息。

支持的安装方法

SQLAlchemy 的安装是通过基于 setuptools 的标准 Python 方法进行的,可以直接引用 setup.py 或使用 pip 或其他兼容 setuptools 的方法。

通过 pip 安装

pip 可用时,可以从 PyPI 下载分发包,并在一步中进行安装:

代码语言:javascript
复制
pip install SQLAlchemy

此命令将从 Python Cheese Shop 下载最新的发布版本的 SQLAlchemy 并安装到您的系统上。对于大多数常见平台,将下载提供原生 Cython / C 扩展的 Python Wheel 文件。

要安装最新的预发行版本,例如2.0.0b1,pip 需要使用 --pre 标志:

代码语言:javascript
复制
pip install --pre SQLAlchemy

在上述情况下,如果最新版本是预发行版本,则会安装该版本而不是最新发布的版本。

手动从源分发安装

当不使用 pip 安装时,可以使用 setup.py 脚本安装源分发:

代码语言:javascript
复制
python setup.py install

源安装与平台无关,无论 Cython / C 构建工具是否安装,都将安装在任何平台上。 正如下一节 构建 Cython 扩展 所述,setup.py 将尝试在可能的情况下使用 Cython / C 进行构建,否则将回退到纯 Python 安装。

构建 Cython 扩展

SQLAlchemy 包含 Cython 扩展,为各个领域提供额外的速度提升,目前重点是 Core 结果集的速度。

从版本 2.0 开始更改:SQLAlchemy C 扩展已使用 Cython 重新编写。

如果检测到合适的平台,则 setup.py 将自动构建扩展,假设 Cython 包已安装。 完整的手动构建如下所示:

代码语言:javascript
复制
# cd into SQLAlchemy source distribution
cd path/to/sqlalchemy

# install cython
pip install cython

# optionally build Cython extensions ahead of install
python setup.py build_ext

# run the install
python setup.py install

源构建也可以使用 PEP 517 技术执行,例如使用 build

代码语言:javascript
复制
# cd into SQLAlchemy source distribution
cd path/to/sqlalchemy

# install build
pip install build

# build source / wheel dists
python -m build

如果由于未安装 Cython、缺少编译器或其他问题而导致 Cython 扩展的构建失败,则设置过程将输出警告消息,并在完成后重新运行不包含 Cython 扩展的构建,报告最终状态。

要在甚至不尝试编译 Cython 扩展的情况下运行构建/安装,可以指定 DISABLE_SQLALCHEMY_CEXT 环境变量。 这样做的用例要么是为了特殊的测试情况,要么是在通常的“重新构建”机制无法解决的兼容性/构建问题的情况下:

代码语言:javascript
复制
export DISABLE_SQLALCHEMY_CEXT=1; python setup.py install
安装数据库 API

SQLAlchemy 设计用于与为特定数据库构建的 DBAPI 实现一起运行,并支持最流行的数据库。 方言 中的个别数据库部分列举了每个数据库的可用 DBAPI,包括外部链接。

检查已安装的 SQLAlchemy 版本

本文档涵盖了 SQLAlchemy 版本 2.0。 如果您正在使用已安装 SQLAlchemy 的系统,请像这样从您的 Python 提示符检查版本:

代码语言:javascript
复制
>>> import sqlalchemy
>>> sqlalchemy.__version__  
2.0.0
下一步

安装了 SQLAlchemy 后,新用户和老用户都可以继续阅读 SQLAlchemy 教程。 ## 1.x 到 2.0 迁移

可在 SQLAlchemy 2.0 - 主要迁移指南 这里找到关于 SQLAlchemy 2.0 新 API 的说明。

文档分为四个部分:

  • SQLAlchemy 统一教程 - 这个全新的针对 1.4/2.0 系列 SQLAlchemy 的教程从 Core 的描述开始,逐渐深入 ORM 特定的概念。 新用户以及从 SQLAlchemy 1.x 系列转来的用户应该从这里开始。
  • SQLAlchemy ORM - 本节介绍了 ORM 的参考文档。
  • SQLAlchemy 核心 - 这里提供了核心内的其他所有内容的参考文档。SQLAlchemy 引擎、连接和池服务也在此处描述。
  • 方言 - 提供了所有 方言 实现的参考文档,包括 DBAPI 具体细节。

代码示例

SQLAlchemy 分发包中包含了工作代码示例,主要涉及 ORM。所有包含的示例应用的描述请参阅 ORM 示例。

还有许多涉及核心 SQLAlchemy 构造和 ORM 的示例在维基上。请参阅 炼金剧场

安装指南

支持的平台

SQLAlchemy 支持以下平台:

  • cPython 3.7 及更高版本
  • PyPy 的 Python-3 兼容版本

从版本 2.0 开始更改:SQLAlchemy 现在面向 Python 3.7 及以上版本。

异步 IO 支持

SQLAlchemy 的 asyncio 支持取决于 greenlet 项目。此依赖项默认情况下会安装在常见的机器平台上,但不支持每个体系结构,并且在不太常见的体系结构上也可能不会默认安装。有关确保存在 asyncio 支持的详细信息,请参阅 Asyncio 平台安装说明(包括 Apple M1) 部分。

支持的安装方法

SQLAlchemy 的安装是通过基于 setuptools 的标准 Python 方法进行的,可以直接参考 setup.py 或使用 pip 或其他与 setuptools 兼容的方法。

通过 pip 安装

pip 可用时,可以从 PyPI 下载分发包,并在一步中安装:

代码语言:javascript
复制
pip install SQLAlchemy

此命令将从 Python 奶酪商店 下载最新的已发布版本的 SQLAlchemy,并将其安装到您的系统上。对于大多数常见平台,将下载提供预构建的本机 Cython / C 扩展的 Python Wheel 文件。

为了安装最新的预发布版本,例如 2.0.0b1,pip 要求必须使用 --pre 标志:

代码语言:javascript
复制
pip install --pre SQLAlchemy

在上述情况下,如果最新版本是预发布版本,则将安装该版本,而不是最新发布的版本。

手动从源代码分发包安装

当不从 pip 安装时,可以使用 setup.py 脚本安装源代码分发包:

代码语言:javascript
复制
python setup.py install

源代码安装不受平台限制,可以在任何平台上安装,无论是否安装了 Cython / C 构建工具。正如下一节构建 Cython 扩展所述,setup.py将尝试使用 Cython / C 进行构建,但如果不行,将退而求其次安装纯 Python 版本。

构建 Cython 扩展

SQLAlchemy 包含 Cython 扩展,这些扩展在各个领域提供了额外的速度提升,目前重点是核心结果集的速度。

在 2.0 版本中的变更:SQLAlchemy 的 C 扩展已使用 Cython 重新编写。

如果检测到合适的平台,则setup.py将自动构建扩展,假设已安装了 Cython 包。 完整的手动构建如下所示:

代码语言:javascript
复制
# cd into SQLAlchemy source distribution
cd path/to/sqlalchemy

# install cython
pip install cython

# optionally build Cython extensions ahead of install
python setup.py build_ext

# run the install
python setup.py install

还可以使用 PEP 517 技术执行源构建,例如使用 build

代码语言:javascript
复制
# cd into SQLAlchemy source distribution
cd path/to/sqlalchemy

# install build
pip install build

# build source / wheel dists
python -m build

如果由于 Cython 未安装、缺少编译器或其他问题而导致 Cython 扩展的构建失败,则设置过程将输出警告消息,并在完成后重新运行构建而不包括 Cython 扩展,报告最终状态。

在即使不尝试编译 Cython 扩展的情况下运行构建/安装时,可以指定DISABLE_SQLALCHEMY_CEXT环境变量。 这种情况的用例要么是为了特殊的测试环境,要么是在通常的“重建”机制无法解决的兼容性/构建问题的情况下:

代码语言:javascript
复制
export DISABLE_SQLALCHEMY_CEXT=1; python setup.py install
安装数据库 API

SQLAlchemy 被设计为与针对特定数据库构建的 DBAPI 实现一起运行,并支持最流行的数据库。 方言 中的各个数据库部分列举了每个数据库的可用 DBAPI,包括外部链接。

检查已安装的 SQLAlchemy 版本

本文档涵盖了 SQLAlchemy 版本 2.0。 如果您正在使用已安装 SQLAlchemy 的系统上工作,请像这样从您的 Python 提示符检查版本:

代码语言:javascript
复制
>>> import sqlalchemy
>>> sqlalchemy.__version__  
2.0.0
下一步

安装了 SQLAlchemy 后,新用户和旧用户都可以 继续进行 SQLAlchemy 教程。

支持的平台

SQLAlchemy 支持以下平台:

  • cPython 3.7 及更高版本
  • PyPy 的 Python-3 兼容版本

在 2.0 版本中的变更:SQLAlchemy 现在针对 Python 3.7 及以上版本。

AsyncIO 支持

SQLAlchemy 的 asyncio 支持取决于 greenlet 项目。 这个依赖关系将默认安装在常见的机器平台上,但不是每个架构都支持,也可能不会默认安装在不太常见的架构上。 有关确保 asyncio 支持存在的详细信息,请参阅 Asyncio 平台安装说明(包括 Apple M1) 部分。

支持的安装方法

SQLAlchemy 的安装是通过基于 setuptools 的标准 Python 方法进行的,可以直接参考setup.py,也可以使用 pip 或其他与 setuptools 兼容的方法。

通过 pip 安装

当存在pip时,可以从 PyPI 下载分发并一步安装:

代码语言:javascript
复制
pip install SQLAlchemy

此命令将从 Python Cheese Shop 下载最新的 发布 版本的 SQLAlchemy,并将其安装到您的系统中。对于大多数常见平台,将下载提供本机 Cython / C 扩展预构建的 Python Wheel 文件。

为了安装最新的 预发布 版本,如 2.0.0b1,pip 要求使用 --pre 标志:

代码语言:javascript
复制
pip install --pre SQLAlchemy

在上述情况下,如果最新版本是预发布版本,则将安装该版本而不是最新发布版本。

从源分发手动安装

当不使用 pip 安装时,可以使用 setup.py 脚本安装源分发:

代码语言:javascript
复制
python setup.py install

源安装是平台无关的,将在任何平台上安装,无论是否安装了 Cython / C 构建工具。如下一节 构建 Cython 扩展 详细说明,setup.py 将尝试使用 Cython / C 进行构建,但否则将退回到纯 Python 安装。

构建 Cython 扩展

SQLAlchemy 包括提供额外速度提升的 Cython 扩展,在各个领域都有当前重点放在核心结果集的速度上。

从版本 2.0 开始更改:SQLAlchemy C 扩展已使用 Cython 重写。

如果检测到适当的平台,setup.py 将自动构建扩展,假设安装了 Cython 包。完整的手动构建如下:

代码语言:javascript
复制
# cd into SQLAlchemy source distribution
cd path/to/sqlalchemy

# install cython
pip install cython

# optionally build Cython extensions ahead of install
python setup.py build_ext

# run the install
python setup.py install

源构建也可以使用 PEP 517 技术执行,例如使用 build

代码语言:javascript
复制
# cd into SQLAlchemy source distribution
cd path/to/sqlalchemy

# install build
pip install build

# build source / wheel dists
python -m build

如果 Cython 扩展构建失败,原因可能是 Cython 未安装、缺少编译器或其他问题,设置过程将输出警告消息,并在完成后重新运行构建,报告最终状态而不包括 Cython 扩展。

若要在甚至不尝试编译 Cython 扩展的情况下运行构建/安装,可以指定 DISABLE_SQLALCHEMY_CEXT 环境变量。这样做的用例是特殊测试情况,或者在通常的“重新构建”机制无法克服的兼容性/构建问题的罕见情况下:

代码语言:javascript
复制
export DISABLE_SQLALCHEMY_CEXT=1; python setup.py install
安装数据库 API

SQLAlchemy 设计用于与为特定数据库构建的 DBAPI 实现一起运行,并支持最流行的数据库。方言中的各个数据库部分列举了每个数据库的可用 DBAPI,包括外部链接。

检查已安装的 SQLAlchemy 版本

本文档涵盖了 SQLAlchemy 2.0 版本。如果你正在使用已安装了 SQLAlchemy 的系统,请在 Python 提示符中检查版本,如下所示:

代码语言:javascript
复制
>>> import sqlalchemy
>>> sqlalchemy.__version__  
2.0.0
下一步

安装了 SQLAlchemy 后,新用户和老用户都可以继续进行 SQLAlchemy 教程。

1.x 到 2.0 迁移

新发布的 SQLAlchemy 2.0 版本的 API 注意事项可以在 SQLAlchemy 2.0 - 主要迁移指南这里找到。

SQLAlchemy Unified Tutorial

原文:docs.sqlalchemy.org/en/20/tutorial/index.html

关于本文档

SQLAlchemy Unified Tutorial 在 SQLAlchemy 的 Core 和 ORM 组件之间集成,并作为 SQLAlchemy 整体的统一介绍。对于在 1.x 系列中使用 SQLAlchemy 的用户,在 2.0 风格下工作的用户,ORM 使用带有select()构造的 Core 风格查询,并且 Core 连接和 ORM 会话之间的事务语义是等效的。注意每个部分的蓝色边框样式,这将告诉您一个特定主题有多“ORM 式”!

对于已经熟悉 SQLAlchemy 的用户,特别是那些希望将现有应用程序迁移到 SQLAlchemy 2.0 系列下的 1.4 过渡阶段的用户,也应查看 SQLAlchemy 2.0 - 重大迁移指南文档。

对于新手来说,这份文档包含大量细节,但到最后他们将被视为炼金术士

SQLAlchemy 被呈现为两个不同的 API,一个建立在另一个之上。这些 API 被称为CoreORM

SQLAlchemy Core是 SQLAlchemy 作为“数据库工具包”的基础架构。该库提供了管理与数据库的连接、与数据库查询和结果的交互以及 SQL 语句的编程构造的工具。

主要仅 Core的部分不会提到 ORM。在这些部分中使用的 SQLAlchemy 构造将从sqlalchemy命名空间导入。作为主题分类的另一个指示符,它们还将在右侧包括一个深蓝色边框。在使用 ORM 时,这些概念仍然存在,但在用户代码中不太明显。ORM 用户应阅读这些部分,但不要期望直接使用这些 API 来编写 ORM 中心的代码。

SQLAlchemy ORM建立在 Core 之上,提供了可选的对象关系映射功能。ORM 提供了一个额外的配置层,允许用户定义的 Python 类被映射到数据库表和其他构造,以及一个称为Session的对象持久性机制。然后,它扩展了 Core 级别的 SQL 表达语言,允许以用户定义的对象的术语来组合和调用 SQL 查询。

主要仅 ORM的部分应该标题包含“ORM”短语,以便清楚地表明这是一个与 ORM 相关的主题。在这些部分中使用的 SQLAlchemy 构造将从sqlalchemy.orm命名空间导入。最后,作为主题分类的另一个指示符,它们还将在左侧包括一个浅蓝色边框。仅 Core 的用户可以跳过这些部分。

大多数本教程中的部分都讨论了与 ORM 明确使用的核心概念。特别是 SQLAlchemy 2.0 在 ORM 中更大程度地整合了核心 API 的使用。

对于这些部分的每一个,都会有介绍性文本讨论 ORM 用户应该期望使用这些编程模式的程度。这些部分中的 SQLAlchemy 构造将从sqlalchemy命名空间中导入,同时可能会使用一些sqlalchemy.orm构造。作为主题分类的额外指示,这些部分还将在左侧具有较薄的浅色边框,并在右侧具有较粗的深色边框。核心和 ORM 用户应该同样熟悉这些部分的概念。

教程概述

教程将以应该学习的自然顺序呈现这两个概念,首先是以主要基于核心的方式,然后扩展到更多的 ORM 中心的概念。

本教程的主要部分如下:

  • 建立连接 - Engine - 所有的 SQLAlchemy 应用都以一个Engine对象开始;这是如何创建一个的方法。
  • 处理事务和 DBAPI - 此处介绍了Engine及其相关对象ConnectionResult的使用 API。此内容是核心为中心的,但 ORM 用户至少应熟悉Result对象。
  • 处理数据库元数据 - SQLAlchemy 的 SQL 抽象以及 ORM 都依赖于将数据库模式构造定义为 Python 对象的系统。本节介绍了如何从核心和 ORM 的角度进行操作。
  • 处理数据 - 在这里我们学习如何在数据库中创建、选择、更新和删除数据。这里所谓的 CRUD 操作以 SQLAlchemy 核心的形式给出,并链接到其 ORM 对应项。在使用 SELECT 语句中详细介绍的 SELECT 操作同样适用于核心和 ORM。
  • 使用 ORM 进行数据操作 - 涵盖了 ORM 的持久化框架;基本上是 ORM 为中心的插入、更新和删除的方式,以及如何处理事务。
  • 处理 ORM 相关对象介绍了 relationship() 构造的概念,并简要概述了其用法,并提供了链接到更深入文档的链接。
  • 进一步阅读 列出了一系列完全记录了本教程介绍的概念的顶级文档部分。
版本检查

本教程是使用称为 doctest 的系统编写的。所有带有 >>> 的代码摘录实际上都作为 SQLAlchemy 的测试套件的一部分运行,并且读者被邀请使用他们自己的 Python 解释器实时处理给定的代码示例。

如果运行示例,建议读者执行快速检查以验证我们在 版本 2.0 的 SQLAlchemy 上:

代码语言:javascript
复制
>>> import sqlalchemy
>>> sqlalchemy.__version__  
2.0.0

教程概述

本教程将按照应该学习的自然顺序呈现这两个概念,首先是基本的 Core 方法,然后扩展到更多的 ORM 方法。

本教程的主要部分如下:

  • 建立连接 - Engine - 所有的 SQLAlchemy 应用程序都始于一个 Engine 对象;这里介绍如何创建一个。
  • 处理事务和 DBAPI - 这里介绍了 Engine 及其相关对象 ConnectionResult 的使用 API。这部分内容主要围绕 Core,但 ORM 用户至少要熟悉 Result 对象。
  • 处理数据库元数据 - SQLAlchemy 的 SQL 抽象以及 ORM 都依赖于将数据库模式构造定义为 Python 对象的系统。本节介绍了如何从 Core 和 ORM 的角度来做到这一点。
  • 处理数据 - 这里我们学习如何在数据库中创建、选择、更新和删除数据。这里所谓的 CRUD 操作以 SQLAlchemy Core 的术语给出,并链接到其 ORM 对应项。在 使用 SELECT 语句 中详细介绍的 SELECT 操作同样适用于 Core 和 ORM。
  • 使用 ORM 进行数据操作涵盖了 ORM 的持久性框架;基本上是 ORM-centric 的插入、更新和删除方式,以及如何处理事务。
  • 使用 ORM 相关对象介绍了relationship()构造的概念,并简要介绍了它的用法,并提供了更深入文档的链接。
  • 进一步阅读列出了一系列主要的顶级文档部分,这些部分完全记录了本教程中介绍的概念。
版本检查

本教程是使用名为doctest的系统编写的。 所有使用>>>编写的代码摘录实际上都作为 SQLAlchemy 测试套件的一部分运行,并且读者被邀请实时使用自己的 Python 解释器与给出的代码示例一起工作。

如果运行示例,则建议读者进行快速检查以验证我们使用的是2.0 版本的 SQLAlchemy:

代码语言:javascript
复制
>>> import sqlalchemy
>>> sqlalchemy.__version__  
2.0.0
版本检查

本教程是使用名为doctest的系统编写的。 所有使用>>>编写的代码摘录实际上都作为 SQLAlchemy 测试套件的一部分运行,并且读者被邀请实时使用自己的 Python 解释器与给出的代码示例一起工作。

如果运行示例,则建议读者进行快速检查以验证我们使用的是2.0 版本的 SQLAlchemy:

代码语言:javascript
复制
>>> import sqlalchemy
>>> sqlalchemy.__version__  
2.0.0

建立连接 - Engine

原文:docs.sqlalchemy.org/en/20/tutorial/engine.html

欢迎 ORM 和 Core 读者!

每一个连接到数据库的 SQLAlchemy 应用程序都需要使用一个 Engine。这个简短的部分适用于所有人。

任何 SQLAlchemy 应用程序的起点是一个称为Engine 的对象。这个对象充当连接到特定数据库的连接的中心来源,提供了一个工厂以及一个称为连接池的保持空间,用于这些数据库连接。该引擎通常是一个全局对象,仅为特定数据库服务器创建一次,并且使用 URL 字符串进行配置,该字符串将描述它应该如何连接到数据库主机或后端。

为了本教程,我们将使用内存中的 SQLite 数据库。这是一个方便的测试方法,无需设置实际的预先存在的数据库。通过使用create_engine() 函数创建 Engine

代码语言:javascript
复制
>>> from sqlalchemy import create_engine
>>> engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)

create_engine 的主要参数是一个字符串 URL,上面传递的字符串是 "sqlite+pysqlite:///:memory:"。这个字符串向 Engine 指示了三个重要的事实:

  1. 我们正在与什么样的数据库通信?上面的 sqlite 部分连接了 SQLAlchemy 到一个称为方言的对象。
  2. 我们正在使用什么 DBAPI?Python DBAPI 是 SQLAlchemy 用来与特定数据库交互的第三方驱动程序。在这种情况下,我们使用的是 pysqlite,在现代 Python 中,它是 SQLite 的sqlite3 标准库接口。如果省略,则 SQLAlchemy 将使用特定数据库的默认 DBAPI。
  3. 我们如何定位数据库?在这种情况下,我们的 URL 包含短语 /:memory:,这是对 sqlite3 模块的一个指示,表明我们将使用一个仅存在于内存中的数据库。这种类型的数据库非常适合用于实验,因为它不需要任何服务器,也不需要创建新文件。

我们还指定了一个参数create_engine.echo,它将指示Engine将其发出的所有 SQL 记录到一个 Python 日志记录器,该记录器将写入标准输出。此标志是一种更正式设置 Python 日志记录的简便方式,并且对于脚本中的实验很有用。许多 SQL 示例将包括此 SQL 日志输出,在点击 [SQL] 链接后,将显示完整的 SQL 交互。

处理事务和 DBAPI

原文:docs.sqlalchemy.org/en/20/tutorial/dbapi_transactions.html

准备好的Engine 对象后,我们现在可以继续深入探讨 Engine 的基本操作及其主要交互端点,即 ConnectionResult。我们还将介绍 ORM 对这些对象的门面,称为 Session

ORM 读者注意

使用 ORM 时,Engine 由另一个称为 Session 的对象管理。现代 SQLAlchemy 中的 Session 强调的是一种事务性和 SQL 执行模式,它与下面讨论的 Connection 的模式基本相同,因此,虽然本小节是以核心为中心的,但这里的所有概念基本上都与 ORM 使用相关,并且建议所有 ORM 学习者阅读。Connection 使用的执行模式将在本节末尾与 Session 的模式进行对比。

由于我们尚未介绍 SQLAlchemy 表达语言,这是 SQLAlchemy 的主要特性,我们将利用该软件包中的一个简单构造,称为text() 构造,它允许我们以文本 SQL的形式编写 SQL 语句。请放心,在日常使用 SQLAlchemy 时,文本 SQL 绝大多数情况下都是例外而不是规则,即使如此,它仍然始终完全可用。

获取连接

Engine对象从用户角度看唯一的目的是提供称为Connection的数据库连接单元。当直接使用核心时,与数据库的所有交互都是通过Connection对象完成的。由于Connection代表着针对数据库的一个开放资源,我们希望始终将对此对象的使用范围限制在特定的上下文中,而使用 Python 上下文管理器形式,也称为with 语句是这样做的最佳方式。下面我们使用文本 SQL 语句说明“Hello World”。文本 SQL 使用一个叫做text()的构造发出,稍后将更详细地讨论:

代码语言:javascript
复制
>>> from sqlalchemy import text

>>> with engine.connect() as conn:
...     result = conn.execute(text("select 'hello world'"))
...     print(result.all())
BEGIN  (implicit)
select  'hello world'
[...]  ()
[('hello world',)]
ROLLBACK 

在上面的示例中,为数据库连接提供了上下文管理器,并将操作放在事务内。Python DBAPI 的默认行为包括事务始终处于进行中;当连接的范围被释放时,会发出 ROLLBACK 以结束事务。事务不会自动提交;当我们想要提交数据时,通常需要调用Connection.commit(),我们将在下一节中看到。

提示

“自动提交”模式适用于特殊情况。章节设置事务隔离级别,包括 DBAPI 自动提交讨论了这一点。

我们的 SELECT 的结果也以一个叫做Result的对象返回,稍后将讨论,但是暂时我们将添加这样一句,最好确保在“connect”块内消耗此对象,并且不要在连接范围之外传递。## 提交更改

我们刚刚学到 DBAPI 连接是非自动提交的。如果我们想提交一些数据怎么办?我们可以修改我们上面的示例来创建一个表并插入一些数据,然后使用Connection.commit()方法在我们获取Connection对象的块内调用进行事务提交:

代码语言:javascript
复制
# "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()
BEGIN  (implicit)
CREATE  TABLE  some_table  (x  int,  y  int)
[...]  ()
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
INSERT  INTO  some_table  (x,  y)  VALUES  (?,  ?)
[...]  [(1,  1),  (2,  4)]
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
COMMIT 

在上面,我们发出了两个通常是事务性的 SQL 语句,“CREATE TABLE”语句[1]和一个参数化的“INSERT”语句(上面的参数化语法在发送多个参数中讨论)。由于我们希望我们所做的工作在我们的块内被提交,我们调用Connection.commit()方法来提交事务。在块内调用此方法后,我们可以继续运行更多的 SQL 语句,如果选择的话,我们可以再次调用Connection.commit()来进行后续语句的提交。SQLAlchemy 将这种风格称为边提交边进行

还有另一种提交数据的风格,即我们可以事先将我们的“connect”块声明为事务块。在这种操作模式下,我们使用Engine.begin()方法来获取连接,而不是使用Engine.connect()方法。这种方法既管理了Connection的范围,也在事务结束时包含了 COMMIT,假设块成功,或者在出现异常时回滚。这种风格被称为一次性开始

代码语言:javascript
复制
# "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}],
...     )
BEGIN  (implicit)
INSERT  INTO  some_table  (x,  y)  VALUES  (?,  ?)
[...]  [(6,  8),  (9,  10)]
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
COMMIT 

“一次性开始”风格通常更受青睐,因为它更简洁,并且事先指示整个块的意图。然而,在本教程中,我们通常会使用“边提交边进行”风格,因为这样更灵活,适合演示目的。## 语句执行的基础知识

我们已经看到了一些例子,针对数据库运行 SQL 语句,利用了一个叫做Connection.execute()的方法,结合一个叫做text()的对象,并返回一个叫做Result的对象。在本节中,我们将更详细地说明这些组件的机制和交互。

当使用 Session.execute() 方法时,本节大部分内容同样适用于现代 ORM 的使用,其工作原理与 Connection.execute() 非常相似,包括 ORM 结果行使用的是与 Core 相同的 Result 接口来传递。

获取行

我们首先通过利用之前插入的行来更仔细地说明 Result 对象,运行一个对我们创建的表进行文本选择的 SELECT 语句:

代码语言:javascript
复制
>>> 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}")
BEGIN  (implicit)
SELECT  x,  y  FROM  some_table
[...]  ()
x: 1  y: 1
x: 2  y: 4
x: 6  y: 8
x: 9  y: 10
ROLLBACK 

上面,我们执行的“SELECT”字符串选择了我们表中的所有行。返回的对象称为 Result,表示结果行的可迭代对象。

Result 有许多用于获取和转换行的方法,例如之前介绍的 Result.all() 方法,它返回所有 Row 对象的列表。它还实现了 Python 迭代器接口,以便我们可以直接对 Row 对象的集合进行迭代。

Row 对象本身旨在像 Python 的命名元组一样运作。下面我们展示了访问行的各种方式。

元组赋值 - 这是最具 Python 风格的方式,即按位置分配变量,就像它们被接收到的那样:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for x, y in result:
    ...

整数索引 - 元组是 Python 序列,因此也可以使用常规整数访问:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for row in result:
    x = row[0]

属性名称 - 由于这些是 Python 命名元组,元组具有与每个列的名称匹配的动态属性名称。这些名称通常是 SQL 语句分配给每行中的列的名称。虽然它们通常是相当可预测的,并且也可以由标签控制,在 less 定义的情况下,它们可能受到特定于数据库的行为的影响:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for row in result:
    y = row.y

    # illustrate use with Python f-strings
    print(f"Row: {row.x}  {y}")

映射访问 - 为了将行作为 Python 映射对象接收,这本质上是 Python 对普通dict对象的只读版本的接口,Result可以通过Result.mappings()修改器转换为MappingResult对象;这是一个生成类似于字典的RowMapping对象而不是Row对象的结果对象:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for dict_row in result.mappings():
    x = dict_row["x"]
    y = dict_row["y"]
```### 发送参数

SQL 语句通常会伴随着要与语句本身一起传递的数据,就像我们之前在 INSERT 示例中看到的那样。因此,Connection.execute()方法也接受参数,这些参数被称为绑定参数。一个基本的例子可能是,如果我们想要将 SELECT 语句限制为只选择满足某些条件的行,比如“y”值大于通过函数传递的某个值的行。

为了达到这样的效果,使得 SQL 语句保持固定,同时驱动程序可以正确地清理值,我们在语句中添加了一个名为“y”的 WHERE 条件;text()构造函数使用冒号格式“:y”接受这些参数。然后,“:y”的实际值作为字典形式的第二个参数传递给Connection.execute()

代码语言:javascript
复制
>>> 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}")
BEGIN  (implicit)
SELECT  x,  y  FROM  some_table  WHERE  y  >  ?
[...]  (2,)
x: 2  y: 4
x: 6  y: 8
x: 9  y: 10
ROLLBACK 

在记录的 SQL 输出中,我们可以看到绑定参数:y在发送到 SQLite 数据库时被转换成了一个问号。这是因为 SQLite 数据库驱动程序使用了一种称为“问号参数风格”的格式,这是 DBAPI 规范允许的六种不同格式之一。SQLAlchemy 将这些格式抽象为一种,即使用冒号的“命名”格式。 ### 发送多个参数

在提交更改的示例中,我们执行了一个 INSERT 语句,似乎我们能够一次将多行插入到数据库中。对于 DML 语句,如“INSERT”,“UPDATE”和“DELETE”,我们可以通过传递一个字典列表而不是单个字典给Connection.execute()方法,从而发送多个参数集,这表明单个 SQL 语句应该被多次调用,每次为一个参数集。这种执行方式称为 executemany:

代码语言:javascript
复制
>>> with engine.connect() as conn:
...     conn.execute(
...         text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
...         [{"x": 11, "y": 12}, {"x": 13, "y": 14}],
...     )
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  some_table  (x,  y)  VALUES  (?,  ?)
[...]  [(11,  12),  (13,  14)]
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
COMMIT 

以上操作等同于针对每个参数集一次运行给定的 INSERT 语句,但该操作将被优化以在许多行上获得更好的性能。

“execute”和“executemany”之间的一个关键行为差异是,后者不支持返回结果行,即使语句包含 RETURNING 子句也是如此。唯一的例外是使用 Core insert()构造时,稍后在本教程的使用 INSERT 语句中介绍,该构造还使用Insert.returning()方法指示 RETURNING。在这种情况下,SQLAlchemy 利用特殊逻辑重新组织 INSERT 语句,以便在支持 RETURNING 的同时可以为多行调用它。

另请参阅

executemany - 在 Glossary 中,描述了 DBAPI 级别的cursor.executemany()方法,用于大多数“executemany”执行。

INSERT 语句的“插入多个值”行为 - 在引擎和连接中,描述了Insert.returning()使用的专门逻辑,以便通过“executemany”执行传递结果集。## 使用 ORM 会话执行

正如前面提到的,上面的大多数模式和示例也适用于与 ORM 一起使用,因此我们在这里介绍这种用法,以便在教程进行时,我们能够将每个模式以 Core 和 ORM 一起使用的方式进行说明。

当使用 ORM 时,与数据库交互的基本事务对象称为Session。在现代 SQLAlchemy 中,此对象的使用方式与Connection非常相似,实际上,当使用Session时,它会内部引用一个Connection,然后使用它来发出 SQL。

当与非 ORM 构造一起使用Session时,它会通过我们给它的 SQL 语句并且通常不会与Connection做什么不同的事情,因此我们可以在这里以我们已经学到的简单文本 SQL 操作来说明它。

Session具有几种不同的创建模式,但在这里我们将展示最基本的一种,它与Connection的使用方式完全一致,即在上下文管理器中构造它:

代码语言:javascript
复制
>>> 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}")
BEGIN  (implicit)
SELECT  x,  y  FROM  some_table  WHERE  y  >  ?  ORDER  BY  x,  y
[...]  (6,)
x: 6  y: 8
x: 9  y: 10
x: 11  y: 12
x: 13  y: 14
ROLLBACK 

上面的示例可以与发送参数中的示例进行比较 - 我们直接将对with engine.connect() as conn的调用替换为with Session(engine) as session,然后像使用Connection.execute()方法一样使用Session.execute()方法。

Connection类似,Session通过使用Session.commit()方法具有“边提交边执行”的行为,如下所示,使用文本 UPDATE 语句来修改部分数据:

代码语言:javascript
复制
>>> with Session(engine) as session:
...     result = session.execute(
...         text("UPDATE some_table SET y=:y WHERE x=:x"),
...         [{"x": 9, "y": 11}, {"x": 13, "y": 15}],
...     )
...     session.commit()
BEGIN  (implicit)
UPDATE  some_table  SET  y=?  WHERE  x=?
[...]  [(11,  9),  (15,  13)]
COMMIT 

在上面的示例中,我们使用绑定参数,“executemany”样式的执行来调用 UPDATE 语句,在发送多个参数中介绍了这种方式,并以“边提交边执行”的方式结束了该块。

提示

Session在事务结束后实际上不会保留Connection对象。它会在下一次执行数据库 SQL 时从Engine中获取一个新的Connection

Session显然有比那更多的技巧,但是了解它有一个Session.execute()方法,与Connection.execute()的使用方式相同,将使我们能够开始后面的示例。

参见

使用会话的基础知识 - 提供了与Session对象的基本创建和使用模式。## 获取连接

从用户的角度来看,Engine对象的唯一目的是提供与数据库的连接单元Connection。当直接使用核心时,与数据库的所有交互都是通过Connection对象完成的。由于Connection表示对数据库的打开资源,我们希望始终将此对象的使用范围限制在特定的上下文中,而最好的方法是使用 Python 上下文管理器形式,也称为with 语句。下面我们以一个使用文本 SQL 语句的“Hello World”为例进行说明。文本 SQL 是使用称为text()的构造发出的,稍后将对其进行更详细的讨论。

代码语言:javascript
复制
>>> from sqlalchemy import text

>>> with engine.connect() as conn:
...     result = conn.execute(text("select 'hello world'"))
...     print(result.all())
BEGIN  (implicit)
select  'hello world'
[...]  ()
[('hello world',)]
ROLLBACK 

在上面的示例中,为数据库连接提供了上下文管理器,并将操作框定在事务内。Python DBAPI 的默认行为包括事务始终在进行中;当连接的范围被释放时,会发出 ROLLBACK 来结束事务。事务不会自动提交;当我们想要提交数据时,通常需要调用Connection.commit(),正如我们将在下一节中看到的。

提示

“自动提交”模式适用于特殊情况。本节设置事务隔离级别,包括 DBAPI 自动提交对此进行了讨论。

我们的 SELECT 的结果也以一个叫做Result的对象返回,稍后将对其进行讨论,但目前我们将补充说明最好确保在“connect”块内消耗此对象,并且不要在连接范围之外传递。

提交更改

我们刚刚学到 DBAPI 连接是非自动提交的。如果我们想提交一些数据怎么办?我们可以修改上面的示例来创建一个表并插入一些数据,然后使用Connection.commit()方法来提交事务,在我们获取Connection对象的块内调用:

代码语言:javascript
复制
# "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()
BEGIN  (implicit)
CREATE  TABLE  some_table  (x  int,  y  int)
[...]  ()
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
INSERT  INTO  some_table  (x,  y)  VALUES  (?,  ?)
[...]  [(1,  1),  (2,  4)]
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
COMMIT 

在上面,我们发出了两个通常是事务性的 SQL 语句,一个是“CREATE TABLE”语句[1],另一个是参数化的“INSERT”语句(上面的参数化语法在发送多个参数一节中讨论)。由于我们希望我们所做的工作在我们的块内被提交,我们调用Connection.commit()方法来提交事务。在我们在块内调用这个方法之后,我们可以继续运行更多的 SQL 语句,如果选择的话,我们可以再次调用Connection.commit()来提交后续的语句。SQLAlchemy 将这种风格称为边做边提交

还有另一种提交数据的风格,即我们可以事先声明我们的“connect”块是一个事务块。对于这种操作模式,我们使用Engine.begin()方法来获取连接,而不是Engine.connect()方法。该方法将管理Connection的范围,并在成功块的情况下封闭事务内的所有内容,或者在出现异常时回滚。这种风格称为一次性开始

代码语言:javascript
复制
# "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}],
...     )
BEGIN  (implicit)
INSERT  INTO  some_table  (x,  y)  VALUES  (?,  ?)
[...]  [(6,  8),  (9,  10)]
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
COMMIT 

“一次性开始”风格通常更受欢迎,因为它更简洁,并且在前面指示了整个块的意图。然而,在本教程中,我们通常会使用“随时提交”风格,因为它对于演示目的更灵活。

语句执行基础

我们已经看到了一些示例,通过一种称为Connection.execute()的方法来执行 SQL 语句,结合一个称为text()的对象,并返回一个称为Result的对象。在本节中,我们将更详细地说明这些组件的机制和交互。

本节中的大部分内容同样适用于在使用Session.execute()方法时的现代 ORM 使用,该方法与Connection.execute()非常相似,包括 ORM 结果行都使用与 Core 相同的Result接口传递。

获取行

我们将首先通过使用先前插入的行来更详细地说明Result对象,对我们创建的表运行一个文本 SELECT 语句:

代码语言:javascript
复制
>>> 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}")
BEGIN  (implicit)
SELECT  x,  y  FROM  some_table
[...]  ()
x: 1  y: 1
x: 2  y: 4
x: 6  y: 8
x: 9  y: 10
ROLLBACK 

在上面,我们执行的“SELECT”字符串选择了我们表中的所有行。返回的对象称为Result,表示一个结果行的可迭代对象。

Result 有很多用于获取和转换行的方法,例如之前示例中说明的 Result.all() 方法,它返回所有 Row 对象的列表。它还实现了 Python 迭代器接口,以便我们可以直接迭代 Row 对象的集合。

Row 对象本身旨在像 Python named tuples 一样行事。下面我们展示了访问行的各种方法。

Tuple Assignment - 这是最 Python 特有的风格,即按位置分配变量,就像它们被接收到的那样:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for x, y in result:
    ...

整数索引 - 元组是 Python 序列,因此也可以进行常规整数访问:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for row in result:
    x = row[0]

属性名称 - 由于这些是 Python 的命名元组,元组具有与每列名称匹配的动态属性名称。这些名称通常是 SQL 语句为每行的列分配的名称。虽然它们通常是相当可预测的,也可以通过标签进行控制,在定义较少的情况下,它们可能受到特定于数据库的行为的影响:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for row in result:
    y = row.y

    # illustrate use with Python f-strings
    print(f"Row: {row.x}  {y}")

映射访问 - 要将行作为 Python mapping 对象接收,这本质上是 Python 对通用 dict 对象的只读版本的接口,可以使用 Result.mappings() 修改器将 Result 转换为 MappingResult 对象;这是一个产生类似于字典的 RowMapping 对象而不是 Row 对象的结果对象:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for dict_row in result.mappings():
    x = dict_row["x"]
    y = dict_row["y"]
```### 发送参数

SQL 语句通常伴随着要与语句本身一起传递的数据,就像我们之前在 INSERT 示例中看到的那样。因此,Connection.execute() 方法还接受参数,这些参数称为 bound parameters。一个简单的示例可能是,如果我们想要将 SELECT 语句限制为仅符合某个条件的行,例如“y”值大于通过函数传入的某个特定值的行。

为了实现这一点,使得 SQL 语句保持不变并且驱动程序可以正确地清理值,我们在语句中添加了一个名为“y”的 WHERE 条件;text()构造使用冒号格式“:y”接受这些参数。然后,“:y”的实际值以字典的形式作为第二个参数传递给Connection.execute()

代码语言:javascript
复制
>>> 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}")
BEGIN  (implicit)
SELECT  x,  y  FROM  some_table  WHERE  y  >  ?
[...]  (2,)
x: 2  y: 4
x: 6  y: 8
x: 9  y: 10
ROLLBACK 

在记录的 SQL 输出中,我们可以看到当绑定参数:y发送到 SQLite 数据库时,它被转换为问号。这是因为 SQLite 数据库驱动程序使用一种称为“qmark 参数样式”的格式,这是 DBAPI 规范允许的六种不同格式之一。SQLAlchemy 将这些格式抽象成了一个,即使用冒号的“named”格式。### 发送多个参数

在 提交更改 的示例中,我们执行了一个 INSERT 语句,其中看起来我们能够一次将多行插入到数据库中。对于“INSERT”、“UPDATE”和“DELETE”等 DML 语句,我们可以通过传递一个字典列表而不是单个字典给Connection.execute()方法来发送多个参数集,这表明应该针对每个参数集调用单个 SQL 语句多次。这种执行方式称为 executemany:

代码语言:javascript
复制
>>> with engine.connect() as conn:
...     conn.execute(
...         text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
...         [{"x": 11, "y": 12}, {"x": 13, "y": 14}],
...     )
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  some_table  (x,  y)  VALUES  (?,  ?)
[...]  [(11,  12),  (13,  14)]
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
COMMIT 

上述操作相当于针对每个参数集合运行给定的 INSERT 语句一次,但该操作将被优化以在多行上获得更好的性能。

“execute”和“executemany”之间的一个关键行为差异是,后者不支持返回结果行,即使语句包含 RETURNING 子句也是如此。唯一的例外是当使用 Core 的insert()构造时,稍后在本教程的使用 INSERT 语句中介绍,该构造还使用Insert.returning()方法指示 RETURNING。在这种情况下,SQLAlchemy 使用特殊逻辑重新组织 INSERT 语句,以便在支持 RETURNING 的同时可以为多行调用它。

另请参阅

executemany - 在 Glossary 中描述了 DBAPI 级别的cursor.executemany() 方法,该方法用于大多数“executemany”执行。

INSERT 语句的“插入多个值”行为 - 在与引擎和连接一起工作中,描述了Insert.returning()用于提供带有“executemany”执行的结果集的专用逻辑。### 获取行

首先,我们将通过利用之前插入的行,对我们创建的表运行文本 SELECT 语句,更详细地说明Result对象:

代码语言:javascript
复制
>>> 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}")
BEGIN  (implicit)
SELECT  x,  y  FROM  some_table
[...]  ()
x: 1  y: 1
x: 2  y: 4
x: 6  y: 8
x: 9  y: 10
ROLLBACK 

上面,我们执行的“SELECT”字符串选择了我们表中的所有行。返回的对象称为Result,表示结果行的可迭代对象。

Result有很多用于获取和转换行的方法,例如之前演示的Result.all()方法,它返回所有Row对象的列表。它还实现了 Python 迭代器接口,以便我们可以直接迭代Row对象的集合。

Row对象本身旨在像 Python 的命名元组一样操作。下面我们演示了多种访问行的方式。

元组赋值 - 这是最符合 Python 习惯的样式,即按位置将变量分配给每行接收到的值:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for x, y in result:
    ...

整数索引 - 元组是 Python 序列,因此也可以使用常规整数访问:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for row in result:
    x = row[0]

属性名称 - 由于这些是 Python 命名元组,元组具有与每列名称匹配的动态属性名称。这些名称通常是 SQL 语句为每行分配的列名称。虽然它们通常相当可预测,并且也可以由标签控制,在定义不太明确的情况下,它们可能受到数据库特定的行为的影响:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for row in result:
    y = row.y

    # illustrate use with Python f-strings
    print(f"Row: {row.x}  {y}")

映射访问 - 要将行作为 Python 映射 对象接收,这实质上是 Python 对普通dict对象的只读接口,可以使用Result通过Result.mappings()修饰符将其转换MappingResult对象;这是一个产生类似于字典的RowMapping对象而不是Row对象的结果对象:

代码语言:javascript
复制
result = conn.execute(text("select x, y from some_table"))

for dict_row in result.mappings():
    x = dict_row["x"]
    y = dict_row["y"]
发送参数

SQL 语句通常伴随着要与语句一起传递的数据,就像我们之前在 INSERT 示例中看到的那样。因此,Connection.execute()方法也接受参数,这些参数被称为绑定参数。一个简单的例子可能是,如果我们想要将 SELECT 语句限制仅适用于满足某些条件的行,例如行中的“y”值大于传入函数的某个值。

为了使 SQL 语句保持不变,以便驱动程序可以正确地对值进行处理,我们在语句中添加了一个名为“y”的 WHERE 条件;text()构造函数接受这些参数,使用冒号格式“:y”。然后,“:y”的实际值作为字典的第二个参数传递给Connection.execute()

代码语言:javascript
复制
>>> 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}")
BEGIN  (implicit)
SELECT  x,  y  FROM  some_table  WHERE  y  >  ?
[...]  (2,)
x: 2  y: 4
x: 6  y: 8
x: 9  y: 10
ROLLBACK 

在记录的 SQL 输出中,我们可以看到绑定参数:y在发送到 SQLite 数据库时被转换为问号。这是因为 SQLite 数据库驱动程序使用一种称为“问号参数样式”的格式,这是 DBAPI 规范允许的六种不同格式之一。SQLAlchemy 将这些格式抽象成了一种格式,即使用冒号的“命名”格式。

发送多个参数

在提交更改的示例中,我们执行了一个 INSERT 语句,看起来我们能够一次性向数据库中插入多行数据。对于 DML 语句,如“INSERT”、“UPDATE”和“DELETE”,我们可以通过传递一个字典列表而不是单个字典给Connection.execute()方法,从而发送多个参数集,这表明单个 SQL 语句应该被多次调用,每次为一个参数集。这种执行方式被称为 executemany:

代码语言:javascript
复制
>>> with engine.connect() as conn:
...     conn.execute(
...         text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
...         [{"x": 11, "y": 12}, {"x": 13, "y": 14}],
...     )
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  some_table  (x,  y)  VALUES  (?,  ?)
[...]  [(11,  12),  (13,  14)]
<sqlalchemy.engine.cursor.CursorResult  object  at  0x...>
COMMIT 

上述操作等同于为每个参数集运行给定的 INSERT 语句一次,只是该操作将被优化以在许多行上获得更好的性能。

“execute”和“executemany”之间的一个关键行为差异是,后者不支持返回结果行,即使语句包含 RETURNING 子句也是如此。唯一的例外是在使用 Core insert()构造时,稍后在本教程的使用 INSERT 语句部分介绍,该构造还使用Insert.returning()方法指示 RETURNING。在这种情况下,SQLAlchemy 利用特殊逻辑重新组织 INSERT 语句,以便可以为多行调用它,同时仍支持 RETURNING。

另请参阅

executemany - 在 Glossary 中描述了用于大多数“executemany”执行的 DBAPI 级别cursor.executemany()方法。

“INSERT 语句的 Insert Many Values”行为 - 在使用引擎和连接中,描述了Insert.returning()使用的专门逻辑,以便通过“executemany”执行交付结果集。

使用 ORM 会话执行

正如之前提到的,上面的大多数模式和示例也适用于与 ORM 一起使用,因此在这里我们将介绍这种用法,以便随着教程的进行,我们将能够以 Core 和 ORM 一起的方式来说明每个模式。

当使用 ORM 时,基本的事务/数据库交互对象称为Session。在现代 SQLAlchemy 中,这个对象的使用方式与Connection非常相似,实际上,当使用Session时,它在内部引用一个Connection,用于发出 SQL。

Session与非 ORM 构造一起使用时,它会通过我们提供的 SQL 语句,并且通常不会与Connection直接执行有太大不同,因此我们可以根据我们已经学过的简单文本 SQL 操作来说明它。

Session有几种不同的创建模式,但在这里我们将说明最基本的一种,它与使用Connection的方式完全一致,即在上下文管理器中构造它:

代码语言:javascript
复制
>>> 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}")
BEGIN  (implicit)
SELECT  x,  y  FROM  some_table  WHERE  y  >  ?  ORDER  BY  x,  y
[...]  (6,)
x: 6  y: 8
x: 9  y: 10
x: 11  y: 12
x: 13  y: 14
ROLLBACK 

上面的示例可以与前一节中发送参数中的示例进行比较 - 我们直接将with engine.connect() as conn的调用替换为with Session(engine) as session,然后像使用Connection.execute()方法一样使用Session.execute()方法。

同样,像Connection一样,Session也具有“边提交边执行”的行为,使用Session.commit()方法,下面通过一个文本 UPDATE 语句来修改一些数据进行说明:

代码语言:javascript
复制
>>> with Session(engine) as session:
...     result = session.execute(
...         text("UPDATE some_table SET y=:y WHERE x=:x"),
...         [{"x": 9, "y": 11}, {"x": 13, "y": 15}],
...     )
...     session.commit()
BEGIN  (implicit)
UPDATE  some_table  SET  y=?  WHERE  x=?
[...]  [(11,  9),  (15,  13)]
COMMIT 

在上面,我们使用绑定参数“executemany”风格的执行方式调用了一个 UPDATE 语句,该语句介绍在发送多个参数中,以“边提交边执行”方式结束了该块。

提示

Session 实际上在结束事务后并不保留 Connection 对象。下次需要对数据库执行 SQL 时,它会从 Engine 获取一个新的 Connection

Session 很显然比那个拥有更多的技巧,然而理解它有一个 Session.execute() 方法,该方法的使用方式与 Connection.execute() 相同,将使我们能够开始后面的示例。

另请参阅

使用会话的基础知识 - 展示了与 Session 对象的基本创建和使用模式。

处理数据库元数据

原文:docs.sqlalchemy.org/en/20/tutorial/metadata.html

随着引擎和 SQL 执行完成,我们准备开始一些 Alchemy。SQLAlchemy Core 和 ORM 的核心元素是 SQL 表达语言,它允许流畅、可组合地构建 SQL 查询。这些查询的基础是代表数据库概念(如表和列)的 Python 对象。这些对象被统称为数据库元数据。

SQLAlchemy 中数据库元数据的最常见基础对象称为MetaDataTableColumn。下面的部分将说明这些对象在 Core 导向风格和 ORM 导向风格中的使用方式。

ORM 读者,请继续关注!

与其他部分一样,Core 用户可以跳过 ORM 部分,但 ORM 用户最好从两个角度熟悉这些对象。这里讨论的Table对象在使用 ORM 时以一种更间接的方式(也是完全 Python 类型化的方式)声明,然而,在 ORM 的配置中仍然有一个Table对象。

使用表对象设置元数据

当我们使用关系型数据库时,数据库中的基本数据保存结构,我们从中查询的结构称为。在 SQLAlchemy 中,数据库“表”最终由一个名为Table的 Python 对象表示。

要开始使用 SQLAlchemy 表达语言,我们需要构建Table对象,这些对象表示我们有兴趣使用的所有数据库表。 Table是通过编程方式构建的,可以直接使用Table构造函数,也可以间接地使用 ORM 映射类(稍后在使用 ORM 声明形式定义表元数据中描述)。还有一种选项可以从现有数据库加载一些或全部表信息,称为反射。

无论使用哪种方法,我们始终从一个集合开始,这个集合将是我们放置表的地方,称为 MetaData 对象。这个对象本质上是一个围绕 Python 字典的 外观,该字典存储了一系列以其字符串名称为键的 Table 对象。虽然 ORM 在获取这个集合的位置上提供了一些选项,但我们始终可以选择直接创建一个,看起来像这样:

代码语言:javascript
复制
>>> from sqlalchemy import MetaData
>>> metadata_obj = MetaData()

一旦我们有了 MetaData 对象,我们可以声明一些 Table 对象。本教程将从经典的 SQLAlchemy 教程模型开始,其中有一个名为 user_account 的表,存储着网站的用户,以及一个相关的 address 表,存储着与 user_account 表中的行相关联的电子邮件地址。当完全不使用 ORM Declarative 模型时,我们直接构造每个 Table 对象,通常将每个对象分配给一个变量,这将是我们在应用程序代码中引用表的方式:

代码语言:javascript
复制
>>> 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 语句相似;从表名开始,然后列出每个列,其中每个列都有一个名称和一个数据类型。我们上面使用的对象是:

Table - 表示数据库表并将自己分配给 MetaData 集合。

Column - 表示数据库表中的列,并将自己分配给 Table 对象。Column 通常包括一个字符串名称和一个类型对象。以父 TableColumn 对象的集合通常通过位于 Table.c 的关联数组来访问:

代码语言:javascript
复制
>>> user_table.c.name
Column('name', String(length=30), table=<user_account>)

>>> user_table.c.keys()
['id', 'name', 'fullname']

IntegerString - 这些类表示 SQL 数据类型,并且可以被传递给具有或没有必要被实例化的Column。在上面的例子中,我们想要给“name”列一个长度为“30”,因此我们实例化了String(30)。但对于“id”和“fullname”,我们没有指定这些,所以我们可以发送类本身。

另请参阅

MetaDataTableColumn的参考和 API 文档位于用 MetaData 描述数据库。数据类型的参考文档位于 SQL 数据类型对象。

在接下来的一节中,我们将说明Table的一个基本功能,即在特定数据库连接上生成 DDL。但首先,我们将声明第二个Table

声明简单约束

示例中的第一个Columnuser_table中包含Column.primary_key参数,这是一种简写技术,表示这个Column应该是这个表的主键的一部分。主键本身通常是隐式声明的,并且由PrimaryKeyConstraint构造表示,我们可以在Table对象的Table.primary_key属性上看到它:

代码语言:javascript
复制
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))

最常显式声明的约束是对应于数据库外键约束的ForeignKeyConstraint对象。当我们声明相互关联的表时,SQLAlchemy 不仅使用这些外键约束声明在向数据库发送 CREATE 语句时将其发送出去,而且还用于帮助构造 SQL 表达式。

一个涉及目标表上仅一个列的ForeignKeyConstraint通常使用列级别的速记符号通过ForeignKey对象声明。下面我们声明了一个将具有引用user表的外键约束的第二个表address

代码语言:javascript
复制
>>> 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数据类型。

在下一节中,我们将发出useraddress表的完整 DDL 以查看完成的结果。

发出 DDL 到数据库

我们已经构建了一个对象结构,代表数据库中的两个数据库表,在根MetaData对象开始,然后进入两个Table对象,每个对象都包含一组ColumnConstraint对象。这个对象结构将成为我们今后使用 Core 和 ORM 执行的大多数操作的中心。

我们可以对此结构进行的第一个有用的操作是发出 CREATE TABLE 语句,或者 DDL 到我们的 SQLite 数据库,以便我们可以从中插入和查询数据。我们已经拥有完成此操作所需的所有工具,通过在我们的MetaData上调用MetaData.create_all()方法,将目标数据库的Engine传递给它:

代码语言:javascript
复制
>>> 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 进程还负责按正确顺序发出 CREATE 语句;以上,FOREIGN KEY 约束依赖于 user 表的存在,因此 address 表在第二创建。在更复杂的依赖情况下,FOREIGN KEY 约束也可能使用 ALTER 在表创建后进行应用。

MetaData 对象还具有一个 MetaData.drop_all() 方法,它将按照与发出 CREATE 相反的顺序发出 DROP 语句以删除模式元素。## 使用 ORM 声明式表单定义表元数据

在使用 ORM 时,声明 Table 元数据的过程通常与声明 映射 类的过程结合在一起。映射类是我们想要创建的任何 Python 类,然后它将具有链接到数据库表中列的属性。虽然有几种实现方式,但最常见的风格称为 声明式,它允许我们一次声明我们的用户定义类和 Table 元数据。

建立声明性基础

在使用 ORM 时,MetaData 集合仍然存在,但它本身与一个仅用于 ORM 的构造关联,通常称为 声明式基础。获取新的声明式基础的最简便方法是创建一个继承 SQLAlchemy DeclarativeBase 类的新类:

代码语言:javascript
复制
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
...     pass

以上,Base 类就是我们将称为声明式基础的类。当我们创建新的类作为 Base 的子类时,并结合适当的类级指令,它们将在类创建时各自作为一个新的 ORM 映射类 建立,每个类通常(但不一定)引用一个特定的 Table 对象。

Declarative Base 指的是一个MetaData集合,它会自动为我们创建,假设我们没有从外部提供。这个MetaData集合可以通过DeclarativeBase.metadata类级别属性访问。当我们创建新的映射类时,它们将分别引用此MetaData集合内的一个Table

代码语言:javascript
复制
>>> Base.metadata
MetaData()

Declarative Base 还指的是一个称为registry的集合,它是 SQLAlchemy ORM 中的中央“映射器配置”单元。虽然很少直接访问,但该对象在映射器配置过程中是至关重要的,因为一组 ORM 映射类将通过此注册表相互协调。与MetaData的情况一样,我们的 Declarative Base 也为我们创建了一个registry(再次提供自己的registry的选项),我们可以通过DeclarativeBase.registry类变量访问它:

代码语言:javascript
复制
>>> Base.registry
<sqlalchemy.orm.decl_api.registry object at 0x...>
```### 声明映射类

有了`Base`类的设立,我们现在可以根据新类`User`和`Address`定义`user_account`和`address`表的 ORM 映射类。我们下面展示了最现代化的 Declarative 形式,它是从[**PEP 484**](https://peps.python.org/pep-0484/)类型注解中驱动的,使用了一个特殊类型`Mapped`,它指示要映射为特定类型的属性:

```py
>>> 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})"

上面的两个类UserAddress现在被称为ORM 映射类,并且可以在 ORM 持久性和查询操作中使用,稍后将对这些类的详细信息进行描述:

每个类都指向一个Table对象,该对象是作为声明性映射过程的一部分生成的,并通过将字符串赋值给DeclarativeBase.__tablename__属性命名。一旦类被创建,这个生成的Table可以通过DeclarativeBase.__table__属性进行访问。

如前所述,这种形式被称为声明性表配置。数种替代声明风格之一会让我们直接构建Table对象,并直接将其分配给DeclarativeBase.__table__。这种风格被称为声明性与命令式表配置。

为了指示Table中的列,我们使用mapped_column()构造,结合基于Mapped类型的类型注释。此对象将生成应用于Table构造的Column对象。

对于简单数据类型且没有其他选项的列,我们可以单独指定Mapped类型注释,使用简单的 Python 类型如intstr表示IntegerString。在声明性映射过程中,如何解释 Python 类型的定制化是非常开放的;请参阅使用注释的声明性表(用于mapped_column()的类型注释形式)和自定义类型映射部分了解背景知识。

根据Optional[<typ>]类型注释(或其等效形式,<typ> | NoneUnion[<typ>, None])的存在,可以将列声明为“可空”或“非空”。还可以显式使用mapped_column.nullable参数(不必与注释的可选性匹配)。

使用显式类型注释是完全可选的。我们也可以在没有注释的情况下使用mapped_column()。在使用这种形式时,我们会根据需要在每个mapped_column()构造内使用更明确的类型对象,如IntegerString以及nullable=False

另外两个属性,User.addressesAddress.user,定义了一种不同类型的属性,称为relationship(),它具有与注释相似的配置样式。relationship()构造在使用 ORM 相关对象中有更详细的讨论。

如果我们没有声明自己的__init__()方法,则会自动为类添加一个__init__()方法。该方法的默认形式接受所有属性名称作为可选关键字参数:

代码语言:javascript
复制
>>> sandy = User(name="sandy", fullname="Sandy Cheeks")

要自动生成一个全功能的__init__()方法,既提供位置参数又提供具有默认关键字值的参数,可以使用在声明式数据类映射中引入的数据类功能。当然,始终可以选择使用显式的__init__()方法。

添加__repr__()方法是为了获得可读的字符串输出;这些方法不需要存在的要求。与__init__()一样,可以使用 dataclasses 功能自动生成__repr__()方法。

另请参阅

ORM 映射风格 - 不同 ORM 配置风格的完整背景。

声明式映射 - 声明式类映射概述

使用mapped_column()的声明式表 - 详细说明如何使用mapped_column()Mapped来定义在使用声明式时要映射的Table中的列。

从 ORM 映射向数据库发出 DDL

由于我们的 ORM 映射类引用包含在MetaData集合中的Table对象,所以根据声明式基类发出 DDL 与在 Emitting DDL to the Database 中描述的过程相同。在我们的情况下,我们已经在我们的 SQLite 数据库中生成了useraddress表。如果我们之前没有这样做,我们可以自由地利用与我们的 ORM 声明基类相关联的MetaData来做到这一点,方法是通过访问DeclarativeBase.metadata属性中的集合,然后像以前一样使用MetaData.create_all()。在这种情况下,会运行 PRAGMA 语句,但不会生成新表,因为已经发现它们已经存在:

代码语言:javascript
复制
>>> 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 与现有数据库一起使用。完全可以在 Python 中显式声明所有元数据,使其结构与现有数据库相对应,这是很典型的。元数据结构也不必包含表、列或其他在本地应用程序中不需要的预先存在数据库中的约束和构造。

作为反射的示例,我们将创建一个新的`Table`对象,该对象表示我们在本文档的前几节中手动创建的`some_table`对象。这样做的方式有很多种,但最基本的方式是构建一个`Table`对象,给定表的名称和它将属于的`MetaData`集合,然后不是指示单独的`Column`和`Constraint`对象,而是传递目标`Engine`使用`Table.autoload_with`参数:

```py
>>> 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完全相同的方式使用:

代码语言:javascript
复制
>>> some_table
Table('some_table', MetaData(),
 Column('x', INTEGER(), table=<some_table>),
 Column('y', INTEGER(), table=<some_table>),
 schema=None)

另请参阅

了解有关表和模式反射的更多信息,请参阅反射数据库对象。

对于 ORM 相关的表反射变体,在使用反射表声明映射一节中包含了可用选项的概述。

下一步

现在我们有一个 SQLite 数据库准备好使用,其中有两个表存在,并且我们可以使用Connection和/或 ORM Session通过 Core 和 ORM 表导向的构造与这些表进行交互。在接下来的章节中,我们将说明如何使用这些结构创建、操作和选择数据。

使用 Table 对象设置 MetaData

当我们使用关系型数据库时,数据库中我们查询的基本数据持有结构被称为。在 SQLAlchemy 中,数据库中的“表”最终由一个名为Table的 Python 对象表示。

要开始使用 SQLAlchemy 表达式语言,我们将希望构建Table对象,这些对象代表我们有兴趣使用的所有数据库表。Table是以编程方式构建的,可以直接使用Table构造函数,也可以间接地使用 ORM 映射的类(稍后在使用 ORM 声明形式定义表元数据中描述)。还可以选择从现有数据库加载一些或所有表信息,称为反射。

无论采用哪种方法,我们始终从一个集合开始,这个集合将是我们放置表的地方,称为MetaData对象。这个对象本质上是一个 Python 字典的外观,它存储了一系列以它们的字符串名称为键的Table对象。虽然 ORM 提供了一些选项来获取此集合,但我们始终有直接制作一个的选择,看起来像这样:

代码语言:javascript
复制
>>> from sqlalchemy import MetaData
>>> metadata_obj = MetaData()

一旦我们有了MetaData对象,我们就可以声明一些Table对象。本教程将从经典的 SQLAlchemy 教程模型开始,其中有一个名为user_account的表,该表存储网站的用户,以及一个相关的address表,该表存储与user_account表中的行关联的电子邮件地址。当完全不使用 ORM 声明模型时,我们直接构建每个Table对象,通常将每个分配给将在应用程序代码中引用表的变量:

代码语言:javascript
复制
>>> 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 语句相似;从表名开始,然后列出每个列,每个列都有一个名称和一个数据类型。我们使用的对象包括:

Table - 表示数据库表并将自己分配给MetaData集合。

Column - 表示数据库表中的列,并将自身分配给Table对象。Column通常包括一个字符串名称和一个类型对象。关于父TableColumn对象的集合通常通过位于Table.c的关联数组进行访问:

代码语言:javascript
复制
>>> user_table.c.name
Column('name', String(length=30), table=<user_account>)

>>> user_table.c.keys()
['id', 'name', 'fullname']

IntegerString - 这些类表示 SQL 数据类型,可以带着或者不带着实例化传递给Column。在上面的例子中,我们想给“name”列指定长度为“30”,所以我们实例化了String(30)。但对于“id”和“fullname”,我们没有指定长度,所以我们可以直接发送类本身。

另请参阅

MetaDataTableColumn的参考文档和 API 文档在使用 MetaData 描述数据库。数据类型的参考文档在 SQL 数据类型对象。

在接下来的一节中,我们将说明Table的一个基本功能,即在特定的数据库连接上生成 DDL。但首先我们将声明第二个Table

声明简单的约束

示例中user_table的第一个Column包括Column.primary_key参数,这是一种指示此Column应该成为该表主键的简写技术。主键本身通常是隐式声明的,并由PrimaryKeyConstraint构造表示,我们可以在Table对象的Table.primary_key属性中看到:

代码语言:javascript
复制
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))

最常明确声明的约束是与数据库外键约束对应的ForeignKeyConstraint对象。当我们声明相互关联的表时,SQLAlchemy 使用这些外键约束声明的存在,不仅在将它们发射到数据库的 CREATE 语句中,还用于辅助构建 SQL 表达式。

只涉及目标表上的单个列的ForeignKeyConstraint通常使用列级别的简写表示法通过ForeignKey对象声明。下面我们声明一个将引用user表的外键约束的第二个表address

代码语言:javascript
复制
>>> 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数据类型。

在下一节中,我们将发射完成的 DDL 到useraddress表,以查看完成的结果。

发射 DDL 到数据库

我们构建了一个对象结构,表示数据库中的两个数据库表,从根MetaData对象开始,然后进入两个Table对象,每个对象都包含一组ColumnConstraint对象。这个对象结构将成为我们今后在 Core 和 ORM 中执行大多数操作的核心。

我们可以使用这个结构的第一项有用的事情是发出 CREATE TABLE 语句,或者 DDL 到我们的 SQLite 数据库中,以便我们可以向其中插入和查询数据。我们已经拥有完成这样做所需的所有工具,只需在我们的MetaData上调用MetaData.create_all()方法,发送给它引用目标数据库的Engine

代码语言:javascript
复制
>>> 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()方法,它将按相反顺序发出 DROP 语句,以便删除模式元素,就像发出 CREATE 语句一样。

Table 的组件

我们可以观察到,Python 中的Table构造与 SQL CREATE TABLE 语句有些相似;从表名开始,然后列出每个列,其中每个列都有一个名称和一个数据类型。我们上面使用的对象是:

Table - 表示数据库表,并将自己分配给MetaData集合。

Column - 表示数据库表中的列,并将自身分配给一个Table对象。Column通常包括一个字符串名称和一个类型对象。关于父TableColumn对象的集合通常通过位于Table.c的关联数组来访问:

代码语言:javascript
复制
>>> user_table.c.name
Column('name', String(length=30), table=<user_account>)

>>> user_table.c.keys()
['id', 'name', 'fullname']

IntegerString - 这些类表示 SQL 数据类型,并且可以被传递给一个Column,无论是否被实例化。在上面的例子中,我们想给“name”列设置长度为“30”,所以我们实例化了String(30)。但是对于“id”和“fullname”,我们没有指定长度,所以我们可以直接发送类本身。

另请参阅

关于MetaDataTableColumn的参考文档和 API 文档位于用 MetaData 描述数据库。数据类型的参考文档位于 SQL 数据类型对象。

在接下来的章节中,我们将说明Table的一个基本功能,即在特定数据库连接上生成 DDL。但首先,我们将声明第二个Table

声明简单约束

示例中的第一个Column包含了Column.primary_key参数,这是一种简写技术,表示这个Column应该是这个表的主键的一部分。主键本身通常是隐式声明的,并且由PrimaryKeyConstraint构造表示,我们可以在Table对象的Table.primary_key属性上看到:

代码语言:javascript
复制
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))

最常明确声明的约束是对应于数据库外键约束的ForeignKeyConstraint对象。当我们声明彼此相关的表时,SQLAlchemy 使用这些外键约束声明的存在不仅使它们在向数据库发送 CREATE 语句时被发射,而且还有助于构建 SQL 表达式。

只涉及目标表中的单个列的ForeignKeyConstraint通常使用列级别的简写符号通过ForeignKey对象声明。下面我们声明了一个第二个表address,它将有一个外键约束,指向user表:

代码语言:javascript
复制
>>> 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数据类型。

在下一节中,我们将发送完成的useraddress表的 DDL 以查看完成的结果。

发送 DDL 到数据库

我们已经构建了一个对象结构,表示数据库中的两个数据库表,从根MetaData对象开始,然后进入两个Table对象,每个对象都持有一组ColumnConstraint对象的集合。这个对象结构将成为我们使用 Core 和 ORM 执行的大多数操作的中心。

我们可以对这个结构进行的第一项有用的事情是发出 CREATE TABLE 语句,或者 DDL 到我们的 SQLite 数据库,这样我们就可以向其中插入和查询数据。我们已经拥有所有必要的工具来做到这一点,通过在我们的MetaData上调用MetaData.create_all()方法,将指向目标数据库的Engine传递给它:

代码语言:javascript
复制
>>> 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()方法,它将按相反顺序发出 DROP 语句,以删除模式元素,与发出 CREATE 语句的顺序相反。

使用 ORM 声明形式定义表元数据

当使用 ORM 时,声明Table元数据的过程通常与声明映射类的过程结合在一起。映射类是我们想要创建的任何 Python 类,然后该类上将具有与数据库表中的列相关联的属性。虽然有几种实现这一目标的方式,但最常见的风格被称为声明式,它允许我们一次声明我们的用户定义类和Table元数据。

建立声明式基础

在使用 ORM 时,MetaData 集合仍然存在,但它本身与一个仅在 ORM 中使用的构造关联,通常称为声明式基础。获得新的声明式基础的最快捷方式是创建一个新类,它是 SQLAlchemy DeclarativeBase 类的子类:

代码语言:javascript
复制
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
...     pass

在上文中,Base 类是我们所称的声明式基础。当我们创建的新类是 Base 的子类,并且结合适当的类级指令时,它们将在类创建时作为一个新的ORM 映射类建立,每个类通常(但不仅限于)引用一个特定的Table对象。

声明式基础指的是一个自动为我们创建的 MetaData 集合,假设我们没有从外部提供。这个 MetaData 集合可通过类级属性 DeclarativeBase.metadata 访问。当我们创建新的映射类时,它们每个都会引用这个 MetaData 集合中的一个 Table

代码语言:javascript
复制
>>> Base.metadata
MetaData()

声明式基础还指一个称为 registry 的集合,这是 SQLAlchemy ORM 中的中心“映射器配置”单元。虽然很少直接访问,但该对象是映射器配置过程的核心,因为一组 ORM 映射类将通过该注册表相互协调。就像 MetaData 一样,我们的声明式基础也为我们创建了一个 registry(再次有选项传递我们自己的 registry),我们可以通过类变量 DeclarativeBase.registry 访问:

代码语言:javascript
复制
>>> Base.registry
<sqlalchemy.orm.decl_api.registry object at 0x...>
```### 声明映射类

有了`Base`类,我们现在可以根据新类`User`和`Address`为`user_account`和`address`表定义 ORM 映射类。我们下面展示了声明性的最现代形式,它是由[**PEP 484**](https://peps.python.org/pep-0484/)类型注释驱动的,使用了一种特殊类型`Mapped`,指示要映射为特定类型的属性:

```py
>>> 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})"

上面的两个类,UserAddress,现在被称为ORM 映射类,并可用于 ORM 持久性和查询操作,稍后将描述。关于这些类的详细信息包括:

每个类都指向一个由声明性映射过程生成的Table对象,通过将字符串赋值给DeclarativeBase.__tablename__属性来命名。一旦类被创建,这个生成的Table可以通过DeclarativeBase.__table__属性访问。

如前所述,此形式被称为声明性表配置。几种替代的声明样式之一将直接构建Table对象,并将其直接分配给DeclarativeBase.__table__。这种风格被称为声明性与命令式表配置。

要指示Table中的列,我们使用mapped_column()结构,结合基于Mapped类型的类型注释。这个对象将生成应用于Table构造的Column对象。

对于具有简单数据类型且没有其他选项的列,我们可以单独指定Mapped类型注释,使用简单的 Python 类型如intstr来表示IntegerString。在 Declarative 映射过程中解释 Python 类型的方式非常开放;请参阅使用注释的 Declarative 表(对 mapped_column() 的类型注释形式)和自定义类型映射部分了解背景信息。

可以根据存在Optional[<typ>]类型注释(或其等效项<typ> | NoneUnion[<typ>, None])来声明列是否“可空”或“非空”。还可以显式使用mapped_column.nullable参数(不必与注释的可选性匹配)。

显式类型注释的使用完全是可选的。我们还可以在没有注释的情况下使用mapped_column()。在使用这种形式时,我们会根据每个mapped_column()构造中的需要使用更明确的类型对象,例如IntegerString,以及nullable=False

两个额外的属性,User.addressesAddress.user,定义了一种称为relationship()的不同类型属性,它具有与上述类似的注释感知配置样式。relationship()构造更详细地讨论在处理 ORM 相关对象中。

如果我们没有声明自己的__init__()方法,类会自动获得一个。默认情况下,这个方法接受所有属性名称作为可选关键字参数:

代码语言:javascript
复制
>>> sandy = User(name="sandy", fullname="Sandy Cheeks")

要自动生成一个支持位置参数以及具有默认关键字值的全功能__init__()方法,可以使用在声明性数据类映射中介绍的 dataclasses 功能。当然,始终可以选择使用显式的__init__()方法。

__repr__() 方法被添加以便我们获得可读的字符串输出;这些方法不要求必须在这里。与 __init__() 一样,可以通过使用 dataclasses 功能来自动生成 __repr__() 方法。

请参阅

ORM 映射样式 - 不同 ORM 配置样式的完整背景。

声明式映射 - 声明类映射的概述

带有 mapped_column() 的声明式表 - 如何使用 mapped_column()Mapped 来定义在使用声明式时映射到 Table 中的列的详细信息。

从 ORM 映射向数据库发出 DDL

由于我们的 ORM 映射类引用了包含在 MetaData 集合中的 Table 对象,因此,使用声明基类发出 DDL 与之前在 将 DDL 发送到数据库 中描述的过程相同。在我们的情况下,我们已经在我们的 SQLite 数据库中生成了 useraddress 表。如果我们还没有这样做,我们可以自由地利用与我们 ORM 声明基类关联的 MetaData 来执行此操作,方法是从 DeclarativeBase.metadata 属性访问集合,然后像之前一样使用 MetaData.create_all()。在这种情况下,将运行 PRAGMA 语句,但不会生成新表,因为已经发现它们已经存在:

代码语言:javascript
复制
>>> Base.metadata.create_all(engine)
BEGIN  (implicit)
PRAGMA  main.table_...info("user_account")
...
PRAGMA  main.table_...info("address")
...
COMMIT 
建立声明基类

当使用 ORM 时,MetaData 集合仍然存在,但它本身与一个仅与 ORM 关联的构造相关联,通常称为声明基类。获取新的声明基类的最方便的方法是创建一个新类,该类是 SQLAlchemy DeclarativeBase 类的子类:

代码语言:javascript
复制
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
...     pass

在上面,Base 类是我们将称之为声明性基类的内容。当我们创建新的类作为 Base 的子类时,结合适当的类级指令,它们将在类创建时分别被确立为新的ORM 映射类,每个类通常(但不是唯一地)引用一个特定的Table对象。

声明性基类引用了一个MetaData集合,如果我们没有从外部提供,将会自动创建。这个MetaData集合可通过DeclarativeBase.metadata类级属性访问。当我们创建新的映射类时,它们每个都将引用此MetaData集合内的一个Table

代码语言:javascript
复制
>>> Base.metadata
MetaData()

声明性基类还引用了一个称为registry的集合,它是 SQLAlchemy ORM 中的中心“映射器配置”单元。虽然很少直接访问,但该对象对映射器配置过程至关重要,因为一组 ORM 映射类将通过此注册表相互协调。与MetaData一样,我们的声明性基类也为我们创建了一个registry(再次有选项传递我们自己的registry),我们可以通过DeclarativeBase.registry类变量访问它:

代码语言:javascript
复制
>>> Base.registry
<sqlalchemy.orm.decl_api.registry object at 0x...>
声明映射类

有了 Base 类的确立,我们现在可以根据 UserAddress 的新类分别为 user_accountaddress 表定义 ORM 映射类。我们下面展示了声明性的最现代形式,它是从PEP 484类型注释驱动的,使用特殊类型Mapped,它指示要映射为特定类型的属性:

代码语言:javascript
复制
>>> 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})"

上面的两个类,UserAddress,现在被称为ORM 映射类,并可用于 ORM 持久性和查询操作,这将在后面进行描述。关于这些类的详细信息包括:

每个类都引用了作为声明性映射过程的一部分生成的Table对象,该对象通过将字符串分配给DeclarativeBase.__tablename__属性而命名。一旦类被创建,此生成的Table可从DeclarativeBase.__table__属性中获得。

如前所述,这种形式称为声明性表配置。几种备选声明样式之一是直接构建Table对象,并将其直接分配给DeclarativeBase.__table__。这种样式称为具有命令式表的声明性。

为了指示Table中的列,我们使用mapped_column()构造,结合基于Mapped类型的类型注释。这个对象将生成应用于Table构造的Column对象。

对于具有简单数据类型且没有其他选项的列,我们可以单独指示Mapped类型注释,使用简单的 Python 类型,如intstr,表示IntegerString。如何在声明性映射过程中解释 Python 类型的定制非常开放;请参阅使用带注释的声明性表(对 mapped_column()的类型注释形式)和自定义类型映射章节了解背景信息。

根据存在的Optional[<typ>]类型注释(或其等效形式<typ> | NoneUnion[<typ>, None]),可以将列声明为“可为空”或“非空”。还可以显式使用mapped_column.nullable参数(不一定要与注释的可选性匹配)。

显式类型注释的使用完全是可选的。我们也可以在不带注释的情况下使用 mapped_column()。在使用这种形式时,我们将根据每个mapped_column()构造中所需的更明确的类型对象,如IntegerString,以及 nullable=False

两个额外的属性,User.addressesAddress.user,定义了一种称为relationship()的不同类型的属性,该属性具有与示例相似的注释感知配置样式。更多关于 relationship() 构造的讨论请见使用 ORM 相关对象。

如果我们没有声明自己的 __init__() 方法,这些类将自动获得一个 __init__() 方法。该方法的默认形式接受所有属性名称作为可选关键字参数:

代码语言:javascript
复制
>>> sandy = User(name="sandy", fullname="Sandy Cheeks")

要自动生成一个提供位置参数以及带有默认关键字值的全功能 __init__() 方法,可以使用在声明性数据类映射中介绍的数据类功能。当然,始终可以选择使用显式的 __init__() 方法。

添加了 __repr__() 方法以获得可读的字符串输出;这些方法没有必须存在的要求。与 __init__() 类似,可以使用 dataclasses 功能自动生成 __repr__() 方法。

另见

ORM 映射样式 - 不同 ORM 配置样式的完整背景。

声明性映射 - 声明性类映射概述

使用 mapped_column() 的声明式表 - 关于如何使用mapped_column()Mapped来定义在声明式使用时要映射的Table中的列的详细信息。

从 ORM 映射向数据库发出 DDL

因为我们的 ORM 映射类引用了包含在MetaData集合中的Table对象,所以给定声明性基类发出 DDL 与先前描述的 Emitting DDL to the Database 相同。在我们的情况下,我们已经在我们的 SQLite 数据库中生成了useraddress表。如果我们还没有这样做,我们可以自由地利用与我们的 ORM 声明性基类关联的MetaData,通过访问从DeclarativeBase.metadata属性获取的集合,然后像以前一样使用MetaData.create_all()。在这种情况下,会运行 PRAGMA 语句,但是不会生成新的表,因为它们已经存在:

代码语言:javascript
复制
>>> 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集合,然后不是指示单独的ColumnConstraint对象,而是使用Table.autoload_with参数传递目标Engine

代码语言:javascript
复制
>>> 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一样使用:

代码语言:javascript
复制
>>> some_table
Table('some_table', MetaData(),
 Column('x', INTEGER(), table=<some_table>),
 Column('y', INTEGER(), table=<some_table>),
 schema=None)

另请参阅

阅读有关表和模式反射的更多信息,请访问反射数据库对象。

对于 ORM 相关的表反射变体,本节使用反射表声明性映射包括了可用选项的概述。

下一步

我们现在有一个准备好的 SQLite 数据库,其中包含两个表,以及我们可以使用它们与这些表进行交互的 Core 和 ORM 表导向结构,通过Connection和/或 ORM Session。在接下来的章节中,我们将说明如何使用这些结构来创建、操作和选择数据。

处理数据

原文:docs.sqlalchemy.org/en/20/tutorial/data.html

在处理事务和 DBAPI 中,我们学习了如何与 Python DBAPI 及其事务状态进行交互的基础知识。然后,在处理数据库元数据中,我们学习了如何使用MetaData和相关对象在 SQLAlchemy 中表示数据库表、列和约束。在本节中,我们将结合上述两个概念来创建、选择和操作关系数据库中的数据。我们与数据库的交互始终是在事务的范围内,即使我们已经设置我们的数据库驱动程序在后台使用自动提交。

本节的组成部分如下:

  • 使用 INSERT 语句 - 为了将一些数据插入数据库,我们介绍并演示了核心Insert构造。从 ORM 的角度来看,INSERT 在下一节使用 ORM 进行数据操作中进行了描述。
  • 使用 SELECT 语句 - 本节将详细描述Select构造,这是 SQLAlchemy 中最常用的对象。Select构造为 Core 和 ORM 中心应用程序发出 SELECT 语句,并且这两种用例将在此处进行描述。在稍后的在查询中使用关系部分以及 ORM 查询指南中还会提到其他 ORM 用例。
  • 使用 UPDATE 和 DELETE 语句 - 补充了数据的插入和选择,本节将从核心的角度描述UpdateDelete构造的使用。ORM 特定的 UPDATE 和 DELETE 同样在使用 ORM 进行数据操作部分中进行描述。

使用插入语句

原文:docs.sqlalchemy.org/en/20/tutorial/data_insert.html

在使用 Core 以及在使用 ORM 进行批量操作时,可以直接使用insert()函数生成 SQL INSERT 语句 - 此函数生成Insert的新实例,表示将新数据添加到表中的 INSERT 语句。

ORM 读者 -

本节详细介绍了在表中添加新行时生成单个 SQL INSERT 语句的核心方法。在使用 ORM 时,我们通常会使用另一个称为 unit of work 的工具,它会自动化一次性生成许多 INSERT 语句。但是,即使 ORM 为我们运行它,了解核心如何处理数据创建和操作也非常有用。此外,ORM 还支持使用称为批量/多行插入、更新和删除的功能直接使用 INSERT。

要直接跳转到使用 ORM 使用正常工作单元模式插入行的方法,请参阅使用 ORM 工作单元模式插入行。

插入(insert())SQL 表达式构造

一种简单的Insert示例,同时说明了目标表和 VALUES 子句:

代码语言:javascript
复制
>>> from sqlalchemy import insert
>>> stmt = insert(user_table).values(name="spongebob", fullname="Spongebob Squarepants")

上述stmt变量是Insert的一个实例。大多数 SQL 表达式都可以直接转换为字符串形式,以便查看生成的通用形式:

代码语言:javascript
复制
>>> print(stmt)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (:name,  :fullname) 

字符串形式是通过生成对象的Compiled形式来创建的,该对象包括语句的数据库特定字符串 SQL 表示;我们可以直接使用ClauseElement.compile()方法获取此对象:

代码语言:javascript
复制
>>> compiled = stmt.compile()

我们的Insert构造是“参数化”构造的一个例子,前面在发送参数已经有过示例;要查看namefullname绑定参数,这些也可以从Compiled构造中获取:

代码语言:javascript
复制
>>> compiled.params
{'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}

执行语句

调用该语句,我们可以将一行插入到user_table中。 可以在 SQL 日志中看到 INSERT SQL 和捆绑参数:

代码语言:javascript
复制
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)
[...]  ('spongebob',  'Spongebob Squarepants')
COMMIT 

在上面的简单形式中,INSERT 语句不会返回任何行,如果只插入了一行,则通常会包括返回有关插入该行期间生成的列级默认值的信息的能力,最常见的是整数主键值。 在上述情况下,SQLite 数据库中的第一行通常会为第一个整数主键值返回 1,我们可以使用CursorResult.inserted_primary_key 访问器获取它:

代码语言:javascript
复制
>>> result.inserted_primary_key
(1,)

提示

CursorResult.inserted_primary_key 返回一个元组,因为主键可能包含多列。 这称为复合主键。 CursorResult.inserted_primary_key 旨在始终包含刚刚插入的记录的完整主键,而不仅仅是“cursor.lastrowid”类型的值,并且旨在无论是否使用了“autoincrement”,都将其填充,因此为了表示完整的主键,它是一个元组。

从版本 1.4.8 中更改:由 CursorResult.inserted_primary_key 返回的元组现在是通过将其作为Row 对象来实现的命名元组。

INSERT 通常会自动生成“values”子句

上面的示例使用了 Insert.values() 方法来显式创建 SQL INSERT 语句的 VALUES 子句。 如果我们实际上不使用 Insert.values() 而只打印出一个“空”语句,我们会得到一个插入表中每一列的 INSERT:

代码语言:javascript
复制
>>> print(insert(user_table))
INSERT  INTO  user_account  (id,  name,  fullname)  VALUES  (:id,  :name,  :fullname) 

如果我们拿一个尚未调用Insert.values()Insert 构造,并执行它而不是打印它,语句将根据我们传递给Connection.execute() 方法的参数编译为一个字符串,而且只包含与传递的参数相关的列。实际上,这是通常使用Insert 插入行的方式,而无需输入显式的 VALUES 子句。下面的示例说明了执行具有一次性参数列表的两列 INSERT 语句:

代码语言:javascript
复制
>>> with engine.connect() as conn:
...     result = conn.execute(
...         insert(user_table),
...         [
...             {"name": "sandy", "fullname": "Sandy Cheeks"},
...             {"name": "patrick", "fullname": "Patrick Star"},
...         ],
...     )
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)
[...]  [('sandy',  'Sandy Cheeks'),  ('patrick',  'Patrick Star')]
COMMIT 

上述执行首次展示了发送多个参数中介绍的“executemany”形式,但与使用text() 构造时不同,我们不必拼写任何 SQL。通过将字典或字典列表传递给Connection.execute() 方法与 Insert 构造一起使用,Connection 确保传递的列名将自动在 Insert 构造的 VALUES 子句中表达。

深度炼金

嗨,欢迎来到深度炼金的第一版。左边的人被称为炼金师,你会注意到他们并不是巫师,因为尖尖的帽子并没有竖起来。炼金师会描述通常更加高级和/或棘手的事物,而且通常不是必需的,但出于某种原因,他们觉得你应该了解 SQLAlchemy 能做的这件事情。

在这个版本中,为了在 address_table 中拥有一些有趣的数据,下面是一个更高级的示例,说明了如何在明确使用 Insert.values() 方法的同时,包含从参数生成的额外 VALUES。一个 标量子查询 被构建,利用了下一节中介绍的 select() 结构,子查询中使用的参数使用明确的绑定参数名设置,使用了 bindparam() 结构。

这是一些稍微深入的炼金术,这样我们就可以在不将主键标识符从 user_table 操作中提取到应用程序中的情况下添加相关行。大多数炼金术师会简单地使用 ORM 来处理这类事情。

代码语言:javascript
复制
>>> from sqlalchemy import select, bindparam
>>> scalar_subq = (
...     select(user_table.c.id)
...     .where(user_table.c.name == bindparam("username"))
...     .scalar_subquery()
... )

>>> with engine.connect() as conn:
...     result = conn.execute(
...         insert(address_table).values(user_id=scalar_subq),
...         [
...             {
...                 "username": "spongebob",
...                 "email_address": "spongebob@sqlalchemy.org",
...             },
...             {"username": "sandy", "email_address": "sandy@sqlalchemy.org"},
...             {"username": "sandy", "email_address": "sandy@squirrelpower.org"},
...         ],
...     )
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  address  (user_id,  email_address)  VALUES  ((SELECT  user_account.id
FROM  user_account
WHERE  user_account.name  =  ?),  ?)
[...]  [('spongebob',  'spongebob@sqlalchemy.org'),  ('sandy',  'sandy@sqlalchemy.org'),
('sandy',  'sandy@squirrelpower.org')]
COMMIT 

有了这个,我们的表中有一些更有趣的数据,我们将在接下来的章节中加以利用。

提示

如果我们在 Insert.values() 中不带参数地指定,将生成一个真正的“空”INSERT,它仅插入表的“默认值”,而不包括任何明确的值;并非每个数据库后端都支持这个功能,但下面是 SQLite 生成的内容:

代码语言:javascript
复制
>>> print(insert(user_table).values().compile(engine))
INSERT  INTO  user_account  DEFAULT  VALUES 
```## INSERT…RETURNING

对于支持的后端,RETURNING 子句会自动被用来检索最后插入的主键值以及服务器默认值。但是 RETURNING 子句也可以使用 `Insert.returning()` 方法来明确指定;在这种情况下,执行语句时返回的 `Result` 对象具有可提取的行:

```py
>>> 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() 结合使用,就像下面的例子一样,它建立在 INSERT…FROM SELECT 中所述的例子之上:

代码语言:javascript
复制
>>> 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 

提示

RETURNING 特性也被 UPDATE 和 DELETE 语句所支持,这将在本教程的后续部分介绍。

对于 INSERT 语句,RETURNING 功能可用于单行语句以及一次插入多行的语句。对于支持 RETURNING 的 SQLAlchemy 中包含的所有方言,多行 INSERT 支持是特定于方言的。请参阅“INSERT 语句的插入多个值”行为部分了解此功能的背景。

另请参阅

ORM 也支持带有或不带有 RETURNING 的批量 INSERT。请参阅 ORM 批量 INSERT 语句以获取参考文档。## INSERT…FROM SELECT

Insert的一个较少使用的特性,但为了完整性,在这里,Insert构造可以使用Insert.from_select()方法直接从 SELECT 中获取行进行插入。此方法接受一个select()构造,下一节将讨论此构造,以及要在实际 INSERT 中定位的列名列表。在下面的示例中,从user_account表中派生的行被添加到address表中,为每个用户提供aol.com的免费电子邮件地址:

代码语言:javascript
复制
>>> 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)
INSERT  INTO  address  (user_id,  email_address)
SELECT  user_account.id,  user_account.name  ||  :name_1  AS  anon_1
FROM  user_account 

当希望直接将数据从数据库的其他部分复制到新的行集时使用此构造,而无需实际从客户端获取和重新发送数据。

另请参阅

Insert - 在 SQL 表达式 API 文档中

insert() SQL 表达式构造

一个简单的Insert示例,同时说明目标表和 VALUES 子句:

代码语言:javascript
复制
>>> from sqlalchemy import insert
>>> stmt = insert(user_table).values(name="spongebob", fullname="Spongebob Squarepants")

上述stmt变量是Insert的一个实例。大多数 SQL 表达式可以直接转换为字符串形式,以查看正在生成的一般形式:

代码语言:javascript
复制
>>> print(stmt)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (:name,  :fullname) 

字符串形式是通过生成对象的Compiled形式创建的,其中包括语句的特定于数据库的字符串 SQL 表示;我们可以直接使用ClauseElement.compile()方法获取此对象:

代码语言:javascript
复制
>>> compiled = stmt.compile()

我们的Insert构造是“参数化”构造的一个示例,在之前的发送参数中已经说明过;要查看namefullname 绑定参数,这些都可以从Compiled构造中获取:

代码语言:javascript
复制
>>> compiled.params
{'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}

执行该语句

调用该语句,我们可以将一行插入到user_table中。可以在 SQL 日志中看到 INSERT SQL 以及捆绑的参数:

代码语言:javascript
复制
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)
[...]  ('spongebob',  'Spongebob Squarepants')
COMMIT 

在上面的简单形式中,INSERT 语句不会返回任何行,如果只插入了一行,则通常会包含返回有关在插入该行期间生成的列级默认值信息的功能,最常见的是整数主键值。在上述情况下,SQLite 数据库中的第一行通常将为第一个整数主键值返回1,我们可以使用CursorResult.inserted_primary_key访问器来获取:

代码语言:javascript
复制
>>> result.inserted_primary_key
(1,)

提示

CursorResult.inserted_primary_key返回一个元组,因为主键可能包含多个列。这称为复合主键。CursorResult.inserted_primary_key旨在始终包含刚刚插入的记录的完整主键,而不仅仅是“cursor.lastrowid”类型的值,并且旨在无论是否使用“autoincrement”,都会填充,因此为了表达完整的主键,它是一个元组。

从版本 1.4.8 中更改:CursorResult.inserted_primary_key返回的元组现在是通过将其返回为Row对象来履行的命名元组。

INSERT 通常会自动生成“values”子句

上面的示例使用了Insert.values()方法来显式创建 SQL INSERT 语句的 VALUES 子句。如果我们实际上不使用Insert.values(),只打印出一个“空”的语句,我们会得到一个对表中每一列进行插入的 INSERT:

代码语言:javascript
复制
>>> print(insert(user_table))
INSERT  INTO  user_account  (id,  name,  fullname)  VALUES  (:id,  :name,  :fullname) 

如果我们对一个尚未调用Insert.values()Insert构造进行执行而不是打印它,该语句将根据我们传递给Connection.execute()方法的参数编译为一个字符串,并且仅包括与传递的参数相关的列。这实际上是使用Insert插入行的常用方式,而无需编写明确的 VALUES 子句。下面的示例说明了如何一次执行具有参数列表的两列 INSERT 语句:

代码语言:javascript
复制
>>> with engine.connect() as conn:
...     result = conn.execute(
...         insert(user_table),
...         [
...             {"name": "sandy", "fullname": "Sandy Cheeks"},
...             {"name": "patrick", "fullname": "Patrick Star"},
...         ],
...     )
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)
[...]  [('sandy',  'Sandy Cheeks'),  ('patrick',  'Patrick Star')]
COMMIT 

上述执行首先展示了“executemany”形式,如发送多个参数中所示,但与使用text()构造时不同,我们不必拼写任何 SQL。通过将字典或字典列表传递给Connection.execute()方法,与Insert构造一起使用,Connection确保传递的列名将自动在Insert构造的 VALUES 子句中表示。

深度魔法

嗨,欢迎来到第一版的深度魔法。左边的人被称为炼金术士,你会注意到他们不是巫师,因为尖顶帽没有竖起来。炼金术士会来描述一些通常更高级和/或棘手的事情,此外通常不需要,但出于某种原因他们觉得你应该知道 SQLAlchemy 能做这件事。

在这个版本中,为了使address_table中有一些有趣的数据,下面是一个更高级的示例,演示了如何在同时包含来自参数的附加 VALUES 的情况下,可以显式使用Insert.values()方法。构造了一个标量子查询,利用了下一节中介绍的select()构造,并且在子查询中使用的参数使用了显式绑定参数名称,使用bindparam()构造建立。

这是一些稍微深入的炼金术,这样我们就可以在不从user_table操作中获取主键标识符的情况下添加相关行到应用程序中。大多数炼金术师将简单地使用 ORM 来处理这类事情。

代码语言:javascript
复制
>>> from sqlalchemy import select, bindparam
>>> scalar_subq = (
...     select(user_table.c.id)
...     .where(user_table.c.name == bindparam("username"))
...     .scalar_subquery()
... )

>>> with engine.connect() as conn:
...     result = conn.execute(
...         insert(address_table).values(user_id=scalar_subq),
...         [
...             {
...                 "username": "spongebob",
...                 "email_address": "spongebob@sqlalchemy.org",
...             },
...             {"username": "sandy", "email_address": "sandy@sqlalchemy.org"},
...             {"username": "sandy", "email_address": "sandy@squirrelpower.org"},
...         ],
...     )
...     conn.commit()
BEGIN  (implicit)
INSERT  INTO  address  (user_id,  email_address)  VALUES  ((SELECT  user_account.id
FROM  user_account
WHERE  user_account.name  =  ?),  ?)
[...]  [('spongebob',  'spongebob@sqlalchemy.org'),  ('sandy',  'sandy@sqlalchemy.org'),
('sandy',  'sandy@squirrelpower.org')]
COMMIT 

有了这个,我们的表中有了一些更有趣的数据,我们将在接下来的章节中使用它们。

提示

如果我们指示不带任何参数的Insert.values(),则生成一个真正的“空”INSERT,仅为表中的“默认值”插入,但并不包括任何显式值;并非所有的数据库后端都支持此功能,但是这是 SQLite 生成的内容:

代码语言:javascript
复制
>>> print(insert(user_table).values().compile(engine))
INSERT  INTO  user_account  DEFAULT  VALUES 

INSERT…RETURNING

支持的后端自动使用 RETURNING 子句以检索最后插入的主键值以及服务器默认值的值。但是,也可以使用Insert.returning()方法显式指定 RETURNING 子句;在这种情况下,执行该语句时返回的Result对象具有可以获取的行:

代码语言:javascript
复制
>>> 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()结合使用,就像下面的示例一样,该示例建立在 INSERT…FROM SELECT 中所述示例的基础上:

代码语言:javascript
复制
>>> 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 

提示

RETURNING 特性也被 UPDATE 和 DELETE 语句支持,这将在本教程的后续部分中介绍。

对于 INSERT 语句,RETURNING 功能可用于单行语句以及一次插入多行的语句。对于具有 RETURNING 功能的多行 INSERT 的支持是方言特定的,但是对于 SQLAlchemy 中支持 RETURNING 的所有方言都是支持的。有关此功能的背景,请参阅 “Insert Many Values” Behavior for INSERT statements 部分。

请参阅

ORM 也支持带有或不带有 RETURNING 的批量插入。请参阅 ORM 批量插入语句 进行参考文档。

INSERT…FROM SELECT

Insert 的一个不太常用的特性,但出于完整性考虑,在这里,Insert 结构可以使用 Insert.from_select() 方法组合一个直接从 SELECT 中获取行的 INSERT。该方法接受一个 select() 结构,下一节将讨论它,以及一个要在实际 INSERT 中定位的列名列表。在下面的示例中,从 user_account 表中的行派生出添加到 address 表中的行,为每个用户提供 aol.com 的免费电子邮件地址:

代码语言:javascript
复制
>>> 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)
INSERT  INTO  address  (user_id,  email_address)
SELECT  user_account.id,  user_account.name  ||  :name_1  AS  anon_1
FROM  user_account 

当一个人想要直接将数据从数据库的某个其他部分复制到一组新的行中时,可以使用这个结构,而不需要从客户端获取和重新发送数据。

请参阅

插入 - SQL Expression API 文档中的 INSERT 不带任何参数的Insert.values(),则生成一个真正的“空”INSERT,仅为表中的“默认值”插入,但并不包括任何显式值;并非所有的数据库后端都支持此功能,但是这是 SQLite 生成的内容:

代码语言:javascript
复制
>>> print(insert(user_table).values().compile(engine))
INSERT  INTO  user_account  DEFAULT  VALUES 

INSERT…RETURNING

支持的后端自动使用 RETURNING 子句以检索最后插入的主键值以及服务器默认值的值。但是,也可以使用Insert.returning()方法显式指定 RETURNING 子句;在这种情况下,执行该语句时返回的Result对象具有可以获取的行:

代码语言:javascript
复制
>>> 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()结合使用,就像下面的示例一样,该示例建立在 INSERT…FROM SELECT 中所述示例的基础上:

代码语言:javascript
复制
>>> 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 

提示

RETURNING 特性也被 UPDATE 和 DELETE 语句支持,这将在本教程的后续部分中介绍。

对于 INSERT 语句,RETURNING 功能可用于单行语句以及一次插入多行的语句。对于具有 RETURNING 功能的多行 INSERT 的支持是方言特定的,但是对于 SQLAlchemy 中支持 RETURNING 的所有方言都是支持的。有关此功能的背景,请参阅 “Insert Many Values” Behavior for INSERT statements 部分。

请参阅

ORM 也支持带有或不带有 RETURNING 的批量插入。请参阅 ORM 批量插入语句 进行参考文档。

INSERT…FROM SELECT

Insert 的一个不太常用的特性,但出于完整性考虑,在这里,Insert 结构可以使用 Insert.from_select() 方法组合一个直接从 SELECT 中获取行的 INSERT。该方法接受一个 select() 结构,下一节将讨论它,以及一个要在实际 INSERT 中定位的列名列表。在下面的示例中,从 user_account 表中的行派生出添加到 address 表中的行,为每个用户提供 aol.com 的免费电子邮件地址:

代码语言:javascript
复制
>>> 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)
INSERT  INTO  address  (user_id,  email_address)
SELECT  user_account.id,  user_account.name  ||  :name_1  AS  anon_1
FROM  user_account 

当一个人想要直接将数据从数据库的某个其他部分复制到一组新的行中时,可以使用这个结构,而不需要从客户端获取和重新发送数据。

请参阅

插入 - SQL Expression API 文档中的 INSERT

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
    • 文档概览
      • 代码示例
        • 安装指南
          • 支持的平台
          • 异步 IO 支持
          • 支持的安装方法
          • 通过 pip 安装
          • 手动从源分发安装
          • 构建 Cython 扩展
          • 安装数据库 API
          • 检查已安装的 SQLAlchemy 版本
          • 下一步
        • 代码示例
          • 安装指南
            • 支持的平台
            • 异步 IO 支持
            • 支持的安装方法
            • 通过 pip 安装
            • 手动从源代码分发包安装
            • 构建 Cython 扩展
            • 安装数据库 API
            • 检查已安装的 SQLAlchemy 版本
            • 下一步
            • 支持的平台
            • AsyncIO 支持
            • 支持的安装方法
            • 通过 pip 安装
            • 从源分发手动安装
            • 构建 Cython 扩展
            • 安装数据库 API
            • 检查已安装的 SQLAlchemy 版本
            • 下一步
          • 1.x 到 2.0 迁移
          • SQLAlchemy Unified Tutorial
            • 教程概述
              • 版本检查
            • 教程概述
              • 版本检查
              • 版本检查
          • 建立连接 - Engine
          • 处理事务和 DBAPI
            • 获取连接
              • 获取行
            • 提交更改
              • 语句执行基础
                • 获取行
                • 发送参数
                • 发送多个参数
              • 使用 ORM 会话执行
              • 处理数据库元数据
                • 使用表对象设置元数据
                  • Table 的组件
                  • 声明简单约束
                  • 发出 DDL 到数据库
                  • 建立声明性基础
                  • 从 ORM 映射向数据库发出 DDL
                • 下一步
                  • 使用 Table 对象设置 MetaData
                    • Table的组成部分
                    • 声明简单的约束
                    • 发射 DDL 到数据库
                    • Table 的组件
                    • 声明简单约束
                    • 发送 DDL 到数据库
                  • 使用 ORM 声明形式定义表元数据
                    • 建立声明式基础
                    • 从 ORM 映射向数据库发出 DDL
                    • 建立声明基类
                    • 声明映射类
                    • 从 ORM 映射向数据库发出 DDL
                  • 表反射
                    • 下一步
                    • 处理数据
                    • 使用插入语句
                      • 插入(insert())SQL 表达式构造
                        • 执行语句
                          • INSERT 通常会自动生成“values”子句
                            • insert() SQL 表达式构造
                              • 执行该语句
                                • INSERT 通常会自动生成“values”子句
                                  • INSERT…RETURNING
                                    • INSERT…FROM SELECT
                                      • INSERT…RETURNING
                                        • INSERT…FROM SELECT
                                        相关产品与服务
                                        数据库
                                        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                                        领券
                                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档