SqlAlchemy 2.0 中文文档(十五)

2024-06-26 14:43:38 浏览数 (2)

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

特殊的关系持久性模式

原文:docs.sqlalchemy.org/en/20/orm/relationship_persistence.html

指向自身的行 / 相互依赖的行

这是一种非常特殊的情况,其中 relationship()必须执行一个 INSERT 和一个第二个 UPDATE,以正确填充一行(反之亦然,为了删除而执行一个 UPDATE 和 DELETE,而不违反外键约束)。这两种用例是:

  • 一个表包含对自身的外键,而且单个行将具有指向其自身主键的外键值。
  • 两个表都包含对另一个表的外键引用,每个表中的一行引用另一个表中的另一行。

例如:

代码语言:javascript复制
 user
---------------------------------
user_id    name   related_user_id
   1       'ed'          1

或:

代码语言:javascript复制
 widget                                                  entry
-------------------------------------------             ---------------------------------
widget_id     name        favorite_entry_id             entry_id      name      widget_id
   1       'somewidget'          5                         5       'someentry'     1

在第一种情况下,一行指向自身。从技术上讲,使用诸如 PostgreSQL 或 Oracle 之类的序列的数据库可以使用先前生成的值一次性插入行,但是依赖于自增样式主键标识符的数据库不能。relationship()始终假定在刷新期间以“父/子”模型进行行填充,因此除非直接填充主键/外键列,否则relationship()需要使用两个语句。

在第二种情况下,“widget”行必须在引用的“entry”行之前插入,但是那个“widget”行的“favorite_entry_id”列在生成“entry”行之前无法设置。在这种情况下,通常无法仅使用两个 INSERT 语句插入“widget”和“entry”行;必须执行 UPDATE 以保持外键约束满足。例外情况是如果外键配置为“延迟至提交”(一些数据库支持的功能),并且标识符是手动填充的(再次基本上绕过relationship())。

要启用补充 UPDATE 语句的使用,我们使用relationship.post_update选项的relationship()。这指定了在两个行都被 INSERTED 之后应使用 UPDATE 语句创建两行之间的关联;它还导致在发出 DELETE 之前通过 UPDATE 将行解除关联。该标志应该放置在一个关系上,最好是一对多的一侧。以下我们举例说明了一个完整的例子,包括两个ForeignKey构造:

代码语言:javascript复制
from sqlalchemy import Integer, ForeignKey
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

class Entry(Base):
    __tablename__ = "entry"
    entry_id = mapped_column(Integer, primary_key=True)
    widget_id = mapped_column(Integer, ForeignKey("widget.widget_id"))
    name = mapped_column(String(50))

class Widget(Base):
    __tablename__ = "widget"

    widget_id = mapped_column(Integer, primary_key=True)
    favorite_entry_id = mapped_column(
        Integer, ForeignKey("entry.entry_id", name="fk_favorite_entry")
    )
    name = mapped_column(String(50))

    entries = relationship(Entry, primaryjoin=widget_id == Entry.widget_id)
    favorite_entry = relationship(
        Entry, primaryjoin=favorite_entry_id == Entry.entry_id, post_update=True
    )

当针对上述配置的结构被刷新时,“widget”行将会插入,但不包括“favorite_entry_id”值,然后所有的“entry”行将被插入,引用父“widget”行,然后一个 UPDATE 语句将填充“widget”表的“favorite_entry_id”列(目前每次一行):

代码语言:javascript复制
>>> w1 = Widget(name="somewidget")
>>> e1 = Entry(name="someentry")
>>> w1.favorite_entry = e1
>>> w1.entries = [e1]
>>> session.add_all([w1, e1])
>>> session.commit()
BEGIN  (implicit)
INSERT  INTO  widget  (favorite_entry_id,  name)  VALUES  (?,  ?)
(None,  'somewidget')
INSERT  INTO  entry  (widget_id,  name)  VALUES  (?,  ?)
(1,  'someentry')
UPDATE  widget  SET  favorite_entry_id=?  WHERE  widget.widget_id  =  ?
(1,  1)
COMMIT 

我们可以指定的另一个配置是在Widget上提供一个更全面的外键约束,以确保favorite_entry_id引用的是也引用此WidgetEntry。我们可以使用复合外键,如下所示:

代码语言:javascript复制
from sqlalchemy import (
    Integer,
    ForeignKey,
    String,
    UniqueConstraint,
    ForeignKeyConstraint,
)
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

class Entry(Base):
    __tablename__ = "entry"
    entry_id = mapped_column(Integer, primary_key=True)
    widget_id = mapped_column(Integer, ForeignKey("widget.widget_id"))
    name = mapped_column(String(50))
    __table_args__ = (UniqueConstraint("entry_id", "widget_id"),)

class Widget(Base):
    __tablename__ = "widget"

    widget_id = mapped_column(Integer, autoincrement="ignore_fk", primary_key=True)
    favorite_entry_id = mapped_column(Integer)

    name = mapped_column(String(50))

    __table_args__ = (
        ForeignKeyConstraint(
            ["widget_id", "favorite_entry_id"],
            ["entry.widget_id", "entry.entry_id"],
            name="fk_favorite_entry",
        ),
    )

    entries = relationship(
        Entry, primaryjoin=widget_id == Entry.widget_id, foreign_keys=Entry.widget_id
    )
    favorite_entry = relationship(
        Entry,
        primaryjoin=favorite_entry_id == Entry.entry_id,
        foreign_keys=favorite_entry_id,
        post_update=True,
    )

上面的映射具有一个复合ForeignKeyConstraint,连接widget_idfavorite_entry_id列。为了确保Widget.widget_id保持为“自动增量”列,我们在Column上的autoincrement参数上指定值"ignore_fk",并且在每个relationship()上,我们必须限制那些被视为外键的列,以用于连接和交叉填充。##可变主键/更新级联

当实体的主键更改时,引用主键的相关项也必须更新。对于强制实施引用完整性的数据库,最佳策略是使用数据库的ON UPDATE CASCADE功能,以便将主键更改传播到引用的外键 - 在事务完成之前,值不能不同步,除非约束标记为“可延迟”。

强烈建议一个希望使用可变值的自然主键的应用程序使用数据库的ON UPDATE CASCADE功能。一个说明此功能的示例映射是:

代码语言:javascript复制
class User(Base):
    __tablename__ = "user"
    __table_args__ = {"mysql_engine": "InnoDB"}

    username = mapped_column(String(50), primary_key=True)
    fullname = mapped_column(String(100))

    addresses = relationship("Address")

class Address(Base):
    __tablename__ = "address"
    __table_args__ = {"mysql_engine": "InnoDB"}

    email = mapped_column(String(50), primary_key=True)
    username = mapped_column(
        String(50), ForeignKey("user.username", onupdate="cascade")
    )

在上面的示例中,我们在ForeignKey对象上说明了onupdate="cascade",并且我们还说明了mysql_engine='InnoDB'设置,在 MySQL 后端上,确保使用支持引用完整性的InnoDB引擎。在使用 SQLite 时,应启用引用完整性,使用 Foreign Key Support 中描述的配置。

也请参阅

使用 ORM 关系的外键 ON DELETE 级联 - 支持使用关系的 ON DELETE CASCADE

mapper.passive_updates - 类似Mapper上的功能

模拟有限的 ON UPDATE CASCADE,没有外键支持

在使用不支持引用完整性的数据库,并且使用具有可变值的自然主键时,SQLAlchemy 提供了一个功能,允许将主键值传播到已引用的外键到有限程度,通过针对立即引用主键列的外键列发出 UPDATE 语句,其值已更改。没有引用完整性功能的主要平台是当使用 MyISAM 存储引擎时的 MySQL,以及当没有使用 PRAGMA foreign_keys=ON 时的 SQLite。Oracle 数据库也不支持 ON UPDATE CASCADE,但因为它仍然强制执行引用完整性,需要将约束标记为可延迟,以便 SQLAlchemy 可以发出 UPDATE 语句。

通过将 relationship.passive_updates 标志设置为 False,最好是在一对多或多对多的 relationship() 上。当“更新”不再是“被动”的时候,这表明 SQLAlchemy 将为父对象引用的集合中的对象单独发出 UPDATE 语句,这些对象的主键值会发生变化。这还意味着如果集合尚未在本地存在,集合将被完全加载到内存中。

我们之前使用 passive_updates=False 的映射如下:

代码语言:javascript复制
class User(Base):
    __tablename__ = "user"

    username = mapped_column(String(50), primary_key=True)
    fullname = mapped_column(String(100))

    # passive_updates=False *only* needed if the database
    # does not implement ON UPDATE CASCADE
    addresses = relationship("Address", passive_updates=False)

class Address(Base):
    __tablename__ = "address"

    email = mapped_column(String(50), primary_key=True)
    username = mapped_column(String(50), ForeignKey("user.username"))

passive_updates=False 的关键限制包括:

  • 它的性能远远不及直接数据库的 ON UPDATE CASCADE,因为它需要使用 SELECT 完全预加载受影响的集合,并且还必须发出针对这些值的 UPDATE 语句,它将尝试以“批量”的方式运行,但仍然在 DBAPI 级别上按行运行。
  • 该功能无法“级联”超过一个级别。也就是说,如果映射 X 有一个外键引用映射 Y 的主键,但是然后映射 Y 的主键本身是映射 Z 的外键,passive_updates=False 无法将主键值从 Z 级联到 X
  • 仅在关系的多对一方配置 passive_updates=False 将不会产生完全的效果,因为工作单元仅在当前身份映射中搜索可能引用具有变异主键的对象,而不是在整个数据库中搜索。

由于现在除了 Oracle 外,几乎所有数据库都支持 ON UPDATE CASCADE,因此强烈建议在使用自然且可变的主键值时使用传统的 ON UPDATE CASCADE 支持。## 指向自身的行 / 相互依赖的行

这是一个非常特殊的情况,其中关系(relationship())必须执行 INSERT 和第二个 UPDATE,以便正确填充一行(反之亦然,执行 UPDATE 和 DELETE 以删除而不违反外键约束)。这两种用例是:

  • 一张表包含一个指向自身的外键,而且一行将具有指向自己主键的外键值。
  • 两个表分别包含一个外键引用另一个表,每个表中的一行引用另一个表。

例如:

代码语言:javascript复制
 user
---------------------------------
user_id    name   related_user_id
   1       'ed'          1

或者:

代码语言:javascript复制
 widget                                                  entry
-------------------------------------------             ---------------------------------
widget_id     name        favorite_entry_id             entry_id      name      widget_id
   1       'somewidget'          5                         5       'someentry'     1

在第一种情况下,一行指向自身。从技术上讲,使用序列(如 PostgreSQL 或 Oracle)的数据库可以使用先前生成的值一次性插入行,但依赖自动增量样式主键标识符的数据库则不能。relationship()始终假定在刷新期间使用“父/子”模型来填充行,因此除非直接填充主键/外键列,否则 relationship() 需要使用两个语句。

在第二种情况下,“widget”行必须在任何引用的“entry”行之前插入,但然后该“widget”行的“favorite_entry_id”列在生成“entry”行之前不能设置。在这种情况下,通常不可能只使用两个 INSERT 语句插入“widget”和“entry”行;必须执行 UPDATE 以保持外键约束得到满足。异常情况是,如果外键配置为“延迟到提交”(某些数据库支持的功能),并且标识符是手动填充的(再次基本上绕过relationship())。

为了启用补充的 UPDATE 语句的使用,我们使用relationship()relationship.post_update选项。这指定在两行都被插入后使用 UPDATE 语句创建两行之间的连接;它还导致在发出 DELETE 之前,通过 UPDATE 将行彼此解除关联。这个标志应该放在其中一个关系上,最好是多对一的关系。下面我们举个完整的例子,包括两个ForeignKey构造:

代码语言:javascript复制
from sqlalchemy import Integer, ForeignKey
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

class Entry(Base):
    __tablename__ = "entry"
    entry_id = mapped_column(Integer, primary_key=True)
    widget_id = mapped_column(Integer, ForeignKey("widget.widget_id"))
    name = mapped_column(String(50))

class Widget(Base):
    __tablename__ = "widget"

    widget_id = mapped_column(Integer, primary_key=True)
    favorite_entry_id = mapped_column(
        Integer, ForeignKey("entry.entry_id", name="fk_favorite_entry")
    )
    name = mapped_column(String(50))

    entries = relationship(Entry, primaryjoin=widget_id == Entry.widget_id)
    favorite_entry = relationship(
        Entry, primaryjoin=favorite_entry_id == Entry.entry_id, post_update=True
    )

当针对上述配置刷新结构时,将插入“widget”行,但不包括“favorite_entry_id”值,然后将插入所有“entry”行,引用父“widget”行,然后将“widget”表的“favorite_entry_id”列的 UPDATE 语句(目前一次一行)填充:

代码语言:javascript复制
>>> w1 = Widget(name="somewidget")
>>> e1 = Entry(name="someentry")
>>> w1.favorite_entry = e1
>>> w1.entries = [e1]
>>> session.add_all([w1, e1])
>>> session.commit()
BEGIN  (implicit)
INSERT  INTO  widget  (favorite_entry_id,  name)  VALUES  (?,  ?)
(None,  'somewidget')
INSERT  INTO  entry  (widget_id,  name)  VALUES  (?,  ?)
(1,  'someentry')
UPDATE  widget  SET  favorite_entry_id=?  WHERE  widget.widget_id  =  ?
(1,  1)
COMMIT 

我们可以指定的另一个配置是在 Widget 上提供更全面的外键约束,以确保 favorite_entry_id 指向也指向此 WidgetEntry。我们可以使用复合外键,如下所示:

代码语言:javascript复制
from sqlalchemy import (
    Integer,
    ForeignKey,
    String,
    UniqueConstraint,
    ForeignKeyConstraint,
)
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

class Entry(Base):
    __tablename__ = "entry"
    entry_id = mapped_column(Integer, primary_key=True)
    widget_id = mapped_column(Integer, ForeignKey("widget.widget_id"))
    name = mapped_column(String(50))
    __table_args__ = (UniqueConstraint("entry_id", "widget_id"),)

class Widget(Base):
    __tablename__ = "widget"

    widget_id = mapped_column(Integer, autoincrement="ignore_fk", primary_key=True)
    favorite_entry_id = mapped_column(Integer)

    name = mapped_column(String(50))

    __table_args__ = (
        ForeignKeyConstraint(
            ["widget_id", "favorite_entry_id"],
            ["entry.widget_id", "entry.entry_id"],
            name="fk_favorite_entry",
        ),
    )

    entries = relationship(
        Entry, primaryjoin=widget_id == Entry.widget_id, foreign_keys=Entry.widget_id
    )
    favorite_entry = relationship(
        Entry,
        primaryjoin=favorite_entry_id == Entry.entry_id,
        foreign_keys=favorite_entry_id,
        post_update=True,
    )

上述映射展示了一个由ForeignKeyConstraint组成的复合键,连接着 widget_idfavorite_entry_id 列。为了确保 Widget.widget_id 仍然是一个“自增”的列,我们在Column上指定了 Column.autoincrement 的值为 "ignore_fk",并且在每个relationship()上,我们必须限制那些被视为外键的列以进行连接和交叉填充。

可变主键 / 更新级联

当实体的主键发生变化时,引用该主键的相关项也必须进行更新。对于强制执行引用完整性的数据库,最佳策略是使用数据库的 ON UPDATE CASCADE 功能,以便将主键更改传播到引用的外键 - 除非约束被标记为“可延迟”,即不执行直到事务完成,否则值不能在任何时刻不同步。

强烈建议希望使用可变值的自然主键的应用程序使用数据库的 ON UPDATE CASCADE 功能。一个示例映射如下:

代码语言:javascript复制
class User(Base):
    __tablename__ = "user"
    __table_args__ = {"mysql_engine": "InnoDB"}

    username = mapped_column(String(50), primary_key=True)
    fullname = mapped_column(String(100))

    addresses = relationship("Address")

class Address(Base):
    __tablename__ = "address"
    __table_args__ = {"mysql_engine": "InnoDB"}

    email = mapped_column(String(50), primary_key=True)
    username = mapped_column(
        String(50), ForeignKey("user.username", onupdate="cascade")
    )

在上文中,我们在 ForeignKey 对象上说明了 onupdate="cascade",并且我们还说明了 mysql_engine='InnoDB' 设置,该设置在 MySQL 后端上确保使用支持引用完整性的 InnoDB 引擎。在使用 SQLite 时,应启用引用完整性,使用 外键支持 中描述的配置。

请参阅

使用 ORM 关系的外键 ON DELETE 级联 - 支持使用关系的 ON DELETE CASCADE

mapper.passive_updates - Mapper 上的类似功能

模拟有限的无外键支持的 ON UPDATE CASCADE

当使用不支持引用完整性的数据库,并且存在具有可变值的自然主键时,SQLAlchemy 提供了一项功能,以允许在有限范围内传播主键值到已引用的外键,方法是针对立即引用其值已更改的主键列发出 UPDATE 语句来更新外键列。不支持引用完整性功能的主要平台是在使用MyISAM存储引擎时的 MySQL,以及在未使用PRAGMA foreign_keys=ON指示的情况下的 SQLite。Oracle 数据库也不支持ON UPDATE CASCADE,但因为它仍然强制执行引用完整性,所以需要将约束标记为可延迟,以便 SQLAlchemy 可以发出 UPDATE 语句。

通过将relationship.passive_updates标志设置为False来启用此功能,最好是在一对多或多对多的relationship()上。当“更新”不再“被动”时,这表示 SQLAlchemy 将为引用具有更改的主键值的父对象的集合中的对象单独发出 UPDATE 语句。这也意味着如果集合尚未在本地存在,那么集合将完全加载到内存中。

我们以前使用passive_updates=False的映射如下:

代码语言:javascript复制
class User(Base):
    __tablename__ = "user"

    username = mapped_column(String(50), primary_key=True)
    fullname = mapped_column(String(100))

    # passive_updates=False *only* needed if the database
    # does not implement ON UPDATE CASCADE
    addresses = relationship("Address", passive_updates=False)

class Address(Base):
    __tablename__ = "address"

    email = mapped_column(String(50), primary_key=True)
    username = mapped_column(String(50), ForeignKey("user.username"))

passive_updates=False的关键限制包括:

  • 它的性能比直接的数据库 ON UPDATE CASCADE 要差得多,因为它需要使用 SELECT 完全预加载受影响的集合,并且还必须针对这些值发出 UPDATE 语句,尽管它将尝试以“批处理”的方式运行,但仍然是在 DBAPI 级别上逐行运行。
  • 此功能不能“级联”超过一级。也就是说,如果映射 X 具有一个外键,它引用映射 Y 的主键,但然后映射 Y 的主键本身是对映射 Z 的外键,则passive_updates=False不能将主键值从Z级联更改到X
  • 仅在关系的多对一一侧上配置passive_updates=False将不会产生完全效果,因为工作单元仅通过当前身份映射搜索可能引用具有变异主键的对象,而不是在整个数据库中搜索。

由于几乎所有的数据库现在都支持ON UPDATE CASCADE,因此强烈建议在使用自然且可变的主键值时使用传统的ON UPDATE CASCADE支持。

模拟无外键支持的有限 ON UPDATE CASCADE

在使用不支持引用完整性的数据库且存在可变值的自然主键的情况下,SQLAlchemy 提供了一种功能,允许在已经引用了外键的情况下将主键值传播到一个有限程度,通过针对立即引用已更改主键列值的主键列的 UPDATE 语句进行发射。主要没有引用完整性功能的平台是在使用 MyISAM 存储引擎时的 MySQL,以及在不使用 PRAGMA foreign_keys=ON pragma 的情况下的 SQLite。Oracle 数据库也不支持 ON UPDATE CASCADE,但由于它仍然强制引用完整性,需要将约束标记为可延迟,以便 SQLAlchemy 可以发出 UPDATE 语句。

通过将 relationship.passive_updates 标志设置为 False 来启用此功能,最好在一对多或多对多的 relationship() 上设置。当“更新”不再是“被动”的时候,这表明 SQLAlchemy 将针对父对象引用的集合中的对象单独发出 UPDATE 语句,而这些对象具有正在更改的主键值。这也意味着,如果集合尚未在本地存在,集合将被完全加载到内存中。

我们之前使用 passive_updates=False 的映射如下所示:

代码语言:javascript复制
class User(Base):
    __tablename__ = "user"

    username = mapped_column(String(50), primary_key=True)
    fullname = mapped_column(String(100))

    # passive_updates=False *only* needed if the database
    # does not implement ON UPDATE CASCADE
    addresses = relationship("Address", passive_updates=False)

class Address(Base):
    __tablename__ = "address"

    email = mapped_column(String(50), primary_key=True)
    username = mapped_column(String(50), ForeignKey("user.username"))

passive_updates=False 的关键限制包括:

  • 它的性能远远不如直接的数据库 ON UPDATE CASCADE,因为它需要使用 SELECT 完全预加载受影响的集合,并且还必须发出针对这些值的 UPDATE 语句,尽管它会尝试以“批量”的方式运行,但仍然在 DBAPI 级别逐行运行。
  • 该功能无法“级联”超过一级。也就是说,如果映射 X 有一个外键引用到映射 Y 的主键,但映射 Y 的主键本身是映射 Z 的外键,passive_updates=False 无法将来自 ZX 的主键值更改级联。
  • 仅在关系的多对一侧配置 passive_updates=False 不会产生完全效果,因为工作单元仅在当前标识映射中搜索可能引用具有突变主键的对象,而不是在整个数据库中搜索。

由于除 Oracle 外的几乎所有数据库现在都支持 ON UPDATE CASCADE,因此强烈建议在使用自然和可变主键值的情况下使用传统的 ON UPDATE CASCADE 支持。

使用遗留的 ‘backref’ 关系参数

原文:docs.sqlalchemy.org/en/20/orm/backref.html

注意

应考虑使用遗留的 relationship.backref 关键字,并且应优先使用明确的 relationship() 构造与 relationship.back_populates。使用单独的 relationship() 构造提供了诸如 ORM 映射类都将其属性提前包括在类构造时等优点,而不是作为延迟步骤,并且配置更为直观,因为所有参数都是明确的。SQLAlchemy 2.0 中的新 PEP 484 特性还利用了属性在源代码中明确存在而不是使用动态属性生成。

请参见

有关双向关系的一般信息,请参阅以下部分:

与 ORM 相关对象一起工作 - 在 SQLAlchemy 统一教程 中,介绍了使用 relationship.back_populates 进行双向关联配置和行为的概览。

双向关系中保存更新级联的行为 - 关于双向 relationship() 行为在 Session 级联行为方面的注意事项。

relationship.back_populates

relationship()构造函数中的relationship.backref关键字参数允许自动生成一个新的relationship(),该关系将自动添加到相关类的 ORM 映射中。然后,它将被放置到当前正在配置的relationship()relationship.back_populates配置中,其中两个relationship()构造相互引用。

以以下示例开始:

代码语言:javascript复制
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase, relationship

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String)

    addresses = relationship("Address", backref="user")

class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    email = mapped_column(String)
    user_id = mapped_column(Integer, ForeignKey("user.id"))

以上配置在User上建立了一个名为User.addressesAddress对象集合。它还在Address上建立了一个.user属性,该属性将指向父User对象。使用relationship.back_populates等效于以下操作:

代码语言:javascript复制
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase, relationship

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String)

    addresses = relationship("Address", back_populates="user")

class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    email = mapped_column(String)
    user_id = mapped_column(Integer, ForeignKey("user.id"))

    user = relationship("User", back_populates="addresses")

User.addressesAddress.user关系的行为是以双向方式进行的,表示关系的一侧发生变化会影响另一侧。有关此行为的示例和讨论,请参阅 SQLAlchemy 统一教程的使用 ORM 相关对象部分。

反向引用默认参数

由于relationship.backref会生成一个全新的relationship(),默认情况下,生成过程将尝试在新的relationship()中包含对应于原始参数的相应参数。举例说明,下面是一个包含自定义连接条件的relationship(),该条件还包括relationship.backref关键字:

代码语言:javascript复制
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase, relationship

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String)

    addresses = relationship(
        "Address",
        primaryjoin=(
            "and_(User.id==Address.user_id, Address.email.startswith('tony'))"
        ),
        backref="user",
    )

class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    email = mapped_column(String)
    user_id = mapped_column(Integer, ForeignKey("user.id"))

当生成“backref”时,relationship.primaryjoin条件也被复制到新的relationship()中:

代码语言:javascript复制
>>> print(User.addresses.property.primaryjoin)
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>
>>> print(Address.user.property.primaryjoin)
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>

其他可转移的参数包括relationship.secondary参数,它指的是多对多关联表,以及“join”参数relationship.primaryjoinrelationship.secondaryjoin;“backref”足够智能,知道在生成相反的一侧时这两个参数也应该被“翻转”。

指定反向引用参数

很多其他用于“backref”的参数都不是隐含的,包括像relationship.lazyrelationship.remote_siderelationship.cascaderelationship.cascade_backrefs等参数。对于这种情况,我们使用backref()函数代替字符串;这将存储一组特定的参数,这些参数将在生成新的relationship()时传递:

代码语言:javascript复制
# <other imports>
from sqlalchemy.orm import backref

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String)

    addresses = relationship(
        "Address",
        backref=backref("user", lazy="joined"),
    )

在上面的例子中,我们只在Address.user一侧放置了lazy="joined"指令,这表示当对Address进行查询时,应自动执行与User实体的连接,这将填充每个返回的Address.user属性。 backref()函数将我们给定的参数格式化成一个由接收的relationship()解释的形式,作为应用于它创建的新关系的附加参数。

反向引用默认参数

由于relationship.backref生成了一个全新的relationship(),默认情况下生成过程将尝试在新的relationship()中包含与原始参数相对应的参数。例如,下面是一个包含自定义连接条件的relationship()示例,该连接条件还包括relationship.backref关键字:

代码语言:javascript复制
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase, relationship

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String)

    addresses = relationship(
        "Address",
        primaryjoin=(
            "and_(User.id==Address.user_id, Address.email.startswith('tony'))"
        ),
        backref="user",
    )

class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    email = mapped_column(String)
    user_id = mapped_column(Integer, ForeignKey("user.id"))

当生成“反向引用”时,relationship.primaryjoin条件也会被复制到新的relationship()中:

代码语言:javascript复制
>>> print(User.addresses.property.primaryjoin)
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>
>>> print(Address.user.property.primaryjoin)
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>

可传递的其他参数包括指向多对多关联表的relationship.secondary参数,以及“join”参数relationship.primaryjoinrelationship.secondaryjoin;“反向引用”足够智能,可以知道在生成相反方向时这两个参数也应该“反转”。

指定反向引用参数

“反向引用”的许多其他参数都不是隐式的,包括像relationship.lazyrelationship.remote_siderelationship.cascaderelationship.cascade_backrefs等参数。对于这种情况,我们使用backref()函数来代替字符串;这将存储一组特定的参数,这些参数在生成新的relationship()时将被传递:

代码语言:javascript复制
# <other imports>
from sqlalchemy.orm import backref

class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String)

    addresses = relationship(
        "Address",
        backref=backref("user", lazy="joined"),
    )

在上面的例子中,我们只在Address.user一侧放置了lazy="joined"指令,这意味着当对Address进行查询时,应自动执行与User实体的连接,从而填充每个返回的Address.user属性。backref()函数将我们给定的参数格式化成一种被接收relationship()解释为要应用于它创建的新关系的附加参数的形式。

关系 API

原文:docs.sqlalchemy.org/en/20/orm/relationship_api.html

对象名称

描述

backref(name, **kwargs)

在使用relationship.backref参数时,提供在生成新的relationship()时使用的特定参数。

dynamic_loader([argument], **kw)

构造一个动态加载的映射器属性。

foreign(expr)

使用‘foreign’注释对主要连接表达式的部分进行注释。

relationship([argument, secondary], *, [uselist, collection_class, primaryjoin, secondaryjoin, back_populates, order_by, backref, overlaps, post_update, cascade, viewonly, init, repr, default, default_factory, compare, kw_only, lazy, passive_deletes, passive_updates, active_history, enable_typechecks, foreign_keys, remote_side, join_depth, comparator_factory, single_parent, innerjoin, distinct_target_key, load_on_pending, query_class, info, omit_join, sync_backref], **kw)

提供两个映射类之间的关联。

remote(expr)

使用‘remote’注释对主要连接表达式的部分进行注释。

代码语言:javascript复制
function sqlalchemy.orm.relationship(argument: _RelationshipArgumentType[Any] | None = None, secondary: _RelationshipSecondaryArgument | None = None, *, uselist: bool | None = None, collection_class: Type[Collection[Any]] | Callable[[], Collection[Any]] | None = None, primaryjoin: _RelationshipJoinConditionArgument | None = None, secondaryjoin: _RelationshipJoinConditionArgument | None = None, back_populates: str | None = None, order_by: _ORMOrderByArgument = False, backref: ORMBackrefArgument | None = None, overlaps: str | None = None, post_update: bool = False, cascade: str = 'save-update, merge', viewonly: bool = False, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: _NoArg | _T = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, lazy: _LazyLoadArgumentType = 'select', passive_deletes: Literal['all'] | bool = False, passive_updates: bool = True, active_history: bool = False, enable_typechecks: bool = True, foreign_keys: _ORMColCollectionArgument | None = None, remote_side: _ORMColCollectionArgument | None = None, join_depth: int | None = None, comparator_factory: Type[RelationshipProperty.Comparator[Any]] | None = None, single_parent: bool = False, innerjoin: bool = False, distinct_target_key: bool | None = None, load_on_pending: bool = False, query_class: Type[Query[Any]] | None = None, info: _InfoType | None = None, omit_join: Literal[None, False] = None, sync_backref: bool | None = None, **kw: Any) → _RelationshipDeclared[Any]

提供两个映射类之间的关联。

这对应于父子或关联表关系。构造的类是Relationship的一个实例。

另请参阅

使用 ORM 相关对象 - 在 SQLAlchemy 统一教程中对relationship()进行教程介绍

关系配置 - 叙述性文档

参数:

argument

此参数指的是要相关联的类。它接受几种形式,包括对目标类本身的直接引用,目标类的Mapper实例,将在调用时返回对类或Mapper的引用的 Python 可调用/ lambda,以及类的字符串名称,这将从正在使用的registry中解析类,以便找到该类,例如:

代码语言:javascript复制
class SomeClass(Base):
    # ...

    related = relationship("RelatedClass")

relationship.argument 也可以完全省略不在 relationship() 构造中传递,而是放置在左侧的 Mapped 注释中,如果关系预期为集合,则应包含 Python 集合类型,例如:

代码语言:javascript复制
class SomeClass(Base):
    # ...

    related_items: Mapped[List["RelatedItem"]] = relationship()

或者对于多对一或一对一关系:

代码语言:javascript复制
class SomeClass(Base):
    # ...

    related_item: Mapped["RelatedItem"] = relationship()

另请参阅

使用声明性定义映射属性 - 在使用声明性时关系配置的更多细节。

secondary

对于多对多关系,指定中间表,通常是 Table 的一个实例。在较不常见的情况下,参数也可以指定为 Alias 构造,甚至是 Join 构造。

relationship.secondary 可以作为一个可调用函数传递,该函数在映射初始化时进行评估。使用声明性时,它也可以是一个字符串参数,指示存在于与父映射的 Table 关联的 MetaData 集合中的 Table 的名称。

警告

当作为 Python 可评估字符串传递时,使用 Python 的 eval() 函数解释该参数。不要将不受信任的输入传递给该字符串。有关声明性评估 relationship() 参数的详细信息,请参阅 关系参数的评估 。

relationship.secondary关键字参数通常适用于中间Table在任何直接类映射中没有其他表达的情况。如果“secondary”表也在其他地方明确映射(例如在关联对象中),则应考虑应用relationship.viewonly标志,以便这个relationship()不用于可能与关联对象模式冲突的持久化操作。

另请参阅

多对多 - “多对多”关系的参考示例。

自引用多对多关系 - 在自引用情况下使用多对多的具体细节。

配置多对多关系 - 在使用声明式时的附加选项。

关联对象 - 在组合关联表关系时的一种替代relationship.secondary的方法,允许在关联表上指定附加属性。

复合“次要”连接 - 一种较少使用的模式,在某些情况下可以使复杂的relationship() SQL 条件得以使用。

active_history=False – 当为True时,表示当替换时应加载多对一引用的“先前”值,如果尚未加载。通常,对于简单的多对一引用,历史跟踪逻辑只需要了解“新”值即可执行刷新。此标志适用于使用get_history()并且还需要知道属性的“先前”值的应用程序。

backref

引用一个字符串关系名称,或者一个backref()构造,将被用来自动生成一个新的relationship()在相关类上,然后使用双向relationship.back_populates配置来引用这个类。

在现代 Python 中,应优先使用relationship()relationship.back_populates的显式用法,因为在映射器配置和概念上更为健壮直观。它还与 SQLAlchemy 2.0 中引入的新的PEP 484类型特性集成,而动态生成属性则不支持此特性。

另请参阅

使用传统的 ‘backref’ 关系参数 - 关于使用relationship.backref的注意事项

与 ORM 相关对象的工作 - 在 SQLAlchemy 统一教程中,使用relationship.back_populates提供了双向关系配置和行为的概述

backref() - 在使用relationship.backref时允许控制relationship()的配置。

back_populates

表示与此类同步的相关类上的relationship()的名称。通常期望相关类上的relationship()也参考了这个。这允许每个relationship()两侧的对象同步 Python 状态变化,并为工作单元刷新过程提供指令,指导沿着这些关系的更改如何持久化。

另请参阅

与 ORM 相关对象的工作 - 在 SQLAlchemy 统一教程中,提供了双向关系配置和行为的概述。

基本关系模式 - 包含了许多relationship.back_populates的示例。

relationship.backref - 旧形式,允许更简洁的配置,但不支持显式类型化

overlaps

字符串名称或以逗号分隔的名称集,位于此映射器、后代映射器或目标映射器上,此关系可以与之同时写入相同的外键。此唯一的效果是消除此关系将在持久化时与另一个关系发生冲突的警告。这用于真正可能在写入时与彼此冲突的关系,但应用程序将确保不会发生此类冲突。

新版本 1.4 中新增。

另请参阅

关系 X 将列 Q 复制到列 P,与关系‘Y’冲突 - 用法示例

cascade

一个逗号分隔的级联规则列表,确定 Session 操作应该如何从父级到子级进行“级联”。默认为 False,表示应该使用默认级联 - 此默认级联为 "save-update, merge"

可用的级联包括 save-updatemergeexpungedeletedelete-orphanrefresh-expire。另一个选项 all 表示 "save-update, merge, refresh-expire, expunge, delete" 的简写,通常用于指示相关对象应在所有情况下跟随父对象,并在取消关联时删除。

另请参阅

级联 - 每个可用级联选项的详细信息。

cascade_backrefs=False

旧版本;此标志始终为 False。

在版本 2.0 中更改:“cascade_backrefs” 功能已被移除。

collection_class

一个类或可调用对象,返回一个新的列表持有对象。将用于代替普通列表存储元素。

另请参阅

自定义集合访问 - 入门文档和示例。

comparator_factory

一个扩展了 Comparator 的类,为比较操作提供自定义 SQL 子句生成。

另请参阅

PropComparator - 在此级别重新定义比较器的一些详细信息。

操作符自定义 - 关于这一特性的简要介绍。

distinct_target_key=None

指示“子查询”预加载是否应将 DISTINCT 关键字应用于内层 SELECT 语句。当留空时,当目标列不包括目标表的完整主键时,将应用 DISTINCT 关键字。当设置为 True 时,DISTINCT 关键字将无条件地应用于内层 SELECT。

当 DISTINCT 降低内层子查询的性能超出重复的内层行可能导致的性能时,将此标志设置为 False 可能是合适的。

另请参阅

关系加载技术 - 包括对子查询预加载的介绍。

doc – 将应用于生成描述符的文档字符串。

foreign_keys

要在此 relationship() 对象的 relationship.primaryjoin 条件的上下文中用作“外键”列或引用远程列中的值的列的列表。也就是说,如果此 relationship()relationship.primaryjoin 条件是 a.id == b.a_id,并且要求 b.a_id 中的值在 a.id 中存在,则此 relationship() 的“外键”列是 b.a_id

在正常情况下,不需要 relationship.foreign_keys 参数。relationship() 将根据那些指定了 ForeignKeyColumn 对象或以其他方式列在 ForeignKeyConstraint 构造中的引用列的那些列自动确定在 relationship.primaryjoin 条件中应被视为“外键”列。只有在以下情况下才需要 relationship.foreign_keys 参数:

  1. 从本地表到远程表的连接可以有多种构造方式,因为存在多个外键引用。设置 foreign_keys 将限制 relationship() 仅考虑此处指定的列作为“外键”。
  2. 被映射的 Table 实际上没有 ForeignKeyForeignKeyConstraint 构造存在,通常是因为该表是从不支持外键反射的数据库(MySQL MyISAM)反射而来。
  3. relationship.primaryjoin 参数用于构建非标准的连接条件,该条件使用通常不会引用其“父”列的列或表达式,例如使用 SQL 函数进行的复杂比较表达的连接条件。

relationship()构造引发信息性错误消息时,建议使用relationship.foreign_keys参数,以处理模棱两可的情况。在典型情况下,如果relationship()没有引发任何异常,则通常不需要relationship.foreign_keys参数。

relationship.foreign_keys 也可以传递为一个在映射器初始化时求值的可调用函数,并且在使用声明性时可以传递为 Python 可评估的字符串。

警告

当作为 Python 可评估的字符串传递时,该参数将使用 Python 的eval()函数进行解释。不要将不受信任的输入传递给此字符串。有关使用relationship()参数的声明性评估的详细信息,请参阅关系参数的评估。

另请参阅

处理多个连接路径

创建自定义外键条件

foreign() - 允许在relationship.primaryjoin条件中直接注释“外键”列。

info – 可选数据字典,将被填充到此对象的MapperProperty.info属性中。

innerjoin=False

当为True时,连接式急加载将使用内连接而不是外连接来与相关表连接。该选项的目的通常是性能之一,因为内连接通常比外连接执行得更好。

当关系引用通过不可为空的本地外键引用对象时,或者引用为一对一或保证具有一个或至少一个条目的集合时,可以将此标志设置为True

该选项支持与joinedload.innerjoin相同的“嵌套”和“未嵌套”选项。有关嵌套/未嵌套行为的详细信息,请参阅该标志。

另请参阅

joinedload.innerjoin - 由加载器选项指定的选项,包括嵌套行为的详细信息。

应该使用什么类型的加载? - 讨论各种加载器选项的一些细节。

join_depth

当非None时,表示“急切”加载器应该在自引用或循环关系上连接多少级深度的整数值。该数字计算相同 Mapper 在加载条件中沿着特定连接分支出现的次数。当保持默认值None时,急切加载器在遇到已经在链中较高位置的相同目标映射器时将停止链接。此选项适用于连接和子查询急切加载器。

另请参见

配置自引用急切加载 - 入门文档和示例。

lazy='select'

指定相关项目应该如何加载。默认值为select。值包括:

  • select - 当首次访问属性时,应该懒加载项目,使用一个单独的 SELECT 语句,或者对于简单的多对一引用,使用标识映射获取。
  • immediate - 项目应该在父项加载时加载,使用一个单独的 SELECT 语句,或者对于简单的多对一引用,使用标识映射获取。
  • joined - 项目应该在与父项相同的查询中“急切”加载,使用 JOIN 或 LEFT OUTER JOIN。JOIN 是“外部”的还是不是由relationship.innerjoin参数确定。
  • subquery - 项目应该在父项加载时“急切”加载,使用一个额外的 SQL 语句,为每个请求的集合发出一个 JOIN 到原始语句的子查询。
  • selectin - 项目应该在父项加载时“急切”加载,使用一个或多个额外的 SQL 语句,发出一个 JOIN 到直接父对象,使用 IN 子句指定主键标识符。
  • noload - 任何时候都不应发生加载。相关集合将保持为空。不建议一般使用noload策略。对于一般的“永不加载”方法,请参见仅写关系。
  • raise - 禁止惰性加载;如果属性的值尚未通过急切加载加载,则访问该属性将引发InvalidRequestError。当对象在加载后要从其附加的Session中分离时,可以使用此策略。
  • raise_on_sql - 禁止发出 SQL 的延迟加载;如果该属性的值尚未通过急加载加载,则访问该属性将引发InvalidRequestError,“如果延迟加载需要发出 SQL”。如果延迟加载可以从标识映射中提取相关值或确定它应该是 None,则加载该值。当对象将保持与附加的Session关联时,可以使用此策略,但应阻止附加的额外 SELECT 语句。
  • write_only - 该属性将配置为具有特殊的“虚拟集合”,该集合可能接收WriteOnlyCollection.add()WriteOnlyCollection.remove()命令以添加或删除单个对象,但绝不会直接从数据库加载或迭代完整对象集。而是提供了诸如WriteOnlyCollection.select()WriteOnlyCollection.insert()WriteOnlyCollection.update()WriteOnlyCollection.delete()等方法,生成可用于批量加载和修改行的 SQL 构造。用于从不适合一次加载到内存中的大型集合。 当在声明性映射的左侧提供WriteOnlyMapped注释时,将自动配置write_only加载程序样式。有关示例,请参阅仅写关系部分。 在版本 2.0 中新增。 另请参阅 仅写关系 - 在 ORM 查询指南中
  • dynamic - 属性将为所有读操作返回预配置的Query对象,可以在迭代结果之前应用进一步的过滤操作。 当在声明式映射中的左侧提供了DynamicMapped注释时,将自动配置dynamic加载程序样式。有关示例,请参见动态关系加载器一节。 传统功能 “动态”懒加载策略是现在描述的“只写”策略的传统形式,详见仅写关系一节。 另请参见 动态关系加载器 - 在 ORM 查询指南中 仅写关系 - 用于大型集合的更普遍有用的方法,不应完全加载到内存中。
  • True - ‘select’的同义词
  • False - ‘joined’的同义词
  • None - ‘noload’的同义词

另请参见

关系加载技术 - 在 ORM 查询指南中关于关系加载程序配置的完整文档。

load_on_pending=False

指示暂态或挂起父对象的加载行为。

当设置为True时,会导致惰性加载程序对尚未持久的父对象发出查询,即从未刷新过的父对象。当自动刷新被禁用时,这可能会对挂起对象产生影响,或者对已“附加”到Session但不属于其挂起集合的暂态对象产生影响。

relationship.load_on_pending标志在 ORM 正常使用时不会改善行为 - 对象引用应在对象级别构造,而不是在外键级别构造,以便它们在刷新进行之前以普通方式存在。此标志不打算供常规使用。

另请参见

Session.enable_relationship_loading() - 此方法为整个对象建立了“在挂起时加载”的行为,还允许在保持为暂态或游离状态的对象上加载。

order_by

指示加载这些项时应应用的排序。relationship.order_by预期引用目标类映射到的一个Column对象之一,或者绑定到引用列的目标类的属性本身。

relationship.order_by 还可以作为可调用函数传递,该函数在映射器初始化时进行评估,并且在使用 Declarative 时可以作为 Python 可评估字符串进行传递。

警告

当作为 Python 可评估字符串传递时,该参数将使用 Python 的 eval() 函数进行解释。不要将不受信任的输入传递给此字符串。有关relationship()参数的声明性评估的详细信息,请参阅关系参数的评估。

passive_deletes=False -

指示删除操作期间的加载行为。

True 的值表示在父对象的删除操作期间不应加载未加载的子项目。通常,当删除父项目时,所有子项目都会加载,以便可以将它们标记为已删除,或者将它们的外键设置为 NULL。将此标志标记为 True 通常意味着已经存在一个 ON DELETE <CASCADE|SET NULL> 规则,该规则将处理数据库端的更新/删除子行。

此外,将标志设置为字符串值“all”将禁用在父对象被删除且未启用删除或删除-孤儿级联时的“空值”子外键。当数据库端存在触发或错误提升方案时,通常会使用此选项。请注意,在刷新后,会话中的子对象上的外键属性不会更改,因此这是一个非常特殊的用例设置。此外,如果子对象与父对象解除关联,则“nulling out”仍会发生。

另请参阅

使用 ORM 关系的外键 ON DELETE 级联 - 入门文档和示例。

passive_updates=True -

指示当引用的主键值在原位更改时要采取的持久性行为,这表示引用的外键列也需要更改其值。

当为 True 时,假定数据库上的外键已配置为 ON UPDATE CASCADE,并且数据库将处理从源列到依赖行的 UPDATE 传播。当为 False 时,SQLAlchemy relationship() 构造将尝试发出自己的 UPDATE 语句以修改相关目标。但请注意,SQLAlchemy 无法 对超过一级的级联发出 UPDATE。此外,将此标志设置为 False 在数据库实际强制执行引用完整性的情况下不兼容,除非这些约束明确为“延迟”,如果目标后端支持。

强烈建议使用可变主键的应用程序将 passive_updates 设置为 True,并且使用数据库本身的引用完整性功能来高效完全处理更改。

另请参阅

可变主键 / 更新级联 - 介绍文档和示例。

mapper.passive_updates - 类似的标志也适用于连接表继承映射。

post_update

这表示关系应该在插入后或删除前通过第二个 UPDATE 语句进行处理。该标志用于处理两个单独行之间的双向依赖关系(即每行引用另一行),否则将无法完全插入或删除两行,因为一行在另一行之前存在。当特定的映射安排将导致两行彼此依赖时,请使用此标志,例如,一个表与一组子行之间存在一对多关系,并且还有一个列引用该列表中的单个子行(即两个表相互包含对方的外键)。如果刷新操作返回检测到“循环依赖”错误,这表明您可能希望使用 relationship.post_update 来“打破”循环。

另请参阅

指向自身的行 / 相互依赖行 - 介绍文档和示例。

primaryjoin

将用作子对象与父对象之间的主要连接的 SQL 表达式,或者在多对多关系中将父对象连接到关联表。默认情况下,此值基于父表和子表(或关联表)的外键关系计算。

relationship.primaryjoin 也可以作为一个可调用函数传递,该函数在映射器初始化时进行评估,并且在使用声明性时可以作为一个可评估的 Python 字符串进行传递。

警告

当作为一个可评估的 Python 字符串传递时,该参数将使用 Python 的 eval() 函数进行解释。不要传递不受信任的输入给此字符串。有关声明性评估 relationship() 参数的详细信息,请参阅关系参数的评估。

另请参阅

指定替代连接条件

remote_side

用于自引用关系,指示形成关系的“远端”的列或列列表。

relationship.remote_side 还可以作为可调用函数传递,在映射器初始化时进行评估,并且在使用声明性时可以作为 Python 可评估字符串传递。

警告

当作为 Python 可评估字符串传递时,该参数将使用 Python 的 eval() 函数进行解释。不要将不受信任的输入传递给此字符串。有关使用 relationship() 参数的声明性评估的详细信息,请参阅关系参数的评估。

另请参阅

邻接列表关系 - 如何配置自引用关系的详细说明,relationship.remote_side 的使用。

remote() - 完成与 relationship.remote_side 相同目的的注释函数,通常在使用自定义 relationship.primaryjoin 条件时使用。

query_class

Query 的子类,将在由“动态”关系返回的 AppenderQuery 内部使用,即指定了 lazy="dynamic" 的关系或以其他方式使用了 dynamic_loader() 函数构造的关系。

另请参阅

动态关联加载器 - “动态”关联加载器的介绍。

secondaryjoin

将用作关联表与子对象的连接的 SQL 表达式。默认情况下,此值根据关联和子表的外键关系计算而来。

relationship.secondaryjoin 还可以作为可调用函数传递,在映射器初始化时进行评估,并且在使用声明性时可以作为 Python 可评估字符串传递。

警告

当作为 Python 可评估字符串传递时,该参数将使用 Python 的 eval() 函数进行解释。不要将不受信任的输入传递给此字符串。有关使用 relationship() 参数的声明性评估的详细信息,请参阅关系参数的评估。

另请参阅

指定替代连接条件

single_parent

当为 True 时,安装一个验证器,该验证器将阻止对象同时与多个父对象关联。这用于应将多对一或多对多关系视为一对一或一对多的情况。除了指定delete-orphan级联选项的多对一或多对多关系外,其使用是可选的。当要求此选项时,relationship()构造本身将引发错误指示。

另请参阅

级联操作 - 包括有关何时适合使用relationship.single_parent标志的详细信息。

uselist

一个布尔值,指示此属性是否应加载为列表或标量。在大多数情况下,此值由relationship()在映射配置时自动确定。当使用显式的Mapped注解时,relationship.uselist可以根据Mapped中的注解是否包含集合类来推导出。否则,relationship.uselist可以从关系的类型和方向推导出 - 一对多形成一个列表,多对一形成一个标量,多对多是一个列表。如果希望在通常存在列表的地方使用标量,例如双向一对一关系,请使用适当的Mapped注解或将relationship.uselist设置为 False。

relationship.uselist标志也可用于现有的relationship()构造,作为一个只读属性,可用于确定此relationship()是否处理集合或标量属性:

代码语言:javascript复制
>>> User.addresses.property.uselist
True

另请参阅

一对一关系 - 介绍了“一对一”关系模式,通常涉及relationship.uselist的备用设置。

viewonly=False

当设置为True时,该关系仅用于加载对象,而不用于任何持久性操作。指定了relationship.viewonlyrelationship()可以在relationship.primaryjoin条件内与更广泛的 SQL 操作一起使用,其中包括使用各种比较运算符以及 SQL 函数,如cast()relationship.viewonly标志在定义任何不代表完整相关对象集的relationship()时也是一般用途,以防止对集合的修改导致持久性操作。

另见

关于使用视图关系参数的注意事项 - 使用relationship.viewonly时的最佳实践的更多细节。

sync_backref -

一个布尔值,用于在此关系是relationship.backrefrelationship.back_populates的目标时启用用于同步 Python 属性的事件。

默认为None,表示应根据relationship.viewonly标志的值选择自动值。在其默认状态下,只有在关系的任一方都不是视图时状态变化才会被回填。

版本 1.3.17 中新增。

从版本 1.4 开始:- 指定了relationship.viewonly的关系自动意味着relationship.sync_backrefFalse

另见

relationship.viewonly

omit_join -

允许手动控制“selectin”自动连接优化。将其设置为False以禁用 SQLAlchemy 1.3 中添加的“omit join”功能;或者将其保留为None以保留自动优化。

注意

此标志只能设置为False。不需要将其设置为True,因为“omit_join”优化会自动检测到;如果未检测到,则不支持优化。

在版本 1.3.11 中更改:设置omit_join为 True 现在会发出警告,因为这不是此标志的预期使用方式。

从版本 1.3 开始新添加。

init – 专门针对声明性数据类映射,指定映射属性是否应作为 dataclass 流程生成的__init__()方法的一部分。

repr – 专门针对声明性数据类映射,指定映射属性是否应作为 dataclass 流程生成的__repr__()方法的一部分。

default_factory – 专门针对声明性数据类映射,指定一个默认值生成函数,该函数将作为 dataclass 流程生成的__init__()方法的一部分进行处理。

compare

专门针对声明性数据类映射,表示在生成映射类的__eq__()__ne__()方法时,此字段是否应包含在比较操作中。

从版本 2.0.0b4 开始新添加。

kw_only – 专门针对声明性数据类映射,表示在生成__init__()时此字段是否应标记为关键字参数。

代码语言:javascript复制
function sqlalchemy.orm.backref(name: str, **kwargs: Any) → ORMBackrefArgument

使用relationship.backref参数时,提供要在生成新的relationship()时使用的特定参数。

例如:

代码语言:javascript复制
'items':relationship(
    SomeItem, backref=backref('parent', lazy='subquery'))

一般认为relationship.backref参数是遗留的;对于现代应用程序,应优先使用显式的relationship()构造,使用relationship.back_populates参数进行链接。

另请参阅

使用传统的‘backref’关系参数的背景信息,请参阅使用传统的‘backref’关系参数。

代码语言:javascript复制
function sqlalchemy.orm.dynamic_loader(argument: _RelationshipArgumentType[Any] | None = None, **kw: Any) → RelationshipProperty[Any]

构造一个动态加载的映射器属性。

这与使用relationship()lazy='dynamic'参数基本相同:

代码语言:javascript复制
dynamic_loader(SomeClass)

# is the same as

relationship(SomeClass, lazy="dynamic")

更多关于动态加载的详细信息,请参阅动态关系加载器一节。

代码语言:javascript复制
function sqlalchemy.orm.foreign(expr: _CEA) → _CEA

使用“foreign”注解注释主要联接表达式的一部分。

请参阅创建自定义外键条件一节,了解其用法描述。

另请参阅

创建自定义外键条件

remote()

代码语言:javascript复制
function sqlalchemy.orm.remote(expr: _CEA) → _CEA

使用“remote”注解注释主要联接表达式的一部分。

参见章节创建自定义外键条件以了解其用法描述。

请参阅也

创建自定义外键条件

foreign()

ORM 查询指南

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

本节概述了使用 SQLAlchemy ORM 发出查询的 2.0 样式用法。

本节的读者应该熟悉 SQLAlchemy 统一教程中的 SQLAlchemy 概述,特别是这里的大部分内容扩展了使用 SELECT 语句的内容。

对于 SQLAlchemy 1.x 的用户

在 SQLAlchemy 2.x 系列中,ORM 的 SQL SELECT 语句是使用与 Core 中相同的select()构造而构建的,然后在Session的上下文中使用Session.execute()方法调用(就像用于 ORM-Enabled INSERT、UPDATE 和 DELETE 语句功能的现在使用的update()delete()构造一样)。然而,遗留的Query对象,它执行与这些步骤相同的操作,更像是一个“一体化”的对象,仍然作为对这个新系统的薄外观保持可用,以支持在 1.x 系列上构建的应用程序,而无需对所有查询进行全面替换。有关此对象的参考,请参阅 Legacy Query API 部分。

  • 为 ORM 映射类编写 SELECT 语句
    • 选择 ORM 实体和属性
      • 选择 ORM 实体
      • 同时选择多个 ORM 实体
      • 选择单个属性
      • 将选定的属性与包一起分组
      • 选择 ORM 别名
      • 从文本语句中获取 ORM 结果
      • 从子查询中选择实体
      • 从 UNIONs 和其他集合操作中选择实体
    • 连接
      • 简单的关系连接
      • 链接多个连接
      • 连接到目标实体
      • 使用 ON 子句连接到目标的连接(Joins to a Target with an ON Clause)
      • 将关系与自定义 ON 条件组合(Combining Relationship with Custom ON Criteria)
      • 使用 Relationship 在别名目标之间进行连接(Using Relationship to join between aliased targets)
      • 连接到子查询(Joining to Subqueries)
      • 沿关系路径连接到子查询(Joining to Subqueries along Relationship paths)
      • 引用多个实体的子查询(Subqueries that Refer to Multiple Entities)
      • 设置连接中的最左侧 FROM 子句(Setting the leftmost FROM clause in a join)
    • 关系 WHERE 操作符(Relationship WHERE Operators)
      • EXISTS 表单:has() / any()(EXISTS forms: has() / any())
      • 关系实例比较操作符(Relationship Instance Comparison Operators)
  • 用于继承映射的写入 SELECT 语句(Writing SELECT statements for Inheritance Mappings)
    • 从基类 vs. 特定子类进行 SELECT(SELECTing from the base class vs. specific sub-classes)
    • 使用 selectin_polymorphic()(使用 selectin_polymorphic())
      • 将 selectin_polymorphic() 应用于现有的急切加载(Applying selectin_polymorphic() to an existing eager load)
      • 将加载器选项应用于由 selectin_polymorphic 加载的子类(Applying loader options to the subclasses loaded by selectin_polymorphic)
      • 在映射器上配置 selectin_polymorphic()(Configuring selectin_polymorphic() on mappers)
    • 使用 with_polymorphic()(Using with_polymorphic())
      • 使用 with_polymorphic() 过滤子类属性(Filtering Subclass Attributes with with_polymorphic())
      • 使用 with_polymorphic 进行别名处理(Using aliasing with with_polymorphic)
      • 在映射器上配置 with_polymorphic()(Configuring with_polymorphic() on mappers)
    • 连接到特定子类型或 with_polymorphic() 实体(Joining to specific sub-types or with_polymorphic() entities)
      • 多态子类型的急切加载(Eager Loading of Polymorphic Subtypes)
    • 单一继承映射的 SELECT 语句(SELECT Statements for Single Inheritance Mappings)
      • 为单一继承优化属性加载(Optimizing Attribute Loads for Single Inheritance)
    • 继承加载 API(Inheritance Loading API)
      • with_polymorphic()with_polymorphic()
      • selectin_polymorphic()selectin_polymorphic()
  • 启用 ORM 的 INSERT、UPDATE 和 DELETE 语句(ORM-Enabled INSERT, UPDATE, and DELETE statements)
    • ORM 批量 INSERT 语句(ORM Bulk INSERT Statements)
      • 使用 RETURNING 获取新对象(Getting new objects with RETURNING)
      • 使用异构参数字典
      • 在 ORM 批量插入语句中发送 NULL 值
      • 连接表继承的批量插入
      • 使用 SQL 表达式的 ORM 批量插入
      • 遗留会话批量插入方法
      • ORM“upsert”语句
    • 按主键进行 ORM 批量更新
      • 为具有多个参数集的 UPDATE 语句禁用按主键进行 ORM 批量更新
      • 用于连接表继承的按主键进行批量更新
      • 遗留会话批量更新方法
    • 使用自定义 WHERE 条件的 ORM UPDATE 和 DELETE
      • ORM 启用的更新和删除的重要说明和注意事项
      • 选择同步策略
      • 使用 RETURNING 与 UPDATE/DELETE 和自定义 WHERE 条件
      • 使用自定义 WHERE 条件的 UPDATE/DELETE 用于连接表继承
      • 遗留查询方法
  • 列加载选项
    • 限制列延迟加载的列
      • 使用load_only()减少加载的列
      • 使用defer()省略特定列
      • 使用 raiseload 防止延迟加载列
    • 配置映射上的列延迟
      • 使用deferred()为命令式映射器、映射的 SQL 表达式
      • 使用undefer()“急切地”加载延迟列
      • 以组加载延迟列
      • 使用undefer_group()按组取消延迟加载
      • 使用通配符取消延迟加载
      • 配置映射器级别的“raiseload”行为
    • 将任意 SQL 表达式加载到对象上
      • 使用 with_expression() 加载 UNIONs、其他子查询
    • 列加载 API
      • defer()
      • deferred()
      • query_expression()
      • load_only()
      • undefer()
      • undefer_group()
      • with_expression()
  • 关系加载技巧
    • 关系加载风格摘要
    • 在映射时配置加载器策略
    • 带有加载器选项的关系加载
      • 向加载器选项添加条件
      • 使用 Load.options() 指定子选项
    • 惰性加载
      • 使用 raiseload 防止不必要的惰性加载
    • 连接式急加载
      • 连接式急加载的禅意
    • 选择 IN 加载
    • 子查询急加载
    • 使用何种加载方式?
    • 多态急加载
    • 通配符加载策略
      • 每个实体的通配符加载策略
    • 将显式连接/语句路由到急加载集合
      • 使用 contains_eager() 加载自定义过滤的集合结果
    • 关系加载器 API
      • contains_eager()
      • defaultload()
      • immediateload()
      • joinedload()
      • lazyload()
      • Load
      • noload()
      • raiseload()
      • selectinload()
      • subqueryload()
  • 查询的 ORM API 特性](api.html)
    • ORM 加载器选项
    • ORM 执行选项
      • 填充现有内容
      • 自动刷新
      • 使用每个结果生成器获取大型结果集
      • 身份标记
  • 旧版查询 API
    • 查询对象
      • 查询
    • ORM 特定的查询构造

为 ORM 映射类编写 SELECT 语句

原文:docs.sqlalchemy.org/en/20/orm/queryguide/select.html

关于本文档

本节利用了首次在 SQLAlchemy 统一教程中展示的 ORM 映射,显示在声明映射类一节中。

查看此页面的 ORM 设置。

SELECT 语句由 select() 函数生成,该函数返回一个 Select 对象。要返回的实体和/或 SQL 表达式(即“columns”子句)按位置传递给该函数。然后,使用其他方法生成完整的语句,例如下面所示的 Select.where() 方法:

代码语言:javascript复制
>>> from sqlalchemy import select
>>> stmt = select(User).where(User.name == "spongebob")

给定一个完成的 Select 对象,为了在 ORM 中执行并获取行,对象被传递给 Session.execute(),然后返回一个 Result 对象:

代码语言:javascript复制
>>> result = session.execute(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[...]  ('spongebob',)
>>> for user_obj in result.scalars():
...     print(f"{user_obj.name} {user_obj.fullname}")
spongebob Spongebob Squarepants

选择 ORM 实体和属性

select() 构造函数接受 ORM 实体,包括映射类以及表示映射列的类级别属性,这些属性在构造时转换为 ORM 注解的 FromClauseColumnElement 元素。

包含 ORM 注解实体的 Select 对象通常使用 Session 对象执行,而不是 Connection 对象,以便 ORM 相关功能生效,包括可以返回 ORM 映射对象的实例。直接使用 Connection 时,结果行仅包含列级数据。

选择 ORM 实体

下面我们从User实体中进行选择,生成一个从User映射到的Table中进行选择的Select

代码语言:javascript复制
>>> result = session.execute(select(User).order_by(User.id))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.id
[...]  () 

当从 ORM 实体中进行选择时,实体本身作为具有单个元素的行返回结果,而不是一系列单独的列;例如上面,Result返回仅在每行具有单个元素的Row对象,该元素保留着一个User对象:

代码语言:javascript复制
>>> result.all()
[(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),),
 (User(id=2, name='sandy', fullname='Sandy Cheeks'),),
 (User(id=3, name='patrick', fullname='Patrick Star'),),
 (User(id=4, name='squidward', fullname='Squidward Tentacles'),),
 (User(id=5, name='ehkrabs', fullname='Eugene H. Krabs'),)]

当选择包含 ORM 实体的单元素行列表时,通常会跳过生成Row对象,而是直接接收 ORM 实体。最简单的方法是使用Session.scalars()方法来执行,而不是Session.execute()方法,这样就会返回一个ScalarResult对象,该对象产生单个元素而不是行:

代码语言:javascript复制
>>> session.scalars(select(User).order_by(User.id)).all()
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.id
[...]  ()
[User(id=1, name='spongebob', fullname='Spongebob Squarepants'),
 User(id=2, name='sandy', fullname='Sandy Cheeks'),
 User(id=3, name='patrick', fullname='Patrick Star'),
 User(id=4, name='squidward', fullname='Squidward Tentacles'),
 User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')]

调用Session.scalars()方法相当于调用Session.execute()来接收一个Result对象,然后调用Result.scalars()来接收一个ScalarResult对象。###同时选择多个 ORM 实体

select()函数一次接受任意数量的 ORM 类和/或列表达式,包括可以请求多个 ORM 类的情况。当从多个 ORM 类中选择时,它们在每个结果行中根据其类名命名。在下面的示例中,针对UserAddress进行 SELECT 的结果行将以UserAddress的名称引用它们:

代码语言:javascript复制
>>> stmt = select(User, Address).join(User.addresses).order_by(User.id, Address.id)
>>> for row in session.execute(stmt):
...     print(f"{row.User.name} {row.Address.email_address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
address.id  AS  id_1,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id
[...]  ()
spongebob spongebob@sqlalchemy.org
sandy sandy@sqlalchemy.org
sandy squirrel@squirrelpower.org
patrick pat999@aol.com
squidward stentcl@sqlalchemy.org

如果我们想要为这些实体在行中分配不同的名称,我们将使用aliased()构造,使用aliased.name参数将它们别名为显式名称:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> user_cls = aliased(User, name="user_cls")
>>> email_cls = aliased(Address, name="email")
>>> stmt = (
...     select(user_cls, email_cls)
...     .join(user_cls.addresses.of_type(email_cls))
...     .order_by(user_cls.id, email_cls.id)
... )
>>> row = session.execute(stmt).first()
SELECT  user_cls.id,  user_cls.name,  user_cls.fullname,
email.id  AS  id_1,  email.user_id,  email.email_address
FROM  user_account  AS  user_cls  JOIN  address  AS  email
ON  user_cls.id  =  email.user_id  ORDER  BY  user_cls.id,  email.id
[...]  ()
>>> print(f"{row.user_cls.name}  {row.email.email_address}")
spongebob spongebob@sqlalchemy.org

上述别名形式在使用关系连接别名目标中进一步讨论。

可以使用Select构造来向其列子句添加 ORM 类和/或列表达式,方法是使用Select.add_columns()方法。我们也可以使用这种形式来生成上述语句:

代码语言:javascript复制
>>> stmt = (
...     select(User).join(User.addresses).add_columns(Address).order_by(User.id, Address.id)
... )
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
address.id  AS  id_1,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id 
选择单个属性

映射类上的属性,如User.nameAddress.email_address,当传递给select()时,可以像Column或其他 SQL 表达式对象一样使用。针对特定列创建一个select()将返回Row对象,而不是UserAddress对象那样的实体。每个Row将单独表示每一列:

代码语言:javascript复制
>>> result = session.execute(
...     select(User.name, Address.email_address)
...     .join(User.addresses)
...     .order_by(User.id, Address.id)
... )
SELECT  user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id
[...]  () 

上述语句返回具有nameemail_address列的Row对象,如下所示的运行时演示:

代码语言:javascript复制
>>> for row in result:
...     print(f"{row.name}  {row.email_address}")
spongebob  spongebob@sqlalchemy.org
sandy  sandy@sqlalchemy.org
sandy  squirrel@squirrelpower.org
patrick  pat999@aol.com
squidward  stentcl@sqlalchemy.org
使用 Bundle 对选定属性进行分组

Bundle 构造是一个可扩展的仅限 ORM 的构造,允许将列表达式集合分组在结果行中:

代码语言:javascript复制
>>> from sqlalchemy.orm import Bundle
>>> stmt = select(
...     Bundle("user", User.name, User.fullname),
...     Bundle("email", Address.email_address),
... ).join_from(User, Address)
>>> for row in session.execute(stmt):
...     print(f"{row.user.name} {row.user.fullname} {row.email.email_address}")
SELECT  user_account.name,  user_account.fullname,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
spongebob Spongebob Squarepants spongebob@sqlalchemy.org
sandy Sandy Cheeks sandy@sqlalchemy.org
sandy Sandy Cheeks squirrel@squirrelpower.org
patrick Patrick Star pat999@aol.com
squidward Squidward Tentacles stentcl@sqlalchemy.org

Bundle 可能对创建轻量级视图和自定义列分组很有用。Bundle 也可以被子类化以返回替代数据结构;参见Bundle.create_row_processor() 以获取示例。

另请参见

Bundle

Bundle.create_row_processor() ### 选择 ORM 别名

如在使用别名的教程中讨论的那样,要创建 ORM 实体的 SQL 别名,可以使用针对映射类的aliased()构造实现:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> u1 = aliased(User)
>>> print(select(u1).order_by(u1.id))
SELECT  user_account_1.id,  user_account_1.name,  user_account_1.fullname
FROM  user_account  AS  user_account_1  ORDER  BY  user_account_1.id 

与使用Table.alias()时的情况一样,SQL 别名是匿名命名的。对于从具有显式名称的行中选择实体的情况,也可以传递aliased.name参数:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> u1 = aliased(User, name="u1")
>>> stmt = select(u1).order_by(u1.id)
>>> row = session.execute(stmt).first()
SELECT  u1.id,  u1.name,  u1.fullname
FROM  user_account  AS  u1  ORDER  BY  u1.id
[...]  ()
>>> print(f"{row.u1.name}")
spongebob

另见

aliased 构造在几种情况下都很重要,包括:

  • 使用 ORM 的子查询;章节从子查询中选择实体和加入子查询进一步讨论了这一点。
  • 控制结果集中实体的名称;参见同时选择多个 ORM 实体以查看示例
  • 多次连接到相同的 ORM 实体;参见使用关系连接到别名目标以查看示例。###从文本语句获取 ORM 结果

ORM 支持从其他来源的 SELECT 语句加载实体。典型的用例是文本 SELECT 语句,在 SQLAlchemy 中使用text()构造表示。可以使用text()构造增强关于该语句将加载的 ORM 映射列的信息;然后可以将其与 ORM 实体本身关联,以便基于此语句加载 ORM 对象。

给定一个文本 SQL 语句,我们希望从中加载:

代码语言:javascript复制
>>> from sqlalchemy import text
>>> textual_sql = text("SELECT id, name, fullname FROM user_account ORDER BY id")

通过使用TextClause.columns()方法,我们可以为语句添加列信息;当调用此方法时,TextClause对象被转换为一个TextualSelect对象,该对象扮演的角色类似于Select构造。TextClause.columns()方法通常传递Column对象或等效对象,在这种情况下,我们可以直接使用User类上映射的属性:

代码语言:javascript复制
>>> textual_sql = textual_sql.columns(User.id, User.name, User.fullname)

现在我们有了一个经过 ORM 配置的 SQL 构造,按照给定的方式,可以单独加载“id”、“name”和“fullname”列。要将此 SELECT 语句用作完整User实体的源,则可以使用Select.from_statement()方法将这些列链接到常规的 ORM 启用的Select构造中:

代码语言:javascript复制
>>> orm_sql = select(User).from_statement(textual_sql)
>>> for user_obj in session.execute(orm_sql).scalars():
...     print(user_obj)
SELECT  id,  name,  fullname  FROM  user_account  ORDER  BY  id
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

同一个TextualSelect对象也可以使用TextualSelect.subquery()方法转换为子查询,并使用aliased()构造将其链接到User实体,方式与下面讨论的从子查询中选择实体类似:

代码语言:javascript复制
>>> orm_subquery = aliased(User, textual_sql.subquery())
>>> stmt = select(orm_subquery)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname
FROM  (SELECT  id,  name,  fullname  FROM  user_account  ORDER  BY  id)  AS  anon_1
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

直接使用TextualSelectSelect.from_statement()相比,使用aliased()的区别在于,在前一种情况下,生成的 SQL 中不会产生子查询。在某些情景下,这样做从性能或复杂性的角度来看可能是有利的。 ### 从子查询中选择实体

在前一节讨论的aliased()构造中,可以与任何来自诸如Select.subquery()之类的方法的Subuqery构造一起使用,以将 ORM 实体链接到该子查询返回的列;子查询返回的列与实体映射的列之间必须存在列对应关系,这意味着子查询最终需要来自这些实体,就像下面的示例中一样:

代码语言:javascript复制
>>> inner_stmt = select(User).where(User.id < 7).order_by(User.id)
>>> subq = inner_stmt.subquery()
>>> aliased_user = aliased(User, subq)
>>> stmt = select(aliased_user)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
  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.id  <  ?  ORDER  BY  user_account.id)  AS  anon_1
[generated  in  ...]  (7,)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

另请参见

ORM 实体子查询/CTEs - 在 SQLAlchemy 统一教程中

加入到子查询 ### 从 UNION 和其他集合操作中选择实体

union()union_all() 函数是最常见的集合操作之一,与except_()intersect()等其他集合操作一起,它们生成一个称为CompoundSelect的对象,该对象由多个使用集合操作关键字连接的Select构造组成。ORM 实体可以使用Select.from_statement()方法从简单的复合选择中选择,该方法如在从文本语句中获取 ORM 结果中所示。在这种方法中,UNION 语句是将呈现的完整语句,不能在使用Select.from_statement()之后添加额外的条件:

代码语言:javascript复制
>>> from sqlalchemy import union_all
>>> u = union_all(
...     select(User).where(User.id < 2), select(User).where(User.id == 3)
... ).order_by(User.id)
>>> stmt = select(User).from_statement(u)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.id  <  ?  UNION  ALL  SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.id  =  ?  ORDER  BY  id
[generated  in  ...]  (2,  3)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=3, name='patrick', fullname='Patrick Star')

CompoundSelect构造可以更灵活地在查询中使用,可以通过将其组织成子查询并使用aliased()将其链接到 ORM 实体来进一步修改,如在从子查询中选择实体中所示。在下面的示例中,我们首先使用CompoundSelect.subquery()创建 UNION ALL 语句的子查询,然后将其打包到aliased()构造中,在其中可以像其他映射实体一样在select()构造中使用,包括我们可以基于其导出的列添加过滤和排序条件:

代码语言:javascript复制
>>> subq = union_all(
...     select(User).where(User.id < 2), select(User).where(User.id == 3)
... ).subquery()
>>> user_alias = aliased(User, subq)
>>> stmt = select(user_alias).order_by(user_alias.id)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
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.id  <  ?  UNION  ALL  SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.id  =  ?)  AS  anon_1  ORDER  BY  anon_1.id
[generated  in  ...]  (2,  3)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=3, name='patrick', fullname='Patrick Star')

另请参阅

从联合中选择 ORM 实体 - 在 SQLAlchemy 统一教程中##连接

Select.join()Select.join_from()方法用于构建针对 SELECT 语句的 SQL JOINs。

本节将详细介绍这些方法的 ORM 用例。有关从核心角度使用它们的通用概述,请参阅明确的 FROM 子句和 JOINs 中的 SQLAlchemy 统一教程。

在 ORM 上下文中使用Select.join()进行 2.0 风格查询的用法大致相同,除了遗留用例外,与 1.x 风格查询中的Query.join()方法的用法相似。

简单的关系连接

考虑两个类UserAddress之间的映射,其中关系User.addresses表示与每个User关联的Address对象的集合。 Select.join()的最常见用法是沿着这个关系创建 JOIN,使用User.addresses属性作为指示器来指示这应该如何发生:

代码语言:javascript复制
>>> stmt = select(User).join(User.addresses)

在上文中,对User.addressesSelect.join()调用将导致 SQL 大致等效于:

代码语言:javascript复制
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

在上面的示例中,我们将User.addresses称为传递给Select.join()的“on clause”,即它指示如何构造 JOIN 语句中的“ON”部分。

Tip

注意,使用Select.join()从一个实体连接到另一个实体会影响 SELECT 语句的 FROM 子句,但不会影响列子句;此示例中的 SELECT 语句将继续只返回User实体的行。要同时从UserAddress选择列/实体,必须在select()函数中命名Address实体,或者使用Select.add_columns()方法在之后将其添加到Select构造中。请参阅 同时选择多个 ORM 实体 部分以了解这两种形式的示例。

链式多重连接

要构建一系列连接,可以使用多个Select.join()调用。关系绑定属性一次暗示了连接的左侧和右侧。考虑额外的实体OrderItem,其中User.orders关系引用了Order实体,而Order.items关系通过关联表order_items引用了Item实体。两个Select.join()调用将首先从UserOrder进行连接,然后从OrderItem进行第二次连接。但是,由于Order.items是多对多关系,它导致两个单独的 JOIN 元素,总共在生成的 SQL 中有三个 JOIN 元素:

代码语言:javascript复制
>>> stmt = select(User).join(User.orders).join(Order.items)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  user_order  ON  user_account.id  =  user_order.user_id
JOIN  order_items  AS  order_items_1  ON  user_order.id  =  order_items_1.order_id
JOIN  item  ON  item.id  =  order_items_1.item_id 

每次调用Select.join()方法的顺序只有在我们想要连接的“左”侧需要在 FROM 列表中出现时才有意义;如果我们指定select(User).join(Order.items).join(User.orders),则Select.join()将不知道如何正确连接,并引发错误。在正确的做法中,应以使 JOIN 子句在 SQL 中呈现方式对齐的方式调用Select.join()方法,并且每次调用应表示从之前的内容清晰地链接过来。

我们在 FROM 子句中目标的所有元素仍然可以作为继续连接 FROM 的潜在点。例如,我们可以继续添加其他元素来连接 FROM 上面的User实体,例如在连接链中添加User.addresses关系:

代码语言:javascript复制
>>> stmt = select(User).join(User.orders).join(Order.items).join(User.addresses)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  user_order  ON  user_account.id  =  user_order.user_id
JOIN  order_items  AS  order_items_1  ON  user_order.id  =  order_items_1.order_id
JOIN  item  ON  item.id  =  order_items_1.item_id
JOIN  address  ON  user_account.id  =  address.user_id 
连接到目标实体

第二种形式的Select.join()允许任何映射实体或核心可选择的构造作为目标。在这种用法中,Select.join()将尝试推断JOIN 的 ON 子句,使用两个实体之间的自然外键关系:

代码语言:javascript复制
>>> stmt = select(User).join(Address)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

在上述调用形式中,Select.join()被调用以自动推断“on 子句”。如果两个映射的Table构造之间没有ForeignKeyConstraint设置,或者存在多个使适当约束使用变得模糊的 ForeignKeyConstraint 链接时,此调用形式最终会引发错误。

注意

当使用 Select.join()Select.join_from() 而不指示 ON 子句时,ORM 配置的relationship()构造不会被考虑。只有在尝试推断 JOIN 的 ON 子句时,才会查阅映射的Table对象级别上的实体之间配置的ForeignKeyConstraint关系。

连接到具有 ON 子句的目标

第三种调用形式允许目标实体以及 ON 子句都明确传递。包含 SQL 表达式作为 ON 子句的示例如下:

代码语言:javascript复制
>>> stmt = select(User).join(Address, User.id == Address.user_id)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

基于表达式的 ON 子句也可以是一个relationship()-绑定属性,就像在简单关系连接中使用的那样:

代码语言:javascript复制
>>> stmt = select(User).join(Address, User.addresses)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

上面的例子看起来是多余的,因为它以两种不同的方式指示了 Address 的目标;然而,当连接到别名实体时,这种形式的效用就变得明显了;请参阅 Using Relationship to join between aliased targets 部分以查看示例。### 结合 Relationship 与自定义 ON 条件

relationship() 构造生成的 ON 子句可能会通过附加条件进行增强。这对于快速限制特定连接的范围以及配置加载器策略(如 joinedload()selectinload())等情况非常有用。PropComparator.and_() 方法按位置接受一系列 SQL 表达式,这些表达式将通过 AND 连接到 JOIN 的 ON 子句。例如,如果我们想要从 User 连接到 Address,但也只限制 ON 条件为特定的电子邮件地址:

代码语言:javascript复制
>>> stmt = select(User.fullname).join(
...     User.addresses.and_(Address.email_address == "squirrel@squirrelpower.org")
... )
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
JOIN  address  ON  user_account.id  =  address.user_id  AND  address.email_address  =  ?
[...]  ('squirrel@squirrelpower.org',)
[('Sandy Cheeks',)]

另见

PropComparator.and_() 方法也适用于加载器策略,如 joinedload()selectinload()。请参阅 Adding Criteria to loader options 部分。### 使用 Relationship 在别名目标之间进行连接

在使用 relationship()-绑定属性指示 ON 子句构建连接时,可以将 Joins to a Target with an ON Clause 中说明的两个参数语法扩展到与 aliased() 构造一起使用,以指示 SQL 别名作为连接的目标,同时仍然利用 relationship()-绑定属性指示 ON 子句,如下例所示,其中 User 实体两次与两个不同的 aliased() 构造连接到 Address 实体:

代码语言:javascript复制
>>> address_alias_1 = aliased(Address)
>>> address_alias_2 = aliased(Address)
>>> stmt = (
...     select(User)
...     .join(address_alias_1, User.addresses)
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join(address_alias_2, User.addresses)
...     .where(address_alias_2.email_address == "patrick@gmail.com")
... )
>>> print(stmt)
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 

可以使用修饰符 PropComparator.of_type() 来更简洁地表达相同的模式,该修饰符可应用于与 relationship() 绑定的属性,一次性传递目标实体以指示一步中的目标。下面的示例使用 PropComparator.of_type() 来生成与刚刚展示的相同的 SQL 语句:

代码语言:javascript复制
>>> print(
...     select(User)
...     .join(User.addresses.of_type(address_alias_1))
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join(User.addresses.of_type(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 

要利用 relationship() 来构建来自别名实体的连接,直接从 aliased() 构造中获取属性即可:

代码语言:javascript复制
>>> user_alias_1 = aliased(User)
>>> print(select(user_alias_1.name).join(user_alias_1.addresses))
SELECT  user_account_1.name
FROM  user_account  AS  user_account_1
JOIN  address  ON  user_account_1.id  =  address.user_id 
```### 加入到子查询

连接的目标可以是任何“可选择”的实体,包括子查询。在使用 ORM 时,通常将这些目标陈述为 `aliased()` 构造的术语,但这不是严格要求的,特别是如果连接的实体不在结果中返回。例如,要从 `User` 实体连接到 `Address` 实体,其中 `Address` 实体表示为行限制的子查询,我们首先使用 `Select.subquery()` 构造了一个 `Subquery` 对象,然后可以将其用作 `Select.join()` 方法的目标:

```py
>>> subq = select(Address).where(Address.email_address == "pat999@aol.com").subquery()
>>> stmt = select(User).join(subq, User.id == subq.c.user_id)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  :email_address_1)  AS  anon_1
ON  user_account.id  =  anon_1.user_id 

上述 SELECT 语句在通过 Session.execute() 调用时,将返回包含 User 实体但不包含 Address 实体的行。为了将 Address 实体包含到将在结果集中返回的实体集合中,我们对 Address 实体和 Subquery 对象构造了一个 aliased() 对象。我们还可能希望对 aliased() 构造应用一个名称,如下面使用的 "address",这样我们就可以在结果行中按名称引用它:

代码语言:javascript复制
>>> address_subq = aliased(Address, subq, name="address")
>>> stmt = select(User, address_subq).join(address_subq)
>>> for row in session.execute(stmt):
...     print(f"{row.User} {row.address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.user_id,  anon_1.email_address
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
[...]  ('pat999@aol.com',)
User(id=3, name='patrick', fullname='Patrick Star') Address(id=4, email_address='pat999@aol.com')
加入到子查询的关联路径

在上一节中说明的子查询形式可以使用relationship()绑定属性更具体地表示,使用使用 Relationship 在别名目标之间进行连接中指示的形式之一。例如,要创建相同的连接,同时确保连接是沿着特定relationship()进行的,我们可以使用PropComparator.of_type()方法,传递包含连接目标的aliased() 构造,该目标是Subquery对象的。

代码语言:javascript复制
>>> address_subq = aliased(Address, subq, name="address")
>>> stmt = select(User, address_subq).join(User.addresses.of_type(address_subq))
>>> for row in session.execute(stmt):
...     print(f"{row.User} {row.address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.user_id,  anon_1.email_address
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
[...]  ('pat999@aol.com',)
User(id=3, name='patrick', fullname='Patrick Star') Address(id=4, email_address='pat999@aol.com')
引用多个实体的子查询

包含跨越多个 ORM 实体列的子查询可以一次应用于多个aliased() 构造,并在同一Select构造中针对每个实体分别使用。然而,从 ORM / Python 的角度来看,渲染的 SQL 将继续将所有这些aliased() 构造视为相同的子查询,但可以通过使用适当的aliased() 构造引用不同的返回值和对象属性。

例如,给定同时引用UserAddress的子查询:

代码语言:javascript复制
>>> user_address_subq = (
...     select(User.id, User.name, User.fullname, Address.id, Address.email_address)
...     .join_from(User, Address)
...     .where(Address.email_address.in_(["pat999@aol.com", "squirrel@squirrelpower.org"]))
...     .subquery()
... )

我们可以针对UserAddress分别创建对同一对象的aliased() 构造:

代码语言:javascript复制
>>> user_alias = aliased(User, user_address_subq, name="user")
>>> address_alias = aliased(Address, user_address_subq, name="address")

从两个实体中选择的Select构造将一次渲染子查询,但在结果行上下文中可以同时返回UserAddress类的对象:

代码语言:javascript复制
>>> stmt = select(user_alias, address_alias).where(user_alias.name == "sandy")
>>> for row in session.execute(stmt):
...     print(f"{row.user} {row.address}")
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname,  anon_1.id_1,  anon_1.email_address
FROM  (SELECT  user_account.id  AS  id,  user_account.name  AS  name,
user_account.fullname  AS  fullname,  address.id  AS  id_1,
address.email_address  AS  email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  address.email_address  IN  (?,  ?))  AS  anon_1
WHERE  anon_1.name  =  ?
[...]  ('pat999@aol.com',  'squirrel@squirrelpower.org',  'sandy')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='squirrel@squirrelpower.org')
设置连接中最左侧的 FROM 子句

在当前Select状态的左侧与我们要连接的内容不一致的情况下,可以使用Select.join_from() 方法:

代码语言:javascript复制
>>> stmt = select(Address).join_from(User, User.addresses).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

Select.join_from() 方法接受两个或三个参数,形式可以是 (<join from>, <onclause>),或者 (<join from>, <join to>, [<onclause>])

代码语言:javascript复制
>>> stmt = select(Address).join_from(User, Address).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

为了为 SELECT 设置初始的 FROM 子句,以便随后可以使用Select.join(),也可以使用Select.select_from()方法:

代码语言:javascript复制
>>> stmt = select(Address).select_from(User).join(Address).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

提示

Select.select_from()方法实际上并不决定 FROM 子句中表的顺序。如果语句还引用了引用不同顺序的现有表的Join构造,那么Join构造将优先。当我们使用Select.join()Select.join_from()等方法时,这些方法最终会创建这样一个Join对象。因此,在这种情况下,我们可以看到Select.select_from()的内容被覆盖:

代码语言:javascript复制
>>> stmt = select(Address).select_from(User).join(Address.user).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  address  JOIN  user_account  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

在上面的例子中,我们看到 FROM 子句是address JOIN user_account,尽管我们首先声明了select_from(User)。由于.join(Address.user)方法调用,该语句最终等同于以下内容:

代码语言:javascript复制
>>> from sqlalchemy.sql import join
>>>
>>> user_table = User.__table__
>>> address_table = Address.__table__
>>>
>>> j = address_table.join(user_table, user_table.c.id == address_table.c.user_id)
>>> stmt = (
...     select(address_table)
...     .select_from(user_table)
...     .select_from(j)
...     .where(user_table.c.name == "sandy")
... )
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  address  JOIN  user_account  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

上面的Join构造被添加为Select.select_from()列表中的另一个条目,它取代了之前的条目。## 关系 WHERE 运算符

除了在Select.join()Select.join_from()方法中使用relationship()构造之外,relationship()还在帮助构建通常用于 WHERE 子句的 SQL 表达式,使用Select.where()方法。

EXISTS 形式:has() / any()

Exists 构造首次出现在 SQLAlchemy 统一教程 的 EXISTS 子查询 部分。此对象用于在标量子查询中与 SQL EXISTS 关键字一起呈现。relationship() 构造提供了一些辅助方法,可用于生成一些常见的 EXISTS 样式的查询,这些查询涉及关系。

对于一对多关系,例如 User.addresses,可以使用与 user_account 表相关联的 address 表的 EXISTS 来产生一个 PropComparator.any()。此方法接受一个可选的 WHERE 条件来限制子查询匹配的行数:

代码语言:javascript复制
>>> stmt = select(User.fullname).where(
...     User.addresses.any(Address.email_address == "squirrel@squirrelpower.org")
... )
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
WHERE  EXISTS  (SELECT  1
FROM  address
WHERE  user_account.id  =  address.user_id  AND  address.email_address  =  ?)
[...]  ('squirrel@squirrelpower.org',)
[('Sandy Cheeks',)]

由于 EXISTS 对于负查找更有效,因此一个常见的查询是定位不存在相关实体的实体。这可以通过短语 ~User.addresses.any() 来简洁地实现,以选择没有相关 Address 行的 User 实体:

代码语言:javascript复制
>>> stmt = select(User.fullname).where(~User.addresses.any())
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
WHERE  NOT  (EXISTS  (SELECT  1
FROM  address
WHERE  user_account.id  =  address.user_id))
[...]  ()
[('Eugene H. Krabs',)]

PropComparator.has() 方法的工作方式基本与 PropComparator.any() 相同,不同之处在于它用于多对一关系,例如,如果我们想要定位所有属于 “sandy” 的 Address 对象。

代码语言:javascript复制
>>> stmt = select(Address.email_address).where(Address.user.has(User.name == "sandy"))
>>> session.execute(stmt).all()
SELECT  address.email_address
FROM  address
WHERE  EXISTS  (SELECT  1
FROM  user_account
WHERE  user_account.id  =  address.user_id  AND  user_account.name  =  ?)
[...]  ('sandy',)
[('sandy@sqlalchemy.org',), ('squirrel@squirrelpower.org',)]
```### 关系实例比较运算符

`relationship()` 绑定属性还提供了一些 SQL 构造实现,这些实现旨在根据相关对象的特定实例来过滤 `relationship()` 绑定属性,该实例可以从给定的 持久化(或不太常见的 分离)对象实例中拆解适当的属性值,并按照目标 `relationship()` 构造 WHERE 条件。

    **多对一等于比较** - 可以将特定对象实例与多对一关系进行比较,以选择目标实体的外键与给定对象的主键值匹配的行:

    ```py
    >>> user_obj = session.get(User, 1)
    SELECT  ...
    >>> print(select(Address).where(Address.user == user_obj))
    SELECT  address.id,  address.user_id,  address.email_address
    FROM  address
    WHERE  :param_1  =  address.user_id 
    ```

    **多对一不等于比较** - 也可以使用不等于运算符:

    ```py
    >>> print(select(Address).where(Address.user != user_obj))
    SELECT  address.id,  address.user_id,  address.email_address
    FROM  address
    WHERE  address.user_id  !=  :user_id_1  OR  address.user_id  IS  NULL 
    ```

    **对象包含在一对多集合中** - 这本质上是“等于”比较的一对多版本,选择主键等于相关对象中外键值的行:

    ```py
    >>> address_obj = session.get(Address, 1)
    SELECT  ...
    >>> print(select(User).where(User.addresses.contains(address_obj)))
    SELECT  user_account.id,  user_account.name,  user_account.fullname
    FROM  user_account
    WHERE  user_account.id  =  :param_1 
    ```

    **从一对多的角度看,对象有一个特定的父对象** - `with_parent()` 函数生成一个比较,返回被给定父对象引用的行,这本质上与在多对一侧使用 `==` 操作符相同:

    ```py
    >>> from sqlalchemy.orm import with_parent
    >>> print(select(Address).where(with_parent(user_obj, User.addresses)))
    SELECT  address.id,  address.user_id,  address.email_address
    FROM  address
    WHERE  :param_1  =  address.user_id 
    ```## 选择 ORM 实体和属性

`select()` 构造接受 ORM 实体,包括映射类以及表示映射列的类级属性,这些在构建时转换为 ORM 注释 的 `FromClause` 和 `ColumnElement` 元素。

包含 ORM 注释实体的 `Select` 对象通常使用 `Session` 对象执行,而不是使用 `Connection` 对象,以便 ORM 相关功能生效,包括可以返回 ORM 映射对象的实例。直接使用 `Connection` 时,结果行将仅包含列级数据。

### 选择 ORM 实体

下面我们从 `User` 实体中选择,生成一个从 `User` 映射到的映射 `Table` 中选择的 `Select`:

```py
>>> result = session.execute(select(User).order_by(User.id))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.id
[...]  () 

在选择 ORM 实体时,实体本身作为具有单个元素的行返回结果,而不是一系列单独的列;例如上面,Result 返回仅具有每行单个元素的 Row 对象,该元素保持一个 User 对象:

代码语言:javascript复制
>>> result.all()
[(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),),
 (User(id=2, name='sandy', fullname='Sandy Cheeks'),),
 (User(id=3, name='patrick', fullname='Patrick Star'),),
 (User(id=4, name='squidward', fullname='Squidward Tentacles'),),
 (User(id=5, name='ehkrabs', fullname='Eugene H. Krabs'),)]

当选择包含 ORM 实体的单元素行列表时,通常会跳过生成Row对象,并直接接收 ORM 实体。这最容易通过使用Session.scalars()方法执行,而不是使用Session.execute()方法来实现,因此返回一个ScalarResult对象,该对象产生单个元素而不是行:

代码语言:javascript复制
>>> session.scalars(select(User).order_by(User.id)).all()
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.id
[...]  ()
[User(id=1, name='spongebob', fullname='Spongebob Squarepants'),
 User(id=2, name='sandy', fullname='Sandy Cheeks'),
 User(id=3, name='patrick', fullname='Patrick Star'),
 User(id=4, name='squidward', fullname='Squidward Tentacles'),
 User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')]

调用Session.scalars()方法相当于调用Session.execute()来接收一个Result对象,然后调用Result.scalars()来接收一个ScalarResult对象。 ### 同时选择多个 ORM 实体

select()函数一次接受任意数量的 ORM 类和/或列表达式,包括可以请求多个 ORM 类。当从多个 ORM 类中选择时,它们在每个结果行中根据其类名命名。在下面的示例中,对UserAddress进行 SELECT 的结果行将以UserAddress的名称引用它们:

代码语言:javascript复制
>>> stmt = select(User, Address).join(User.addresses).order_by(User.id, Address.id)
>>> for row in session.execute(stmt):
...     print(f"{row.User.name} {row.Address.email_address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
address.id  AS  id_1,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id
[...]  ()
spongebob spongebob@sqlalchemy.org
sandy sandy@sqlalchemy.org
sandy squirrel@squirrelpower.org
patrick pat999@aol.com
squidward stentcl@sqlalchemy.org

如果我们想要在这些实体中的行上分配不同的名称,我们将使用aliased()构造,使用aliased.name参数将它们别名为一个明确的名称:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> user_cls = aliased(User, name="user_cls")
>>> email_cls = aliased(Address, name="email")
>>> stmt = (
...     select(user_cls, email_cls)
...     .join(user_cls.addresses.of_type(email_cls))
...     .order_by(user_cls.id, email_cls.id)
... )
>>> row = session.execute(stmt).first()
SELECT  user_cls.id,  user_cls.name,  user_cls.fullname,
email.id  AS  id_1,  email.user_id,  email.email_address
FROM  user_account  AS  user_cls  JOIN  address  AS  email
ON  user_cls.id  =  email.user_id  ORDER  BY  user_cls.id,  email.id
[...]  ()
>>> print(f"{row.user_cls.name}  {row.email.email_address}")
spongebob spongebob@sqlalchemy.org

上述的别名形式在使用关系连接别名目标之间有进一步讨论。

一个现有的Select构造也可以使用Select.add_columns()方法将 ORM 类和/或列表达式添加到其列子句中。我们也可以使用这种形式生成与上述相同的语句:

代码语言:javascript复制
>>> stmt = (
...     select(User).join(User.addresses).add_columns(Address).order_by(User.id, Address.id)
... )
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
address.id  AS  id_1,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id 
选择单个属性

映射类上的属性,如User.nameAddress.email_address,可以像传递给select()Column或其他 SQL 表达式对象一样使用。创建针对特定列的select()将返回Row对象,而不是UserAddress对象那样的实体。每个Row将分别表示每个列:

代码语言:javascript复制
>>> result = session.execute(
...     select(User.name, Address.email_address)
...     .join(User.addresses)
...     .order_by(User.id, Address.id)
... )
SELECT  user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id
[...]  () 

上述语句返回Row对象,具有nameemail_address列,如下所示的运行时演示:

代码语言:javascript复制
>>> for row in result:
...     print(f"{row.name}  {row.email_address}")
spongebob  spongebob@sqlalchemy.org
sandy  sandy@sqlalchemy.org
sandy  squirrel@squirrelpower.org
patrick  pat999@aol.com
squidward  stentcl@sqlalchemy.org
使用 Bundles 分组选择的属性

Bundle构造是一个可扩展的仅 ORM 构造,允许将列表达式集合分组在结果行中:

代码语言:javascript复制
>>> from sqlalchemy.orm import Bundle
>>> stmt = select(
...     Bundle("user", User.name, User.fullname),
...     Bundle("email", Address.email_address),
... ).join_from(User, Address)
>>> for row in session.execute(stmt):
...     print(f"{row.user.name} {row.user.fullname} {row.email.email_address}")
SELECT  user_account.name,  user_account.fullname,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
spongebob Spongebob Squarepants spongebob@sqlalchemy.org
sandy Sandy Cheeks sandy@sqlalchemy.org
sandy Sandy Cheeks squirrel@squirrelpower.org
patrick Patrick Star pat999@aol.com
squidward Squidward Tentacles stentcl@sqlalchemy.org

Bundle可能对创建轻量级视图和自定义列分组有用。Bundle也可以被子类化以返回替代数据结构;请参阅Bundle.create_row_processor()获取示例。

另请参阅

Bundle

Bundle.create_row_processor() ### 选择 ORM 别名

如在使用别名的教程中所讨论的,要创建 ORM 实体的 SQL 别名是使用针对映射类的aliased()构造实现的:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> u1 = aliased(User)
>>> print(select(u1).order_by(u1.id))
SELECT  user_account_1.id,  user_account_1.name,  user_account_1.fullname
FROM  user_account  AS  user_account_1  ORDER  BY  user_account_1.id 

与使用Table.alias()时一样,SQL 别名是匿名命名的。对于从具有显式名称的行中选择实体的情况,还可以传递aliased.name参数:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> u1 = aliased(User, name="u1")
>>> stmt = select(u1).order_by(u1.id)
>>> row = session.execute(stmt).first()
SELECT  u1.id,  u1.name,  u1.fullname
FROM  user_account  AS  u1  ORDER  BY  u1.id
[...]  ()
>>> print(f"{row.u1.name}")
spongebob

另请参阅

aliased构造在几个用例中都很重要,包括:

  • 利用 ORM 进行子查询;章节从子查询中选择实体和加入子查询进一步讨论了这一点。
  • 控制结果集中实体的名称;参见同时选择多个 ORM 实体的示例。
  • 加入到同一个 ORM 实体多次;参见使用关系连接别名目标之间的示例。### 从文本语句中获取 ORM 结果

ORM 支持从来自其他来源的 SELECT 语句加载实体。典型用例是文本 SELECT 语句,在 SQLAlchemy 中使用text()构造表示。text()构造可以通过有关将加载该语句的 ORM 映射列的信息进行增强;然后可以将其与 ORM 实体本身关联,以便基于此语句加载 ORM 对象。

给定一个文本 SQL 语句,我们希望从中加载:

代码语言:javascript复制
>>> from sqlalchemy import text
>>> textual_sql = text("SELECT id, name, fullname FROM user_account ORDER BY id")

我们可以通过使用TextClause.columns()方法向语句添加列信息;当调用此方法时,TextClause对象转换为TextualSelect对象,其扮演与Select构造类似的角色。TextClause.columns()方法通常传递Column对象或等效对象,在这种情况下,我们可以直接使用User类上的 ORM 映射属性:

代码语言:javascript复制
>>> textual_sql = textual_sql.columns(User.id, User.name, User.fullname)

现在我们有一个经过 ORM 配置的 SQL 构造,可以分别加载“id”、“name”和“fullname”列。要将此 SELECT 语句作为完整User实体的来源,我们可以使用Select.from_statement()方法将这些列链接到常规的 ORM 启用的Select构造:

代码语言:javascript复制
>>> orm_sql = select(User).from_statement(textual_sql)
>>> for user_obj in session.execute(orm_sql).scalars():
...     print(user_obj)
SELECT  id,  name,  fullname  FROM  user_account  ORDER  BY  id
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

相同的TextualSelect对象也可以使用TextualSelect.subquery()方法转换为子查询,并使用aliased()构造将其链接到User实体中,方式与下文中从子查询中选择实体中所讨论的类似:

代码语言:javascript复制
>>> orm_subquery = aliased(User, textual_sql.subquery())
>>> stmt = select(orm_subquery)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname
FROM  (SELECT  id,  name,  fullname  FROM  user_account  ORDER  BY  id)  AS  anon_1
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

直接使用TextualSelectSelect.from_statement()与使用aliased()之间的区别在于,在前一种情况下,结果 SQL 中不会生成子查询。在某些情况下,从性能或复杂性的角度来看,这可能是有利的。### 从子查询中选择实体

前一节讨论的aliased()构造可以与任何Subquery构造一起使用,该构造来自诸如Select.subquery()之类的方法,以将 ORM 实体链接到该子查询返回的列;子查询返回的列与实体映射的列之间必须存在列对应关系,这意味着子查询最终需要源自这些实体,就像下面的示例中所示:

代码语言:javascript复制
>>> inner_stmt = select(User).where(User.id < 7).order_by(User.id)
>>> subq = inner_stmt.subquery()
>>> aliased_user = aliased(User, subq)
>>> stmt = select(aliased_user)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
  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.id  <  ?  ORDER  BY  user_account.id)  AS  anon_1
[generated  in  ...]  (7,)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

也请参见

ORM 实体子查询/CTEs - 在 SQLAlchemy 统一教程中

连接到子查询 ### 从 UNION 和其他集合操作中选择实体

union()union_all() 函数是最常见的集合操作,与其他集合操作(例如 except_()intersect() 等)一起提供了一个称为 CompoundSelect 的对象,该对象由多个由集合操作关键字连接的 Select 构造组成。ORM 实体可以使用 Select.from_statement() 方法从简单的复合选择中选择,如前面在从文本语句中获取 ORM 结果中所示。在此方法中,UNION 语句是将呈现的完整语句,不能在使用 Select.from_statement() 后添加额外的条件:

代码语言:javascript复制
>>> from sqlalchemy import union_all
>>> u = union_all(
...     select(User).where(User.id < 2), select(User).where(User.id == 3)
... ).order_by(User.id)
>>> stmt = select(User).from_statement(u)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.id  <  ?  UNION  ALL  SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.id  =  ?  ORDER  BY  id
[generated  in  ...]  (2,  3)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=3, name='patrick', fullname='Patrick Star')

在查询中,CompoundSelect 构造可以更灵活地使用,可以通过将其组织成子查询并使用 aliased() 连接到 ORM 实体来进一步修改,如前面在从子查询中选择实体中所示。在下面的示例中,我们首先使用 CompoundSelect.subquery() 创建 UNION ALL 语句的子查询,然后将其打包到 aliased() 构造中,在这里它可以像任何其他映射实体一样在 select() 构造中使用,包括我们可以基于其导出列添加过滤和排序条件:

代码语言:javascript复制
>>> subq = union_all(
...     select(User).where(User.id < 2), select(User).where(User.id == 3)
... ).subquery()
>>> user_alias = aliased(User, subq)
>>> stmt = select(user_alias).order_by(user_alias.id)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
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.id  <  ?  UNION  ALL  SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.id  =  ?)  AS  anon_1  ORDER  BY  anon_1.id
[generated  in  ...]  (2,  3)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=3, name='patrick', fullname='Patrick Star')

请参阅

从联合中选择 ORM 实体 - 在 SQLAlchemy 统一教程中 ### 选择 ORM 实体

下面我们从 User 实体中进行选择,生成一个从 User 映射到的映射 Table 中进行选择的 Select

代码语言:javascript复制
>>> result = session.execute(select(User).order_by(User.id))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.id
[...]  () 

当从 ORM 实体中进行选择时,实体本身作为包含单个元素的行返回结果,而不是一系列单独的列;例如上面的例子,Result 返回仅具有每行单个元素的 Row 对象,该元素保存一个 User 对象:

代码语言:javascript复制
>>> result.all()
[(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),),
 (User(id=2, name='sandy', fullname='Sandy Cheeks'),),
 (User(id=3, name='patrick', fullname='Patrick Star'),),
 (User(id=4, name='squidward', fullname='Squidward Tentacles'),),
 (User(id=5, name='ehkrabs', fullname='Eugene H. Krabs'),)]

当选择包含 ORM 实体的单元素行列表时,通常会跳过生成 Row 对象,而是直接接收 ORM 实体。这最容易通过使用 Session.scalars() 方法执行,而不是 Session.execute() 方法来实现,以便返回一个 ScalarResult 对象,该对象产生单个元素而不是行:

代码语言:javascript复制
>>> session.scalars(select(User).order_by(User.id)).all()
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  ORDER  BY  user_account.id
[...]  ()
[User(id=1, name='spongebob', fullname='Spongebob Squarepants'),
 User(id=2, name='sandy', fullname='Sandy Cheeks'),
 User(id=3, name='patrick', fullname='Patrick Star'),
 User(id=4, name='squidward', fullname='Squidward Tentacles'),
 User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')]

调用 Session.scalars() 方法相当于调用 Session.execute() 来接收一个 Result 对象,然后调用 Result.scalars() 来接收一个 ScalarResult 对象。

同时选择多个 ORM 实体

select() 函数一次接受任意数量的 ORM 类和/或列表达式,包括可以请求多个 ORM 类的情况。当从多个 ORM 类中进行 SELECT 时,它们在每个结果行中基于其类名命名。在下面的示例中,对 UserAddress 进行 SELECT 的结果行将以 UserAddress 为名称进行引用:

代码语言:javascript复制
>>> stmt = select(User, Address).join(User.addresses).order_by(User.id, Address.id)
>>> for row in session.execute(stmt):
...     print(f"{row.User.name} {row.Address.email_address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
address.id  AS  id_1,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id
[...]  ()
spongebob spongebob@sqlalchemy.org
sandy sandy@sqlalchemy.org
sandy squirrel@squirrelpower.org
patrick pat999@aol.com
squidward stentcl@sqlalchemy.org

如果我们想要为这些实体在行中分配不同的名称,我们将使用 aliased() 构造,并使用 aliased.name 参数将它们别名为具有显式名称的实体:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> user_cls = aliased(User, name="user_cls")
>>> email_cls = aliased(Address, name="email")
>>> stmt = (
...     select(user_cls, email_cls)
...     .join(user_cls.addresses.of_type(email_cls))
...     .order_by(user_cls.id, email_cls.id)
... )
>>> row = session.execute(stmt).first()
SELECT  user_cls.id,  user_cls.name,  user_cls.fullname,
email.id  AS  id_1,  email.user_id,  email.email_address
FROM  user_account  AS  user_cls  JOIN  address  AS  email
ON  user_cls.id  =  email.user_id  ORDER  BY  user_cls.id,  email.id
[...]  ()
>>> print(f"{row.user_cls.name}  {row.email.email_address}")
spongebob spongebob@sqlalchemy.org

上面的别名形式在使用关系来在别名目标之间进行连接中进一步讨论。

现有的 Select 结构也可以使用 Select.add_columns() 方法将 ORM 类和/或列表达式添加到其列子句中。我们也可以使用这种形式来生成与上面相同的语句:

代码语言:javascript复制
>>> stmt = (
...     select(User).join(User.addresses).add_columns(Address).order_by(User.id, Address.id)
... )
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname,
address.id  AS  id_1,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id 
选择单个属性

映射类上的属性,如 User.nameAddress.email_address,当传递给 select() 时,可以像 Column 或其他 SQL 表达式对象一样使用。创建针对特定列的 select() 将返回 Row 对象,而不是像 UserAddress 对象那样的实体。每个 Row 将分别表示每个列:

代码语言:javascript复制
>>> result = session.execute(
...     select(User.name, Address.email_address)
...     .join(User.addresses)
...     .order_by(User.id, Address.id)
... )
SELECT  user_account.name,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
ORDER  BY  user_account.id,  address.id
[...]  () 

上面的语句返回具有 nameemail_address 列的 Row 对象,如下运行时演示所示:

代码语言:javascript复制
>>> for row in result:
...     print(f"{row.name}  {row.email_address}")
spongebob  spongebob@sqlalchemy.org
sandy  sandy@sqlalchemy.org
sandy  squirrel@squirrelpower.org
patrick  pat999@aol.com
squidward  stentcl@sqlalchemy.org
使用 Bundle 分组选择的属性

Bundle 构造是一个可扩展的仅限 ORM 的构造,允许将列表达式集合分组在结果行中:

代码语言:javascript复制
>>> from sqlalchemy.orm import Bundle
>>> stmt = select(
...     Bundle("user", User.name, User.fullname),
...     Bundle("email", Address.email_address),
... ).join_from(User, Address)
>>> for row in session.execute(stmt):
...     print(f"{row.user.name} {row.user.fullname} {row.email.email_address}")
SELECT  user_account.name,  user_account.fullname,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
[...]  ()
spongebob Spongebob Squarepants spongebob@sqlalchemy.org
sandy Sandy Cheeks sandy@sqlalchemy.org
sandy Sandy Cheeks squirrel@squirrelpower.org
patrick Patrick Star pat999@aol.com
squidward Squidward Tentacles stentcl@sqlalchemy.org

Bundle 可能对创建轻量级视图和自定义列分组很有用。Bundle 也可以被子类化以返回替代数据结构;请参见 Bundle.create_row_processor() 以获取示例。

另请参阅

Bundle

Bundle.create_row_processor()

选择 ORM 别名

如使用别名教程中所述,创建 ORM 实体的 SQL 别名是通过对映射类使用 aliased() 构造完成的:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> u1 = aliased(User)
>>> print(select(u1).order_by(u1.id))
SELECT  user_account_1.id,  user_account_1.name,  user_account_1.fullname
FROM  user_account  AS  user_account_1  ORDER  BY  user_account_1.id 

就像使用Table.alias()时一样,SQL 别名是匿名命名的。对于从具有显式名称的行中选择实体的情况,也可以传递aliased.name参数:

代码语言:javascript复制
>>> from sqlalchemy.orm import aliased
>>> u1 = aliased(User, name="u1")
>>> stmt = select(u1).order_by(u1.id)
>>> row = session.execute(stmt).first()
SELECT  u1.id,  u1.name,  u1.fullname
FROM  user_account  AS  u1  ORDER  BY  u1.id
[...]  ()
>>> print(f"{row.u1.name}")
spongebob

另请参阅

aliased结构对于多种用例至关重要,包括:

  • 利用 ORM 的子查询;章节从子查询中选择实体和与子查询连接进一步讨论了这一点。
  • 控制结果集中实体的名称;参见同时选择多个 ORM 实体以获取示例
  • 多次连接到同一 ORM 实体;参见使用关系在别名目标之间连接以获取示例。
从文本语句中获取 ORM 结果

对象关系映射(ORM)支持从其他来源的 SELECT 语句加载实体。典型用例是文本 SELECT 语句,在 SQLAlchemy 中使用text()结构表示。text()结构可以附加有关语句将加载的 ORM 映射列的信息;然后可以将其与 ORM 实体本身关联,以便基于此语句加载 ORM 对象。

给定要加载的文本 SQL 语句:

代码语言:javascript复制
>>> from sqlalchemy import text
>>> textual_sql = text("SELECT id, name, fullname FROM user_account ORDER BY id")

我们可以使用TextClause.columns()方法向语句添加列信息;当调用此方法时,TextClause对象将转换为TextualSelect对象,其承担的角色可与Select构造类似。TextClause.columns()方法通常传递Column对象或等效对象,在这种情况下,我们可以直接使用User类上的 ORM 映射属性:

代码语言:javascript复制
>>> textual_sql = textual_sql.columns(User.id, User.name, User.fullname)

现在,我们有一个经过 ORM 配置的 SQL 构造,可以分别加载“id”、“name”和“fullname”列。要将此 SELECT 语句用作完整User实体的来源,我们可以使用Select.from_statement()方法将这些列链接到常规的 ORM 启用的Select构造中:

代码语言:javascript复制
>>> orm_sql = select(User).from_statement(textual_sql)
>>> for user_obj in session.execute(orm_sql).scalars():
...     print(user_obj)
SELECT  id,  name,  fullname  FROM  user_account  ORDER  BY  id
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

相同的TextualSelect对象也可以使用TextualSelect.subquery()方法转换为子查询,并使用aliased()构造将其链接到User实体,方式与下面讨论的从子查询中选择实体类似:

代码语言:javascript复制
>>> orm_subquery = aliased(User, textual_sql.subquery())
>>> stmt = select(orm_subquery)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname
FROM  (SELECT  id,  name,  fullname  FROM  user_account  ORDER  BY  id)  AS  anon_1
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

直接使用TextualSelectSelect.from_statement()相比,使用aliased()的区别在于,在前一种情况下,生成的 SQL 中不会产生子查询。在某些情况下,这可能有利于性能或复杂性方面。

从子查询中选择实体

在前一节讨论的aliased()构造中,可以与任何Subuqery构造一起使用,该构造来自诸如Select.subquery()之类的方法,以将 ORM 实体链接到该子查询返回的列;子查询返回的列与实体映射的列之间必须存在列对应关系,这意味着子查询最终需要源自这些实体,例如下面的示例:

代码语言:javascript复制
>>> inner_stmt = select(User).where(User.id < 7).order_by(User.id)
>>> subq = inner_stmt.subquery()
>>> aliased_user = aliased(User, subq)
>>> stmt = select(aliased_user)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
  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.id  <  ?  ORDER  BY  user_account.id)  AS  anon_1
[generated  in  ...]  (7,)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=3, name='patrick', fullname='Patrick Star')
User(id=4, name='squidward', fullname='Squidward Tentacles')
User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')

另请参见

ORM 实体子查询/CTEs - 在 SQLAlchemy 统一教程中

加入子查询

从 UNION 和其他集合操作中选择实体

union()union_all() 函数是最常见的集合操作,与其他集合操作(如 except_()intersect() 等)一起,提供了一个称为 CompoundSelect 的对象,它由多个通过集合操作关键字连接的 Select 构造组成。ORM 实体可以通过简单的复合选择使用 Select.from_statement() 方法进行选择,该方法在 从文本语句中获取 ORM 结果 中已经说明。在这种方法中,UNION 语句是将被渲染的完整语句,不能在使用 Select.from_statement() 后添加额外的条件:

代码语言:javascript复制
>>> from sqlalchemy import union_all
>>> u = union_all(
...     select(User).where(User.id < 2), select(User).where(User.id == 3)
... ).order_by(User.id)
>>> stmt = select(User).from_statement(u)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.id  <  ?  UNION  ALL  SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.id  =  ?  ORDER  BY  id
[generated  in  ...]  (2,  3)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=3, name='patrick', fullname='Patrick Star')

CompoundSelect 构造可以在更灵活的查询中更灵活地使用,该查询可以通过将其组织成子查询并使用 aliased() 将其链接到 ORM 实体来进一步修改,如 从子查询中选择实体 中已说明。在下面的示例中,我们首先使用 CompoundSelect.subquery() 创建 UNION ALL 语句的子查询,然后将其打包到 aliased() 构造中,在这里它可以像其他映射实体一样用于 select() 构造中,包括我们可以根据其导出的列添加过滤和排序条件:

代码语言:javascript复制
>>> subq = union_all(
...     select(User).where(User.id < 2), select(User).where(User.id == 3)
... ).subquery()
>>> user_alias = aliased(User, subq)
>>> stmt = select(user_alias).order_by(user_alias.id)
>>> for user_obj in session.execute(stmt).scalars():
...     print(user_obj)
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.id  <  ?  UNION  ALL  SELECT  user_account.id  AS  id,  user_account.name  AS  name,  user_account.fullname  AS  fullname
FROM  user_account
WHERE  user_account.id  =  ?)  AS  anon_1  ORDER  BY  anon_1.id
[generated  in  ...]  (2,  3)
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=3, name='patrick', fullname='Patrick Star')

另请参阅

从 Union 中选择 ORM 实体 - 在 SQLAlchemy 统一教程 中

连接

Select.join()Select.join_from()方法用于构建针对 SELECT 语句的 SQL JOINs。

本节将详细介绍这些方法在 ORM 中的用例。有关从 Core 视角的使用的一般概述,请参阅显式 FROM 子句和 JOINs 中的 SQLAlchemy 统一教程。

在 ORM 上下文中使用Select.join()进行 2.0 风格查询的用法基本上等同于除了遗留用例之外,在 1.x 风格查询中使用Query.join()方法的用法。

简单的关系连接

考虑两个类UserAddress之间的映射,其中关系User.addresses表示与每个User关联的Address对象的集合。Select.join()最常见的用法是沿着这种关系创建一个 JOIN,使用User.addresses属性作为指示器来指示应该如何发生这种情况:

代码语言:javascript复制
>>> stmt = select(User).join(User.addresses)

在上面的例子中,对Select.join()User.addresses的调用将导致大致等效的 SQL 语句:

代码语言:javascript复制
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

在上面的例子中,我们将User.addresses称为传递给Select.join()的“on clause”,即它指示如何构建 JOIN 的“ON”部分。

提示

请注意,使用Select.join()从一个实体连接到另一个实体会影响 SELECT 语句的 FROM 子句,但不会影响列子句;在这个示例中,SELECT 语句将继续仅返回User实体的行。要同时从UserAddress中选择列/实体,必须在select()函数中也命名Address实体,或者在使用Select.add_columns()方法后将其添加到Select构造中。有关这两种形式的示例,请参阅同时选择多个 ORM 实体部分。

链式多重连接

要构建连接链,可以使用多个Select.join()调用。关联属性同时涵盖连接的左侧和右侧。考虑额外的实体OrderItem,其中User.orders关系指向Order实体,而Order.items关系指向Item实体,通过一个关联表order_items。两个Select.join()调用将导致第一个 JOIN 从UserOrder,第二个从OrderItem。然而,由于Order.items是多对多关系,它会导致两个独立的 JOIN 元素,总共有三个 JOIN 元素在结果 SQL 中:

代码语言:javascript复制
>>> stmt = select(User).join(User.orders).join(Order.items)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  user_order  ON  user_account.id  =  user_order.user_id
JOIN  order_items  AS  order_items_1  ON  user_order.id  =  order_items_1.order_id
JOIN  item  ON  item.id  =  order_items_1.item_id 

每次调用Select.join()方法的顺序只有在我们想要从中连接的“左”侧需要出现在 FROM 列表中时才重要,然后我们才能指示一个新的目标。例如,如果我们指定select(User).join(Order.items).join(User.orders)Select.join()就不会知道如何正确地进行连接,它会引发错误。在正确的实践中,应该以与我们希望在 SQL 中呈现 JOIN 子句相匹配的方式调用Select.join()方法,并且每次调用都应该表示从前面的内容清晰链接。

我们在 FROM 子句中定位的所有元素仍然可用作继续连接 FROM 的潜在点。例如,我们可以继续将其他元素添加到上述User实体的 FROM 连接中,例如在我们的连接链中添加User.addresses关系:

代码语言:javascript复制
>>> stmt = select(User).join(User.orders).join(Order.items).join(User.addresses)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  user_order  ON  user_account.id  =  user_order.user_id
JOIN  order_items  AS  order_items_1  ON  user_order.id  =  order_items_1.order_id
JOIN  item  ON  item.id  =  order_items_1.item_id
JOIN  address  ON  user_account.id  =  address.user_id 
连接到目标实体

第二种形式的Select.join()允许将任何映射实体或核心可选择的构造作为目标。在此用法中,Select.join() 将尝试推断JOIN 的 ON 子句,使用两个实体之间的自然外键关系:

代码语言:javascript复制
>>> stmt = select(User).join(Address)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

在上述调用形式中,Select.join() 被调用以自动推断“on clause”。如果两个映射的Table构造之间没有设置任何ForeignKeyConstraint,或者如果它们之间有多个ForeignKeyConstraint链接,使得要使用的适当约束不明确,此调用形式最终将引发错误。

注意

在使用Select.join()Select.join_from()而不指定 ON 子句时,ORM 配置的relationship()构造不会被考虑。仅在尝试为 JOIN 推断 ON 子句时,才会在映射的Table对象级别上查阅配置的ForeignKeyConstraint关系。

到具有 ON 子句的目标的连接

第三种调用形式允许显式传递目标实体以及 ON 子句。包含 SQL 表达式作为 ON 子句的示例如下:

代码语言:javascript复制
>>> stmt = select(User).join(Address, User.id == Address.user_id)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

基于表达式的 ON 子句也可以是一个relationship()绑定属性,就像在简单关系连接中使用的方式一样:

代码语言:javascript复制
>>> stmt = select(User).join(Address, User.addresses)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

上述示例似乎多余,因为它以两种不同的方式指示了Address的目标;然而,当加入到别名实体时,这种形式的实用性变得明显;请参见使用关系连接别名目标中的示例。### 将关系与自定义 ON 条件结合使用

relationship()构造生成的 ON 子句可以通过附加的额外条件进行增强。这对于快速限制特定关系路径上连接范围的方式以及配置加载策略(如joinedload()selectinload())非常有用。PropComparator.and_()方法按位置接受一系列 SQL 表达式,这些表达式将通过 AND 连接到 JOIN 的 ON 子句。例如,如果我们想要从User JOIN 到Address,但也限制 ON 条件仅适用于某些电子邮件地址:

代码语言:javascript复制
>>> stmt = select(User.fullname).join(
...     User.addresses.and_(Address.email_address == "squirrel@squirrelpower.org")
... )
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
JOIN  address  ON  user_account.id  =  address.user_id  AND  address.email_address  =  ?
[...]  ('squirrel@squirrelpower.org',)
[('Sandy Cheeks',)]

另请参阅

PropComparator.and_()方法也适用于加载策略,如joinedload()selectinload()。请参见向加载选项添加条件部分。### 使用关系连接别名目标

当使用relationship()绑定属性构建连接以指示 ON 子句时,使用带有 ON 子句的目标的连接中说明的两参数语法可以扩展为与aliased()构造一起使用,以指示 SQL 别名作为连接的目标,同时仍然利用relationship()绑定属性来指示 ON 子句,如下例所示,其中User实体两次与两个不同的aliased()构造连接到Address实体:

代码语言:javascript复制
>>> address_alias_1 = aliased(Address)
>>> address_alias_2 = aliased(Address)
>>> stmt = (
...     select(User)
...     .join(address_alias_1, User.addresses)
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join(address_alias_2, User.addresses)
...     .where(address_alias_2.email_address == "patrick@gmail.com")
... )
>>> print(stmt)
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 

同样的模式可以更简洁地使用修饰符 PropComparator.of_type() 表达,该修饰符可以应用于 relationship() 绑定的属性,传递目标实体以一步指示目标。下面的示例使用 PropComparator.of_type() 来生成与刚刚示例相同的 SQL 语句:

代码语言:javascript复制
>>> print(
...     select(User)
...     .join(User.addresses.of_type(address_alias_1))
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join(User.addresses.of_type(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 

要利用一个 relationship() 来构建一个从别名实体连接的连接,该属性直接从 aliased() 构造中可用:

代码语言:javascript复制
>>> user_alias_1 = aliased(User)
>>> print(select(user_alias_1.name).join(user_alias_1.addresses))
SELECT  user_account_1.name
FROM  user_account  AS  user_account_1
JOIN  address  ON  user_account_1.id  =  address.user_id 
```### 连接到子查询

连接的目标可以是任何可选择的实体,包括子查询。在使用 ORM 时,通常会以 `aliased()` 构造来表示这些目标,但这并不是严格要求的,特别是如果连接的实体不会在结果中返回时。例如,要从 `User` 实体连接到 `Address` 实体,在这里 `Address` 实体被表示为一行限制的子查询,我们首先使用 `Select.subquery()` 构造一个 `Subquery` 对象,然后可以将其用作 `Select.join()` 方法的目标:

```py
>>> subq = select(Address).where(Address.email_address == "pat999@aol.com").subquery()
>>> stmt = select(User).join(subq, User.id == subq.c.user_id)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  :email_address_1)  AS  anon_1
ON  user_account.id  =  anon_1.user_id 

上述的 SELECT 语句在通过 Session.execute() 调用时将返回包含 User 实体的行,但不包含 Address 实体。为了将 Address 实体包含到将在结果集中返回的实体集合中,我们构造了一个针对 Address 实体和 Subquery 对象的 aliased() 对象。我们还可以希望对 aliased() 构造应用一个名称,例如下面使用的 "address",这样我们就可以在结果行中通过名称引用它:

代码语言:javascript复制
>>> address_subq = aliased(Address, subq, name="address")
>>> stmt = select(User, address_subq).join(address_subq)
>>> for row in session.execute(stmt):
...     print(f"{row.User} {row.address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.user_id,  anon_1.email_address
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
[...]  ('pat999@aol.com',)
User(id=3, name='patrick', fullname='Patrick Star') Address(id=4, email_address='pat999@aol.com')
沿关系路径连接到子查询

在前一节中示例的子查询形式可以使用更具体的方式来表达,使用一个relationship()绑定的属性,使用使用关系在别名目标之间进行连接中指示的形式之一。例如,要创建相同的连接,并确保连接沿着特定relationship()进行,我们可以使用PropComparator.of_type()方法,传递包含要连接的Subquery对象的aliased()构造:

代码语言:javascript复制
>>> address_subq = aliased(Address, subq, name="address")
>>> stmt = select(User, address_subq).join(User.addresses.of_type(address_subq))
>>> for row in session.execute(stmt):
...     print(f"{row.User} {row.address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.user_id,  anon_1.email_address
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
[...]  ('pat999@aol.com',)
User(id=3, name='patrick', fullname='Patrick Star') Address(id=4, email_address='pat999@aol.com')
引用多个实体的子查询

包含跨越多个 ORM 实体的列的子查询可以同时应用于多个aliased()构造,并在相同的Select构造中按照每个实体分别处理。然而,生成的 SQL 仍将所有这些aliased()构造视为相同的子查询,但是从 ORM / Python 的角度来看,可以使用适当的aliased()构造来引用不同的返回值和对象属性。

例如,给定一个同时引用UserAddress的子查询:

代码语言:javascript复制
>>> user_address_subq = (
...     select(User.id, User.name, User.fullname, Address.id, Address.email_address)
...     .join_from(User, Address)
...     .where(Address.email_address.in_(["pat999@aol.com", "squirrel@squirrelpower.org"]))
...     .subquery()
... )

我们可以针对UserAddress分别创建aliased()构造,它们各自指向相同的对象:

代码语言:javascript复制
>>> user_alias = aliased(User, user_address_subq, name="user")
>>> address_alias = aliased(Address, user_address_subq, name="address")

从两个实体中进行选择的Select构造将会渲染子查询一次,但在结果行上下文中可以同时返回UserAddress类的对象:

代码语言:javascript复制
>>> stmt = select(user_alias, address_alias).where(user_alias.name == "sandy")
>>> for row in session.execute(stmt):
...     print(f"{row.user} {row.address}")
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname,  anon_1.id_1,  anon_1.email_address
FROM  (SELECT  user_account.id  AS  id,  user_account.name  AS  name,
user_account.fullname  AS  fullname,  address.id  AS  id_1,
address.email_address  AS  email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  address.email_address  IN  (?,  ?))  AS  anon_1
WHERE  anon_1.name  =  ?
[...]  ('pat999@aol.com',  'squirrel@squirrelpower.org',  'sandy')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='squirrel@squirrelpower.org')
设置连接中最左侧的 FROM 子句

在当前Select状态的左侧与我们想要连接的内容不符合的情况下,可以使用Select.join_from()方法:

代码语言:javascript复制
>>> stmt = select(Address).join_from(User, User.addresses).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

Select.join_from()方法接受两个或三个参数,可以是(<join from>, <onclause>)形式,也可以是(<join from>, <join to>, [<onclause>])形式:

代码语言:javascript复制
>>> stmt = select(Address).join_from(User, Address).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

为了设置初始的 FROM 子句,以便之后可以使用 Select.join(),可以使用 Select.select_from() 方法:

代码语言:javascript复制
>>> stmt = select(Address).select_from(User).join(Address).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

提示

Select.select_from() 方法实际上并不决定 FROM 子句中表的顺序。如果语句还引用了指向不同顺序的现有表的 Join 结构,那么 Join 结构将优先。当我们使用 Select.join()Select.join_from() 等方法时,这些方法最终创建了这样一个 Join 对象。因此,在这种情况下,我们可以看到 Select.select_from() 的内容被覆盖了:

代码语言:javascript复制
>>> stmt = select(Address).select_from(User).join(Address.user).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  address  JOIN  user_account  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

在上面的例子中,我们看到 FROM 子句是 address JOIN user_account,尽管我们首先声明了 select_from(User)。由于 .join(Address.user) 方法调用,该语句最终等同于以下内容:

代码语言:javascript复制
>>> from sqlalchemy.sql import join
>>>
>>> user_table = User.__table__
>>> address_table = Address.__table__
>>>
>>> j = address_table.join(user_table, user_table.c.id == address_table.c.user_id)
>>> stmt = (
...     select(address_table)
...     .select_from(user_table)
...     .select_from(j)
...     .where(user_table.c.name == "sandy")
... )
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  address  JOIN  user_account  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

上述的 Join 结构被添加为 Select.select_from() 列表中的另一个条目,它取代了先前的条目。 ### 简单的关系连接

考虑两个类 UserAddress 之间的映射,其中关系 User.addresses 表示与每个 User 关联的 Address 对象的集合。Select.join() 的最常见用法是沿着这种关系创建 JOIN,使用 User.addresses 属性作为指示器指示应该如何进行连接:

代码语言:javascript复制
>>> stmt = select(User).join(User.addresses)

在上面的例子中,对 User.addresses 使用 Select.join() 的调用将导致大致等效于以下 SQL:

代码语言:javascript复制
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

在上面的示例中,我们将User.addresses称为传递给Select.join()的“on clause”,即指示如何构建 JOIN 的“ON”部分。

提示

请注意,使用Select.join()从一个实体 JOIN 到另一个实体会影响 SELECT 语句的 FROM 子句,但不影响列子句;在这个示例中,SELECT 语句仍将只返回来自User实体的行。要同时从UserAddress中 SELECT 列/实体,必须在select()函数中也命名Address实体,或者使用Select.add_columns()方法在之后将其添加到Select构造中。有关这两种形式的示例,请参见同时选择多个 ORM 实体部分。

链接多个表

要构建一系列 JOIN,可以使用多个Select.join()调用。关系绑定属性同时暗示 JOIN 的左侧和右侧。考虑额外的实体OrderItem,其中User.orders关系指向Order实体,而Order.items关系通过关联表order_items指向Item实体。两个Select.join()调用将导致从UserOrder的第一个 JOIN,以及从OrderItem的第二个 JOIN。然而,由于Order.items是多对多关系,它会导致两个独立的 JOIN 元素,总共在生成的 SQL 中有三个 JOIN 元素:

代码语言:javascript复制
>>> stmt = select(User).join(User.orders).join(Order.items)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  user_order  ON  user_account.id  =  user_order.user_id
JOIN  order_items  AS  order_items_1  ON  user_order.id  =  order_items_1.order_id
JOIN  item  ON  item.id  =  order_items_1.item_id 

每次调用Select.join()方法的顺序只有在我们希望连接的“左”侧需要在 FROM 列表中存在之前才会产生影响。例如,如果我们指定了select(User).join(Order.items).join(User.orders),那么Select.join()将无法正确连接,并且会引发错误。在正确的实践中,应以类似于 SQL 中 JOIN 子句应该呈现的方式调用Select.join()方法,并且每次调用应该代表与其前面的内容之间的清晰链接。

我们在 FROM 子句中定位的所有元素仍然可以作为继续连接 FROM 的潜在点。例如,我们可以在上面的User实体上继续添加其他元素以连接 FROM,例如在我们的连接链中添加User.addresses关系:

代码语言:javascript复制
>>> stmt = select(User).join(User.orders).join(Order.items).join(User.addresses)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  user_order  ON  user_account.id  =  user_order.user_id
JOIN  order_items  AS  order_items_1  ON  user_order.id  =  order_items_1.order_id
JOIN  item  ON  item.id  =  order_items_1.item_id
JOIN  address  ON  user_account.id  =  address.user_id 
连接到目标实体

Select.join()的第二种形式允许任何映射实体或核心可选择的构造作为目标。在这种用法中,Select.join()将尝试推断连接的 ON 子句,使用两个实体之间的自然外键关系:

代码语言:javascript复制
>>> stmt = select(User).join(Address)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

在上述调用形式中,Select.join()被调用来自动推断“on 子句”。如果两个映射的Table构造之间没有设置任何ForeignKeyConstraint,或者如果它们之间存在多个ForeignKeyConstraint链接,使得要使用的适当约束不明确,这种调用形式最终将引发错误。

注意

当使用 Select.join()Select.join_from() 而没有指定 ON 子句时,ORM 配置的 relationship() 构建不会考虑。只有在尝试推断 JOIN 的 ON 子句时,才会查询映射的 Table 对象级别的实体之间配置的 ForeignKeyConstraint 关系。

加入带有 ON 子句的目标

第三种调用形式允许同时显式传递目标实体和 ON 子句。一个包含 SQL 表达式作为 ON 子句的示例如下:

代码语言:javascript复制
>>> stmt = select(User).join(Address, User.id == Address.user_id)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

表达式基于的 ON 子句也可以是 relationship() 绑定的属性,就像在 简单 Relationship 加入 中使用的方式一样:

代码语言:javascript复制
>>> stmt = select(User).join(Address, User.addresses)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id 

上述示例似乎冗余,因为它以两种不同的方式指示了 Address 的目标;然而,当加入别名实体时,这种形式的实用性就变得明显了;请参见 使用 Relationship 在别名目标之间加入 部分的示例。

将 Relationship 与自定义 ON 条件相结合

relationship() 构建生成的 ON 子句可以通过附加的条件进行增强。这对于快速限制特定关系路径上连接的范围的方法以及配置加载器策略(例如 joinedload()selectinload())等情况都很有用。PropComparator.and_() 方法接受一系列 SQL 表达式,这些表达式将通过 AND 连接到 JOIN 的 ON 子句中。例如,如果我们想要从 User 加入到 Address,但也只限制 ON 条件到某些电子邮件地址:

代码语言:javascript复制
>>> stmt = select(User.fullname).join(
...     User.addresses.and_(Address.email_address == "squirrel@squirrelpower.org")
... )
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
JOIN  address  ON  user_account.id  =  address.user_id  AND  address.email_address  =  ?
[...]  ('squirrel@squirrelpower.org',)
[('Sandy Cheeks',)]

另请参见

PropComparator.and_()方法也适用于加载策略,如joinedload()selectinload()。参见向加载选项添加条件一节。

使用关系连接别名目标

当使用relationship()绑定的属性来指示 ON 子句构建连接时,可以将具有 ON 子句的目标的连接中示例的二参数语法扩展到与aliased()构造一起工作,以指示 SQL 别名作为连接的目标,同时仍然利用relationship()绑定的属性来指示 ON 子句,如下例所示,其中User实体两次与两个不同的aliased()构造连接到Address实体:

代码语言:javascript复制
>>> address_alias_1 = aliased(Address)
>>> address_alias_2 = aliased(Address)
>>> stmt = (
...     select(User)
...     .join(address_alias_1, User.addresses)
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join(address_alias_2, User.addresses)
...     .where(address_alias_2.email_address == "patrick@gmail.com")
... )
>>> print(stmt)
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 

使用修饰符PropComparator.of_type()可以更简洁地表达相同的模式,该修饰符可以应用于relationship()绑定的属性,通过传递目标实体以一步指示目标。下面的示例使用PropComparator.of_type()来生成与刚刚示例相同的 SQL 语句:

代码语言:javascript复制
>>> print(
...     select(User)
...     .join(User.addresses.of_type(address_alias_1))
...     .where(address_alias_1.email_address == "patrick@aol.com")
...     .join(User.addresses.of_type(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 

要利用relationship()构建从别名实体的连接,可以直接从aliased()构造中使用属性:

代码语言:javascript复制
>>> user_alias_1 = aliased(User)
>>> print(select(user_alias_1.name).join(user_alias_1.addresses))
SELECT  user_account_1.name
FROM  user_account  AS  user_account_1
JOIN  address  ON  user_account_1.id  =  address.user_id 
连接到子查询

加入的目标可以是任何“可选择”的实体,包括子查询。在使用 ORM 时,通常会使用 aliased() 构造来表示这些目标,但这不是严格要求的,特别是如果加入的实体不会在结果中返回的情况下。例如,要从 User 实体加入到 Address 实体,其中 Address 实体被表示为一行限制的子查询,我们首先使用 Select.subquery() 构造一个 Subquery 对象,然后可以将其用作 Select.join() 方法的目标:

代码语言:javascript复制
>>> subq = select(Address).where(Address.email_address == "pat999@aol.com").subquery()
>>> stmt = select(User).join(subq, User.id == subq.c.user_id)
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  :email_address_1)  AS  anon_1
ON  user_account.id  =  anon_1.user_id 

上述 SELECT 语句在通过 Session.execute() 调用时将返回包含 User 实体但不包含 Address 实体的行。为了将 Address 实体包含到将在结果集中返回的实体集中,我们针对 Address 实体和 Subquery 对象构造了一个 aliased() 对象。我们可能还希望对 aliased() 构造应用一个名称,例如下面使用的 "address",以便我们可以通过名称在结果行中引用它:

代码语言:javascript复制
>>> address_subq = aliased(Address, subq, name="address")
>>> stmt = select(User, address_subq).join(address_subq)
>>> for row in session.execute(stmt):
...     print(f"{row.User} {row.address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.user_id,  anon_1.email_address
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
[...]  ('pat999@aol.com',)
User(id=3, name='patrick', fullname='Patrick Star') Address(id=4, email_address='pat999@aol.com')
沿着关系路径加入子查询

前面部分中示例的子查询形式可以使用一个 relationship() 绑定属性更具体地表示,使用 使用 Relationship 在别名目标之间加入 中指示的其中一种形式。例如,为了创建相同的加入并确保加入是沿着特定 relationship() 进行的,我们可以使用 PropComparator.of_type() 方法,传递包含加入目标的 aliased() 构造的 Subquery 对象:

代码语言:javascript复制
>>> address_subq = aliased(Address, subq, name="address")
>>> stmt = select(User, address_subq).join(User.addresses.of_type(address_subq))
>>> for row in session.execute(stmt):
...     print(f"{row.User} {row.address}")
SELECT  user_account.id,  user_account.name,  user_account.fullname,
anon_1.id  AS  id_1,  anon_1.user_id,  anon_1.email_address
FROM  user_account
JOIN  (SELECT  address.id  AS  id,
address.user_id  AS  user_id,  address.email_address  AS  email_address
FROM  address
WHERE  address.email_address  =  ?)  AS  anon_1  ON  user_account.id  =  anon_1.user_id
[...]  ('pat999@aol.com',)
User(id=3, name='patrick', fullname='Patrick Star') Address(id=4, email_address='pat999@aol.com')
引用多个实体的子查询

包含跨越多个 ORM 实体的列的子查询可以同时应用于多个aliased()构造,并且在每个实体的情况下都可以在相同的Select构造中使用。生成的 SQL 将继续将所有这样的aliased()构造视为相同的子查询,但是从 ORM / Python 的角度来看,可以通过使用适当的aliased()构造来引用不同的返回值和对象属性。

给定一个同时引用UserAddress的子查询,例如:

代码语言:javascript复制
>>> user_address_subq = (
...     select(User.id, User.name, User.fullname, Address.id, Address.email_address)
...     .join_from(User, Address)
...     .where(Address.email_address.in_(["pat999@aol.com", "squirrel@squirrelpower.org"]))
...     .subquery()
... )

我们可以创建针对UserAddressaliased()构造,它们各自都引用相同的对象:

代码语言:javascript复制
>>> user_alias = aliased(User, user_address_subq, name="user")
>>> address_alias = aliased(Address, user_address_subq, name="address")

从两个实体中选择的Select构造将只渲染子查询一次,但在结果行上下文中可以同时返回UserAddress类的对象:

代码语言:javascript复制
>>> stmt = select(user_alias, address_alias).where(user_alias.name == "sandy")
>>> for row in session.execute(stmt):
...     print(f"{row.user} {row.address}")
SELECT  anon_1.id,  anon_1.name,  anon_1.fullname,  anon_1.id_1,  anon_1.email_address
FROM  (SELECT  user_account.id  AS  id,  user_account.name  AS  name,
user_account.fullname  AS  fullname,  address.id  AS  id_1,
address.email_address  AS  email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  address.email_address  IN  (?,  ?))  AS  anon_1
WHERE  anon_1.name  =  ?
[...]  ('pat999@aol.com',  'squirrel@squirrelpower.org',  'sandy')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='squirrel@squirrelpower.org')
设置连接中最左边的 FROM 子句

在当前Select状态的左侧与我们要连接的内容不一致的情况下,可以使用Select.join_from()方法:

代码语言:javascript复制
>>> stmt = select(Address).join_from(User, User.addresses).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

Select.join_from()方法接受两个或三个参数,形式可以是(<join from>, <onclause>),或者是(<join from>, <join to>, [<onclause>])

代码语言:javascript复制
>>> stmt = select(Address).join_from(User, Address).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

为了为 SELECT 设置初始 FROM 子句,以便之后可以使用Select.join(),也可以使用Select.select_from()方法:

代码语言:javascript复制
>>> stmt = select(Address).select_from(User).join(Address).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  user_account  JOIN  address  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

提示

Select.select_from()方法实际上并没有最终决定 FROM 子句中表的顺序。如果语句还引用了一个Join构造,该构造引用了不同顺序的现有表,则Join构造优先。当我们使用像Select.join()Select.join_from()这样的方法时,这些方法最终会创建这样一个Join对象。因此,在这种情况下,我们可以看到Select.select_from()的内容被覆盖:

代码语言:javascript复制
>>> stmt = select(Address).select_from(User).join(Address.user).where(User.name == "sandy")
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  address  JOIN  user_account  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

在上述例子中,我们看到 FROM 子句是address JOIN user_account,尽管我们首先声明了select_from(User)。由于.join(Address.user)方法调用,语句最终等效于以下内容:

代码语言:javascript复制
>>> from sqlalchemy.sql import join
>>>
>>> user_table = User.__table__
>>> address_table = Address.__table__
>>>
>>> j = address_table.join(user_table, user_table.c.id == address_table.c.user_id)
>>> stmt = (
...     select(address_table)
...     .select_from(user_table)
...     .select_from(j)
...     .where(user_table.c.name == "sandy")
... )
>>> print(stmt)
SELECT  address.id,  address.user_id,  address.email_address
FROM  address  JOIN  user_account  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  :name_1 

上述Join构造是作为Select.select_from()列表中的另一个条目添加的,它取代了先前的条目。

关系 WHERE 运算符

除了在Select.join()Select.join_from()方法中使用relationship()构造之外,relationship()还在帮助构造通常用于 WHERE 子句的 SQL 表达式,使用Select.where()方法。

EXISTS 形式:has() / any()

Exists构造首次在 SQLAlchemy 统一教程的 EXISTS 子查询部分中介绍。该对象用于在标量子查询与 SQL EXISTS 关键字一起呈现。relationship()构造提供了一些辅助方法,可以用于以关系的方式生成一些常见的 EXISTS 风格的查询。

对于像User.addresses这样的一对多关系,可以使用PropComparator.any()针对与user_account表相关联的address表进行 EXISTS 查询。此方法接受一个可选的 WHERE 条件来限制子查询匹配的行:

代码语言:javascript复制
>>> stmt = select(User.fullname).where(
...     User.addresses.any(Address.email_address == "squirrel@squirrelpower.org")
... )
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
WHERE  EXISTS  (SELECT  1
FROM  address
WHERE  user_account.id  =  address.user_id  AND  address.email_address  =  ?)
[...]  ('squirrel@squirrelpower.org',)
[('Sandy Cheeks',)]

由于 EXISTS 倾向于更有效地进行负查找,一个常见的查询是定位没有相关实体的实体。这可以简洁地使用短语~User.addresses.any()来实现,以选择没有相关Address行的User实体:

代码语言:javascript复制
>>> stmt = select(User.fullname).where(~User.addresses.any())
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
WHERE  NOT  (EXISTS  (SELECT  1
FROM  address
WHERE  user_account.id  =  address.user_id))
[...]  ()
[('Eugene H. Krabs',)]

PropComparator.has()方法的工作方式与PropComparator.any()基本相同,只是它用于一对多关系,例如,如果我们想要定位所有属于“sandy”的Address对象:

代码语言:javascript复制
>>> stmt = select(Address.email_address).where(Address.user.has(User.name == "sandy"))
>>> session.execute(stmt).all()
SELECT  address.email_address
FROM  address
WHERE  EXISTS  (SELECT  1
FROM  user_account
WHERE  user_account.id  =  address.user_id  AND  user_account.name  =  ?)
[...]  ('sandy',)
[('sandy@sqlalchemy.org',), ('squirrel@squirrelpower.org',)]
```### 关系实例比较运算符

`relationship()`-绑定的属性还提供了一些 SQL 构造实现,这些实现旨在根据相关对象的特定实例来过滤`relationship()`-绑定的属性,它可以从给定的持久(或较少见的分离)对象实例中解包适当的属性值,并构造 WHERE 条件,以便针对目标`relationship()`。

    **一对多等于比较** - 可以将特定对象实例与一对多关系进行比较,以选择外键与给定对象的主键值匹配的行:

    ```py
    >>> user_obj = session.get(User, 1)
    SELECT  ...
    >>> print(select(Address).where(Address.user == user_obj))
    SELECT  address.id,  address.user_id,  address.email_address
    FROM  address
    WHERE  :param_1  =  address.user_id 
    ```

    **一对多不等于比较** - 也可以使用不等于运算符:

    ```py
    >>> print(select(Address).where(Address.user != user_obj))
    SELECT  address.id,  address.user_id,  address.email_address
    FROM  address
    WHERE  address.user_id  !=  :user_id_1  OR  address.user_id  IS  NULL 
    ```

    **对象包含在一对多集合中** - 这基本上是“等于”比较的一对多版本,选择主键等于相关对象中的外键值的行:

    ```py
    >>> address_obj = session.get(Address, 1)
    SELECT  ...
    >>> print(select(User).where(User.addresses.contains(address_obj)))
    SELECT  user_account.id,  user_account.name,  user_account.fullname
    FROM  user_account
    WHERE  user_account.id  =  :param_1 
    ```

    **从一对多的角度来看,对象有一个特定的父对象** - `with_parent()`函数生成一个比较,返回被给定父对象引用的行,这本质上与使用一对多方的`==`运算符相同:

    ```py
    >>> from sqlalchemy.orm import with_parent
    >>> print(select(Address).where(with_parent(user_obj, User.addresses)))
    SELECT  address.id,  address.user_id,  address.email_address
    FROM  address
    WHERE  :param_1  =  address.user_id 
    ```### EXISTS forms: has() / any()

`Exists`构造首次在 SQLAlchemy 统一教程中的 EXISTS 子查询一节中引入。该对象用于在标量子查询与 SQL EXISTS 关键字一起渲染。`relationship()`构造提供了一些辅助方法,可以用于根据关系生成一些常见的 EXISTS 风格的查询。

对于像`User.addresses`这样的一对多关系,可以使用与`user_account`表关联的`address`表的 EXISTS 来生成 `PropComparator.any()`。此方法接受一个可选的 WHERE 条件来限制子查询匹配的行:

```py
>>> stmt = select(User.fullname).where(
...     User.addresses.any(Address.email_address == "squirrel@squirrelpower.org")
... )
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
WHERE  EXISTS  (SELECT  1
FROM  address
WHERE  user_account.id  =  address.user_id  AND  address.email_address  =  ?)
[...]  ('squirrel@squirrelpower.org',)
[('Sandy Cheeks',)]

由于 EXISTS 倾向于对负查询更有效,一个常见的查询是定位那些不存在相关实体的实体。这可以用如~User.addresses.any()这样的短语来简洁地实现,以选择没有相关Address行的User实体:

代码语言:javascript复制
>>> stmt = select(User.fullname).where(~User.addresses.any())
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
WHERE  NOT  (EXISTS  (SELECT  1
FROM  address
WHERE  user_account.id  =  address.user_id))
[...]  ()
[('Eugene H. Krabs',)]

PropComparator.has()方法的工作方式基本与PropComparator.any()相同,只是它用于多对一的关系,比如我们想要定位所有属于“sandy”的Address对象:

代码语言:javascript复制
>>> stmt = select(Address.email_address).where(Address.user.has(User.name == "sandy"))
>>> session.execute(stmt).all()
SELECT  address.email_address
FROM  address
WHERE  EXISTS  (SELECT  1
FROM  user_account
WHERE  user_account.id  =  address.user_id  AND  user_account.name  =  ?)
[...]  ('sandy',)
[('sandy@sqlalchemy.org',), ('squirrel@squirrelpower.org',)]
Relationship Instance Comparison Operators

relationship()绑定属性还提供了一些 SQL 构建实现,用于基于特定相关对象的实例来过滤relationship()绑定属性,这可以从给定的持久(或更少见的分离)对象实例中解包适当的属性值,并根据目标relationship()构造 WHERE 条件。

多对一等于比较 - 一个特定的对象实例可以与多对一关系进行比较,以选择外键与目标实体的主键值匹配的行:

代码语言:javascript复制
>>> user_obj = session.get(User, 1)
SELECT  ...
>>> print(select(Address).where(Address.user == user_obj))
SELECT  address.id,  address.user_id,  address.email_address
FROM  address
WHERE  :param_1  =  address.user_id 

多对一不等于比较 - 也可以使用不等于运算符:

代码语言:javascript复制
>>> print(select(Address).where(Address.user != user_obj))
SELECT  address.id,  address.user_id,  address.email_address
FROM  address
WHERE  address.user_id  !=  :user_id_1  OR  address.user_id  IS  NULL 

对象包含在一对多集合中 - 这基本上是“等于”比较的一对多版本,选择主键等于相关对象中外键值的行:

代码语言:javascript复制
>>> address_obj = session.get(Address, 1)
SELECT  ...
>>> print(select(User).where(User.addresses.contains(address_obj)))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.id  =  :param_1 

从一对多的角度看,对象有一个特定的父对象 - with_parent() 函数生成一个比较,返回由给定父对象引用的行,这与使用==运算符与多对一方面基本相同:

代码语言:javascript复制
>>> from sqlalchemy.orm import with_parent
>>> print(select(Address).where(with_parent(user_obj, User.addresses)))
SELECT  address.id,  address.user_id,  address.email_address
FROM  address
WHERE  :param_1  =  address.user_id 

, user_account.name, user_account.fullname FROM user_account WHERE user_account.id = :param_1 ```

从一对多的角度来看,对象有一个特定的父对象 - with_parent()函数生成一个比较,返回被给定父对象引用的行,这本质上与使用一对多方的==运算符相同:

代码语言:javascript复制
>>> from sqlalchemy.orm import with_parent
>>> print(select(Address).where(with_parent(user_obj, User.addresses)))
SELECT  address.id,  address.user_id,  address.email_address
FROM  address
WHERE  :param_1  =  address.user_id 
```### EXISTS forms: has() / any()

Exists构造首次在 SQLAlchemy 统一教程中的 EXISTS 子查询一节中引入。该对象用于在标量子查询与 SQL EXISTS 关键字一起渲染。relationship()构造提供了一些辅助方法,可以用于根据关系生成一些常见的 EXISTS 风格的查询。

对于像User.addresses这样的一对多关系,可以使用与user_account表关联的address表的 EXISTS 来生成 PropComparator.any()。此方法接受一个可选的 WHERE 条件来限制子查询匹配的行:

代码语言:javascript复制
>>> stmt = select(User.fullname).where(
...     User.addresses.any(Address.email_address == "squirrel@squirrelpower.org")
... )
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
WHERE  EXISTS  (SELECT  1
FROM  address
WHERE  user_account.id  =  address.user_id  AND  address.email_address  =  ?)
[...]  ('squirrel@squirrelpower.org',)
[('Sandy Cheeks',)]

由于 EXISTS 倾向于对负查询更有效,一个常见的查询是定位那些不存在相关实体的实体。这可以用如~User.addresses.any()这样的短语来简洁地实现,以选择没有相关Address行的User实体:

代码语言:javascript复制
>>> stmt = select(User.fullname).where(~User.addresses.any())
>>> session.execute(stmt).all()
SELECT  user_account.fullname
FROM  user_account
WHERE  NOT  (EXISTS  (SELECT  1
FROM  address
WHERE  user_account.id  =  address.user_id))
[...]  ()
[('Eugene H. Krabs',)]

PropComparator.has()方法的工作方式基本与PropComparator.any()相同,只是它用于多对一的关系,比如我们想要定位所有属于“sandy”的Address对象:

代码语言:javascript复制
>>> stmt = select(Address.email_address).where(Address.user.has(User.name == "sandy"))
>>> session.execute(stmt).all()
SELECT  address.email_address
FROM  address
WHERE  EXISTS  (SELECT  1
FROM  user_account
WHERE  user_account.id  =  address.user_id  AND  user_account.name  =  ?)
[...]  ('sandy',)
[('sandy@sqlalchemy.org',), ('squirrel@squirrelpower.org',)]
Relationship Instance Comparison Operators

relationship()绑定属性还提供了一些 SQL 构建实现,用于基于特定相关对象的实例来过滤relationship()绑定属性,这可以从给定的持久(或更少见的分离)对象实例中解包适当的属性值,并根据目标relationship()构造 WHERE 条件。

多对一等于比较 - 一个特定的对象实例可以与多对一关系进行比较,以选择外键与目标实体的主键值匹配的行:

代码语言:javascript复制
>>> user_obj = session.get(User, 1)
SELECT  ...
>>> print(select(Address).where(Address.user == user_obj))
SELECT  address.id,  address.user_id,  address.email_address
FROM  address
WHERE  :param_1  =  address.user_id 

多对一不等于比较 - 也可以使用不等于运算符:

代码语言:javascript复制
>>> print(select(Address).where(Address.user != user_obj))
SELECT  address.id,  address.user_id,  address.email_address
FROM  address
WHERE  address.user_id  !=  :user_id_1  OR  address.user_id  IS  NULL 

对象包含在一对多集合中 - 这基本上是“等于”比较的一对多版本,选择主键等于相关对象中外键值的行:

代码语言:javascript复制
>>> address_obj = session.get(Address, 1)
SELECT  ...
>>> print(select(User).where(User.addresses.contains(address_obj)))
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.id  =  :param_1 

从一对多的角度看,对象有一个特定的父对象 - with_parent() 函数生成一个比较,返回由给定父对象引用的行,这与使用==运算符与多对一方面基本相同:

代码语言:javascript复制
>>> from sqlalchemy.orm import with_parent
>>> print(select(Address).where(with_parent(user_obj, User.addresses)))
SELECT  address.id,  address.user_id,  address.email_address
FROM  address
WHERE  :param_1  =  address.user_id 

0 人点赞