SqlAlchemy-2-0-中文文档-一-

SqlAlchemy 2.0 中文文档(一)

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

概述

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

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

_images/sqla_arch_small.png

上面,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 下载分发包,并在一步中进行安装:

pip install SQLAlchemy

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

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

pip install --pre SQLAlchemy

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

手动从源分发安装

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

python setup.py install

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

构建 Cython 扩展

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

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

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

# 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

# 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 环境变量。 这样做的用例要么是为了特殊的测试情况,要么是在通常的“重新构建”机制无法解决的兼容性/构建问题的情况下:

export DISABLE_SQLALCHEMY_CEXT=1; python setup.py install

安装数据库 API

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

检查已安装的 SQLAlchemy 版本

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

>>> 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 下载分发包,并在一步中安装:

pip install SQLAlchemy

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

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

pip install --pre SQLAlchemy

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

手动从源代码分发包安装

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

python setup.py install

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

构建 Cython 扩展

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

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

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

# 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

# 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环境变量。 这种情况的用例要么是为了特殊的测试环境,要么是在通常的“重建”机制无法解决的兼容性/构建问题的情况下:

export DISABLE_SQLALCHEMY_CEXT=1; python setup.py install

安装数据库 API

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

检查已安装的 SQLAlchemy 版本

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

>>> 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 下载分发并一步安装:

pip install SQLAlchemy

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

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

pip install --pre SQLAlchemy

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

从源分发手动安装

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

python setup.py install

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

构建 Cython 扩展

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

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

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

# 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

# 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 环境变量。这样做的用例是特殊测试情况,或者在通常的“重新构建”机制无法克服的兼容性/构建问题的罕见情况下:

export DISABLE_SQLALCHEMY_CEXT=1; python setup.py install

安装数据库 API

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

检查已安装的 SQLAlchemy 版本

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

>>> 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 上:

>>> 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:

>>> import sqlalchemy
>>> sqlalchemy.__version__  
2.0.0

版本检查

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

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

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

>>> 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()的构造发出,稍后将更详细地讨论:

>>> 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对象的块内调用进行事务提交:

# "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,假设块成功,或者在出现异常时回滚。这种风格被称为一次性开始

# "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 语句:

>>> 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 风格的方式,即按位置分配变量,就像它们被接收到的那样:

    result = conn.execute(text("select x, y from some_table"))
    
    for x, y in result:
        ...
    
  • 整数索引 - 元组是 Python 序列,因此也可以使用常规整数访问:

    result = conn.execute(text("select x, y from some_table"))
    
    for row in result:
        x = row[0]
    
  • 属性名称 - 由于这些是 Python 命名元组,元组具有与每个列的名称匹配的动态属性名称。这些名称通常是 SQL 语句分配给每行中的列的名称。虽然它们通常是相当可预测的,并且也可以由标签控制,在 less 定义的情况下,它们可能受到特定于数据库的行为的影响:

    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对象的结果对象:

    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()

>>> 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:

>>> 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的使用方式完全一致,即在上下文管理器中构造它:

>>> 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 语句来修改部分数据:

>>> 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()的构造发出的,稍后将对其进行更详细的讨论。

>>> 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对象的块内调用:

# "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的范围,并在成功块的情况下封闭事务内的所有内容,或者在出现异常时回滚。这种风格称为一次性开始

# "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 语句:

>>> 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 特有的风格,即按位置分配变量,就像它们被接收到的那样:

    result = conn.execute(text("select x, y from some_table"))
    
    for x, y in result:
        ...
    
  • 整数索引 - 元组是 Python 序列,因此也可以进行常规整数访问:

    result = conn.execute(text("select x, y from some_table"))
    
    for row in result:
        x = row[0]
    
  • 属性名称 - 由于这些是 Python 的命名元组,元组具有与每列名称匹配的动态属性名称。这些名称通常是 SQL 语句为每行的列分配的名称。虽然它们通常是相当可预测的,也可以通过标签进行控制,在定义较少的情况下,它们可能受到特定于数据库的行为的影响:

    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 对象的结果对象:

    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()

>>> 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:

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

>>> 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 习惯的样式,即按位置将变量分配给每行接收到的值:

    result = conn.execute(text("select x, y from some_table"))
    
    for x, y in result:
        ...
    
  • 整数索引 - 元组是 Python 序列,因此也可以使用常规整数访问:

    result = conn.execute(text("select x, y from some_table"))
    
    for row in result:
        x = row[0]
    
  • 属性名称 - 由于这些是 Python 命名元组,元组具有与每列名称匹配的动态属性名称。这些名称通常是 SQL 语句为每行分配的列名称。虽然它们通常相当可预测,并且也可以由标签控制,在定义不太明确的情况下,它们可能受到数据库特定的行为的影响:

    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对象的结果对象:

    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()

>>> 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:

>>> 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的方式完全一致,即在上下文管理器中构造它:

>>> 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 语句来修改一些数据进行说明:

>>> 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 在获取这个集合的位置上提供了一些选项,但我们始终可以选择直接创建一个,看起来像这样:

>>> from sqlalchemy import MetaData
>>> metadata_obj = MetaData()

一旦我们有了 MetaData 对象,我们可以声明一些 Table 对象。本教程将从经典的 SQLAlchemy 教程模型开始,其中有一个名为 user_account 的表,存储着网站的用户,以及一个相关的 address 表,存储着与 user_account 表中的行相关联的电子邮件地址。当完全不使用 ORM Declarative 模型时,我们直接构造每个 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 语句相似;从表名开始,然后列出每个列,其中每个列都有一个名称和一个数据类型。我们上面使用的对象是:

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

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

    >>> 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属性上看到它:

>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))

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

一个涉及目标表上仅一个列的ForeignKeyConstraint通常使用列级别的速记符号通过ForeignKey对象声明。下面我们声明了一个将具有引用user表的外键约束的第二个表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参数进行指示。

提示

Column定义中使用ForeignKey对象时,我们可以省略该Column的数据类型;它会自动从相关列的数据类型中推断出来,在上面的示例中是user_account.id列的Integer数据类型。

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

发出 DDL 到数据库

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

我们可以对此结构进行的第一个有用的操作是发出 CREATE TABLE 语句,或者 DDL 到我们的 SQLite 数据库,以便我们可以从中插入和查询数据。我们已经拥有完成此操作所需的所有工具,通过在我们的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 进程还负责按正确顺序发出 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 对象。

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

>>> Base.metadata
MetaData()

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

>>> 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__()方法。该方法的默认形式接受所有属性名称作为可选关键字参数:

    >>> 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 语句,但不会生成新表,因为已经发现它们已经存在:

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

>>> 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 提供了一些选项来获取此集合,但我们始终有直接制作一个的选择,看起来像这样:

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

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

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

    >>> 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属性中看到:

>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))

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

只涉及目标表上的单个列的ForeignKeyConstraint通常使用列级别的简写表示法通过ForeignKey对象声明。下面我们声明一个将引用user表的外键约束的第二个表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参数指示。

提示

Column定义中使用ForeignKey对象时,我们可以省略该Column的数据类型;它将自动从相关列的数据类型推断出来,在上面的示例中为user_account.id列的Integer数据类型。

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

发射 DDL 到数据库

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

我们可以使用这个结构的第一项有用的事情是发出 CREATE TABLE 语句,或者 DDL 到我们的 SQLite 数据库中,以便我们可以向其中插入和查询数据。我们已经拥有完成这样做所需的所有工具,只需在我们的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()方法,它将按相反顺序发出 DROP 语句,以便删除模式元素,就像发出 CREATE 语句一样。

Table 的组件

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

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

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

    >>> 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属性上看到:

>>> 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数据类型。

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

发送 DDL 到数据库

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

我们可以对这个结构进行的第一项有用的事情是发出 CREATE TABLE 语句,或者 DDL 到我们的 SQLite 数据库,这样我们就可以向其中插入和查询数据。我们已经拥有所有必要的工具来做到这一点,通过在我们的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()方法,它将按相反顺序发出 DROP 语句,以删除模式元素,与发出 CREATE 语句的顺序相反。

使用 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**](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__()方法,类会自动获得一个。默认情况下,这个方法接受所有属性名称作为可选关键字参数:

    >>> 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 语句,但不会生成新表,因为已经发现它们已经存在:

>>> Base.metadata.create_all(engine)
BEGIN  (implicit)
PRAGMA  main.table_...info("user_account")
...
PRAGMA  main.table_...info("address")
...
COMMIT 

建立声明基类

当使用 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 类的确立,我们现在可以根据 UserAddress 的新类分别为 user_accountaddress 表定义 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})"

上面的两个类,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__() 方法。该方法的默认形式接受所有属性名称作为可选关键字参数:

    >>> 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 语句,但是不会生成新的表,因为它们已经存在:

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

>>> 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)

另请参阅

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

对于 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 子句:

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

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

>>> print(stmt)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (:name,  :fullname) 

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

>>> compiled = stmt.compile()

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

>>> compiled.params
{'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}

执行语句

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

>>> 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 访问器获取它:

>>> 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:

>>> print(insert(user_table))
INSERT  INTO  user_account  (id,  name,  fullname)  VALUES  (:id,  :name,  :fullname) 

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

>>> 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 来处理这类事情。

>>> 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 生成的内容:

>>> 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 中所述的例子之上:

>>> 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的免费电子邮件地址:

>>> 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 子句:

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

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

>>> print(stmt)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (:name,  :fullname) 

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

>>> compiled = stmt.compile()

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

>>> compiled.params
{'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}

执行该语句

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

>>> 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访问器来获取:

>>> 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:

>>> print(insert(user_table))
INSERT  INTO  user_account  (id,  name,  fullname)  VALUES  (:id,  :name,  :fullname) 

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

>>> 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 来处理这类事情。

>>> 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 生成的内容:

>>> print(insert(user_table).values().compile(engine))
INSERT  INTO  user_account  DEFAULT  VALUES 

INSERT…RETURNING

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

>>> 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 中所述示例的基础上:

>>> 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 的免费电子邮件地址:

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

使用 SELECT 语句

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

对于 Core 和 ORM,select() 函数生成一个用于所有 SELECT 查询的 Select 构造。传递给 Core 中的 Connection.execute() 方法和 ORM 中的 Session.execute() 方法,在当前事务中发出 SELECT 语句并通过返回的 Result 对象获取结果行。

ORM 读者 - 这里的内容同样适用于 Core 和 ORM 使用,并提到了基本 ORM 变体用例。然而,还有更多的 ORM 特定功能可用;这些在 ORM 查询指南中有文档记录。

select() SQL 表达式构造

select() 构造以与 insert() 相同的方式构建语句,使用 生成式 方法,其中每个方法都会将更多的状态添加到对象上。与其他 SQL 构造一样,它可以在原地字符串化:

>>> from sqlalchemy import select
>>> stmt = select(user_table).where(user_table.c.name == "spongebob")
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  :name_1 

与所有其他语句级别的 SQL 构造相同,要实际运行语句,我们将其传递给执行方法。由于 SELECT 语句返回行,我们始终可以迭代结果对象以获取 Row 对象返回:

>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(row)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[...]  ('spongebob',)
(1, 'spongebob', 'Spongebob Squarepants')
ROLLBACK 

当使用 ORM 时,特别是对 ORM 实体组成的 select() 结构执行时,我们将希望使用 Session.execute() 方法在 Session 上执行它;通过这种方法,我们继续从结果中获取 Row 对象,但是这些行现在可以包括完整的实体,例如 User 类的实例,作为每行中的单独元素:

>>> stmt = select(User).where(User.name == "spongebob")
>>> with Session(engine) as session:
...     for row in session.execute(stmt):
...         print(row)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[...]  ('spongebob',)
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
ROLLBACK 

以下各节将更详细地讨论 SELECT 构造。

设置 COLUMNS 和 FROM 子句

select() 函数接受表示任意数量Column和/或Table表达式的位置元素,以及一系列兼容对象,这些对象将解析为要从中选择的 SQL 表达式列表,这些表达式将作为结果集中的列返回。这些元素在更简单的情况下还用于创建 FROM 子句,该子句是从传递的列和类似表达式中推断出来的:

>>> print(select(user_table))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account 

使用核心方法从单独列进行 SELECT 操作时,可以直接访问Table.c访问器中的Column对象;FROM 子句将被推断为由这些列所代表的所有Table和其他FromClause对象的集合:

>>> print(select(user_table.c.name, user_table.c.fullname))
SELECT  user_account.name,  user_account.fullname
FROM  user_account 

或者,当使用任何FromClauseFromClause.c集合,例如Table时,可以通过使用字符串名称的元组指定多个列进行select()操作:

>>> print(select(user_table.c["name", "fullname"]))
SELECT  user_account.name,  user_account.fullname
FROM  user_account 

2.0 版本中新增:为FromClause.c集合添加了元组访问器功能

选择 ORM 实体和列

ORM 实体,例如我们的User类以及其上的列映射属性,例如User.name,也参与 SQL 表达语言系统,表示表和列。下面举例说明了从User实体中进行 SELECT 操作的示例,最终呈现的方式与直接使用user_table相同:

>>> print(select(User))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account 

当使用 ORM Session.execute()方法执行类似上述语句时,当我们从完整实体(例如User)选择时,与user_table相反,有一个重要的区别,即实体本身作为每行的单个元素返回。也就是说,当我们从上述语句中获取行时,因为在要获取的内容列表中只有User实体,所以我们会收到仅包含一个元素的Row对象,其中包含User类的实例:

>>> row = session.execute(select(User)).first()
BEGIN...
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> row
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)

上述Row只有一个元素,代表User实体:

>>> row[0]
User(id=1, name='spongebob', fullname='Spongebob Squarepants')

实现与上述相同结果的一种高度推荐的便利方法是使用Session.scalars()方法直接执行语句;此方法将返回一个ScalarResult对象,该对象一次性返回每行的第一个“列”,在本例中是User类的实例:

>>> user = session.scalars(select(User)).first()
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> user
User(id=1, name='spongebob', fullname='Spongebob Squarepants')

或者,我们可以使用类绑定的属性选择 ORM 实体的各个列作为结果行中的单独元素;当这些属性传递给诸如select()之类的构造时,它们会解析为每个属性代表的Column或其他 SQL 表达式:

>>> print(select(User.name, User.fullname))
SELECT  user_account.name,  user_account.fullname
FROM  user_account 

当我们使用Session.execute()调用语句时,我们现在会收到每个值具有单独元素的行,每个元素对应一个单独的列或其他 SQL 表达式:

>>> row = session.execute(select(User.name, User.fullname)).first()
SELECT  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> row
('spongebob', 'Spongebob Squarepants')

可以混合使用这些方法,如下所示,我们选择User实体的name属性作为行的第一个元素,并将其与完整的Address实体组合为第二个元素:

>>> session.execute(
...     select(User.name, Address).where(User.id == Address.user_id).order_by(Address.id)
... ).all()
SELECT  user_account.name,  address.id,  address.email_address,  address.user_id
FROM  user_account,  address
WHERE  user_account.id  =  address.user_id  ORDER  BY  address.id
[...]  ()
[('spongebob', Address(id=1, email_address='spongebob@sqlalchemy.org')),
('sandy', Address(id=2, email_address='sandy@sqlalchemy.org')),
('sandy', Address(id=3, email_address='sandy@squirrelpower.org'))]

在选择 ORM 实体和列以及将行转换为常见方法方面的方法进一步讨论。

另请参阅

选择 ORM 实体和列 - 在 ORM 查询指南中

从带标签的 SQL 表达式中进行选择

ColumnElement.label()方法以及可用于 ORM 属性的同名方法提供列或表达式的 SQL 标签,允许它在结果集中具有特定名称。当通过名称引用结果行中的任意 SQL 表达式时,这可能会有所帮助:

>>> from sqlalchemy import func, cast
>>> stmt = select(
...     ("Username: " + user_table.c.name).label("username"),
... ).order_by(user_table.c.name)
>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(f"{row.username}")
BEGIN  (implicit)
SELECT  ?  ||  user_account.name  AS  username
FROM  user_account  ORDER  BY  user_account.name
[...]  ('Username: ',)
Username: patrick
Username: sandy
Username: spongebob
ROLLBACK 

另请参阅

按标签排序或分组 - 我们创建的标签名称也可以在Select的 ORDER BY 或 GROUP BY 子句中引用。

使用文本列表达式进行选择

当我们使用select()函数构造一个Select对象时,通常会向其中传递一系列使用 table metadata 定义的TableColumn对象,或者在使用 ORM 时,我们可能会发送代表表列的 ORM 映射属性。然而,有时也需要在语句中制造任意 SQL 块,比如常量字符串表达式,或者一些直接编写的任意 SQL。

在 Working with Transactions and the DBAPI 中介绍的text()构造实际上可以直接嵌入到Select构造中,如下所示,我们制造了一个硬编码的字符串字面量'some phrase'并将其嵌入到 SELECT 语句中:

>>> from sqlalchemy import text
>>> stmt = select(text("'some phrase'"), user_table.c.name).order_by(user_table.c.name)
>>> with engine.connect() as conn:
...     print(conn.execute(stmt).all())
BEGIN  (implicit)
SELECT  'some phrase',  user_account.name
FROM  user_account  ORDER  BY  user_account.name
[generated  in  ...]  ()
[('some phrase', 'patrick'), ('some phrase', 'sandy'), ('some phrase', 'spongebob')]
ROLLBACK 

虽然text()构造可用于大多数位置来插入文字 SQL 短语,但我们实际上更多地处理的是每个代表单个列表达式的文本单元。在这种常见情况下,我们可以使用literal_column()构造来获得更多的功能。此对象类似于text(),但它不是表示任意形式的任意 SQL,而是明确表示一个“列”,然后可以在子查询和其他表达式中进行标记和引用:

>>> from sqlalchemy import literal_column
>>> stmt = select(literal_column("'some phrase'").label("p"), user_table.c.name).order_by(
...     user_table.c.name
... )
>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(f"{row.p}, {row.name}")
BEGIN  (implicit)
SELECT  'some phrase'  AS  p,  user_account.name
FROM  user_account  ORDER  BY  user_account.name
[generated  in  ...]  ()
some phrase, patrick
some phrase, sandy
some phrase, spongebob
ROLLBACK 

请注意,在使用 text()literal_column() 时,我们正在编写一个语法上的 SQL 表达式,而不是一个字面值。因此,我们必须包括所需的任何引号或语法,以便我们想要看到的 SQL 被呈现出来。## WHERE 子句

SQLAlchemy 允许我们通过使用标准 Python 运算符结合 Column 和类似对象来组合 SQL 表达式,例如 name = 'squidward'user_id > 10。对于布尔表达式,大多数 Python 运算符(如 ==!=<>= 等)生成新的 SQL 表达式对象,而不是纯粹的布尔 True/False 值:

>>> print(user_table.c.name == "squidward")
user_account.name = :name_1

>>> print(address_table.c.user_id > 10)
address.user_id > :user_id_1

我们可以使用这样的表达式来生成 WHERE 子句,方法是将生成的对象传递给 Select.where() 方法:

>>> print(select(user_table).where(user_table.c.name == "squidward"))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  :name_1 

要生成由 AND 连接的多个表达式,可以多次调用 Select.where() 方法:

>>> print(
...     select(address_table.c.email_address)
...     .where(user_table.c.name == "squidward")
...     .where(address_table.c.user_id == user_table.c.id)
... )
SELECT  address.email_address
FROM  address,  user_account
WHERE  user_account.name  =  :name_1  AND  address.user_id  =  user_account.id 

对于具有相同效果的多个表达式,单次调用 Select.where() 也可以接受多个表达式:

>>> print(
...     select(address_table.c.email_address).where(
...         user_table.c.name == "squidward",
...         address_table.c.user_id == user_table.c.id,
...     )
... )
SELECT  address.email_address
FROM  address,  user_account
WHERE  user_account.name  =  :name_1  AND  address.user_id  =  user_account.id 

“AND” 和 “OR” 连接词可以直接使用 and_()or_() 函数,下面是在 ORM 实体方面的示例:

>>> from sqlalchemy import and_, or_
>>> print(
...     select(Address.email_address).where(
...         and_(
...             or_(User.name == "squidward", User.name == "sandy"),
...             Address.user_id == User.id,
...         )
...     )
... )
SELECT  address.email_address
FROM  address,  user_account
WHERE  (user_account.name  =  :name_1  OR  user_account.name  =  :name_2)
AND  address.user_id  =  user_account.id 

对于针对单个实体的简单“相等性”比较,还有一种称为 Select.filter_by() 的流行方法,它接受与列键或 ORM 属性名称匹配的关键字参数。它将针对最左边的 FROM 子句或最后一个连接的实体进行过滤:

>>> print(select(User).filter_by(name="spongebob", fullname="Spongebob Squarepants"))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  :name_1  AND  user_account.fullname  =  :fullname_1 

另请参阅

运算符参考 - SQLAlchemy 中大多数 SQL 运算符函数的描述## 明确的 FROM 子句和 JOINs

正如前面提到的,FROM 子句通常是基于我们在列子句中设置的表达式以及 Select 的其他元素而 推断 的。

如果我们在 COLUMNS 子句中设置了一个特定 Table 的单个列,它也会将该 Table 放在 FROM 子句中:

>>> print(select(user_table.c.name))
SELECT  user_account.name
FROM  user_account 

如果我们从两个表中取列,那么我们得到一个用逗号分隔的 FROM 子句:

>>> print(select(user_table.c.name, address_table.c.email_address))
SELECT  user_account.name,  address.email_address
FROM  user_account,  address 

为了将这两个表 JOIN 在一起,我们通常在 Select 上使用两种方法之一。第一种是 Select.join_from() 方法,它允许我们明确指示 JOIN 的左侧和右侧:

>>> print(
...     select(user_table.c.name, address_table.c.email_address).join_from(
...         user_table, address_table
...     )
... )
SELECT  user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

另一个是 Select.join() 方法,它表示 JOIN 的右侧,左侧被推断:

>>> print(select(user_table.c.name, address_table.c.email_address).join(address_table))
SELECT  user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

如果 FROM 子句没有按照我们想要的方式进行推断,我们还可以选择将元素明确添加到 FROM 子句中。我们使用 Select.select_from() 方法来实现这一点,如下所示,我们将 user_table 设为 FROM 子句中的第一个元素,然后使用 Select.join()address_table 设为第二个元素:

>>> print(select(address_table.c.email_address).select_from(user_table).join(address_table))
SELECT  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

我们可能想要使用 Select.select_from() 的另一个示例是,如果我们的 columns 子句没有足够的信息提供 FROM 子句。例如,要从常见的 SQL 表达式 count(*) 中选择,我们使用名为 sqlalchemy.sql.expression.func 的 SQLAlchemy 元素来生成 SQL count() 函数:

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

另请参阅

在连接中设置最左侧的 FROM 子句 - 在 ORM 查询指南 - 包含有关 Select.select_from()Select.join() 互动的附加示例和注释。

设置 ON 子句

前面 JOIN 的示例说明了 Select 结构可以在两个表之间进行 JOIN,并自动生成 ON 子句。这在这些示例中发生,因为 user_tableaddress_table Table 对象包含单个 ForeignKeyConstraint 定义,用于形成此 ON 子句。

如果连接的左右目标没有这样的约束,或者存在多个约束,则需要直接指定 ON 子句。 Select.join()Select.join_from() 都接受用于 ON 子句的额外参数,其使用与我们在 WHERE 子句 中看到的 SQL 表达式机制相同:

>>> print(
...     select(address_table.c.email_address)
...     .select_from(user_table)
...     .join(address_table, user_table.c.id == address_table.c.user_id)
... )
SELECT  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

ORM 提示 - 在使用 ORM 实体时,当使用 relationship() 构造时,还有另一种生成 ON 子句的方式,就像在 声明映射类 中的前一节设置的映射一样。这是一个单独的主题,详细介绍在 使用关系连接。

OUTER 和 FULL join

Select.join()Select.join_from() 方法都接受关键字参数 Select.join.isouterSelect.join.full,分别会渲染 LEFT OUTER JOIN 和 FULL OUTER JOIN:

>>> print(select(user_table).join(address_table, isouter=True))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  LEFT  OUTER  JOIN  address  ON  user_account.id  =  address.user_id
>>> print(select(user_table).join(address_table, full=True))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  FULL  OUTER  JOIN  address  ON  user_account.id  =  address.user_id 

还有一个方法 Select.outerjoin(),它等同于使用 .join(..., isouter=True)

提示

SQL 还有一个“RIGHT OUTER JOIN”。SQLAlchemy 不会直接呈现这个;相反,反转表的顺序并使用“LEFT OUTER JOIN”。 ## ORDER BY、GROUP BY、HAVING

SELECT SQL 语句包括一个称为 ORDER BY 的子句,用于以给定顺序返回所选行。

GROUP BY 子句的构造方式类似于 ORDER BY 子句,其目的是将所选行细分为特定的分组,从而可以对这些分组调用聚合函数。HAVING 子句通常与 GROUP BY 一起使用,其形式与 WHERE 子句类似,只是它应用于分组内使用的聚合函数。

ORDER BY

ORDER BY 子句是根据 SQL 表达式构造的,通常基于Column或类似对象。Select.order_by()方法按位置接受一个或多个这些表达式:

>>> print(select(user_table).order_by(user_table.c.name))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.name 

升序/降序可以从ColumnElement.asc()ColumnElement.desc()修饰符中获得,这些修饰符也存在于 ORM 绑定的属性中:

>>> print(select(User).order_by(User.fullname.desc()))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.fullname  DESC 

上述语句将按照user_account.fullname列按降序排序的行。### 带有 GROUP BY / HAVING 的聚合函数

在 SQL 中,聚合函数允许跨多行的列表达式聚合在一起,以产生单个结果。示例包括计数、计算平均值,以及在一组值中定位最大值或最小值。

SQLAlchemy 以一种开放式的方式提供 SQL 函数,使用一个名为func的命名空间。这是一个特殊的构造对象,当给定特定 SQL 函数的名称时,它将创建Function的新实例,该函数可以有任何名称,以及零个或多个要传递给函数的参数,就像在所有其他情况下一样,都是 SQL 表达式构造。例如,要针对user_account.id列渲染 SQL COUNT()函数,我们调用count()名称:

>>> from sqlalchemy import func
>>> count_fn = func.count(user_table.c.id)
>>> print(count_fn)
count(user_account.id) 

SQL 函数在本教程的后面部分详细描述,链接在使用 SQL 函数。

在 SQL 中使用聚合函数时,GROUP BY 子句是必不可少的,因为它允许将行分成组,其中聚合函数将分别应用于每个组。在 SELECT 语句的 COLUMNS 子句中请求非聚合列时,SQL 要求这些列都受到 GROUP BY 子句的约束,直接或间接地基于主键关联。然后,HAVING 子句类似于 WHERE 子句,不同之处在于它根据聚合值而不是直接行内容来过滤行。

SQLAlchemy 提供了这两个子句的功能,使用 Select.group_by()Select.having() 方法。下面我们示例选择用户名称字段以及地址计数,对于那些拥有多个地址的用户:

>>> with engine.connect() as conn:
...     result = conn.execute(
...         select(User.name, func.count(Address.id).label("count"))
...         .join(Address)
...         .group_by(User.name)
...         .having(func.count(Address.id) > 1)
...     )
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name,  count(address.id)  AS  count
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id  GROUP  BY  user_account.name
HAVING  count(address.id)  >  ?
[...]  (1,)
[('sandy', 2)]
ROLLBACK 
```  ### 按标签排序或分组

特别重要的一项技术,在某些数据库后端特别是,是有能力按照已在列子句中已经声明的表达式进行 ORDER BY 或 GROUP BY,而无需在 ORDER BY 或 GROUP BY 子句中重新声明表达式,而是使用 COLUMNS 子句中的列名或标记名。可以通过将名称的字符串文本传递给 `Select.order_by()` 或 `Select.group_by()` 方法来使用这种形式。传递的文本**不会直接渲染**;而是在列子句中给定的表达式名称,并在上下文中呈现为该表达式名称,如果找不到匹配项,则会引发错误。这种形式也可以使用一元修饰符 `asc()` 和 `desc()`:

```py
>>> from sqlalchemy import func, desc
>>> stmt = (
...     select(Address.user_id, func.count(Address.id).label("num_addresses"))
...     .group_by("user_id")
...     .order_by("user_id", desc("num_addresses"))
... )
>>> print(stmt)
SELECT  address.user_id,  count(address.id)  AS  num_addresses
FROM  address  GROUP  BY  address.user_id  ORDER  BY  address.user_id,  num_addresses  DESC 
```  ## 使用别名

现在我们正在从多个表中进行选择并使用连接,我们很快就会遇到需要在语句的 FROM 子句中多次引用同一张表的情况。我们使用 SQL **别名** 来实现这一点,这是一种为表或子查询提供替代名称的语法,可以在语句中引用它。

在 SQLAlchemy 表达语言中,这些“名称”代替了 `FromClause` 对象,被称为 `Alias` 构造,在 Core 中使用 `FromClause.alias()` 方法构造。一个 `Alias` 构造就像一个 `Table` 构造一样,它也有一个在 `Alias.c` 集合中的 `Column` 对象的命名空间。例如下面的 SELECT 语句返回所有唯一的用户名对:

```py
>>> user_alias_1 = user_table.alias()
>>> user_alias_2 = user_table.alias()
>>> print(
...     select(user_alias_1.c.name, user_alias_2.c.name).join_from(
...         user_alias_1, user_alias_2, user_alias_1.c.id > user_alias_2.c.id
...     )
... )
SELECT  user_account_1.name,  user_account_2.name  AS  name_1
FROM  user_account  AS  user_account_1
JOIN  user_account  AS  user_account_2  ON  user_account_1.id  >  user_account_2.id 

ORM 实体别名

ORM 中与 FromClause.alias() 方法对应的方法是 ORM aliased() 函数,可应用于实体,如 UserAddress。这将在内部生成一个 Alias 对象,针对原始映射的 Table 对象,同时保持 ORM 功能。下面的 SELECT 从 User 实体中选择包含两个特定电子邮件地址的所有对象:

>>> from sqlalchemy.orm import aliased
>>> address_alias_1 = aliased(Address)
>>> address_alias_2 = aliased(Address)
>>> print(
...     select(User)
...     .join_from(User, address_alias_1)
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join_from(User, address_alias_2)
...     .where(address_alias_2.email_address == "patrick@gmail.com")
... )
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  address  AS  address_1  ON  user_account.id  =  address_1.user_id
JOIN  address  AS  address_2  ON  user_account.id  =  address_2.user_id
WHERE  address_1.email_address  =  :email_address_1
AND  address_2.email_address  =  :email_address_2 

Tip

如在 设置 ON 子句 中提到的,ORM 提供了使用 relationship() 结构进行连接的另一种方式。上述使用别名的示例是使用 relationship() 在 使用关系在别名目标之间进行连接 中演示的。## 子查询和 CTE

SQL 中的子查询是在括号内呈现并放置在封闭语句上下文中的 SELECT 语句,通常是 SELECT 语句,但不一定。

本节将介绍所谓的“非标量”子查询,通常放置在封闭 SELECT 的 FROM 子句中。我们还将介绍通用表达式(Common Table Expression,CTE),它与子查询的使用方式类似,但包含其他功能。

SQLAlchemy 使用 Subquery 对象表示子查询,使用 CTE 表示 CTE,通常分别从 Select.subquery()Select.cte() 方法获取。这两个对象都可以作为较大的 select() 结构中的 FROM 元素使用。

我们可以构造一个 Subquery ,将从 address 表中选择行的聚合计数(聚合函数和 GROUP BY 在 具有 GROUP BY / HAVING 的聚合函数 中已介绍):

>>> subq = (
...     select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .subquery()
... )

单独将子查询字符串化,而不将其嵌入到另一个Select或其他语句中,会生成不带任何封闭括号的普通 SELECT 语句:

>>> print(subq)
SELECT  count(address.id)  AS  count,  address.user_id
FROM  address  GROUP  BY  address.user_id 

Subquery对象的行为类似于任何其他 FROM 对象,例如Table,特别是它包含一个Subquery.c列的命名空间,该命名空间选择它。 我们可以使用此命名空间来引用user_id列以及我们的自定义标记的count表达式:

>>> print(select(subq.c.user_id, subq.c.count))
SELECT  anon_1.user_id,  anon_1.count
FROM  (SELECT  count(address.id)  AS  count,  address.user_id  AS  user_id
FROM  address  GROUP  BY  address.user_id)  AS  anon_1 

通过包含在subq对象中的一系列行的选择,我们可以将该对象应用于一个更大的Select,将数据连接到user_account表:

>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
...     user_table, subq
... )

>>> print(stmt)
SELECT  user_account.name,  user_account.fullname,  anon_1.count
FROM  user_account  JOIN  (SELECT  count(address.id)  AS  count,  address.user_id  AS  user_id
FROM  address  GROUP  BY  address.user_id)  AS  anon_1  ON  user_account.id  =  anon_1.user_id 

为了从user_account连接到address,我们利用了Select.join_from()方法。 正如之前所说明的,此连接的 ON 子句再次基于外键约束推断。 即使 SQL 子查询本身没有任何约束,SQLAlchemy 也可以根据列上表示的约束来操作列,从而确定subq.c.user_id派生自表达外键关系的address_table.c.user_id列,该列又表达了与user_table.c.id列的外键关系,然后用于生成 ON 子句。

公共表达式(CTEs)

在 SQLAlchemy 中使用CTE结构的用法与使用Subquery结构几乎相同。 通过将Select.subquery()方法的调用更改为使用Select.cte()而不是,我们可以像以前一样使用结果对象作为 FROM 元素,但是渲染的 SQL 是非常不同的常用表达式语法:

>>> subq = (
...     select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .cte()
... )

>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
...     user_table, subq
... )

>>> print(stmt)
WITH  anon_1  AS
(SELECT  count(address.id)  AS  count,  address.user_id  AS  user_id
FROM  address  GROUP  BY  address.user_id)
  SELECT  user_account.name,  user_account.fullname,  anon_1.count
FROM  user_account  JOIN  anon_1  ON  user_account.id  =  anon_1.user_id 

CTE结构还具有以“递归”方式使用的能力,并且在更复杂的情况下可以由 INSERT、UPDATE 或 DELETE 语句的 RETURNING 子句组成。 CTE的文档字符串包含有关这些附加模式的详细信息。

在这两种情况下,子查询和 CTE 在 SQL 层面上都被命名为“匿名”名称。在 Python 代码中,我们根本不需要提供这些名称。当渲染时,SubqueryCTE 实例的对象标识作为对象的语法标识。可以通过将其作为 Select.subquery()Select.cte() 方法的第一个参数来提供在 SQL 中呈现的名称。

参见

Select.subquery() - 关于子查询的进一步细节

Select.cte() - 包括如何使用 RECURSIVE 以及面向 DML 的 CTE 的示例

ORM 实体子查询/CTEs

在 ORM 中,aliased() 构造可用于将 ORM 实体(例如我们的 UserAddress 类)与表示行来源的任何 FromClause 概念相关联。前一节 ORM 实体别名 演示了如何使用 aliased() 将映射类与其映射的 TableAlias 相关联。在这里,我们演示了 aliased() 对一个 Subquery 以及对一个由 Select 构造生成的 CTE 执行相同操作,最终从相同的映射 Table 派生。

下面是将aliased() 应用到Subquery 构造的示例,以便从其行中提取 ORM 实体。结果显示了一系列UserAddress对象,其中每个Address对象的数据最终来自于针对address表的子查询,而不是直接来自该表:

>>> subq = select(Address).where(~Address.email_address.like("%@aol.com")).subquery()
>>> address_subq = aliased(Address, subq)
>>> stmt = (
...     select(User, address_subq)
...     .join_from(User, address_subq)
...     .order_by(User.id, address_subq.id)
... )
>>> with Session(engine) as session:
...     for user, address in session.execute(stmt):
...         print(f"{user} {address}")
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.email_address,  anon_1.user_id
FROM  user_account  JOIN
(SELECT  address.id  AS  id,  address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  address.email_address  NOT  LIKE  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.id
[...]  ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
ROLLBACK 

下面是另一个例子,与之前的例子完全相同,只是它使用了CTE 构造:

>>> cte_obj = select(Address).where(~Address.email_address.like("%@aol.com")).cte()
>>> address_cte = aliased(Address, cte_obj)
>>> stmt = (
...     select(User, address_cte)
...     .join_from(User, address_cte)
...     .order_by(User.id, address_cte.id)
... )
>>> with Session(engine) as session:
...     for user, address in session.execute(stmt):
...         print(f"{user} {address}")
BEGIN  (implicit)
WITH  anon_1  AS
(SELECT  address.id  AS  id,  address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  address.email_address  NOT  LIKE  ?)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.email_address,  anon_1.user_id
FROM  user_account
JOIN  anon_1  ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.id
[...]  ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
ROLLBACK 

另请参阅

从子查询中选择实体 - 在 ORM 查询指南 ## 标量和相关子查询

标量子查询是一个返回零行或一行且一列的子查询。然后,该子查询在包含 SELECT 语句的 COLUMNS 或 WHERE 子句中使用,并且与常规子查询不同之处在于它不在 FROM 子句中使用。相关子查询 是指在包含 SELECT 语句中引用表的标量子查询。

SQLAlchemy 使用ScalarSelect 构造来表示标量子查询,该构造是ColumnElement 表达式层次结构的一部分,与常规子查询不同,常规子查询由Subquery 构造表示,该构造位于FromClause 层次结构中。

标量子查询通常与聚合函数一起使用,但不一定要这样,之前在带有 GROUP BY / HAVING 的聚合函数中介绍过。标量子查询通过显式使用Select.scalar_subquery() 方法来指示。下面是一个示例,其默认的字符串形式在单独字符串化时呈现为从两个表中选择的普通 SELECT 语句:

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .scalar_subquery()
... )
>>> print(subq)
(SELECT  count(address.id)  AS  count_1
FROM  address,  user_account
WHERE  user_account.id  =  address.user_id) 

上述subq对象现在位于ColumnElement SQL 表达式层次结构中,因此它可以像任何其他列表达式一样使用:

>>> print(subq == 5)
(SELECT  count(address.id)  AS  count_1
FROM  address,  user_account
WHERE  user_account.id  =  address.user_id)  =  :param_1 

虽然标量子查询本身在自身字符串化时在其 FROM 子句中呈现了user_accountaddress,但是,当将其嵌入到处理user_account表的封闭select()构造中时,user_account表会自动相关联,这意味着它不会在子查询的 FROM 子句中呈现:

>>> stmt = select(user_table.c.name, subq.label("address_count"))
>>> print(stmt)
SELECT  user_account.name,  (SELECT  count(address.id)  AS  count_1
FROM  address
WHERE  user_account.id  =  address.user_id)  AS  address_count
FROM  user_account 

简单的相关子查询通常会执行所需的正确操作。但是,在相关性不明确的情况下,SQLAlchemy 将通知我们需要更清晰:

>>> stmt = (
...     select(
...         user_table.c.name,
...         address_table.c.email_address,
...         subq.label("address_count"),
...     )
...     .join_from(user_table, address_table)
...     .order_by(user_table.c.id, address_table.c.id)
... )
>>> print(stmt)
Traceback (most recent call last):
...
InvalidRequestError: Select statement '<... Select object at ...>' returned
no FROM clauses due to auto-correlation; specify correlate(<tables>) to
control correlation manually.

要指定user_table是我们要关联的表,我们使用ScalarSelect.correlate()ScalarSelect.correlate_except()方法来指定:

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .scalar_subquery()
...     .correlate(user_table)
... )

然后,该语句可以像处理其他列一样返回此列的数据:

>>> with engine.connect() as conn:
...     result = conn.execute(
...         select(
...             user_table.c.name,
...             address_table.c.email_address,
...             subq.label("address_count"),
...         )
...         .join_from(user_table, address_table)
...         .order_by(user_table.c.id, address_table.c.id)
...     )
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name,  address.email_address,  (SELECT  count(address.id)  AS  count_1
FROM  address
WHERE  user_account.id  =  address.user_id)  AS  address_count
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id  ORDER  BY  user_account.id,  address.id
[...]  ()
[('spongebob', 'spongebob@sqlalchemy.org', 1), ('sandy', 'sandy@sqlalchemy.org', 2),
 ('sandy', 'sandy@squirrelpower.org', 2)]
ROLLBACK 

LATERAL 相关性

LATERAL 相关性是 SQL 相关性的一种特殊子类,它允许可选单元在单个 FROM 子句内引用另一个可选单元。这是一个极其特殊的用例,虽然是 SQL 标准的一部分,但只有最近版本的 PostgreSQL 已知支持。

通常,如果 SELECT 语句在其 FROM 子句中引用了table1 JOIN (SELECT ...) AS subquery,则右侧的子查询可能不会引用左侧的“table1”表达式;相关联可能只引用完全包围此 SELECT 的另一个 SELECT 的表。LATERAL 关键字允许我们改变这种行为,允许来自右侧 JOIN 的相关联。

SQLAlchemy 支持使用Select.lateral()方法实现此功能,该方法创建一个称为Lateral的对象。LateralSubqueryAlias位于同一家族,但在将构造添加到封闭 SELECT 的 FROM 子句时还包括相关联行为。以下示例说明了使用 LATERAL 的 SQL 查询,选择“用户帐户/电子邮件地址计数”数据,如前一节所讨论的:

>>> subq = (
...     select(
...         func.count(address_table.c.id).label("address_count"),
...         address_table.c.email_address,
...         address_table.c.user_id,
...     )
...     .where(user_table.c.id == address_table.c.user_id)
...     .lateral()
... )
>>> stmt = (
...     select(user_table.c.name, subq.c.address_count, subq.c.email_address)
...     .join_from(user_table, subq)
...     .order_by(user_table.c.id, subq.c.email_address)
... )
>>> print(stmt)
SELECT  user_account.name,  anon_1.address_count,  anon_1.email_address
FROM  user_account
JOIN  LATERAL  (SELECT  count(address.id)  AS  address_count,
address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  user_account.id  =  address.user_id)  AS  anon_1
ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.email_address 

在上述示例中,JOIN 的右侧是一个子查询,它与左侧的user_account表相关联。

当使用Select.lateral()时,Select.correlate()Select.correlate_except() 方法的行为也会应用于Lateral 结构。

另请参阅

Lateral

Select.lateral() ## UNION、UNION ALL 和其他集合操作

在 SQL 中,SELECT 语句可以使用 UNION 或 UNION ALL SQL 操作合并在一起,它产生由一个或多个语句一起产生的所有行的集合。还可以进行其他集合操作,例如 INTERSECT [ALL] 和 EXCEPT [ALL]。

SQLAlchemy 的Select 结构支持使用像union()intersect()except_() 这样的函数进行此类组合,以及“all”对应项 union_all()intersect_all()except_all()。这些函数都接受任意数量的子可选择项,通常是Select 结构,但也可以是现有的组合。

这些函数生成的构造物是 CompoundSelect,其使用方式与 Select 构造物相同,只是方法较少。例如,由 union_all() 生成的 CompoundSelect 可以直接使用 Connection.execute() 调用:

>>> from sqlalchemy import union_all
>>> stmt1 = select(user_table).where(user_table.c.name == "sandy")
>>> stmt2 = select(user_table).where(user_table.c.name == "spongebob")
>>> u = union_all(stmt1, stmt2)
>>> with engine.connect() as conn:
...     result = conn.execute(u)
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
UNION  ALL  SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[generated  in  ...]  ('sandy',  'spongebob')
[(2, 'sandy', 'Sandy Cheeks'), (1, 'spongebob', 'Spongebob Squarepants')]
ROLLBACK 

要将 CompoundSelect 用作子查询,就像 Select 一样,它提供了一个 SelectBase.subquery() 方法,该方法将生成一个带有 FromClause.c 集合的 Subquery 对象,该集合可以在封闭的 select() 中引用:

>>> u_subq = u.subquery()
>>> stmt = (
...     select(u_subq.c.name, address_table.c.email_address)
...     .join_from(address_table, u_subq)
...     .order_by(u_subq.c.name, address_table.c.email_address)
... )
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  anon_1.name,  address.email_address
FROM  address  JOIN
  (SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
  FROM  user_account
  WHERE  user_account.name  =  ?
UNION  ALL
  SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
  FROM  user_account
  WHERE  user_account.name  =  ?)
AS  anon_1  ON  anon_1.id  =  address.user_id
ORDER  BY  anon_1.name,  address.email_address
[generated  in  ...]  ('sandy',  'spongebob')
[('sandy', 'sandy@sqlalchemy.org'), ('sandy', 'sandy@squirrelpower.org'), ('spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

从并集中选择 ORM 实体

前面的例子演示了如何构造一个 UNION,给定两个 Table 对象,然后返回数据库行。如果我们想要使用 UNION 或其他集合操作来选择行,然后将其作为 ORM 对象接收,有两种方法可以使用。在这两种情况下,我们首先构造一个 select()CompoundSelect 对象,该对象表示我们想要执行的 SELECT / UNION / 等语句;这个语句应该针对目标 ORM 实体或它们的基础映射 Table 对象组成:

>>> stmt1 = select(User).where(User.name == "sandy")
>>> stmt2 = select(User).where(User.name == "spongebob")
>>> u = union_all(stmt1, stmt2)

对于一个简单的 SELECT 和 UNION,如果它还没有嵌套在子查询中,那么可以经常在 ORM 对象获取的上下文中使用Select.from_statement()方法。通过这种方法,UNION 语句表示整个查询;在使用Select.from_statement()之后,不能添加额外的条件:

>>> orm_stmt = select(User).from_statement(u)
>>> with Session(engine) as session:
...     for obj in session.execute(orm_stmt).scalars():
...         print(obj)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?  UNION  ALL  SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[generated  in  ...]  ('sandy',  'spongebob')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
ROLLBACK 

要以更灵活的方式将 UNION 或其他集合相关的构造用作实体相关组件,可以使用CompoundSelect构造将其组织到一个子查询中,然后使用aliased()函数将其链接到 ORM 对象。这与在 ORM 实体子查询/CTEs 中引入的方式相同,首先创建我们想要的实体到子查询的临时“映射”,然后从新实体中选择,就像它是任何其他映射类一样。在下面的示例中,我们可以添加额外的条件,比如在 UNION 之外进行 ORDER BY,因为我们可以过滤或按子查询导出的列进行排序:

>>> user_alias = aliased(User, u.subquery())
>>> orm_stmt = select(user_alias).order_by(user_alias.id)
>>> with Session(engine) as session:
...     for obj in session.execute(orm_stmt).scalars():
...         print(obj)
BEGIN  (implicit)
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname
FROM  (SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.name  =  ?  UNION  ALL  SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.name  =  ?)  AS  anon_1  ORDER  BY  anon_1.id
[generated  in  ...]  ('sandy',  'spongebob')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
ROLLBACK 

另请参阅

从 UNIONs 和其他集合操作中选择实体 - 在 ORM 查询指南中的 ## EXISTS 子查询

SQL EXISTS 关键字是与标量子查询一起使用的运算符,根据 SELECT 语句是否返回行来返回布尔值 true 或 false。SQLAlchemy 包含一个称为ScalarSelect的对象变体,它将生成一个 EXISTS 子查询,并且最方便地使用SelectBase.exists()方法生成。下面我们生成一个 EXISTS,以便我们可以返回user_account中有多个相关行的行:

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .having(func.count(address_table.c.id) > 1)
... ).exists()
>>> with engine.connect() as conn:
...     result = conn.execute(select(user_table.c.name).where(subq))
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name
FROM  user_account
WHERE  EXISTS  (SELECT  count(address.id)  AS  count_1
FROM  address
WHERE  user_account.id  =  address.user_id  GROUP  BY  address.user_id
HAVING  count(address.id)  >  ?)
[...]  (1,)
[('sandy',)]
ROLLBACK 

EXISTS 构造更常用于否定,例如 NOT EXISTS,因为它提供了一种 SQL 效率高的形式来定位一个相关表没有行的行。下面我们选择没有电子邮件地址的用户名称;注意第二个 WHERE 子句中使用的二进制否定运算符 (~):

>>> subq = (
...     select(address_table.c.id).where(user_table.c.id == address_table.c.user_id)
... ).exists()
>>> with engine.connect() as conn:
...     result = conn.execute(select(user_table.c.name).where(~subq))
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name
FROM  user_account
WHERE  NOT  (EXISTS  (SELECT  address.id
FROM  address
WHERE  user_account.id  =  address.user_id))
[...]  ()
[('patrick',)]
ROLLBACK 
```  ## 使用 SQL 函数

在本节早些时候介绍的 带 GROUP BY / HAVING 的聚合函数,`func` 对象充当创建新的 `Function` 对象的工厂,当在像 `select()` 这样的结构中使用时,会产生一个 SQL 函数显示,通常由名称、一些括号(虽然不总是),以及可能的一些参数组成。典型的 SQL 函数示例包括:

+   `count()` 函数,计算返回的行数的聚合函数:

    ```py
    >>> print(select(func.count()).select_from(user_table))
    SELECT  count(*)  AS  count_1
    FROM  user_account 
    ```

+   `lower()` 函数,将字符串转换为小写的字符串函数:

    ```py
    >>> print(select(func.lower("A String With Much UPPERCASE")))
    SELECT  lower(:lower_2)  AS  lower_1 
    ```

+   `now()` 函数,提供当前日期和时间;由于这是一个常见的函数,SQLAlchemy 知道如何为每个后端呈现这个函数的不同表现形式,在 SQLite 中使用 CURRENT_TIMESTAMP 函数:

    ```py
    >>> stmt = select(func.now())
    >>> with engine.connect() as conn:
    ...     result = conn.execute(stmt)
    ...     print(result.all())
    BEGIN  (implicit)
    SELECT  CURRENT_TIMESTAMP  AS  now_1
    [...]  ()
    [(datetime.datetime(...),)]
    ROLLBACK 
    ```

由于大多数数据库后端都具有几十甚至上百种不同的 SQL 函数,`func` 尽可能宽松地接受任何输入。从这个命名空间访问的任何名称都自动被视为是一个 SQL 函数,将以一种通用的方式呈现:

```py
>>> print(select(func.some_crazy_function(user_table.c.name, 17)))
SELECT  some_crazy_function(user_account.name,  :some_crazy_function_2)  AS  some_crazy_function_1
FROM  user_account 

与此同时,一组相对较小的极其常见的 SQL 函数,如countnowmaxconcat 包括它们自己的预打包版本,这些版本提供了适当的类型信息,并在某些情况下提供特定于后端的 SQL 生成。下面的示例对比了 PostgreSQL 方言和 Oracle 方言对 now 函数的 SQL 生成:

>>> from sqlalchemy.dialects import postgresql
>>> print(select(func.now()).compile(dialect=postgresql.dialect()))
SELECT  now()  AS  now_1
>>> from sqlalchemy.dialects import oracle
>>> print(select(func.now()).compile(dialect=oracle.dialect()))
SELECT  CURRENT_TIMESTAMP  AS  now_1  FROM  DUAL 

函数具有返回类型

由于函数是列表达式,它们还有 SQL 数据类型,描述了生成的 SQL 表达式的数据类型。我们在这里将这些类型称为“SQL 返回类型”,指的是在数据库端 SQL 表达式上下文中由函数返回的 SQL 值的类型,而不是 Python 函数的“返回类型”。

任何 SQL 函数的 SQL 返回类型可以通过引用 Function.type 属性来访问,通常用于调试目的:

>>> func.now().type
DateTime()

这些 SQL 返回类型在将函数表达式用于更大表达式的上下文中时很重要;也就是说,数学运算符在表达式的数据类型为IntegerNumeric之类时效果更佳,为了使 JSON 访问器能够工作,需要使用诸如JSON之类的类型。某些类别的函数返回整行而不是列值,需要引用特定列;这些函数被称为 table valued functions。

在执行语句并获取行时,函数的 SQL 返回类型也可能很重要,特别是对于那些 SQLAlchemy 必须应用结果集处理的情况。一个典型的例子是 SQLite 上的日期相关函数,其中 SQLAlchemy 的DateTime和相关数据类型在收到结果行时扮演了将字符串值转换为 Python datetime()对象的角色。

要将特定类型应用于我们创建的函数,我们使用Function.type_参数传递它;类型参数可以是TypeEngine类或实例。在下面的示例中,我们传递JSON类以生成 PostgreSQL 的json_object()函数,注意 SQL 返回类型将是 JSON 类型:

>>> from sqlalchemy import JSON
>>> function_expr = func.json_object('{a, 1, b, "def", c, 3.5}', type_=JSON)

通过使用带有JSON数据类型的 JSON 函数,SQL 表达式对象具有了与 JSON 相关的功能,例如访问元素:

>>> stmt = select(function_expr["def"])
>>> print(stmt)
SELECT  json_object(:json_object_1)[:json_object_2]  AS  anon_1 

内置函数具有预配置的返回类型

对于像countmaxmin等常见的聚合函数,以及非常少数的日期函数,比如now和字符串函数,比如concat,SQL 返回类型将适当地设置,有时是基于用法。max函数和类似的聚合过滤函数将根据给定的参数设置 SQL 返回类型:

>>> m1 = func.max(Column("some_int", Integer))
>>> m1.type
Integer()

>>> m2 = func.max(Column("some_str", String))
>>> m2.type
String()

日期和时间函数通常对应于由DateTimeDateTime描述的 SQL 表达式:

>>> func.now().type
DateTime()
>>> func.current_date().type
Date()

已知的字符串函数,如concat,将知道 SQL 表达式的类型为String

>>> func.concat("x", "y").type
String()

但是,对于绝大多数 SQL 函数,SQLAlchemy 并没有将它们显式地列在已知函数的非常小的列表中。例如,虽然通常使用 SQL 函数 func.lower()func.upper() 来转换字符串的大小写没有问题,但 SQLAlchemy 实际上并不知道这些函数,因此它们具有“null”SQL 返回类型:

>>> func.upper("lowercase").type
NullType()

对于像upperlower这样的简单函数,通常情况下问题不是很严重,因为字符串值可以在没有任何特殊类型处理的情况下从数据库接收,而且 SQLAlchemy 的类型转换规则通常也能够正确猜测意图;例如,Python 的+操作符会根据表达式的两边正确解释为字符串连接操作符:

>>> print(select(func.upper("lowercase") + " suffix"))
SELECT  upper(:upper_1)  ||  :upper_2  AS  anon_1 

总的来说,Function.type_ 参数可能是必要的情况是:

  1. 如果函数不是 SQLAlchemy 的内置函数;这可以通过创建函数并观察Function.type属性来证明,即:

    >>> func.count().type
    Integer()
    

    与:

    >>> func.json_object('{"a", "b"}').type
    NullType()
    
  2. 需要支持函数感知的表达式;这通常是指与诸如JSONARRAY之类的数据类型相关的特殊操作符

  3. 需要结果值处理,其中可能包括诸如DateTimeBooleanEnum或者再次是特殊的数据类型,如JSONARRAY

高级 SQL 函数技术

以下各小节说明了可以使用 SQL 函数做的更多事情。虽然这些技术比基本的 SQL 函数使用更不常见且更高级,但它们仍然非常受欢迎,这在很大程度上是由于 PostgreSQL 强调更复杂的函数形式,包括与 JSON 数据流行的表和列值形式。

使用窗口函数

窗口函数是 SQL 聚合函数的特殊用法,它在处理个别结果行时计算在一组中返回的行上的聚合值。而像 MAX() 这样的函数将为你提供一组行中的列的最高值,使用相同函数作为“窗口函数”将为你提供每行的最高值,截至该行

在 SQL 中,窗口函数允许指定应该应用函数的行、一个考虑不同行子集的“分区”值以及一个重要的指示行应该应用到聚合函数的顺序的“order by”表达式。

在 SQLAlchemy 中,由 func 命名空间生成的所有 SQL 函数都包括一个 FunctionElement.over() 方法,它授予窗口函数或“OVER”语法;生成的结构是 Over 结构。

与窗口函数一起常用的函数是 row_number() 函数,它简单地计算行数。我们可以根据用户名对此行计数进行分区,以为个别用户的电子邮件地址编号:

>>> stmt = (
...     select(
...         func.row_number().over(partition_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  row_number()  OVER  (PARTITION  BY  user_account.name)  AS  anon_1,
user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
[(1, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (1, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

上文中,使用了 FunctionElement.over.partition_by 参数,以使 PARTITION BY 子句在 OVER 子句中呈现。我们还可以使用 FunctionElement.over.order_by 来使用 ORDER BY 子句:

>>> stmt = (
...     select(
...         func.count().over(order_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  count(*)  OVER  (ORDER  BY  user_account.name)  AS  anon_1,
user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
[(2, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (3, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

窗口函数的更多选项包括使用范围;请参阅 over() 以获取更多示例。

提示

需要注意的是,FunctionElement.over() 方法仅适用于那些实际上是聚合函数的 SQL 函数;虽然 Over 结构会愉快地为任何给定的 SQL 函数渲染自己,但如果函数本身不是 SQL 聚合函数,数据库将拒绝该表达式。 #### 特殊修饰符 WITHIN GROUP, FILTER

"WITHIN GROUP" SQL 语法与“有序集合”或“假设集合”聚合函数一起使用。常见的“有序集合”函数包括percentile_cont()rank()。SQLAlchemy 包含内置实现rank, dense_rank, mode, percentile_contpercentile_disc,其中包括一个 FunctionElement.within_group() 方法:

>>> print(
...     func.unnest(
...         func.percentile_disc([0.25, 0.5, 0.75, 1]).within_group(user_table.c.name)
...     )
... )
unnest(percentile_disc(:percentile_disc_1)  WITHIN  GROUP  (ORDER  BY  user_account.name)) 

"FILTER" 受一些后端支持,用于将聚合函数的范围限制为与返回的总行范围相比的特定子集,可使用 FunctionElement.filter() 方法:

>>> stmt = (
...     select(
...         func.count(address_table.c.email_address).filter(user_table.c.name == "sandy"),
...         func.count(address_table.c.email_address).filter(
...             user_table.c.name == "spongebob"
...         ),
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  count(address.email_address)  FILTER  (WHERE  user_account.name  =  ?)  AS  anon_1,
count(address.email_address)  FILTER  (WHERE  user_account.name  =  ?)  AS  anon_2
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ('sandy',  'spongebob')
[(2, 1)]
ROLLBACK 
```  #### 表值函数

表值 SQL 函数支持包含命名子元素的标量表示形式。通常用于 JSON 和 ARRAY 导向的函数以及像`generate_series()`这样的函数,表值函数在 FROM 子句中指定,然后被引用为表,有时甚至作为列。这种形式的函数在 PostgreSQL 数据库中非常突出,但某些形式的表值函数也受 SQLite、Oracle 和 SQL Server 支持。

另请参阅

表值、表和列值函数、行和元组对象 - 在 PostgreSQL 文档中。

虽然许多数据库支持表值和其他特殊形式,但 PostgreSQL 往往是对这些功能需求最大的地方。请参阅本节,了解 PostgreSQL 语法的附加示例以及其他功能。

SQLAlchemy 提供了 `FunctionElement.table_valued()` 方法作为基本的“表值函数”构造,它将一个 `func` 对象转换为一个包含一系列命名列的 FROM 子句,这些列是基于按位置传递的字符串名称。这将返回一个 `TableValuedAlias` 对象,它是一个启用函数的 `Alias` 构造,可像在 使用别名 中介绍的其他 FROM 子句一样使用。下面我们举例说明 `json_each()` 函数,尽管在 PostgreSQL 上很常见,但也受到现代版本的 SQLite 的支持:

```py
>>> onetwothree = func.json_each('["one", "two", "three"]').table_valued("value")
>>> stmt = select(onetwothree).where(onetwothree.c.value.in_(["two", "three"]))
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     result.all()
BEGIN  (implicit)
SELECT  anon_1.value
FROM  json_each(?)  AS  anon_1
WHERE  anon_1.value  IN  (?,  ?)
[...]  ('["one", "two", "three"]',  'two',  'three')
[('two',), ('three',)]
ROLLBACK 

在上面的例子中,我们使用了 SQLite 和 PostgreSQL 支持的 json_each() JSON 函数来生成一个具有单列(称为 value)的表值表达式,并选择了其三行中的两行。

另请参阅

表值函数 - 在 PostgreSQL 文档中 - 此部分将详细介绍其他语法,例如特殊列派生和“WITH ORDINALITY”,已知可与 PostgreSQL 一起使用。

PostgreSQL 和 Oracle 支持的特殊语法是在 FROM 子句中引用函数,然后将其自身作为 SELECT 语句或其他列表达式上的列传递到列子句中。 PostgreSQL 在 json_array_elements()json_object_keys()json_each_text()json_each() 等函数中广泛使用此语法。

SQLAlchemy 将其称为“列值函数”,可通过将 FunctionElement.column_valued() 修饰符应用于 Function 构造来使用:

>>> from sqlalchemy import select, func
>>> stmt = select(func.json_array_elements('["one", "two"]').column_valued("x"))
>>> print(stmt)
SELECT  x
FROM  json_array_elements(:json_array_elements_1)  AS  x 

“列值形式”也受到 Oracle 方言的支持,可以用于自定义 SQL 函数:

>>> from sqlalchemy.dialects import oracle
>>> stmt = select(func.scalar_strings(5).column_valued("s"))
>>> print(stmt.compile(dialect=oracle.dialect()))
SELECT  s.COLUMN_VALUE
FROM  TABLE  (scalar_strings(:scalar_strings_1))  s 

另请参阅

列值函数 - 在 PostgreSQL 文档中。 ## 数据转换和类型强制

在 SQL 中,我们经常需要明确指定表达式的数据类型,要么是为了告诉数据库在一个否则模棱两可的表达式中期望的类型是什么,要么是在某些情况下,当我们想要将 SQL 表达式的隐含数据类型转换为其他内容时。SQL CAST 关键字用于此任务,在 SQLAlchemy 中由cast()函数提供。该函数接受列表达式和数据类型对象作为参数,如下所示,我们从user_table.c.id列对象生成一个 SQL 表达式CAST(user_account.id AS VARCHAR)

>>> from sqlalchemy import cast
>>> stmt = select(cast(user_table.c.id, String))
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     result.all()
BEGIN  (implicit)
SELECT  CAST(user_account.id  AS  VARCHAR)  AS  id
FROM  user_account
[...]  ()
[('1',), ('2',), ('3',)]
ROLLBACK 

cast()函数不仅会渲染 SQL CAST 语法,还会生成一个 SQLAlchemy 列表达式,在 Python 端也将作为给定的数据类型。将字符串表达式cast()JSON将获得 JSON 下标和比较运算符,例如:

>>> from sqlalchemy import JSON
>>> print(cast("{'a': 'b'}", JSON)["a"])
CAST(:param_1  AS  JSON)[:param_2] 

type_coerce() - 一个仅限于 Python 的“类型转换”函数

有时需要让 SQLAlchemy 知道表达式的数据类型,出于前述所有原因,但是不要在 SQL 端渲染 CAST 表达式本身,因为它可能会干扰已经正常工作的 SQL 操作。对于这种相当常见的用例,有另一个函数type_coerce(),它与cast()密切相关,它将设置一个 Python 表达式为具有特定 SQL 数据库类型,但不会在数据库端渲染 CAST 关键字或数据类型。当处理JSON数据类型时,type_coerce()特别重要,它通常与不同平台上的字符串定向数据类型有着错综复杂的关系,甚至可能不是一个显式的数据类型,例如在 SQLite 和 MariaDB 上。下面,我们使用type_coerce()将一个 Python 结构作为 JSON 字符串传递给 MySQL 的一个 JSON 函数:

>>> import json
>>> from sqlalchemy import JSON
>>> from sqlalchemy import type_coerce
>>> from sqlalchemy.dialects import mysql
>>> s = select(type_coerce({"some_key": {"foo": "bar"}}, JSON)["some_key"])
>>> print(s.compile(dialect=mysql.dialect()))
SELECT  JSON_EXTRACT(%s,  %s)  AS  anon_1 

在上面的例子中,调用了 MySQL 的 JSON_EXTRACT SQL 函数,因为我们使用 type_coerce() 指示我们的 Python 字典应该被视为 JSON。Python 的 __getitem__ 运算符,在这种情况下,['some_key'] 变得可用,并允许一个 JSON_EXTRACT 路径表达式(但在本例中没有显示,最终将是 '$."some_key"')被渲染。

选择(select())SQL 表达式构造

select() 构造以与 insert() 相同的方式构建语句,使用一种生成式方法,其中每个方法都向对象添加更多状态。与其他 SQL 构造一样,它可以在原地转换为字符串:

>>> from sqlalchemy import select
>>> stmt = select(user_table).where(user_table.c.name == "spongebob")
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  :name_1 

同样,与所有其他语句级 SQL 构造一样,要实际运行语句,我们将其传递给执行方法。由于 SELECT 语句返回行,我们总是可以迭代结果对象以获取 Row 对象:

>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(row)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[...]  ('spongebob',)
(1, 'spongebob', 'Spongebob Squarepants')
ROLLBACK 

当使用 ORM,特别是对 ORM 实体组成的 select() 构造进行操作时,我们将希望使用 Session.execute() 方法执行它;使用这种方法,我们仍然从结果中获取 Row 对象,但是这些行现在可以包括完整的实体,例如 User 类的实例,作为每一行中的单独元素:

>>> stmt = select(User).where(User.name == "spongebob")
>>> with Session(engine) as session:
...     for row in session.execute(stmt):
...         print(row)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[...]  ('spongebob',)
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
ROLLBACK 

下面的章节将更详细地讨论 SELECT 构造。

设置 COLUMNS 和 FROM 子句

select() 函数接受表示任意数量的 Column 和/或 Table 表达式的位置元素,以及一系列兼容的对象,这些对象被解析为要从中选择的 SQL 表达式列表,将作为结果集中的列返回。这些元素还在更简单的情况下用于创建 FROM 子句,该子句从传递的列和类似表达式中推断出:

>>> print(select(user_table))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account 

使用核心方法进行按列选择时,从Table.c访问器中访问Column对象,并且可以直接发送;FROM 子句将被推断为所有由这些列表示的Table和其他FromClause对象的集合:

>>> print(select(user_table.c.name, user_table.c.fullname))
SELECT  user_account.name,  user_account.fullname
FROM  user_account 

或者,当使用任何FromClauseFromClause.c集合时,可以通过使用字符串名称的元组指定select()的多列:

>>> print(select(user_table.c["name", "fullname"]))
SELECT  user_account.name,  user_account.fullname
FROM  user_account 

版本 2.0 中的新功能:为FromClause.c集合添加了元组访问器功能

选择 ORM 实体和列

ORM 实体,例如我们的User类以及其上的列映射属性,例如User.name,也参与到表示表和列的 SQL 表达式语言系统中。下面举例说明了从User实体中进行 SELECT 的示例,这最终的渲染方式与直接使用user_table相同:

>>> print(select(User))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account 

当使用 ORM Session.execute()方法执行类似上面的语句时,当我们从完整实体如User中选择时,与user_table相反,有一个重要的区别,即实体本身作为每行中的单个元素返回。也就是说,当我们从上述语句中提取行时,由于要提取的东西列表中只有User实体,我们会得到仅有一个元素的Row对象,其中包含User类的实例:

>>> row = session.execute(select(User)).first()
BEGIN...
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> row
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)

上述Row仅有一个元素,代表着User实体:

>>> row[0]
User(id=1, name='spongebob', fullname='Spongebob Squarepants')

实现与上述相同结果的一个强烈推荐的便利方法是使用 Session.scalars() 方法直接执行该语句;该方法将返回一个 ScalarResult 对象,该对象一次提供每行的第一个“列”,在本例中为 User 类的实例:

>>> user = session.scalars(select(User)).first()
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> user
User(id=1, name='spongebob', fullname='Spongebob Squarepants')

或者,我们可以选择 ORM 实体的单独列作为结果行中的不同元素,通过使用类绑定的属性;当这些属性传递给 select() 这样的构造时,它们会解析为每个属性所表示的 Column 或其他 SQL 表达式:

>>> print(select(User.name, User.fullname))
SELECT  user_account.name,  user_account.fullname
FROM  user_account 

当我们使用 Session.execute() 调用 语句时,我们现在会收到每个值都有独立元素的行,每个元素对应于一个单独的列或其他 SQL 表达式:

>>> row = session.execute(select(User.name, User.fullname)).first()
SELECT  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> row
('spongebob', 'Spongebob Squarepants')

这些方法也可以混合使用,如下所示,我们将 User 实体的 name 属性选择为行的第一个元素,并将其与完整的 Address 实体组合在第二个元素中:

>>> session.execute(
...     select(User.name, Address).where(User.id == Address.user_id).order_by(Address.id)
... ).all()
SELECT  user_account.name,  address.id,  address.email_address,  address.user_id
FROM  user_account,  address
WHERE  user_account.id  =  address.user_id  ORDER  BY  address.id
[...]  ()
[('spongebob', Address(id=1, email_address='spongebob@sqlalchemy.org')),
('sandy', Address(id=2, email_address='sandy@sqlalchemy.org')),
('sandy', Address(id=3, email_address='sandy@squirrelpower.org'))]

进一步讨论了选择 ORM 实体和列以及将行转换为常见方法的方法,请参阅 选择 ORM 实体和属性。

另请参阅

选择 ORM 实体和属性 - 在 ORM 查询指南 中

从带标签的 SQL 表达式中选择

ColumnElement.label() 方法以及可用于 ORM 属性的同名方法提供了列或表达式的 SQL 标签,允许在结果集中使用特定名称引用任意 SQL 表达式。当通过名称引用结果行中的任意 SQL 表达式时,这可能会有所帮助:

>>> from sqlalchemy import func, cast
>>> stmt = select(
...     ("Username: " + user_table.c.name).label("username"),
... ).order_by(user_table.c.name)
>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(f"{row.username}")
BEGIN  (implicit)
SELECT  ?  ||  user_account.name  AS  username
FROM  user_account  ORDER  BY  user_account.name
[...]  ('Username: ',)
Username: patrick
Username: sandy
Username: spongebob
ROLLBACK 

另请参阅

按标签排序或分组 - 我们创建的标签名称也可以在 Select 的 ORDER BY 或 GROUP BY 子句中引用。

使用文本列表达式进行选择

当我们使用select()函数构造一个Select对象时,通常会传递一系列使用表元数据定义的TableColumn对象,或者在使用 ORM 时,我们可能会发送表示表列的 ORM 映射属性。然而,有时也需要在语句内部制造任意 SQL 块,比如常量字符串表达式,或者一些更容易直接写的任意 SQL。

在处理事务和 DBAPI 中介绍的text()构造实际上可以直接嵌入到Select构造中,例如下面我们制造一个硬编码的字符串字面值 'some phrase' 并将其嵌入到 SELECT 语句中:

>>> from sqlalchemy import text
>>> stmt = select(text("'some phrase'"), user_table.c.name).order_by(user_table.c.name)
>>> with engine.connect() as conn:
...     print(conn.execute(stmt).all())
BEGIN  (implicit)
SELECT  'some phrase',  user_account.name
FROM  user_account  ORDER  BY  user_account.name
[generated  in  ...]  ()
[('some phrase', 'patrick'), ('some phrase', 'sandy'), ('some phrase', 'spongebob')]
ROLLBACK 

虽然text()构造可以用于大多数地方来注入字面 SQL 词组,但更多时候,我们实际上处理的是每个表示单独列表达式的文本单元。在这种常见情况下,我们可以使用literal_column()构造来获得更多文本片段的功能。该对象类似于text(),只是它不是表示任意形式的任意 SQL,而是明确表示一个“列”,然后可以在子查询和其他表达式中标记和引用:

>>> from sqlalchemy import literal_column
>>> stmt = select(literal_column("'some phrase'").label("p"), user_table.c.name).order_by(
...     user_table.c.name
... )
>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(f"{row.p}, {row.name}")
BEGIN  (implicit)
SELECT  'some phrase'  AS  p,  user_account.name
FROM  user_account  ORDER  BY  user_account.name
[generated  in  ...]  ()
some phrase, patrick
some phrase, sandy
some phrase, spongebob
ROLLBACK 

注意,在使用text()literal_column()时,我们正在编写一个语法上的 SQL 表达式,而不是一个字面值。因此,我们必须包含所需的引号或语法,以便渲染我们想要看到的 SQL。 ### 选择 ORM 实体和列

ORM 实体,如我们的User类以及其上的列映射属性,如User.name,也参与 SQL 表达式语言系统,表示表和列。下面演示了从User实体中选择的示例,这最终呈现的方式与直接使用user_table时相同:

>>> print(select(User))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account 

当使用 ORM Session.execute()方法执行类似上述的语句时,当我们从完整实体(如User)中选择时,与user_table相比,有一个重要的区别,即实体本身作为每行中的单个元素返回。也就是说,当我们从上述语句中提取行时,由于要提取的内容列表中只有User实体,因此我们得到的是仅包含一个元素的Row对象,其中包含User类的实例:

>>> row = session.execute(select(User)).first()
BEGIN...
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> row
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)

上述Row只有一个元素,表示User实体:

>>> row[0]
User(id=1, name='spongebob', fullname='Spongebob Squarepants')

实现与上述相同结果的一个强烈推荐的便捷方法是使用Session.scalars()方法直接执行语句;此方法将返回一个ScalarResult对象,一次传递每行的第一个“列”,在本例中是User类的实例:

>>> user = session.scalars(select(User)).first()
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> user
User(id=1, name='spongebob', fullname='Spongebob Squarepants')

或者,我们可以选择 ORM 实体的各个列作为结果行中的不同元素,方法是使用类绑定的属性;当这些属性传递给诸如select()的构造时,它们将解析为每个属性表示的Column或其他 SQL 表达式:

>>> print(select(User.name, User.fullname))
SELECT  user_account.name,  user_account.fullname
FROM  user_account 

当我们使用Session.execute()调用语句时,现在我们会收到每个值对应的单独元素的行,每个元素对应一个单独的列或其他 SQL 表达式:

>>> row = session.execute(select(User.name, User.fullname)).first()
SELECT  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
>>> row
('spongebob', 'Spongebob Squarepants')

这些方法也可以混合使用,如下所示,我们选择User实体的name属性作为行的第一个元素,并将其与完整的Address实体组合成第二个元素:

>>> session.execute(
...     select(User.name, Address).where(User.id == Address.user_id).order_by(Address.id)
... ).all()
SELECT  user_account.name,  address.id,  address.email_address,  address.user_id
FROM  user_account,  address
WHERE  user_account.id  =  address.user_id  ORDER  BY  address.id
[...]  ()
[('spongebob', Address(id=1, email_address='spongebob@sqlalchemy.org')),
('sandy', Address(id=2, email_address='sandy@sqlalchemy.org')),
('sandy', Address(id=3, email_address='sandy@squirrelpower.org'))]

关于选择 ORM 实体和列的方法以及将行转换为常见方法的进一步讨论,请参阅选择 ORM 实体和属性。

另请参阅

选择 ORM 实体和属性 - 在 ORM 查询指南中

从带标签的 SQL 表达式中进行选择

ColumnElement.label()方法以及 ORM 属性上提供的同名方法都提供了列或表达式的 SQL 标签,允许它在结果集中具有特定名称。当通过名称引用结果行中的任意 SQL 表达式时,这可能会有所帮助:

>>> from sqlalchemy import func, cast
>>> stmt = select(
...     ("Username: " + user_table.c.name).label("username"),
... ).order_by(user_table.c.name)
>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(f"{row.username}")
BEGIN  (implicit)
SELECT  ?  ||  user_account.name  AS  username
FROM  user_account  ORDER  BY  user_account.name
[...]  ('Username: ',)
Username: patrick
Username: sandy
Username: spongebob
ROLLBACK 

另请参阅

按标签排序或分组 - 我们创建的标签名称也可以在Select的 ORDER BY 或 GROUP BY 子句中引用。

使用文本列表达式进行选择

当我们使用select()函数构造一个Select对象时,通常会向其传递使用表元数据定义的TableColumn对象,或者在使用 ORM 时,可能会发送代表表列的 ORM 映射属性。然而,有时也需要在语句中制造任意 SQL 块,例如常量字符串表达式,或者只是一些更快以文字形式编写的任意 SQL。

在处理事务和 DBAPI 中介绍的text()构造实际上可以直接嵌入到Select构造中,例如下面我们制造了一个硬编码的字符串字面量'some phrase'并将其嵌入到 SELECT 语句中:

>>> from sqlalchemy import text
>>> stmt = select(text("'some phrase'"), user_table.c.name).order_by(user_table.c.name)
>>> with engine.connect() as conn:
...     print(conn.execute(stmt).all())
BEGIN  (implicit)
SELECT  'some phrase',  user_account.name
FROM  user_account  ORDER  BY  user_account.name
[generated  in  ...]  ()
[('some phrase', 'patrick'), ('some phrase', 'sandy'), ('some phrase', 'spongebob')]
ROLLBACK 

虽然text()构造可以在大多数地方用于插入文字 SQL 短语,但更常见的情况是我们实际上正在处理每个代表单独列表达式的文本单元。在这种常见情况下,我们可以使用literal_column()构造从我们的文本片段中获得更多功能。这个对象类似于text(),只是它不代表任意形式的任意 SQL,而是明确表示一个“列”,然后可以在子查询和其他表达式中进行标记和引用:

>>> from sqlalchemy import literal_column
>>> stmt = select(literal_column("'some phrase'").label("p"), user_table.c.name).order_by(
...     user_table.c.name
... )
>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(f"{row.p}, {row.name}")
BEGIN  (implicit)
SELECT  'some phrase'  AS  p,  user_account.name
FROM  user_account  ORDER  BY  user_account.name
[generated  in  ...]  ()
some phrase, patrick
some phrase, sandy
some phrase, spongebob
ROLLBACK 

注意,在使用 text()literal_column() 时,我们正在编写一种句法 SQL 表达式,而不是文字值。因此,我们必须包括所需的引号或语法以获取我们想要看到的 SQL。

WHERE 子句

SQLAlchemy 允许我们通过使用 Column 和类似对象结合标准 Python 运算符来组合 SQL 表达式,例如 name = 'squidward'user_id > 10。对于布尔表达式,大多数 Python 运算符,如 ==!=<>= 等,都会生成新的 SQL 表达式对象,而不是纯粹的布尔 True/False 值:

>>> print(user_table.c.name == "squidward")
user_account.name = :name_1

>>> print(address_table.c.user_id > 10)
address.user_id > :user_id_1

我们可以使用这些表达式来通过将生成的对象传递给 Select.where() 方法来生成 WHERE 子句:

>>> print(select(user_table).where(user_table.c.name == "squidward"))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  :name_1 

要生成由 AND 连接的多个表达式,可以多次调用 Select.where() 方法:

>>> print(
...     select(address_table.c.email_address)
...     .where(user_table.c.name == "squidward")
...     .where(address_table.c.user_id == user_table.c.id)
... )
SELECT  address.email_address
FROM  address,  user_account
WHERE  user_account.name  =  :name_1  AND  address.user_id  =  user_account.id 

单次调用 Select.where() 也可以接受多个表达式,效果相同:

>>> print(
...     select(address_table.c.email_address).where(
...         user_table.c.name == "squidward",
...         address_table.c.user_id == user_table.c.id,
...     )
... )
SELECT  address.email_address
FROM  address,  user_account
WHERE  user_account.name  =  :name_1  AND  address.user_id  =  user_account.id 

“AND” 和 “OR” 连接词都可以直接使用 and_()or_() 函数,在 ORM 实体方面的示例如下所示:

>>> from sqlalchemy import and_, or_
>>> print(
...     select(Address.email_address).where(
...         and_(
...             or_(User.name == "squidward", User.name == "sandy"),
...             Address.user_id == User.id,
...         )
...     )
... )
SELECT  address.email_address
FROM  address,  user_account
WHERE  (user_account.name  =  :name_1  OR  user_account.name  =  :name_2)
AND  address.user_id  =  user_account.id 

对于针对单个实体的简单“相等性”比较,还有一种常用方法称为 Select.filter_by(),它接受与列键或 ORM 属性名称匹配的关键字参数。它将针对最左边的 FROM 子句或最后一个已连接的实体进行过滤:

>>> print(select(User).filter_by(name="spongebob", fullname="Spongebob Squarepants"))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  :name_1  AND  user_account.fullname  =  :fullname_1 

另请参阅

操作符参考 - SQLAlchemy 中大多数 SQL 操作符函数的描述

明确的 FROM 子句和 JOINs

如前所述,FROM 子句通常是根据我们在列子句中设置的表达式以及 Select 的其他元素来 推断 的。

如果我们在 COLUMNS 子句中从特定的 Table 中设置单个列,则它也将该 Table 放入 FROM 子句中:

>>> print(select(user_table.c.name))
SELECT  user_account.name
FROM  user_account 

如果我们要将两个表的列放在一起,那么我们会得到一个用逗号分隔的 FROM 子句:

>>> print(select(user_table.c.name, address_table.c.email_address))
SELECT  user_account.name,  address.email_address
FROM  user_account,  address 

为了将这两个表 JOIN 在一起,我们通常在 Select 上使用以下两种方法之一。第一种是 Select.join_from() 方法,它允许我们明确指定 JOIN 的左右两边:

>>> print(
...     select(user_table.c.name, address_table.c.email_address).join_from(
...         user_table, address_table
...     )
... )
SELECT  user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

另一个是 Select.join() 方法,它仅指示 JOIN 的右侧,左侧将被推断:

>>> print(select(user_table.c.name, address_table.c.email_address).join(address_table))
SELECT  user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

我们还可以选择显式地向 FROM 子句添加元素,如果它没有从列子句中以我们希望的方式推断出来。我们使用 Select.select_from() 方法来实现这一点,如下所示,我们将 user_table 建立为 FROM 子句中的第一个元素,使用 Select.join() 来建立 address_table 作为第二个元素:

>>> print(select(address_table.c.email_address).select_from(user_table).join(address_table))
SELECT  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

另一个我们可能想要使用 Select.select_from() 的示例是,如果我们的列子句没有足够的信息来提供 FROM 子句。例如,要从常见的 SQL 表达式 count(*) 中选择,我们使用 SQLAlchemy 元素 sqlalchemy.sql.expression.func 来生成 SQL count() 函数:

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

另请参阅

设置 JOIN 中最左边的 FROM 子句 - 在 ORM 查询指南 中 - 包含有关 Select.select_from()Select.join() 交互的其他示例和注意事项。

设置 ON 子句

之前 JOIN 的示例说明了 Select 结构可以在两个表之间进行 JOIN 并自动生成 ON 子句。这是因为 user_tableaddress_table Table 对象包含单个 ForeignKeyConstraint 定义,用于形成此 ON 子句。

如果连接的左右目标没有这样的约束,或者有多个约束存在,我们需要直接指定 ON 子句。Select.join()Select.join_from()都接受额外的参数用于 ON 子句,这是使用与我们在 WHERE 子句中看到的相同的 SQL 表达式机制来陈述的:

>>> print(
...     select(address_table.c.email_address)
...     .select_from(user_table)
...     .join(address_table, user_table.c.id == address_table.c.user_id)
... )
SELECT  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

ORM 提示 - 在使用 ORM 实体生成 ON 子句时,还有另一种方法,这些实体使用了relationship()构造,就像在声明映射类的上一节中设置的映射一样。这是一个单独的主题,详细介绍在使用关系连接。

OUTER 和 FULL 连接

Select.join()Select.join_from()方法都接受关键字参数Select.join.isouterSelect.join.full,分别渲染 LEFT OUTER JOIN 和 FULL OUTER JOIN:

>>> print(select(user_table).join(address_table, isouter=True))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  LEFT  OUTER  JOIN  address  ON  user_account.id  =  address.user_id
>>> print(select(user_table).join(address_table, full=True))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  FULL  OUTER  JOIN  address  ON  user_account.id  =  address.user_id 

还有一种方法Select.outerjoin(),它等同于使用.join(..., isouter=True)

提示

SQL 也有“RIGHT OUTER JOIN”。SQLAlchemy 不直接渲染这个;相反,倒转表的顺序并使用“LEFT OUTER JOIN”。

设置 ON 子句

之前的 JOIN 示例说明了Select构造可以在两个表之间进行连接并自动产生 ON 子句。这在那些示例中发生是因为user_tableaddress_tableTable对象包括一个单一的ForeignKeyConstraint定义,该定义用于形成这个 ON 子句。

如果连接的左右目标没有这样的约束,或者存在多个约束,我们需要直接指定 ON 子句。Select.join()Select.join_from() 都接受 ON 子句的额外参数,该参数使用与我们在 WHERE 子句 中看到的相同的 SQL 表达式机制进行说明:

>>> print(
...     select(address_table.c.email_address)
...     .select_from(user_table)
...     .join(address_table, user_table.c.id == address_table.c.user_id)
... )
SELECT  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

ORM 提示 - 当使用 ORM 实体并使用 relationship() 构造时,还有另一种生成 ON 子句的方法,就像前一节中在 声明映射类 中设置的映射一样。这是一个整体主题,详细介绍在 使用关系进行连接。

OUTER 和 FULL 连接

Select.join()Select.join_from() 方法都接受关键字参数 Select.join.isouterSelect.join.full,分别对应 LEFT OUTER JOIN 和 FULL OUTER JOIN:

>>> print(select(user_table).join(address_table, isouter=True))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  LEFT  OUTER  JOIN  address  ON  user_account.id  =  address.user_id
>>> print(select(user_table).join(address_table, full=True))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  FULL  OUTER  JOIN  address  ON  user_account.id  =  address.user_id 

还有一个等同于使用 .join(..., isouter=True) 的方法 Select.outerjoin()

提示

SQL 还有一个“RIGHT OUTER JOIN”。SQLAlchemy 不会直接渲染这个,而是将表的顺序反转并使用“LEFT OUTER JOIN”。

ORDER BY, GROUP BY, HAVING

SELECT SQL 语句包含一个叫做 ORDER BY 的子句,用于按照给定的顺序返回所选行。

GROUP BY 子句的构造方式类似于 ORDER BY 子句,其目的是将所选行分成特定的组,以便对这些组中的聚合函数进行调用。HAVING 子句通常与 GROUP BY 一起使用,其形式与 WHERE 子句类似,只是应用于组内使用的聚合函数。

ORDER BY

ORDER BY 子句是根据 SQL 表达式构造的,通常基于Column或类似对象。Select.order_by()方法可以按位置接受一个或多个这些表达式:

>>> print(select(user_table).order_by(user_table.c.name))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.name 

升序/降序可以从ColumnElement.asc()ColumnElement.desc()修饰符中获得,这些修饰符也存在于 ORM 绑定属性中:

>>> print(select(User).order_by(User.fullname.desc()))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.fullname  DESC 

上述语句将按照user_account.fullname列的降序排序。### 带有 GROUP BY / HAVING 的聚合函数

在 SQL 中,聚合函数允许将多行的列表达式聚合在一起,以产生单个结果。示例包括计数、计算平均值,以及定位一组值中的最大或最小值。

SQLAlchemy 以一种开放式的方式提供了 SQL 函数,使用了一个名为func的命名空间。这是一个特殊的构造对象,当给出特定 SQL 函数的名称时,它将创建Function的新实例,该函数可以具有任何名称,以及零个或多个要传递给函数的参数,这些参数像所有其他情况一样是 SQL 表达式构造。例如,要针对user_account.id列渲染 SQL COUNT() 函数,我们调用count()名称:

>>> from sqlalchemy import func
>>> count_fn = func.count(user_table.c.id)
>>> print(count_fn)
count(user_account.id) 

SQL 函数将在本教程后面的使用 SQL 函数中详细描述。

在 SQL 中使用聚合函数时,GROUP BY 子句至关重要,因为它允许将行分成组,其中将对每个组单独应用聚合函数。在 SELECT 语句的 COLUMNS 子句中请求非聚合列时,SQL 要求这些列都受到 GROUP BY 子句的约束,直接或间接地基于主键关联。然后,HAVING 子句类似于 WHERE 子句,但其根据聚合值而不是直接行内容来过滤行。

SQLAlchemy 提供了使用 Select.group_by()Select.having() 方法的这两个子句。下面我们演示选择用户姓名字段以及地址数量,对于那些拥有多个地址的用户:

>>> with engine.connect() as conn:
...     result = conn.execute(
...         select(User.name, func.count(Address.id).label("count"))
...         .join(Address)
...         .group_by(User.name)
...         .having(func.count(Address.id) > 1)
...     )
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name,  count(address.id)  AS  count
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id  GROUP  BY  user_account.name
HAVING  count(address.id)  >  ?
[...]  (1,)
[('sandy', 2)]
ROLLBACK 
```  ### 按标签分组或排序

一种重要的技术,特别是在某些数据库后端上,是有能力按已在列子句中已经说明的表达式排序或分组,而不需要在 ORDER BY 或 GROUP BY 子句中重新说明该表达式,而是使用 COLUMNS 子句中的列名或标签名。通过将名称的字符串文本传递给`Select.order_by()` 或 `Select.group_by()` 方法来实现此形式。传递的文本**不会直接呈现**;而是在上下文中以该表达式名称的形式呈现,并在没有找到匹配项时引发错误。这种形式还可以使用一元修饰符`asc()` 和 `desc()`。

```py
>>> from sqlalchemy import func, desc
>>> stmt = (
...     select(Address.user_id, func.count(Address.id).label("num_addresses"))
...     .group_by("user_id")
...     .order_by("user_id", desc("num_addresses"))
... )
>>> print(stmt)
SELECT  address.user_id,  count(address.id)  AS  num_addresses
FROM  address  GROUP  BY  address.user_id  ORDER  BY  address.user_id,  num_addresses  DESC 
```  ### 按顺序排列

`ORDER BY` 子句是根据通常基于`Column` 或类似对象的 SQL 表达式构造的。 `Select.order_by()` 方法按位置接受一个或多个这些表达式:

```py
>>> print(select(user_table).order_by(user_table.c.name))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.name 

升序 / 降序可以从ColumnElement.asc()ColumnElement.desc() 修饰符中获得,这些修饰符也存在于 ORM 绑定的属性中:

>>> print(select(User).order_by(User.fullname.desc()))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.fullname  DESC 

上述语句将按 user_account.fullname 列按降序排列的行。

带有 GROUP BY / HAVING 的聚合函数

在 SQL 中,聚合函数允许跨多行的列表达式聚合在一起以产生单个结果。例子包括计数、计算平均值,以及查找一组值中的最大值或最小值。

SQLAlchemy 以一种开放的方式提供 SQL 函数,使用一个名为func的命名空间。这是一个特殊的构造对象,当给定特定 SQL 函数的名称时,它将创建Function的新实例,该函数可以具有任何名称,以及零个或多个要传递给函数的参数,就像在所有其他情况下一样,是 SQL 表达式构造。例如,要针对user_account.id列渲染 SQL COUNT()函数,我们调用count()名称:

>>> from sqlalchemy import func
>>> count_fn = func.count(user_table.c.id)
>>> print(count_fn)
count(user_account.id) 

SQL 函数在本教程的稍后部分使用 SQL 函数中有更详细的描述。

在 SQL 中使用聚合函数时,GROUP BY 子句是必不可少的,因为它允许将行分成组,其中聚合函数将分别应用于每个组。在 SELECT 语句的 COLUMNS 子句中请求非聚合列时,SQL 要求这些列都受到 GROUP BY 子句的约束,直接或间接地基于主键关联。然后,HAVING 子句类似于 WHERE 子句的使用方式,不同之处在于它根据聚合值而不是直接行内容来过滤行。

SQLAlchemy 提供了这两个子句,使用Select.group_by()Select.having()方法。下面我们展示选择用户名称字段以及地址计数,对于那些拥有多个地址的用户:

>>> with engine.connect() as conn:
...     result = conn.execute(
...         select(User.name, func.count(Address.id).label("count"))
...         .join(Address)
...         .group_by(User.name)
...         .having(func.count(Address.id) > 1)
...     )
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name,  count(address.id)  AS  count
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id  GROUP  BY  user_account.name
HAVING  count(address.id)  >  ?
[...]  (1,)
[('sandy', 2)]
ROLLBACK 

按标签排序或分组

一种重要的技术,特别是在某些数据库后端上,是能够按照已在列子句中声明的表达式进行 ORDER BY 或 GROUP BY,而无需在 ORDER BY 或 GROUP BY 子句中重新声明表达式,而是使用 COLUMNS 子句中的列名或标记名称。通过将名称的字符串文本传递给Select.order_by()Select.group_by()方法来实现这种形式。传递的文本不会直接呈现;相反,在列子句中给定表达式的名称,并在上下文中呈现为该表达式名称,如果找不到匹配项,则会引发错误。一元修饰符asc()desc()也可以在此形式中使用:

>>> from sqlalchemy import func, desc
>>> stmt = (
...     select(Address.user_id, func.count(Address.id).label("num_addresses"))
...     .group_by("user_id")
...     .order_by("user_id", desc("num_addresses"))
... )
>>> print(stmt)
SELECT  address.user_id,  count(address.id)  AS  num_addresses
FROM  address  GROUP  BY  address.user_id  ORDER  BY  address.user_id,  num_addresses  DESC 

使用别名

现在我们正在从多个表中进行选择并使用连接,我们很快就会遇到需要在语句的 FROM 子句中多次引用同一张表的情况。我们通过使用 SQL 别名 来实现这一点,别名是一种为表或子查询提供替代名称的语法,可以在语句中引用它。

在 SQLAlchemy 表达式语言中,这些“名称”实际上是由称为FromClause的对象表示的,它们构成了 Core 中的Alias构造,该构造使用FromClause.alias()方法构建。Alias构造就像Table构造一样,它也有一个Column对象的命名空间,位于Alias.c集合中。下面的 SELECT 语句例如返回所有唯一的用户名对:

>>> user_alias_1 = user_table.alias()
>>> user_alias_2 = user_table.alias()
>>> print(
...     select(user_alias_1.c.name, user_alias_2.c.name).join_from(
...         user_alias_1, user_alias_2, user_alias_1.c.id > user_alias_2.c.id
...     )
... )
SELECT  user_account_1.name,  user_account_2.name  AS  name_1
FROM  user_account  AS  user_account_1
JOIN  user_account  AS  user_account_2  ON  user_account_1.id  >  user_account_2.id 

ORM 实体别名

FromClause.alias()方法的 ORM 等效方法是 ORM aliased()函数,它可以应用于诸如 UserAddress 等实体。这将在内部生成一个针对原始映射Table对象的Alias对象,同时保持 ORM 功能。下面的 SELECT 从 User 实体中选择所有包含两个特定电子邮件地址的对象:

>>> from sqlalchemy.orm import aliased
>>> address_alias_1 = aliased(Address)
>>> address_alias_2 = aliased(Address)
>>> print(
...     select(User)
...     .join_from(User, address_alias_1)
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join_from(User, address_alias_2)
...     .where(address_alias_2.email_address == "patrick@gmail.com")
... )
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  address  AS  address_1  ON  user_account.id  =  address_1.user_id
JOIN  address  AS  address_2  ON  user_account.id  =  address_2.user_id
WHERE  address_1.email_address  =  :email_address_1
AND  address_2.email_address  =  :email_address_2 

提示

如 设置 ON 子句 中所述,ORM 还提供了另一种使用 relationship() 构造进行连接的方法。使用别名的上述示例在使用关系将别名目标连接起来中使用了 relationship()。 ### ORM 实体别名

FromClause.alias() 方法的 ORM 等效方法是 ORM aliased() 函数,可以应用于实体,如 UserAddress。这在内部产生一个 Alias 对象,针对原始映射的 Table 对象,同时保持 ORM 功能。下面的 SELECT 从 User 实体中选择包含两个特定电子邮件地址的所有对象:

>>> from sqlalchemy.orm import aliased
>>> address_alias_1 = aliased(Address)
>>> address_alias_2 = aliased(Address)
>>> print(
...     select(User)
...     .join_from(User, address_alias_1)
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join_from(User, address_alias_2)
...     .where(address_alias_2.email_address == "patrick@gmail.com")
... )
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  address  AS  address_1  ON  user_account.id  =  address_1.user_id
JOIN  address  AS  address_2  ON  user_account.id  =  address_2.user_id
WHERE  address_1.email_address  =  :email_address_1
AND  address_2.email_address  =  :email_address_2 

提示

如 设置 ON 子句 中所述,ORM 提供了另一种使用 relationship() 构造连接的方式。上面使用别名的示例是在 使用关系连接别名目标 中使用 relationship() 进行演示的。

子查询和公共表达式

SQL 中的子查询是一个放在括号中并放置在封闭语句上下文中的 SELECT 语句,通常是一个 SELECT 语句,但不一定是这样。

本节将涵盖所谓的“非标量”子查询,通常放置在封闭 SELECT 的 FROM 子句中。我们还将介绍所谓的公共表达式或 CTE,它与子查询类似,但包括其他功能。

SQLAlchemy 使用 Subquery 对象来表示子查询,使用 CTE 来表示公共表达式,通常可以通过 Select.subquery()Select.cte() 方法获取。这两种对象都可以作为一个更大的 select() 结构中的 FROM 元素。

我们可以构造一个 Subquery,它将从 address 表中选择行的聚合计数(聚合函数和 GROUP BY 在之前的 带有 GROUP BY / HAVING 的聚合函数 中介绍过):

>>> subq = (
...     select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .subquery()
... )

仅将子查询字符串化而不将其嵌入到另一个 Select 或其他语句中会产生不包含任何括号的普通 SELECT 语句:

>>> print(subq)
SELECT  count(address.id)  AS  count,  address.user_id
FROM  address  GROUP  BY  address.user_id 

Subquery 对象的行为类似于任何其他 FROM 对象,比如 Table,特别是它包含一个 Subquery.c 命名空间,其中包括它所选择的列。我们可以使用此命名空间来引用 user_id 列以及我们自定义标记的 count 表达式:

>>> print(select(subq.c.user_id, subq.c.count))
SELECT  anon_1.user_id,  anon_1.count
FROM  (SELECT  count(address.id)  AS  count,  address.user_id  AS  user_id
FROM  address  GROUP  BY  address.user_id)  AS  anon_1 

通过在 subq 对象中包含的行的选择,我们可以将对象应用于较大的 Select ,该对象将数据连接到 user_account 表:

>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
...     user_table, subq
... )

>>> print(stmt)
SELECT  user_account.name,  user_account.fullname,  anon_1.count
FROM  user_account  JOIN  (SELECT  count(address.id)  AS  count,  address.user_id  AS  user_id
FROM  address  GROUP  BY  address.user_id)  AS  anon_1  ON  user_account.id  =  anon_1.user_id 

为了从 user_account 连接到 address,我们使用了 Select.join_from() 方法。正如之前所说明的,此连接的 ON 子句再次是根据外键约束 推断 出来的。即使 SQL 子查询本身没有任何约束,SQLAlchemy 也可以根据在列上表示的约束来处理列上的约束,确定 subq.c.user_id 列是 派生address_table.c.user_id 列,后者表示与 user_table.c.id 列的外键关系,然后用于生成 ON 子句。

通用表达式(CTEs)

SQLAlchemy 中使用 CTE 构造的用法与使用 Subquery 构造的用法几乎相同。通过将 Select.subquery() 方法的调用更改为使用 Select.cte() ,我们可以以相同的方式使用生成的对象作为 FROM 元素,但所呈现的 SQL 是非常不同的常规表达式语法:

>>> subq = (
...     select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .cte()
... )

>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
...     user_table, subq
... )

>>> print(stmt)
WITH  anon_1  AS
(SELECT  count(address.id)  AS  count,  address.user_id  AS  user_id
FROM  address  GROUP  BY  address.user_id)
  SELECT  user_account.name,  user_account.fullname,  anon_1.count
FROM  user_account  JOIN  anon_1  ON  user_account.id  =  anon_1.user_id 

CTE 构造还具有以“递归”样式使用的能力,并且在更复杂的情况下可能由 INSERT、UPDATE 或 DELETE 语句的 RETURNING 子句组成。CTE 的文档字符串包含有关这些额外模式的详细信息。

在这两种情况下,子查询和 CTE 在 SQL 层面上都使用“匿名”名称命名。在 Python 代码中,我们根本不需要提供这些名称。当渲染时,SubqueryCTE 实例的对象标识充当对象的语法标识。在 SQL 中将要呈现的名称可以通过将其作为 Select.subquery()Select.cte() 方法的第一个参数传递来提供。

另见

Select.subquery() - 关于子查询的进一步细节

Select.cte() - 包括如何使用 RECURSIVE 以及面向 DML 的 CTE 的示例

ORM 实体子查询/CTEs

在 ORM 中,aliased() 构造可用于将 ORM 实体(例如我们的 UserAddress 类)与任何表示行源的 FromClause 概念相关联。前面的部分 ORM 实体别名 演示了如何使用 aliased() 将映射类与其映射的 TableAlias 关联起来。这里我们演示了 aliased() 对同一个映射的 Table 生成的 Select 构造的 SubqueryCTE 进行相同操作。

以下是将aliased()应用于Subquery构造的示例,以便可以从其行中提取 ORM 实体。结果显示了一系列UserAddress对象,其中每个Address对象的数据最终来自对address表的子查询,而不是直接来自该表:

>>> subq = select(Address).where(~Address.email_address.like("%@aol.com")).subquery()
>>> address_subq = aliased(Address, subq)
>>> stmt = (
...     select(User, address_subq)
...     .join_from(User, address_subq)
...     .order_by(User.id, address_subq.id)
... )
>>> with Session(engine) as session:
...     for user, address in session.execute(stmt):
...         print(f"{user} {address}")
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.email_address,  anon_1.user_id
FROM  user_account  JOIN
(SELECT  address.id  AS  id,  address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  address.email_address  NOT  LIKE  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.id
[...]  ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
ROLLBACK 

另一个例子如下,除了它使用了CTE构造之外,其他完全相同:

>>> cte_obj = select(Address).where(~Address.email_address.like("%@aol.com")).cte()
>>> address_cte = aliased(Address, cte_obj)
>>> stmt = (
...     select(User, address_cte)
...     .join_from(User, address_cte)
...     .order_by(User.id, address_cte.id)
... )
>>> with Session(engine) as session:
...     for user, address in session.execute(stmt):
...         print(f"{user} {address}")
BEGIN  (implicit)
WITH  anon_1  AS
(SELECT  address.id  AS  id,  address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  address.email_address  NOT  LIKE  ?)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.email_address,  anon_1.user_id
FROM  user_account
JOIN  anon_1  ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.id
[...]  ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
ROLLBACK 

另请参阅

从子查询中选择实体 - 在 ORM 查询指南

公共表达式(CTEs)

使用CTE构造在 SQLAlchemy 中的使用方式与Subquery构造几乎相同。将Select.subquery()方法的调用更改为使用Select.cte(),我们可以以相同的方式将生成的对象用作 FROM 元素,但所呈现的 SQL 语法是非常不同的通用表达式语法:

>>> subq = (
...     select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .cte()
... )

>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
...     user_table, subq
... )

>>> print(stmt)
WITH  anon_1  AS
(SELECT  count(address.id)  AS  count,  address.user_id  AS  user_id
FROM  address  GROUP  BY  address.user_id)
  SELECT  user_account.name,  user_account.fullname,  anon_1.count
FROM  user_account  JOIN  anon_1  ON  user_account.id  =  anon_1.user_id 

CTE构造还具有以“递归”方式使用的能力,并且在更复杂的情况下可以从 INSERT、UPDATE 或 DELETE 语句的 RETURNING 子句组成。CTE的文档字符串包含了有关这些附加模式的详细信息。

在这两种情况下,子查询和 CTE 都在 SQL 级别使用“匿名”名称命名。在 Python 代码中,我们根本不需要提供这些名称。当呈现时,SubqueryCTE实例的对象标识作为对象的句法标识。可以通过将其作为Select.subquery()Select.cte()方法的第一个参数传递来提供将在 SQL 中呈现的名称。

另请参阅

Select.subquery() - 关于子查询的更多细节

Select.cte() - CTE 的示例,包括如何使用 RECURSIVE 以及面向 DML 的 CTE 的示例

ORM 实体子查询/CTEs

在 ORM 中,aliased() 构造可用于将 ORM 实体(例如我们的 UserAddress 类)与代表行来源的任何 FromClause 概念关联起来。上一节 ORM 实体别名 说明了如何使用 aliased() 将映射类与其映射的 TableAlias 关联起来。这里我们说明了 aliased() 如何对一个 Subquery 以及一个针对从同一映射的 Table 派生的 Select 构造的 CTE 进行相同的操作。

下面是将 aliased() 应用于 Subquery 构造的示例,以便从其行中提取 ORM 实体。结果显示了一系列 UserAddress 对象,其中每个 Address 对象的数据最终来自于对 address 表的子查询,而不是直接来自该表:

>>> subq = select(Address).where(~Address.email_address.like("%@aol.com")).subquery()
>>> address_subq = aliased(Address, subq)
>>> stmt = (
...     select(User, address_subq)
...     .join_from(User, address_subq)
...     .order_by(User.id, address_subq.id)
... )
>>> with Session(engine) as session:
...     for user, address in session.execute(stmt):
...         print(f"{user} {address}")
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.email_address,  anon_1.user_id
FROM  user_account  JOIN
(SELECT  address.id  AS  id,  address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  address.email_address  NOT  LIKE  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.id
[...]  ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
ROLLBACK 

另一个例子如下,与之前的例子完全相同,只是使用了 CTE 构造:

>>> cte_obj = select(Address).where(~Address.email_address.like("%@aol.com")).cte()
>>> address_cte = aliased(Address, cte_obj)
>>> stmt = (
...     select(User, address_cte)
...     .join_from(User, address_cte)
...     .order_by(User.id, address_cte.id)
... )
>>> with Session(engine) as session:
...     for user, address in session.execute(stmt):
...         print(f"{user} {address}")
BEGIN  (implicit)
WITH  anon_1  AS
(SELECT  address.id  AS  id,  address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  address.email_address  NOT  LIKE  ?)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.email_address,  anon_1.user_id
FROM  user_account
JOIN  anon_1  ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.id
[...]  ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
ROLLBACK 

另请参阅

从子查询中选择实体 - 在 ORM 查询指南 中

标量和关联子查询

标量子查询是返回零行或一行以及一列的子查询。然后,在封闭的 SELECT 语句的 COLUMNS 或 WHERE 子句中使用该子查询,它与常规子查询不同,因为它不在 FROM 子句中使用。相关子查询是指涉及封闭 SELECT 语句中的表的标量子查询。

SQLAlchemy 使用ScalarSelect结构来表示标量子查询,该结构是ColumnElement表达式层次结构的一部分,与常规子查询不同,常规子查询由Subquery结构表示,后者属于FromClause层次结构。

标量子查询通常与聚合函数一起使用,但不一定要这样做,之前在带有 GROUP BY / HAVING 的聚合函数中介绍过。标量子查询通过显式地使用Select.scalar_subquery()方法来表示,如下所示。当单独字符串化时,默认的字符串形式呈现为一个普通的 SELECT 语句,该语句从两个表中选择:

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .scalar_subquery()
... )
>>> print(subq)
(SELECT  count(address.id)  AS  count_1
FROM  address,  user_account
WHERE  user_account.id  =  address.user_id) 

上述 subq 对象现在属于ColumnElement SQL 表达式层次结构,因此它可以像任何其他列表达式一样使用:

>>> print(subq == 5)
(SELECT  count(address.id)  AS  count_1
FROM  address,  user_account
WHERE  user_account.id  =  address.user_id)  =  :param_1 

尽管单独字符串化时,标量子查询会在其 FROM 子句中同时呈现user_accountaddress,但当将其嵌入到处理user_account表的封闭select()构造中时,user_account表会自动关联,这意味着它不会出现在子查询的 FROM 子句中:

>>> stmt = select(user_table.c.name, subq.label("address_count"))
>>> print(stmt)
SELECT  user_account.name,  (SELECT  count(address.id)  AS  count_1
FROM  address
WHERE  user_account.id  =  address.user_id)  AS  address_count
FROM  user_account 

简单的相关子查询通常会执行所需的正确操作。然而,在相关性不明确的情况下,SQLAlchemy 会提醒我们需要更多的明确性:

>>> stmt = (
...     select(
...         user_table.c.name,
...         address_table.c.email_address,
...         subq.label("address_count"),
...     )
...     .join_from(user_table, address_table)
...     .order_by(user_table.c.id, address_table.c.id)
... )
>>> print(stmt)
Traceback (most recent call last):
...
InvalidRequestError: Select statement '<... Select object at ...>' returned
no FROM clauses due to auto-correlation; specify correlate(<tables>) to
control correlation manually.

要指定我们要关联的user_table是哪一个,我们使用ScalarSelect.correlate()ScalarSelect.correlate_except()方法来指定:

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .scalar_subquery()
...     .correlate(user_table)
... )

然后语句就可以像对待其他列一样返回该列的数据:

>>> with engine.connect() as conn:
...     result = conn.execute(
...         select(
...             user_table.c.name,
...             address_table.c.email_address,
...             subq.label("address_count"),
...         )
...         .join_from(user_table, address_table)
...         .order_by(user_table.c.id, address_table.c.id)
...     )
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name,  address.email_address,  (SELECT  count(address.id)  AS  count_1
FROM  address
WHERE  user_account.id  =  address.user_id)  AS  address_count
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id  ORDER  BY  user_account.id,  address.id
[...]  ()
[('spongebob', 'spongebob@sqlalchemy.org', 1), ('sandy', 'sandy@sqlalchemy.org', 2),
 ('sandy', 'sandy@squirrelpower.org', 2)]
ROLLBACK 

LATERAL 关联

LATERAL 关联是 SQL 关联的一个特殊子类别,允许可选择的单元引用同一 FROM 子句内的另一个可选择单元。这是一个极其特殊的用例,虽然它是 SQL 标准的一部分,但目前只知道最近的 PostgreSQL 版本支持它。

通常,如果 SELECT 语句在其 FROM 子句中引用 table1 JOIN (SELECT ...) AS subquery,右侧的子查询可能无法引用左侧的“table1”表达式;关联只能引用完全包含此 SELECT 的另一个 SELECT 的表。LATERAL 关键字允许我们改变这种行为,并允许来自右侧 JOIN 的关联。

SQLAlchemy 支持使用 Select.lateral() 方法来实现此功能,该方法创建一个称为 Lateral 的对象。LateralSubqueryAlias 属于同一家族,但在将构造添加到包含 SELECT 的 FROM 子句时还包括关联行为。以下示例说明了使用 LATERAL 的 SQL 查询,选择了在前一节中讨论过的“用户账户/电子邮件地址计数”数据:

>>> subq = (
...     select(
...         func.count(address_table.c.id).label("address_count"),
...         address_table.c.email_address,
...         address_table.c.user_id,
...     )
...     .where(user_table.c.id == address_table.c.user_id)
...     .lateral()
... )
>>> stmt = (
...     select(user_table.c.name, subq.c.address_count, subq.c.email_address)
...     .join_from(user_table, subq)
...     .order_by(user_table.c.id, subq.c.email_address)
... )
>>> print(stmt)
SELECT  user_account.name,  anon_1.address_count,  anon_1.email_address
FROM  user_account
JOIN  LATERAL  (SELECT  count(address.id)  AS  address_count,
address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  user_account.id  =  address.user_id)  AS  anon_1
ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.email_address 

在上面的例子中,JOIN 的右侧是一个与左侧连接的 user_account 表的子查询。

使用 Select.lateral() 时,Select.correlate()Select.correlate_except() 方法的行为也适用于 Lateral 构造。

另请参阅

Lateral

Select.lateral() ### LATERAL 关联

横向关联是 SQL 关联的一个特殊子类别,它允许一个可选择的单元在单个 FROM 子句内引用另一个可选择的单元。这是一个非常特殊的用例,虽然是 SQL 标准的一部分,但只有最近版本的 PostgreSQL 已知支持。

通常,如果一个 SELECT 语句在其 FROM 子句中引用了table1 JOIN (SELECT ...) AS subquery,则右侧的子查询可能不会引用左侧的“table1”表达式;关联可能仅引用完全包含此 SELECT 的另一个 SELECT 的表。LATERAL 关键字允许我们改变这种行为,允许从右侧 JOIN 进行关联。

SQLAlchemy 通过Select.lateral()方法支持此功能,该方法创建一个称为横向关联的对象。 横向关联子查询别名属于同一系列,但是当将构造添加到包围 SELECT 的 FROM 子句时,还包括关联行为。以下示例说明了使用 LATERAL 的 SQL 查询,选择了前一节中讨论的“用户帐户/电子邮件地址计数”数据:

>>> subq = (
...     select(
...         func.count(address_table.c.id).label("address_count"),
...         address_table.c.email_address,
...         address_table.c.user_id,
...     )
...     .where(user_table.c.id == address_table.c.user_id)
...     .lateral()
... )
>>> stmt = (
...     select(user_table.c.name, subq.c.address_count, subq.c.email_address)
...     .join_from(user_table, subq)
...     .order_by(user_table.c.id, subq.c.email_address)
... )
>>> print(stmt)
SELECT  user_account.name,  anon_1.address_count,  anon_1.email_address
FROM  user_account
JOIN  LATERAL  (SELECT  count(address.id)  AS  address_count,
address.email_address  AS  email_address,  address.user_id  AS  user_id
FROM  address
WHERE  user_account.id  =  address.user_id)  AS  anon_1
ON  user_account.id  =  anon_1.user_id
ORDER  BY  user_account.id,  anon_1.email_address 

上述,JOIN 的右侧是一个子查询,它与 JOIN 左侧的user_account表相关联。

使用Select.lateral()时,Select.correlate()Select.correlate_except()方法的行为也适用于横向关联构造。

另请参见

横向关联

Select.lateral()

UNION、UNION ALL 和其他集合操作

在 SQL 中,SELECT 语句可以使用 UNION 或 UNION ALL SQL 操作合并在一起,该操作生成由一个或多个语句一起生成的所有行的集合。还可以执行其他集合操作,如 INTERSECT [ALL]和 EXCEPT [ALL]。

SQLAlchemy 的Select构造支持使用诸如union()intersect()except_()之类的函数进行这种性质的组合,以及“all”对应项union_all()intersect_all()except_all()。这些函数都接受任意数量的子可选择项,通常是Select构造,但也可以是现有的组合。

由这些函数生成的构造是CompoundSelect,其使用方式与Select构造相同,只是它的方法较少。例如,由union_all()产生的CompoundSelect可以直接通过Connection.execute()调用:

>>> from sqlalchemy import union_all
>>> stmt1 = select(user_table).where(user_table.c.name == "sandy")
>>> stmt2 = select(user_table).where(user_table.c.name == "spongebob")
>>> u = union_all(stmt1, stmt2)
>>> with engine.connect() as conn:
...     result = conn.execute(u)
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
UNION  ALL  SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[generated  in  ...]  ('sandy',  'spongebob')
[(2, 'sandy', 'Sandy Cheeks'), (1, 'spongebob', 'Spongebob Squarepants')]
ROLLBACK 

要将CompoundSelect用作子查询,就像Select一样,它提供了一个SelectBase.subquery()方法,它将生成一个带有FromClause.c集合的Subquery对象,可以在封闭的select()中引用:

>>> u_subq = u.subquery()
>>> stmt = (
...     select(u_subq.c.name, address_table.c.email_address)
...     .join_from(address_table, u_subq)
...     .order_by(u_subq.c.name, address_table.c.email_address)
... )
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  anon_1.name,  address.email_address
FROM  address  JOIN
  (SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
  FROM  user_account
  WHERE  user_account.name  =  ?
UNION  ALL
  SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
  FROM  user_account
  WHERE  user_account.name  =  ?)
AS  anon_1  ON  anon_1.id  =  address.user_id
ORDER  BY  anon_1.name,  address.email_address
[generated  in  ...]  ('sandy',  'spongebob')
[('sandy', 'sandy@sqlalchemy.org'), ('sandy', 'sandy@squirrelpower.org'), ('spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

从联合中选择 ORM 实体

前面的示例说明了如何构造一个 UNION,给定两个Table对象,然后返回数据库行。如果我们想要使用 UNION 或其他集合操作来选择行,然后将其作为 ORM 对象接收,有两种方法可以使用。在这两种情况下,我们首先构造一个表示我们想要执行的 SELECT / UNION / 等语句的select()CompoundSelect对象;这个语句应该针对目标 ORM 实体或它们的底层映射的Table对象组成:

>>> stmt1 = select(User).where(User.name == "sandy")
>>> stmt2 = select(User).where(User.name == "spongebob")
>>> u = union_all(stmt1, stmt2)

对于一个简单的 SELECT,带有 UNION,它尚未嵌套在子查询内部,通常可以通过使用Select.from_statement()方法在 ORM 对象获取上下文中使用。通过这种方法,UNION 语句代表整个查询;在使用Select.from_statement()之后,不能添加额外的条件:

>>> orm_stmt = select(User).from_statement(u)
>>> with Session(engine) as session:
...     for obj in session.execute(orm_stmt).scalars():
...         print(obj)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?  UNION  ALL  SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[generated  in  ...]  ('sandy',  'spongebob')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
ROLLBACK 

要以更灵活的方式将 UNION 或其他与实体相关的构造用作实体相关组件,可以使用CompoundSelect构造,使用CompoundSelect.subquery()将其组织成子查询,然后使用aliased()函数将其链接到 ORM 对象。这与在 ORM 实体子查询/CTEs 中介绍的方式相同,首先创建我们所需实体的临时“映射”,然后从该新实体选择,就像它是任何其他映射类一样。在下面的示例中,我们能够添加额外的条件,例如在 UNION 之外的 ORDER BY,因为我们可以过滤或按子查询导出的列排序:

>>> user_alias = aliased(User, u.subquery())
>>> orm_stmt = select(user_alias).order_by(user_alias.id)
>>> with Session(engine) as session:
...     for obj in session.execute(orm_stmt).scalars():
...         print(obj)
BEGIN  (implicit)
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname
FROM  (SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.name  =  ?  UNION  ALL  SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.name  =  ?)  AS  anon_1  ORDER  BY  anon_1.id
[generated  in  ...]  ('sandy',  'spongebob')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
ROLLBACK 

另请参阅

从 UNIONs 和其他集合操作中选择实体 - 在 ORM 查询指南 中的 ORM 实体从联合中选择

前面的示例说明了如何在给定两个Table对象的情况下构造一个 UNION,然后返回数据库行。如果我们想要使用 UNION 或其他集合操作来选择行,然后将其作为 ORM 对象接收,有两种方法可以使用。在这两种情况下,我们首先构造一个select()CompoundSelect对象,该对象表示我们要执行的 SELECT / UNION /等语句;此语句应针对目标 ORM 实体或其底层映射的Table对象组成:

>>> stmt1 = select(User).where(User.name == "sandy")
>>> stmt2 = select(User).where(User.name == "spongebob")
>>> u = union_all(stmt1, stmt2)

对于不在子查询内部的简单 SELECT 与 UNION,通常可以使用Select.from_statement()方法在 ORM 对象获取上下文中使用。通过这种方法,UNION 语句表示整个查询;在使用Select.from_statement()之后,不能添加额外的条件:

>>> orm_stmt = select(User).from_statement(u)
>>> with Session(engine) as session:
...     for obj in session.execute(orm_stmt).scalars():
...         print(obj)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?  UNION  ALL  SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[generated  in  ...]  ('sandy',  'spongebob')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
ROLLBACK 

要以更灵活的方式将 UNION 或其他集相关构造用作实体相关组件,可以使用CompoundSelect构造将其组织到子查询中,然后使用CompoundSelect.subquery()将其链接到 ORM 对象,然后使用aliased()函数。这与 ORM 实体子查询/ CTEs 中介绍的方式相同,首先创建我们所需实体到子查询的临时“映射”,然后从该新实体中选择,就像它是任何其他映射类一样。在下面的示例中,我们能够添加额外的条件,例如在 UNION 本身之外进行 ORDER BY,因为我们可以通过子查询导出的列进行过滤或排序:

>>> user_alias = aliased(User, u.subquery())
>>> orm_stmt = select(user_alias).order_by(user_alias.id)
>>> with Session(engine) as session:
...     for obj in session.execute(orm_stmt).scalars():
...         print(obj)
BEGIN  (implicit)
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname
FROM  (SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.name  =  ?  UNION  ALL  SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.name  =  ?)  AS  anon_1  ORDER  BY  anon_1.id
[generated  in  ...]  ('sandy',  'spongebob')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
ROLLBACK 

另请参阅

从 UNION 和其他集合操作中选择实体 - 在 ORM 查询指南中

EXISTS 子查询

SQL EXISTS 关键字是一个与标量子查询一起使用的运算符,根据 SELECT 语句是否返回行来返回布尔值 true 或 false。SQLAlchemy 包含一个名为ExistsScalarSelect对象的变体,它将生成一个 EXISTS 子查询,并且最方便的方式是使用SelectBase.exists()方法生成。下面我们生成一个 EXISTS,以便我们可以返回user_account行,其中address有多于一个相关行。

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .having(func.count(address_table.c.id) > 1)
... ).exists()
>>> with engine.connect() as conn:
...     result = conn.execute(select(user_table.c.name).where(subq))
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name
FROM  user_account
WHERE  EXISTS  (SELECT  count(address.id)  AS  count_1
FROM  address
WHERE  user_account.id  =  address.user_id  GROUP  BY  address.user_id
HAVING  count(address.id)  >  ?)
[...]  (1,)
[('sandy',)]
ROLLBACK 

EXISTS 结构更常用于否定,例如 NOT EXISTS,因为它提供了一种 SQL 效率高的方式来定位一个相关表没有行的行。下面我们选择没有电子邮件地址的用户名称;注意在第二个 WHERE 子句中使用的二进制否定运算符(~):

>>> subq = (
...     select(address_table.c.id).where(user_table.c.id == address_table.c.user_id)
... ).exists()
>>> with engine.connect() as conn:
...     result = conn.execute(select(user_table.c.name).where(~subq))
...     print(result.all())
BEGIN  (implicit)
SELECT  user_account.name
FROM  user_account
WHERE  NOT  (EXISTS  (SELECT  address.id
FROM  address
WHERE  user_account.id  =  address.user_id))
[...]  ()
[('patrick',)]
ROLLBACK 

使用 SQL 函数

此部分较早前在带有 GROUP BY / HAVING 的聚合函数中首次介绍,func对象用作创建新的Function对象的工厂,在像select()这样的构造中使用时,会产生一个 SQL 函数显示,通常包含一个名称、一些括号(尽管不总是),以及可能的一些参数。典型 SQL 函数的示例包括:

  • count()函数,一个聚合函数,用于计算返回的行数:

    >>> print(select(func.count()).select_from(user_table))
    SELECT  count(*)  AS  count_1
    FROM  user_account 
    
  • lower()函数,一个字符串函数,用于将字符串转换为小写:

    >>> print(select(func.lower("A String With Much UPPERCASE")))
    SELECT  lower(:lower_2)  AS  lower_1 
    
  • now()函数,提供当前日期和时间;由于这是一个常见的函数,SQLAlchemy 知道如何为每个后端呈现不同的结果,在 SQLite 中使用 CURRENT_TIMESTAMP 函数:

    >>> stmt = select(func.now())
    >>> with engine.connect() as conn:
    ...     result = conn.execute(stmt)
    ...     print(result.all())
    BEGIN  (implicit)
    SELECT  CURRENT_TIMESTAMP  AS  now_1
    [...]  ()
    [(datetime.datetime(...),)]
    ROLLBACK 
    

由于大多数数据库后端包含数十甚至数百个不同的 SQL 函数,func尝试在接受的内容上尽可能宽松。从此命名空间中访问的任何名称都会自动被视为一个 SQL 函数,以一种通用的方式呈现:

>>> print(select(func.some_crazy_function(user_table.c.name, 17)))
SELECT  some_crazy_function(user_account.name,  :some_crazy_function_2)  AS  some_crazy_function_1
FROM  user_account 

同时,一组相对较小但极其常见的 SQL 函数,比如 countnowmaxconcat 等,包含了它们自己的预打包版本,这些版本提供了正确的类型信息以及在某些情况下特定于后端的 SQL 生成。下面的示例对比了 PostgreSQL 方言和 Oracle 方言中 now 函数的 SQL 生成:

>>> from sqlalchemy.dialects import postgresql
>>> print(select(func.now()).compile(dialect=postgresql.dialect()))
SELECT  now()  AS  now_1
>>> from sqlalchemy.dialects import oracle
>>> print(select(func.now()).compile(dialect=oracle.dialect()))
SELECT  CURRENT_TIMESTAMP  AS  now_1  FROM  DUAL 

函数具有返回类型

由于函数是列表达式,它们还有描述生成的 SQL 表达式的数据类型的 SQL 数据类型。我们在这里将这些类型称为“SQL 返回类型”,指的是在数据库端 SQL 表达式的上下文中函数返回的 SQL 值类型,而不是 Python 函数的“返回类型”。

任何 SQL 函数的 SQL 返回类型都可以访问,通常用于调试目的,方法是引用 Function.type 属性:

>>> func.now().type
DateTime()

这些 SQL 返回类型在使用函数表达式时非常重要,特别是在更大表达式的上下文中;也就是说,当表达式的数据类型是类似 IntegerNumeric 这样的类型时,数学运算符将更有效,为了让 JSON 访问器正常工作,需要使用类似 JSON 这样的类型。某些类别的函数返回整行而不是列值,在需要引用特定列的情况下;这些函数被称为表值函数。

当执行语句并获取行时,函数的 SQL 返回类型也可能很重要,对于 SQLAlchemy 需要应用结果集处理的情况来说尤其如此。SQLite 上的日期相关函数是一个典型例子,其中 SQLAlchemy 的 DateTime 和相关数据类型在接收到结果行时起到将字符串值转换为 Python datetime() 对象的作用。

要将特定类型应用于我们正在创建的函数,我们可以使用 Function.type_ 参数进行传递;类型参数可以是 TypeEngine 类或实例。在下面的示例中,我们将 JSON 类传递给生成 PostgreSQL json_object() 函数,注意 SQL 返回类型将是 JSON 类型:

>>> from sqlalchemy import JSON
>>> function_expr = func.json_object('{a, 1, b, "def", c, 3.5}', type_=JSON)

通过使用具有 JSON 数据类型的 JSON 函数,SQL 表达式对象具有与 JSON 相关的功能,例如访问元素:

>>> stmt = select(function_expr["def"])
>>> print(stmt)
SELECT  json_object(:json_object_1)[:json_object_2]  AS  anon_1 

内置函数具有预配置的返回类型

对于像countmaxmin这样的常见聚合函数,以及一些非常少数的日期函数,比如now和字符串函数,SQL 返回类型会根据使用情况进行适当设置。max函数和类似的聚合过滤函数将根据给定的参数设置 SQL 返回类型:

>>> m1 = func.max(Column("some_int", Integer))
>>> m1.type
Integer()

>>> m2 = func.max(Column("some_str", String))
>>> m2.type
String()

日期和时间函数通常对应于由 DateTimeDateTime 描述的 SQL 表达式:

>>> func.now().type
DateTime()
>>> func.current_date().type
Date()

已知的字符串函数,如 concat,将知道 SQL 表达式的类型将是 String

>>> func.concat("x", "y").type
String()

但是,对于绝大多数 SQL 函数,SQLAlchemy 并没有在其极少量的已知函数列表中明确地提供它们。例如,虽然通常使用 SQL 函数 func.lower()func.upper() 来转换字符串的大小写没有问题,但 SQLAlchemy 实际上并不知道这些函数,因此它们具有“null”SQL 返回类型:

>>> func.upper("lowercase").type
NullType()

对于像upperlower这样的简单函数,问题通常不是很重要,因为字符串值可能从数据库接收而不需要在 SQLAlchemy 端进行任何特殊类型处理,而且 SQLAlchemy 的类型强制规则通常也可以正确猜测意图;例如,Python 的+运算符将根据表达式两侧的内容正确解释为字符串连接运算符:

>>> print(select(func.upper("lowercase") + " suffix"))
SELECT  upper(:upper_1)  ||  :upper_2  AS  anon_1 

总的来说,Function.type_参数可能是必要的情况是:

  1. 函数不是 SQLAlchemy 内置函数;这可以通过创建函数并观察Function.type属性来证明,即:

    >>> func.count().type
    Integer()
    

    vs.:

    >>> func.json_object('{"a", "b"}').type
    NullType()
    
  2. 需要函数感知表达式支持;这通常指的是与数据类型相关的特殊运算符,如JSONARRAY

  3. 需要结果值处理,可能包括诸如DateTimeBooleanEnum等类型,或者再次特殊数据类型,如JSONARRAY

高级 SQL 函数技术

以下各小节说明了 SQL 函数可以做的更多事情。虽然这些技术比基本 SQL 函数使用更不常见和更高级,但它们仍然非常流行,主要是由于 PostgreSQL 强调更复杂的函数形式,包括对 JSON 数据流行的表和列值形式。

使用窗口函数

窗口函数是 SQL 聚合函数的一种特殊用法,它在处理单个结果行时计算返回组中的行上的聚合值。而像MAX()这样的函数会给出一组行中某一列的最高值,使用相同函数作为“窗口函数”将为每一行给出最高值,截至该行

在 SQL 中,窗口函数允许指定应用函数的行,一个“分区”值,考虑窗口在不同子行集上的情况,以及一个“order by”表达式,重要的是指示应用到聚合函数的行的顺序。

在 SQLAlchemy 中,func命名空间生成的所有 SQL 函数都包括一个FunctionElement.over()方法,该方法授予窗口函数或“OVER”语法;生成的构造是Over构造函数。

与窗口函数一起使用的常见函数是row_number()函数,它简单地计算行数。我们可以将这个行数按用户名分区,以为每个用户的电子邮件地址编号:

>>> stmt = (
...     select(
...         func.row_number().over(partition_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  row_number()  OVER  (PARTITION  BY  user_account.name)  AS  anon_1,
user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
[(1, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (1, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

上述中,FunctionElement.over.partition_by参数用于在 OVER 子句内呈现 PARTITION BY 子句。我们还可以使用FunctionElement.over.order_by来使用 ORDER BY 子句:

>>> stmt = (
...     select(
...         func.count().over(order_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  count(*)  OVER  (ORDER  BY  user_account.name)  AS  anon_1,
user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
[(2, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (3, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

窗口函数的进一步选项包括使用范围;更多示例请参见over()

提示

需要注意的是,FunctionElement.over()方法仅适用于实际上是聚合函数的 SQL 函数;虽然Over构造函数将愉快地为任何给定的 SQL 函数呈现自身,但如果函数本身不是 SQL 聚合函数,则数据库将拒绝表达式。#### 特殊修饰符 WITHIN GROUP, FILTER

“WITHIN GROUP” SQL 语法与“ordered set”或“假设性集合”聚合函数一起使用。常见的“ordered set”函数包括percentile_cont()rank()。SQLAlchemy 包括内置实现rankdense_rankmodepercentile_contpercentile_disc等函数,并包括一个FunctionElement.within_group()方法:

>>> print(
...     func.unnest(
...         func.percentile_disc([0.25, 0.5, 0.75, 1]).within_group(user_table.c.name)
...     )
... )
unnest(percentile_disc(:percentile_disc_1)  WITHIN  GROUP  (ORDER  BY  user_account.name)) 

“FILTER” 受一些后端支持,可以通过使用 FunctionElement.filter() 方法将聚合函数的范围限制为与返回的总行范围相比的特定子集:

>>> stmt = (
...     select(
...         func.count(address_table.c.email_address).filter(user_table.c.name == "sandy"),
...         func.count(address_table.c.email_address).filter(
...             user_table.c.name == "spongebob"
...         ),
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  count(address.email_address)  FILTER  (WHERE  user_account.name  =  ?)  AS  anon_1,
count(address.email_address)  FILTER  (WHERE  user_account.name  =  ?)  AS  anon_2
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ('sandy',  'spongebob')
[(2, 1)]
ROLLBACK 
```  #### 表值函数

表值 SQL 函数支持包含命名子元素的标量表示。通常用于 JSON 和 ARRAY 导向函数以及诸如 `generate_series()` 等函数,表值函数在 FROM 子句中指定,然后被引用为表,有时甚至被引用为列。这种形式的函数在 PostgreSQL 数据库中非常突出,然而某些形式的表值函数也受到 SQLite、Oracle 和 SQL Server 的支持。

另请参阅

表值、表值和列值函数、行和元组对象 - 在 PostgreSQL 文档中。

虽然许多数据库支持表值函数和其他特殊形式,但 PostgreSQL 往往是对这些功能需求最多的地方。有关 PostgreSQL 语法的其他示例以及其他功能,请参见本节。

SQLAlchemy 提供了 `FunctionElement.table_valued()` 方法作为基本的“表值函数”构造,它将一个 `func` 对象转换为一个包含一系列命名列的 FROM 子句,这些命名是按位置传递的字符串名称。这将返回一个 `TableValuedAlias` 对象,它是一个启用函数的 `Alias` 构造,可以像其他 FROM 子句一样使用,如 Using Aliases 中介绍的那样。下面我们演示了 `json_each()` 函数,虽然它在 PostgreSQL 上很常见,但也被现代版本的 SQLite 支持:

```py
>>> onetwothree = func.json_each('["one", "two", "three"]').table_valued("value")
>>> stmt = select(onetwothree).where(onetwothree.c.value.in_(["two", "three"]))
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     result.all()
BEGIN  (implicit)
SELECT  anon_1.value
FROM  json_each(?)  AS  anon_1
WHERE  anon_1.value  IN  (?,  ?)
[...]  ('["one", "two", "three"]',  'two',  'three')
[('two',), ('three',)]
ROLLBACK 

在上面的示例中,我们使用了 SQLite 和 PostgreSQL 支持的 json_each() JSON 函数来生成一个包含一个称为 value 的单列的表值表达式,然后选择了其中的两行。

另请参阅

表值函数 - 在 PostgreSQL 文档中 - 本节将详细说明其他语法,例如特殊列推导和“WITH ORDINALITY”,这些语法已知适用于 PostgreSQL。 #### 列值函数 - 表值函数作为标量列

PostgreSQL 和 Oracle 支持的一种特殊语法是在 FROM 子句中引用函数,然后在 SELECT 语句或其他列表达式上下文中将其自身作为单个列传递。PostgreSQL 在诸如json_array_elements()json_object_keys()json_each_text()json_each()等函数中广泛使用此语法。

SQLAlchemy 将此称为“列值”函数,并通过将FunctionElement.column_valued()修饰符应用于Function构造来使用:

>>> from sqlalchemy import select, func
>>> stmt = select(func.json_array_elements('["one", "two"]').column_valued("x"))
>>> print(stmt)
SELECT  x
FROM  json_array_elements(:json_array_elements_1)  AS  x 

“列值”形式也受 Oracle 方言支持,可用于自定义 SQL 函数:

>>> from sqlalchemy.dialects import oracle
>>> stmt = select(func.scalar_strings(5).column_valued("s"))
>>> print(stmt.compile(dialect=oracle.dialect()))
SELECT  s.COLUMN_VALUE
FROM  TABLE  (scalar_strings(:scalar_strings_1))  s 

另请参阅

列值函数 - 在 PostgreSQL 文档中。

函数具有返回类型

由于函数是列表达式,它们还具有描述生成的 SQL 表达式的数据类型的 SQL 数据类型。我们在这里将这些类型称为“SQL 返回类型”,指的是在数据库端 SQL 表达式的上下文中函数返回的 SQL 值的类型,而不是 Python 函数的“返回类型”。

任何 SQL 函数的 SQL 返回类型可以通过引用Function.type属性来访问,通常用于调试目的:

>>> func.now().type
DateTime()

当在更大表达式的上下文中使用函数表达式时,这些 SQL 返回类型很重要;也就是说,数学运算符在表达式的数据类型为IntegerNumeric时会更好地工作,为了使 JSON 访问器正常工作,需要使用诸如JSON之类的类型。某些类别的函数返回整行而不是列值,需要引用特定列;这些函数被称为表值函数。

当执行语句并获取行时,函数的 SQL 返回类型也可能很重要,对于那些 SQLAlchemy 需要应用结果集处理的情况。一个典型的例子是 SQLite 上的日期相关函数,在那里 SQLAlchemy 的DateTime和相关数据类型扮演着将字符串值转换为 Python datetime()对象的角色,当接收到结果行时。

要将特定类型应用于我们正在创建的函数,我们使用 Function.type_ 参数传递它;类型参数可以是 TypeEngine 类,也可以是一个实例。在下面的示例中,我们传递 JSON 类来生成 PostgreSQL json_object() 函数,注意 SQL 返回类型将是 JSON 类型:

>>> from sqlalchemy import JSON
>>> function_expr = func.json_object('{a, 1, b, "def", c, 3.5}', type_=JSON)

通过使用 JSON 数据类型创建我们的 JSON 函数,SQL 表达式对象具有了 JSON 相关的功能,比如访问元素:

>>> stmt = select(function_expr["def"])
>>> print(stmt)
SELECT  json_object(:json_object_1)[:json_object_2]  AS  anon_1 

内置函数具有预配置的返回类型

对于常见的聚合函数,比如 countmaxmin 以及极少数日期函数,比如 now 和字符串函数,比如 concat,SQL 返回类型将被适当地设置,有时根据使用情况。max 函数和类似的聚合过滤函数将根据给定的参数设置 SQL 返回类型:

>>> m1 = func.max(Column("some_int", Integer))
>>> m1.type
Integer()

>>> m2 = func.max(Column("some_str", String))
>>> m2.type
String()

日期和时间函数通常对应于由 DateTimeDateTime 描述的 SQL 表达式:

>>> func.now().type
DateTime()
>>> func.current_date().type
Date()

一个已知的字符串函数,比如 concat,会知道 SQL 表达式的类型将是 String

>>> func.concat("x", "y").type
String()

然而,对于绝大多数 SQL 函数,SQLAlchemy 并没有在其非常小的已知函数列表中显式地提供它们。例如,虽然通常可以使用 SQL 函数 func.lower()func.upper() 来转换字符串的大小写,但 SQLAlchemy 实际上并不知道这些函数,因此它们具有“null”SQL 返回类型:

>>> func.upper("lowercase").type
NullType()

对于诸如 upperlower 这样的简单函数,通常问题并不重要,因为字符串值可以直接从数据库接收,SQLAlchemy 方面不需要进行任何特殊类型处理,而 SQLAlchemy 的类型强制转换规则通常可以正确猜测意图;例如,Python 的 + 操作符将根据表达式的两侧正确解释为字符串连接操作符:

>>> print(select(func.upper("lowercase") + " suffix"))
SELECT  upper(:upper_1)  ||  :upper_2  AS  anon_1 

总的来说,Function.type_ 参数可能是必要的场景是:

  1. 如果函数不是 SQLAlchemy 内置函数,则需要创建该函数并观察 Function.type 属性,即:

    >>> func.count().type
    Integer()
    

    vs.:

    >>> func.json_object('{"a", "b"}').type
    NullType()
    
  2. 需要函数感知表达式支持;这通常指的是与数据类型相关的特殊运算符,如 JSON 或者 ARRAY

  3. 需要进行结果值处理,可能涉及到诸如 DateTimeBooleanEnum 或者特殊的数据类型如 JSONARRAY

高级 SQL 函数技巧

以下各小节说明了可以使用 SQL 函数执行的更多操作。虽然这些技术比基本的 SQL 函数使用更少见、更高级,但它们仍然非常受欢迎,主要是由于 PostgreSQL 对更复杂的函数形式的强调,包括对 JSON 数据非常流行的表值和列值形式。

使用窗口函数

窗口函数是 SQL 聚合函数的一种特殊用法,它在处理个别结果行时计算返回组中的行的聚合值。而像 MAX() 这样的函数会给出一组行中的列的最大值,使用同样的函数作为“窗口函数”将为每一行给出最高的值,截至到那一行

在 SQL 中,窗口函数允许指定应应用函数的行,一个“分区”值,它考虑在不同行子集上的窗口,以及一个“order by”表达式,它重要地指示应该将行应用到聚合函数的顺序。

在 SQLAlchemy 中,由 func 命名空间生成的所有 SQL 函数都包括一个方法 FunctionElement.over(),它授予了窗口函数或“OVER”语法;产生的构造是 Over 构造。

与窗口函数一起使用的常见函数是 row_number() 函数,它简单地计算行数。我们可以根据用户名对此行计数进行分区,以对各个用户的电子邮件地址进行编号:

>>> stmt = (
...     select(
...         func.row_number().over(partition_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  row_number()  OVER  (PARTITION  BY  user_account.name)  AS  anon_1,
user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
[(1, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (1, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

在上述代码中,FunctionElement.over.partition_by 参数被使用,以便在 OVER 子句中呈现 PARTITION BY 子句。我们还可以使用 FunctionElement.over.order_by 使用 ORDER BY 子句:

>>> stmt = (
...     select(
...         func.count().over(order_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  count(*)  OVER  (ORDER  BY  user_account.name)  AS  anon_1,
user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
[(2, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (3, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

更多窗口函数的选项包括使用范围;有关更多示例,请参阅over()

提示

需要注意的是,FunctionElement.over() 方法仅适用于实际上是聚合函数的 SQL 函数;虽然 Over 构造将愉快地为任何给定的 SQL 函数呈现自身,但如果函数本身不是 SQL 聚合函数,数据库将拒绝表达式。 #### 特殊修饰符 WITHIN GROUP, FILTER

“WITHIN GROUP” SQL 语法与“有序集”或“假设集”聚合函数结合使用。常见的“有序集”函数包括 percentile_cont()rank()。SQLAlchemy 包括内置实现 rankdense_rankmodepercentile_contpercentile_disc,其中包括一个 FunctionElement.within_group() 方法:

>>> print(
...     func.unnest(
...         func.percentile_disc([0.25, 0.5, 0.75, 1]).within_group(user_table.c.name)
...     )
... )
unnest(percentile_disc(:percentile_disc_1)  WITHIN  GROUP  (ORDER  BY  user_account.name)) 

“FILTER” 受到某些后端的支持,用于将聚合函数的范围限制为与返回的总行数的特定子集相比较,可使用 FunctionElement.filter() 方法获得:

>>> stmt = (
...     select(
...         func.count(address_table.c.email_address).filter(user_table.c.name == "sandy"),
...         func.count(address_table.c.email_address).filter(
...             user_table.c.name == "spongebob"
...         ),
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  count(address.email_address)  FILTER  (WHERE  user_account.name  =  ?)  AS  anon_1,
count(address.email_address)  FILTER  (WHERE  user_account.name  =  ?)  AS  anon_2
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ('sandy',  'spongebob')
[(2, 1)]
ROLLBACK 
```#### 表值函数

表值 SQL 函数支持包含命名子元素的标量表示。通常用于 JSON 和数组导向的函数以及诸如 `generate_series()` 等函数,表值函数在 FROM 子句中指定,然后被引用为表,有时甚至被引用为列。这种形式的函数在 PostgreSQL 数据库中非常突出,但某些形式的表值函数也受到 SQLite、Oracle 和 SQL Server 的支持。

另请参阅

表值、表和列值函数、行和元组对象 - 在 PostgreSQL 文档中。

虽然许多数据库支持表值和其他特殊形式,但 PostgreSQL 往往是这些特性需求最大的地方。有关 PostgreSQL 语法的其他示例以及其他功能,请参阅本节。

SQLAlchemy 提供了 `FunctionElement.table_valued()` 方法作为基本的“表值函数”构造,它将 `func` 对象转换为包含一系列命名列的 FROM 子句,基于传递的字符串名称位置。这将返回一个 `TableValuedAlias` 对象,这是一个启用了函数的 `Alias` 构造,可以像介绍中的使用别名那样用作任何其他 FROM 子句。下面我们举例说明 `json_each()` 函数,尽管在 PostgreSQL 上很常见,但现代版本的 SQLite 也支持它:

```py
>>> onetwothree = func.json_each('["one", "two", "three"]').table_valued("value")
>>> stmt = select(onetwothree).where(onetwothree.c.value.in_(["two", "three"]))
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     result.all()
BEGIN  (implicit)
SELECT  anon_1.value
FROM  json_each(?)  AS  anon_1
WHERE  anon_1.value  IN  (?,  ?)
[...]  ('["one", "two", "three"]',  'two',  'three')
[('two',), ('three',)]
ROLLBACK 

上面,我们使用了 SQLite 和 PostgreSQL 支持的 json_each() JSON 函数来生成一个具有单列的表值表达式,该列被称为 value,然后选择了它的三行中的两行。

另请参阅

表值函数 - 在 PostgreSQL 文档中 - 本节将详细介绍额外的语法,例如特殊列派生和“WITH ORDINALITY”,这些都是已知与 PostgreSQL 兼容的。#### 列值函数 - 表值函数作为标量列

PostgreSQL 和 Oracle 支持的一种特殊语法是在 FROM 子句中引用函数,然后将其自身作为单个列提供给 SELECT 语句或其他列表达式上下文中。 PostgreSQL 非常善于使用此语法,用于诸如json_array_elements()json_object_keys()json_each_text()json_each()等函数。

SQLAlchemy 将此称为“列值”函数,并通过将FunctionElement.column_valued()修改器应用于Function构造来使用:

>>> from sqlalchemy import select, func
>>> stmt = select(func.json_array_elements('["one", "two"]').column_valued("x"))
>>> print(stmt)
SELECT  x
FROM  json_array_elements(:json_array_elements_1)  AS  x 

“列值”形式也受 Oracle 方言支持,可用于自定义 SQL 函数:

>>> from sqlalchemy.dialects import oracle
>>> stmt = select(func.scalar_strings(5).column_valued("s"))
>>> print(stmt.compile(dialect=oracle.dialect()))
SELECT  s.COLUMN_VALUE
FROM  TABLE  (scalar_strings(:scalar_strings_1))  s 

另请参阅

列值函数 - 在 PostgreSQL 文档中。#### 使用窗口函数

窗口函数是 SQL 聚合函数的特殊用法,它计算在处理单个结果行时返回的行中的聚合值。而像MAX()这样的函数将为一组行中的一列给出最高值,将相同函数用作“窗口函数”将为每一行给出最高值,截至该行

在 SQL 中,窗口函数允许指定应用函数的行,一个“分区”值,该值考虑了对不同行子集的窗口,以及一个“order by”表达式,这个表达式重要地指示应用到聚合函数的行的顺序。

在 SQLAlchemy 中,由func命名空间生成的所有 SQL 函数都包含一个方法FunctionElement.over(),该方法授予窗口函数或“OVER”语法;产生的构造是Over构造。

与窗口函数一起使用的常见函数是row_number()函数,它只是计算行数。我们可以将这个行计数按用户名分区,为个别用户的电子邮件地址编号:

>>> stmt = (
...     select(
...         func.row_number().over(partition_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  row_number()  OVER  (PARTITION  BY  user_account.name)  AS  anon_1,
user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
[(1, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (1, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

上面,使用FunctionElement.over.partition_by参数,以便在 OVER 子句中呈现PARTITION BY子句。我们还可以使用FunctionElement.over.order_by使用ORDER BY子句:

>>> stmt = (
...     select(
...         func.count().over(order_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  count(*)  OVER  (ORDER  BY  user_account.name)  AS  anon_1,
user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
[(2, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (3, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK 

窗口函数的进一步选项包括使用范围;更多示例请参见 over()

提示

注意,FunctionElement.over() 方法仅适用于那些实际上是聚合函数的 SQL 函数;而 Over 构造会为任何给定的 SQL 函数自动渲染自身,但如果函数本身不是 SQL 聚合函数,则数据库将拒绝该表达式。

特殊修饰符 WITHIN GROUP, FILTER

“WITHIN GROUP” SQL 语法与“有序集合”或“假设集合”聚合函数一起使用。常见的“有序集合”函数包括 percentile_cont()rank()。SQLAlchemy 包括内置的实现 rankdense_rankmodepercentile_contpercentile_disc,其中包括一个 FunctionElement.within_group() 方法:

>>> print(
...     func.unnest(
...         func.percentile_disc([0.25, 0.5, 0.75, 1]).within_group(user_table.c.name)
...     )
... )
unnest(percentile_disc(:percentile_disc_1)  WITHIN  GROUP  (ORDER  BY  user_account.name)) 

“FILTER” 被一些后端支持,以限制聚合函数的范围仅适用于与返回的行的总范围相比的特定子集,可使用 FunctionElement.filter() 方法:

>>> stmt = (
...     select(
...         func.count(address_table.c.email_address).filter(user_table.c.name == "sandy"),
...         func.count(address_table.c.email_address).filter(
...             user_table.c.name == "spongebob"
...         ),
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN  (implicit)
SELECT  count(address.email_address)  FILTER  (WHERE  user_account.name  =  ?)  AS  anon_1,
count(address.email_address)  FILTER  (WHERE  user_account.name  =  ?)  AS  anon_2
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ('sandy',  'spongebob')
[(2, 1)]
ROLLBACK 

表值函数

表值 SQL 函数支持包含命名子元素的标量表示。表值函数通常用于 JSON 和 ARRAY 导向的函数以及像 generate_series() 这样的函数,该表值函数在 FROM 子句中指定,然后作为表或有时甚至作为列引用。这种形式的函数在 PostgreSQL 数据库中很突出,然而一些形式的表值函数也受 SQLite、Oracle 和 SQL Server 支持。

另请参见

Table values, Table and Column valued functions, Row and Tuple objects - 在 PostgreSQL 文档中。

虽然许多数据库支持表值函数和其他特殊形式,但 PostgreSQL 往往是最需要这些功能的地方。请参阅此部分,了解 PostgreSQL 语法的其他示例以及其他功能。

SQLAlchemy 提供了 FunctionElement.table_valued() 方法作为基本的“表值函数”构造,它将一个 func 对象转换为一个包含一系列命名列的 FROM 子句,这些列是基于按位置传递的字符串名称的。这将返回一个 TableValuedAlias 对象,这是一个启用函数的 Alias 构造,可以像其他 FROM 子句一样使用,如 使用别名 中介绍的。下面我们示例了 json_each() 函数,虽然它在 PostgreSQL 上很常见,但也受到了现代版本 SQLite 的支持:

>>> onetwothree = func.json_each('["one", "two", "three"]').table_valued("value")
>>> stmt = select(onetwothree).where(onetwothree.c.value.in_(["two", "three"]))
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     result.all()
BEGIN  (implicit)
SELECT  anon_1.value
FROM  json_each(?)  AS  anon_1
WHERE  anon_1.value  IN  (?,  ?)
[...]  ('["one", "two", "three"]',  'two',  'three')
[('two',), ('three',)]
ROLLBACK 

在上面的示例中,我们使用了 SQLite 和 PostgreSQL 支持的 json_each() JSON 函数,以生成一个带有一个称为 value 的单列的表值表达式,然后选择了其中的两行。

另请参见

表值函数 - 在 PostgreSQL 文档中 - 此部分将详细介绍一些额外的语法,例如特殊的列派生和“WITH ORDINALITY”,这些语法已知可与 PostgreSQL 一起使用。

列值函数 - 表值函数作为标量列

PostgreSQL 和 Oracle 支持的一个特殊语法是在 FROM 子句中引用函数,然后在 SELECT 语句或其他列表达式上下文的列子句中将其自身作为单列传递。PostgreSQL 在诸如 json_array_elements()json_object_keys()json_each_text()json_each() 等函数中广泛使用此语法。

SQLAlchemy 将此称为“列值”函数,并可通过将 FunctionElement.column_valued() 修饰符应用于 Function 构造来使用:

>>> from sqlalchemy import select, func
>>> stmt = select(func.json_array_elements('["one", "two"]').column_valued("x"))
>>> print(stmt)
SELECT  x
FROM  json_array_elements(:json_array_elements_1)  AS  x 

Oracle 方言也支持“列值”形式,可用于自定义 SQL 函数:

>>> from sqlalchemy.dialects import oracle
>>> stmt = select(func.scalar_strings(5).column_valued("s"))
>>> print(stmt.compile(dialect=oracle.dialect()))
SELECT  s.COLUMN_VALUE
FROM  TABLE  (scalar_strings(:scalar_strings_1))  s 

另请参见

列值函数 - 在 PostgreSQL 文档中。

数据类型转换和类型强制转换

在 SQL 中,我们经常需要明确指示表达式的数据类型,要么是告诉数据库在其他情况下模棱两可的表达式中期望的类型,要么在某些情况下,当我们想要将 SQL 表达式的隐含数据类型转换为其他东西时。SQL CAST 关键字用于此任务,在 SQLAlchemy 中由cast()函数提供。该函数接受列表达式和数据类型对象作为参数,如下所示,我们从user_table.c.id列对象产生一个 SQL 表达式CAST(user_account.id AS VARCHAR)

>>> from sqlalchemy import cast
>>> stmt = select(cast(user_table.c.id, String))
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     result.all()
BEGIN  (implicit)
SELECT  CAST(user_account.id  AS  VARCHAR)  AS  id
FROM  user_account
[...]  ()
[('1',), ('2',), ('3',)]
ROLLBACK 

cast()函数不仅会渲染 SQL CAST 语法,还会生成一个 SQLAlchemy 列表达式,在 Python 端也将作为给定的数据类型。一个被cast()转换为JSON的字符串表达式将获得 JSON 下标和比较运算符,例如:

>>> from sqlalchemy import JSON
>>> print(cast("{'a': 'b'}", JSON)["a"])
CAST(:param_1  AS  JSON)[:param_2] 

type_coerce() - 一个仅限 Python 的“转换”

有时候,需要让 SQLAlchemy 知道表达式的数据类型,出于上述所有原因,但不要在 SQL 端渲染 CAST 表达式本身,因为它可能会干扰已经在没有它的情况下正常工作的 SQL 操作。对于这种相当常见的用例,还有另一个函数type_coerce(),它与cast()密切相关,它设置一个 Python 表达式为具有特定 SQL 数据库类型,但不会在数据库端渲染 CAST 关键字或数据类型。当处理JSON数据类型时,type_coerce()特别重要,它通常与不同平台上的面向字符串的数据类型有着错综复杂的关系,甚至可能不是一个显式的数据类型,比如在 SQLite 和 MariaDB 上。下面,我们使用type_coerce()将 Python 结构传递为 JSON 字符串到 MySQL 的 JSON 函数之一:

>>> import json
>>> from sqlalchemy import JSON
>>> from sqlalchemy import type_coerce
>>> from sqlalchemy.dialects import mysql
>>> s = select(type_coerce({"some_key": {"foo": "bar"}}, JSON)["some_key"])
>>> print(s.compile(dialect=mysql.dialect()))
SELECT  JSON_EXTRACT(%s,  %s)  AS  anon_1 

上面,MySQL 的JSON_EXTRACT SQL 函数被调用,因为我们使用type_coerce()指示我们的 Python 字典应该被视为JSON。Python 的__getitem__运算符,在这种情况下是['some_key'],由此产生并允许一个JSON_EXTRACT路径表达式(但在这种情况下没有显示,最终它将是'$."some_key"')被渲染。

type_coerce() - 一个仅限 Python 的“强制转换”

有时候,需要让 SQLAlchemy 了解表达式的数据类型,出于上述所有原因,但不要在 SQL 端渲染 CAST 表达式本身,因为这可能会干扰已经可以在没有它的情况下工作的 SQL 操作。对于这种相当常见的用例,有另一个函数type_coerce(),它与cast()密切相关,它设置一个 Python 表达式为具有特定 SQL 数据库类型,但不在数据库端渲染 CAST 关键字或数据类型。当处理JSON数据类型时,type_coerce()尤为重要,它通常与不同平台上的字符串导向数据类型有复杂的关系,甚至可能不是显式数据类型,例如在 SQLite 和 MariaDB 上。在下面的例子中,我们使用type_coerce()将 Python 结构传递为 JSON 字符串到 MySQL 的一个 JSON 函数中:

>>> import json
>>> from sqlalchemy import JSON
>>> from sqlalchemy import type_coerce
>>> from sqlalchemy.dialects import mysql
>>> s = select(type_coerce({"some_key": {"foo": "bar"}}, JSON)["some_key"])
>>> print(s.compile(dialect=mysql.dialect()))
SELECT  JSON_EXTRACT(%s,  %s)  AS  anon_1 

上面,MySQL 的JSON_EXTRACT SQL 函数被调用,因为我们使用type_coerce()指示我们的 Python 字典应该被视为JSON。Python 的__getitem__运算符,在这种情况下是['some_key'],由此产生并允许一个JSON_EXTRACT路径表达式(但在这种情况下没有显示,最终它将是'$."some_key"')被渲染。

posted @ 2024-06-22 11:44  绝不原创的飞龙  阅读(8)  评论(0编辑  收藏  举报