SqlAlchemy-2-0-中文文档-七-
SqlAlchemy 2.0 中文文档(七)
集合自定义和 API 详情
relationship()
函数定义了两个类之间的链接。当链接定义了一对多或多对多的关系时,在加载和操作对象时,它被表示为 Python 集合。本节介绍了有关集合配置和技术的其他信息。
自定义集合访问
将一对多或多对多的关系映射为一组可通过父实例上的属性访问的值的集合。对于这些关系的两种常见集合类型是 list
和 set
,在使用 Mapped
的 声明式 映射中,通过在 Mapped
容器中使用集合类型来建立,如下面的 Parent.children
集合中所示,其中使用了 list
:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class Parent(Base):
__tablename__ = "parent"
parent_id: Mapped[int] = mapped_column(primary_key=True)
# use a list
children: Mapped[List["Child"]] = relationship()
class Child(Base):
__tablename__ = "child"
child_id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))
或者使用 set
,在相同的 Parent.children
集合中进行说明:
from typing import Set
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class Parent(Base):
__tablename__ = "parent"
parent_id: Mapped[int] = mapped_column(primary_key=True)
# use a set
children: Mapped[Set["Child"]] = relationship()
class Child(Base):
__tablename__ = "child"
child_id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))
注意
如果使用 Python 3.7 或 3.8,则集合的注释需要使用 typing.List
或 typing.Set
,例如 Mapped[List["Child"]]
或 Mapped[Set["Child"]]
;在这些 Python 版本中,list
和 set
Python 内置的类型尚不支持通用注释,例如:
from typing import List
class Parent(Base):
__tablename__ = "parent"
parent_id: Mapped[int] = mapped_column(primary_key=True)
# use a List, Python 3.8 and earlier
children: Mapped[List["Child"]] = relationship()
当使用没有 Mapped
注释的映射时,比如使用 命令式映射 或者未经类型化的 Python 代码,以及在一些特殊情况下,relationship()
的集合类始终可以直接使用 relationship.collection_class
参数进行指定:
# non-annotated mapping
class Parent(Base):
__tablename__ = "parent"
parent_id = mapped_column(Integer, primary_key=True)
children = relationship("Child", collection_class=set)
class Child(Base):
__tablename__ = "child"
child_id = mapped_column(Integer, primary_key=True)
parent_id = mapped_column(ForeignKey("parent.id"))
在缺少 relationship.collection_class
或 Mapped
的情况下,默认的集合类型是 list
。
除了内置的 list
和 set
,还支持两种字典的变体,下文将进行描述 字典集合。还支持任何任意可变序列类型可以设置为目标集合,需要一些额外的配置步骤;这在 自定义集合实现 部分进行了描述。
字典集合
当使用字典作为集合时需要一些额外的细节。这是因为对象总是作为列表从数据库加载的,必须提供一种键生成策略才能正确地填充字典。attribute_keyed_dict()
函数是实现简单字典集合的最常见方式。它生成一个字典类,该类将映射类的特定属性作为键。下面我们映射了一个包含以Note.keyword
属性作为键的Note
项目字典的Item
类。当使用attribute_keyed_dict()
时,可以使用Mapped
注释,可以使用KeyFuncDict
或仅使用普通的dict
,如下面的示例所示。然而,在这种情况下,需要使用relationship.collection_class
参数,以便适当地参数化attribute_keyed_dict()
:
from typing import Dict
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import attribute_keyed_dict
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=attribute_keyed_dict("keyword"),
cascade="all, delete-orphan",
)
class Note(Base):
__tablename__ = "note"
id: Mapped[int] = mapped_column(primary_key=True)
item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
keyword: Mapped[str]
text: Mapped[Optional[str]]
def __init__(self, keyword: str, text: str):
self.keyword = keyword
self.text = text
Item.notes
然后是一个字典:
>>> item = Item()
>>> item.notes["a"] = Note("a", "atext")
>>> item.notes.items()
{'a': <__main__.Note object at 0x2eaaf0>}
attribute_keyed_dict()
将确保每个Note
的.keyword
属性与字典中的键相匹配。例如,当分配给Item.notes
时,我们提供的字典键必须与实际Note
对象的键相匹配:
item = Item()
item.notes = {
"a": Note("a", "atext"),
"b": Note("b", "btext"),
}
attribute_keyed_dict()
用作键的属性根本不需要被映射!使用常规的 Python @property
允许使用对象的几乎任何细节或组合细节作为键,就像下面我们将其建立为Note.keyword
元组和Note.text
字段的前十个字母一样:
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=attribute_keyed_dict("note_key"),
back_populates="item",
cascade="all, delete-orphan",
)
class Note(Base):
__tablename__ = "note"
id: Mapped[int] = mapped_column(primary_key=True)
item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
keyword: Mapped[str]
text: Mapped[str]
item: Mapped["Item"] = relationship()
@property
def note_key(self):
return (self.keyword, self.text[0:10])
def __init__(self, keyword: str, text: str):
self.keyword = keyword
self.text = text
在上面,我们添加了一个带有双向relationship.back_populates
配置的Note.item
关系。将Note
分配给这个反向关系时,Note
被添加到Item.notes
字典中,并且键会自动为我们生成:
>>> item = Item()
>>> n1 = Note("a", "atext")
>>> n1.item = item
>>> item.notes
{('a', 'atext'): <__main__.Note object at 0x2eaaf0>}
其他内置的字典类型包括column_keyed_dict()
,几乎与attribute_keyed_dict()
类似,只是直接给出Column
对象:
from sqlalchemy.orm import column_keyed_dict
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=column_keyed_dict(Note.__table__.c.keyword),
cascade="all, delete-orphan",
)
以及mapped_collection()
,它接受任何可调用函数。请注意,通常更容易使用前面提到的@property
与attribute_keyed_dict()
一起使用:
from sqlalchemy.orm import mapped_collection
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=mapped_collection(lambda note: note.text[0:10]),
cascade="all, delete-orphan",
)
字典映射经常与“Association Proxy”扩展组合以产生简化的字典视图。请参阅 Proxying to Dictionary Based Collections 和 Composite Association Proxies 以获取示例。
处理键突变和字典集合的反向填充
当使用attribute_keyed_dict()
时,字典的“键”来自目标对象上的属性。对此键的更改不会被跟踪。这意味着必须在第一次使用时分配键,并且如果键发生更改,则集合将不会突变。在依赖于反向引用来填充属性映射集合时,这可能是一个典型的问题。给定以下情况:
class A(Base):
__tablename__ = "a"
id: Mapped[int] = mapped_column(primary_key=True)
bs: Mapped[Dict[str, "B"]] = relationship(
collection_class=attribute_keyed_dict("data"),
back_populates="a",
)
class B(Base):
__tablename__ = "b"
id: Mapped[int] = mapped_column(primary_key=True)
a_id: Mapped[int] = mapped_column(ForeignKey("a.id"))
data: Mapped[str]
a: Mapped["A"] = relationship(back_populates="bs")
上面,如果我们创建一个引用特定A()
的B()
,那么反向填充将将B()
添加到A.bs
集合中,但是如果B.data
的值尚未设置,则键将为None
:
>>> a1 = A()
>>> b1 = B(a=a1)
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}
设置b1.data
之后不会更新集合:
>>> b1.data = "the key"
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}
如果尝试在构造函数中设置B()
,也可以看到这一点。参数顺序更改了结果:
>>> B(a=a1, data="the key")
<test3.B object at 0x7f7b10114280>
>>> a1.bs
{None: <test3.B object at 0x7f7b10114280>}
vs:
>>> B(data="the key", a=a1)
<test3.B object at 0x7f7b10114340>
>>> a1.bs
{'the key': <test3.B object at 0x7f7b10114340>}
如果正在以这种方式使用反向引用,请确保使用__init__
方法按正确顺序填充属性。
以下事件处理程序也可以用于跟踪集合中的更改:
from sqlalchemy import event
from sqlalchemy.orm import attributes
@event.listens_for(B.data, "set")
def set_item(obj, value, previous, initiator):
if obj.a is not None:
previous = None if previous == attributes.NO_VALUE else previous
obj.a.bs[value] = obj
obj.a.bs.pop(previous)
``` ## 自定义集合实现
您也可以为集合使用自己的类型。在简单情况下,继承自`list`或`set`,添加自定义行为就足够了。在其他情况下,需要特殊的装饰器来告诉 SQLAlchemy 关于集合操作的更多详细信息。
SQLAlchemy 中的集合是透明的*instrumented*。仪器化意味着对集合的常规操作将被跟踪,并且在刷新时将更改写入数据库。此外,集合操作可以触发*事件*,这些事件表明必须进行某些次要操作。次要操作的示例包括将子项保存在父项的`Session`中(即`save-update`级联),以及同步双向关系的状态(即`backref()`)。
集合包理解列表、集合和字典的基本接口,并将自动对这些内置类型及其子类应用仪表化。实现基本集合接口的对象衍生类型会通过鸭子类型检测到并进行仪表化:
```py
class ListLike:
def __init__(self):
self.data = []
def append(self, item):
self.data.append(item)
def remove(self, item):
self.data.remove(item)
def extend(self, items):
self.data.extend(items)
def __iter__(self):
return iter(self.data)
def foo(self):
return "foo"
append
、remove
和extend
是list
的已知成员,并且将自动进行仪表化。__iter__
不是一个修改器方法,不会进行仪表化,foo
也不会进行仪表化。
当然,鸭子类型(即猜测)并不是十分可靠,因此您可以通过提供__emulates__
类属性明确地指定您要实现的接口:
class SetLike:
__emulates__ = set
def __init__(self):
self.data = set()
def append(self, item):
self.data.add(item)
def remove(self, item):
self.data.remove(item)
def __iter__(self):
return iter(self.data)
这个类看起来类似于 Python 的list
(即“类似列表”),因为它有一个append
方法,但是__emulates__
属性将其强制视为set
。remove
被认为是集合接口的一部分,并将被仪表化。
但是这个类目前还不起作用:需要一点粘合剂来使其适应 SQLAlchemy 的使用。ORM 需要知道使用哪些方法来附加、删除和迭代集合的成员。当使用list
或set
等类型时,适当的方法是众所周知的,并且在存在时会自动使用。然而,上面的类只粗略地类似于set
,并没有提供预期的add
方法,因此我们必须告诉 ORM 将代替add
方法的方法,在本例中使用装饰器@collection.appender
来说明这一点;这将在下一节中进行说明。
通过装饰器对自定义集合进行注释
当您的类不完全符合其容器类型的常规接口时,或者当您希望以不同的方法完成工作时,可以使用装饰器标记单个方法供 ORM 管理集合时使用。
from sqlalchemy.orm.collections import collection
class SetLike:
__emulates__ = set
def __init__(self):
self.data = set()
@collection.appender
def append(self, item):
self.data.add(item)
def remove(self, item):
self.data.remove(item)
def __iter__(self):
return iter(self.data)
这就是完成示例所需的全部内容。SQLAlchemy 将通过append
方法添加实例。remove
和__iter__
是集合的默认方法,并将用于删除和迭代。默认方法也可以更改:
from sqlalchemy.orm.collections import collection
class MyList(list):
@collection.remover
def zark(self, item):
# do something special...
...
@collection.iterator
def hey_use_this_instead_for_iteration(self): ...
完全不需要“类似列表”或“类似集合”。集合类可以是任何形状,只要它们具有由 SQLAlchemy 标记的附加、删除和迭代接口。附加和删除方法将以映射的实体作为单个参数调用,迭代器方法将不带参数调用,并且必须返回一个迭代器。
自定义基于字典的集合
KeyFuncDict
类可用作自定义类型的基类,也可以用作快速将dict
集合支持添加到其他类的混合。它使用键函数来委托给__setitem__
和__delitem__
:
from sqlalchemy.orm.collections import KeyFuncDict
class MyNodeMap(KeyFuncDict):
"""Holds 'Node' objects, keyed by the 'name' attribute."""
def __init__(self, *args, **kw):
super().__init__(keyfunc=lambda node: node.name)
dict.__init__(self, *args, **kw)
当子类化 KeyFuncDict
时,如果调用相同的方法 __setitem__()
或 __delitem__()
,则用户定义的版本应当被装饰 collection.internally_instrumented()
,如果 它们在 KeyFuncDict
上调用这些方法。因为 KeyFuncDict
上的方法已经被内部装饰 - 在已经被内部装饰的调用中调用它们可能会导致事件被重复触发,或不恰当地,在极少数情况下导致内部状态损坏:
from sqlalchemy.orm.collections import KeyFuncDict, collection
class MyKeyFuncDict(KeyFuncDict):
"""Use @internally_instrumented when your methods
call down to already-instrumented methods.
"""
@collection.internally_instrumented
def __setitem__(self, key, value, _sa_initiator=None):
# do something with key, value
super(MyKeyFuncDict, self).__setitem__(key, value, _sa_initiator)
@collection.internally_instrumented
def __delitem__(self, key, _sa_initiator=None):
# do something with key
super(MyKeyFuncDict, self).__delitem__(key, _sa_initiator)
ORM 理解 dict
接口就像列表和集合一样,并且如果选择子类化 dict
或在鸭子类型类中提供类似于 dict 的集合行为,则会自动为所有“类似于字典”的方法进行仪器化。然而,您必须装饰添加器和删除器方法-因为 SQLAlchemy 没有默认使用的基本字典接口的兼容方法。迭代将通过 values()
进行,除非另有装饰。
仪器化和自定义类型
许多自定义类型和现有库类可以直接用作实体集合类型而无需进一步操作。但是,重要的是要注意,仪器化过程将修改类型,自动在方法周围添加装饰器。
这些装饰很轻量级,在关系之外是无操作的,但是在其他地方触发时会增加不必要的开销。当将库类用作集合时,将装饰限制为仅在关系中使用的“简单子类”技巧是一个好习惯。例如:
class MyAwesomeList(some.great.library.AwesomeList):
pass
# ... relationship(..., collection_class=MyAwesomeList)
ORM 对内置类型使用此方法,当直接使用 list
、set
或 dict
时,会悄悄地替换为一个简单的子类。
集合 API
对象名称 | 描述 |
---|---|
attribute_keyed_dict(attr_name, *, [ignore_unpopulated_attribute]) | 基于字典的集合类型,具有基于属性的键。 |
attribute_mapped_collection | 基于字典的集合类型,具有基于属性的键。 |
column_keyed_dict(mapping_spec, *, [ignore_unpopulated_attribute]) | 基于列的键的字典型集合类型。 |
column_mapped_collection | 基于列的键的字典型集合类型。 |
keyfunc_mapping(keyfunc, *, [ignore_unpopulated_attribute]) | 基于字典的集合类型,具有任意的键。 |
KeyFuncDict | 基于 ORM 映射字典类的基类。 |
mapped_collection | 基于字典的集合类型,具有任意的键。 |
MappedCollection | ORM 映射字典类的基类。 |
function sqlalchemy.orm.attribute_keyed_dict(attr_name: str, *, ignore_unpopulated_attribute: bool = False) → Type[KeyFuncDict[Any, Any]]
基于属性键的字典类型的集合。
版本 2.0 中的更改:将attribute_mapped_collection
重命名为attribute_keyed_dict()
。
返回一个KeyFuncDict
工厂,它将根据 ORM 映射实例上的特定命名属性的值生成新的字典键,以添加到字典中。
注意
目标属性的值必须在将对象添加到字典集合时被赋予其值。另外,不会跟踪键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关详细信息,请参阅处理键变异和反向填充字典集合。
另请参见
字典集合 - 使用背景
参数:
-
attr_name
- 映射类上的 ORM 映射属性的字符串名称,其值将在特定实例上用作新字典条目的键。 -
ignore_unpopulated_attribute
-如果为 True,并且对象上的目标属性根本未填充,则操作将被静默跳过。默认情况下,会引发错误。
版本 2.0 中的新特性:如果确定用于字典键的属性从未填充过任何值,则默认会引发错误。可以设置
attribute_keyed_dict.ignore_unpopulated_attribute
参数,该参数将指示忽略此条件,并在静默跳过附加操作。这与 1.x 系列的行为相反,后者会错误地使用任意键值None
填充字典中的值。
function sqlalchemy.orm.column_keyed_dict(mapping_spec: Type[_KT] | Callable[[_KT], _VT], *, ignore_unpopulated_attribute: bool = False) → Type[KeyFuncDict[_KT, _KT]]
基于列键的字典类型的集合。
版本 2.0 中的更改:将column_mapped_collection
重命名为column_keyed_dict
。
返回一个KeyFuncDict
工厂,它将根据 ORM 映射实例上的特定Column
映射属性的值生成新的字典键,以添加到字典中。
注意
目标属性的值必须在将对象添加到字典集合时分配其值。此外,不会跟踪键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关详细信息,请参见处理键突变和为字典集合回填。
另请参阅
字典集合 - 使用背景
参数:
-
mapping_spec
- 一个预期由目标映射器映射到映射类上特定属性的Column
对象,其在特定实例上的值将用作该实例的新字典条目的键。 -
ignore_unpopulated_attribute
-如果为 True,并且对象上由给定
Column
目标属性指示的映射属性根本未填充,则操作将被静默跳过。默认情况下,会引发错误。2.0 版本中的新功能:如果确定用于字典键的属性从未填充任何值,则默认情况下会引发错误。可以设置
column_keyed_dict.ignore_unpopulated_attribute
参数,该参数将指示应忽略此条件,并且附加操作将被静默跳过。这与 1.x 系列的行为相反,后者会错误地使用任意键值None
填充字典中的值。
function sqlalchemy.orm.keyfunc_mapping(keyfunc: _F, *, ignore_unpopulated_attribute: bool = False) → Type[KeyFuncDict[_KT, Any]]
基于字典的集合类型,具有任意键。
2.0 版本中的更改:将mapped_collection
重命名为keyfunc_mapping()
。
返回一个从 keyfunc 生成的键函数的KeyFuncDict
工厂,一个可调用对象,接受一个实体并返回一个键值。
注意
给定的 keyfunc 仅在将目标对象添加到集合时调用一次。不会跟踪函数返回的有效值的更改。
另请参阅
字典集合 - 使用背景
参数:
-
keyfunc
- 一个可调用对象,将传递 ORM 映射的实例,然后生成一个用于字典中的新键。如果返回的值是LoaderCallableStatus.NO_VALUE
,则会引发错误。 -
ignore_unpopulated_attribute
-如果为 True,并且可调用函数对特定实例返回
LoaderCallableStatus.NO_VALUE
,则操作将被静默跳过。默认情况下会引发错误。2.0 版本中的新功能:如果用于字典键的可调用函数返回
LoaderCallableStatus.NO_VALUE
,则默认情况下会引发错误,这在 ORM 属性上下文中表示从未填充任何值的属性。可以设置mapped_collection.ignore_unpopulated_attribute
参数,该参数将指示应忽略此条件,并且附加操作将被静默跳过。这与 1.x 系列的行为相反,后者会错误地使用任意键值None
填充字典中的值。
sqlalchemy.orm.attribute_mapped_collection = <function attribute_keyed_dict>
基于属性的键的字典集合类型。
2.0 版本中的更改:将attribute_mapped_collection
重命名为attribute_keyed_dict()
。
返回一个KeyFuncDict
工厂,该工厂将根据 ORM 映射实例上特定命名属性的值生成新的字典键,以添加到字典中。
注意
目标属性的值必须在将对象添加到字典集合时分配其值。此外,不会跟踪键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关详细信息,请参阅处理键突变和为字典集合回填。
另请参见
字典集合 - 使用背景
参数:
-
attr_name
– 映射类上 ORM 映射属性的字符串名称,特定实例上的该值将用作该实例的新字典条目的键。 -
ignore_unpopulated_attribute
–如果为 True,并且对象上的目标属性根本未填充,则操作将被静默跳过。默认情况下会引发错误。
2.0 版新功能:默认情况下,如果确定用于字典键的属性从未被填充任何值,则将引发错误。可以设置
attribute_keyed_dict.ignore_unpopulated_attribute
参数,以指示应忽略此条件,并静默跳过追加操作。这与 1.x 系列的行为相反,后者将错误地使用任意键值None
填充字典中的值。
sqlalchemy.orm.column_mapped_collection = <function column_keyed_dict>
基于字典的集合类型,使用列作为键。
2.0 版更改:将 column_mapped_collection
重命名为 column_keyed_dict
。
返回一个 KeyFuncDict
工厂,它将根据 ORM 映射实例上的特定 Column
映射的属性的值产生新的字典键,并将其添加到字典中。
注意
目标属性的值必须在将对象添加到字典集合时被赋值。另外,不会跟踪键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。参见处理键变化和字典集合的反填充获取更多详细信息。
另见
字典集合 - 使用背景
参数:
-
mapping_spec
– 一个预期由目标映射器映射到映射类上特定属性的Column
对象,其在特定实例上的值将用作该实例的新字典条目的键。 -
ignore_unpopulated_attribute
–如果为 True,并且对象上由给定
Column
目标属性指示的映射属性根本未被填充,则操作将被静默跳过。默认情况下,将引发错误。2.0 版新功能:默认情况下,如果确定用于字典键的属性从未被填充任何值,则将引发错误。可以设置
column_keyed_dict.ignore_unpopulated_attribute
参数,以指示应忽略此条件,并静默跳过追加操作。这与 1.x 系列的行为相反,后者将错误地使用任意键值None
填充字典中的值。
sqlalchemy.orm.mapped_collection = <function keyfunc_mapping>
一种基于字典的集合类型,具有任意键。
从版本 2.0 开始更改:将mapped_collection
重命名为keyfunc_mapping()
。
返回一个KeyFuncDict
工厂,其中包含从 keyfunc 生成的键函数,一个接受实体并返回键值的可调用对象。
注意
给定的 keyfunc 仅在将目标对象添加到集合时调用一次。不会跟踪函数返回的有效值的更改。
另请参见
字典集合 - 使用背景
参数:
-
keyfunc
– 一个可调用对象,将传递给 ORM 映射的实例,然后生成一个新的键用于字典。如果返回的值是LoaderCallableStatus.NO_VALUE
,则会引发错误。 -
ignore_unpopulated_attribute
–如果为 True,并且可调用对象对于特定实例返回
LoaderCallableStatus.NO_VALUE
,则操作将被静默跳过。默认情况下,会引发错误。从版本 2.0 开始:如果用于字典键的可调用对象返回
LoaderCallableStatus.NO_VALUE
,则默认情况下会引发错误,这在 ORM 属性上下文中表示从未用任何值填充的属性。可以设置mapped_collection.ignore_unpopulated_attribute
参数,该参数将指示应忽略此条件,并且附加操作将被静默跳过。这与 1.x 系列的行为相反,后者会错误地使用任意键值None
填充字典中的值。
class sqlalchemy.orm.KeyFuncDict
ORM 映射字典类的基础。
使用额外方法扩展了dict
类型,这些方法是 SQLAlchemy ORM 集合类所需的。最直接使用attribute_keyed_dict()
或column_keyed_dict()
类工厂来使用KeyFuncDict
。KeyFuncDict
也可以作为用户定义的自定义字典类的基础。
从版本 2.0 开始更改:将MappedCollection
重命名为KeyFuncDict
。
另请参见
attribute_keyed_dict()
column_keyed_dict()
字典集合
自定义集合实现
成员
init(), clear(), pop(), popitem(), remove(), set(), setdefault(), update()
类签名
类 sqlalchemy.orm.KeyFuncDict
(builtins.dict
, typing.Generic
)
method __init__(keyfunc: _F, *dict_args: Any, ignore_unpopulated_attribute: bool = False) → None
使用 keyfunc 提供的键创建一个新的集合。
keyfunc 可以是任何接受对象并返回用作字典键的对象的可调用对象。
每次 ORM 需要按值添加成员(例如从数据库加载实例时)或移除成员时都会调用 keyfunc。通常的字典键警告适用- keyfunc(object)
应该在集合的生命周期内返回相同的输出。基于可变属性的键可能会导致集合中“丢失”的不可达实例。
method clear() → None. Remove all items from D.
method pop(k[, d]) → v, remove specified key and return the corresponding value.
如果未找到键,则返回给定的默认值;否则,引发 KeyError。
method popitem()
移除并返回一个(键,值)对作为 2 元组。
对中的对以 LIFO(后进先出)顺序返回。如果字典为空,则引发 KeyError。
method remove(value: _KT, _sa_initiator: AttributeEventToken | Literal[None, False] = None) → None
通过值删除项,查询 keyfunc 以获取键。
method set(value: _KT, _sa_initiator: AttributeEventToken | Literal[None, False] = None) → None
通过值添加项,查询 keyfunc 以获取键。
method setdefault(key, default=None)
如果键不在字典中,则将键插入并将默认值设置为默认值。
如果键在字典中,则返回键的值,否则返回默认值。
method update([E, ]**F) → None. Update D from dict/iterable E and F.
如果 E 存在并且具有 .keys() 方法,则执行以下操作:for k in E: D[k] = E[k] 如果 E 存在并且缺少 .keys() 方法,则执行以下操作:for k, v in E: D[k] = v 在任一情况下,这之后都会执行:for k in F: D[k] = F[k]
sqlalchemy.orm.MappedCollection = <class 'sqlalchemy.orm.mapped_collection.KeyFuncDict'>
ORM 映射字典类的基础。
扩展了 dict
类型,提供了 SQLAlchemy ORM 集合类所需的附加方法。最直接使用 attribute_keyed_dict()
或 column_keyed_dict()
类工厂创建 KeyFuncDict
。KeyFuncDict
也可以作为用户定义的自定义字典类的基类。
在 2.0 版本中更改:将 MappedCollection
重命名为 KeyFuncDict
。
另请参阅
attribute_keyed_dict()
column_keyed_dict()
字典集合
自定义集合实现
集合内部
对象名称 | 描述 |
---|---|
bulk_replace(values, existing_adapter, new_adapter[, initiator]) | 加载一个新的集合,根据先前的成员关系触发事件。 |
collection | 实体集合类的装饰器。 |
collection_adapter | attrgetter(attr, …) –> attrgetter 对象 |
CollectionAdapter | ORM 和任意 Python 集合之间的桥梁。 |
InstrumentedDict | 内置字典的受控版本。 |
InstrumentedList | 内置列表的受控版本。 |
InstrumentedSet | 内置集合的受控版本。 |
prepare_instrumentation(factory) | 为将来用作集合类工厂的可调用对象做准备。 |
function sqlalchemy.orm.collections.bulk_replace(values, existing_adapter, new_adapter, initiator=None)
加载一个新的集合,根据先前的成员关系触发事件。
将values
中的实例附加到new_adapter
。对于existing_adapter
中不存在的任何实例,将触发事件。对于existing_adapter
中存在但在values
中不存在的任何实例,将触发删除事件。
参数:
-
values
– 一个集合成员实例的可迭代对象 -
existing_adapter
– 要替换的实例的CollectionAdapter
-
new_adapter
– 一个空的CollectionAdapter
,用于加载values
class sqlalchemy.orm.collections.collection
实体集合类的装饰器。
这些装饰器分为两组:注释和拦截配方。
标注装饰器(appender, remover, iterator, converter, internally_instrumented)指示方法的目的,并且不接受任何参数。它们不带括号:
@collection.appender
def append(self, append): ...
所有配方装饰器都需要括号,即使没有参数:
成员
adds(), appender(), converter(), internally_instrumented(), iterator(), remover(), removes(), removes_return(), replaces()
@collection.adds('entity')
def insert(self, position, entity): ...
@collection.removes_return()
def popitem(self): ...
method static adds(arg)
将方法标记为向集合添加实体。
将“添加到集合”处理添加到方法中。装饰器参数指示哪个方法参数保存着与 SQLAlchemy 相关的值。参数可以通过位置指定(即整数),也可以通过名称指定:
@collection.adds(1)
def push(self, item): ...
@collection.adds('entity')
def do_stuff(self, thing, entity=None): ...
method static appender(fn)
将方法标记为集合附加器。
调用 appender 方法时,将使用一个位置参数:要附加的值。如果尚未装饰该方法,将自动用 'adds(1)' 装饰该方法:
@collection.appender
def add(self, append): ...
# or, equivalently
@collection.appender
@collection.adds(1)
def add(self, append): ...
# for mapping type, an 'append' may kick out a previous value
# that occupies that slot. consider d['a'] = 'foo'- any previous
# value in d['a'] is discarded.
@collection.appender
@collection.replaces(1)
def add(self, entity):
key = some_key_func(entity)
previous = None
if key in self:
previous = self[key]
self[key] = entity
return previous
如果要附加的值不允许在集合中,您可以引发异常。需要记住的一件事是,对于由数据库查询映射的每个对象,都会调用 appender。如果数据库包含违反集合语义的行,则需要有创造性地解决问题,因为通过集合访问将无法正常工作。
如果 appender 方法在内部被仪器化,则还必须接收关键字参数 ‘_sa_initiator’ 并确保其在集合事件中的传播。
method static converter(fn)
将方法标记为集合转换器。
自版本 1.3 起弃用:collection.converter()
处理程序已弃用,并将在将来的版本中删除。请参考 listen()
函数结合 bulk_replace
监听器接口。
当要完全替换集合时,将调用此可选方法,例如:
myobj.acollection = [newvalue1, newvalue2]
转换器方法将接收到被分配的对象,并应返回适用于 appender
方法使用的值的可迭代对象。转换器不能分配值或改变集合,它的唯一工作是将用户提供的值适应为 ORM 使用的值的可迭代对象。
默认的转换器实现将使用鸭子类型进行转换。类似字典的集合将转换为字典值的可迭代对象,而其他类型将简单地进行迭代:
@collection.converter
def convert(self, other): ...
如果对象的鸭子类型与此集合的类型不匹配,则会引发 TypeError。
如果要扩展可以批量分配的可能类型的范围,或者对即将被分配的值执行验证,请提供此方法的实现。
method static internally_instrumented(fn)
将方法标记为受仪器控制的。
此标记将阻止对该方法应用任何装饰。如果您正在编排自己对 collection_adapter()
的调用,并且在基本 SQLAlchemy 接口方法中的一个方法中,或者防止自动 ABC 方法装饰包装您的实现,请使用此标记:
# normally an 'extend' method on a list-like class would be
# automatically intercepted and re-implemented in terms of
# SQLAlchemy events and append(). your implementation will
# never be called, unless:
@collection.internally_instrumented
def extend(self, items): ...
method static iterator(fn)
将方法标记为集合移除器。
调用 iterator 方法时不使用参数。预期它将返回所有集合成员的迭代器:
@collection.iterator
def __iter__(self): ...
method static remover(fn)
将方法标记为集合移除器。
remover
方法接受一个位置参数:要移除的值。如果方法尚未装饰,则会自动装饰为 removes_return()
:
@collection.remover
def zap(self, entity): ...
# or, equivalently
@collection.remover
@collection.removes_return()
def zap(self, ): ...
如果要移除的值在集合中不存在,则可以引发异常或返回 None 以忽略错误。
如果 remove 方法在内部被检测,则还必须接收关键字参数 ‘_sa_initiator’ 并确保其传播到集合事件。
method static removes(arg)
将该方法标记为从集合中移除实体。
将“从集合中移除”处理添加到方法中。装饰器参数指示哪个方法参数保存了要从 SQLAlchemy 中移除的值。参数可以通过位置(即整数)或名称指定:
@collection.removes(1)
def zap(self, item): ...
对于在调用时不知道要移除的值的方法,请使用 collection.removes_return
。
method static removes_return()
将该方法标记为从集合中移除实体。
将“从集合中移除”处理添加到方法中。如果有,则方法的返回值将被视为要移除的值。不会检查方法参数:
@collection.removes_return()
def pop(self): ...
对于在调用时知道要移除的值的方法,请使用 collection.remove
。
method static replaces(arg)
将该方法标记为替换集合中的实体。
将“添加到集合中”和“从集合中移除”处理添加到方法中。装饰器参数指示哪个方法参数保存了要添加到 SQLAlchemy 中的值,如果有,则返回值将被视为要移除的值。
参数可以通过位置(即整数)或名称指定:
@collection.replaces(2)
def __setitem__(self, index, item): ...
sqlalchemy.orm.collections.collection_adapter = operator.attrgetter('_sa_adapter')
attrgetter(attr, …)
–> attrgetter
对象
返回一个可调用对象,它从其操作数中获取给定属性。执行 f = attrgetter('name')
后,调用 f(r)
返回 r.name
。执行 g = attrgetter('name', 'date')
后,调用 g(r)
返回 (r.name, r.date)
。执行 h = attrgetter('name.first', 'name.last')
后,调用 h(r)
返回 (r.name.first, r.name.last)
。
class sqlalchemy.orm.collections.CollectionAdapter
在 ORM 和任意 Python 集合之间架设桥梁。
将基本级别的集合操作(追加、移除、迭代)代理到底层的 Python 集合,并为进入或离开集合的实体发出添加/移除事件。
ORM 专门使用 CollectionAdapter
与实体集合交互。
class sqlalchemy.orm.collections.InstrumentedDict
内置字典的受检测版本。
类签名
sqlalchemy.orm.collections.InstrumentedDict
类(builtins.dict
, typing.Generic
)
class sqlalchemy.orm.collections.InstrumentedList
内置列表的受检测版本。
类签名
sqlalchemy.orm.collections.InstrumentedList
类(builtins.list
, typing.Generic
)
class sqlalchemy.orm.collections.InstrumentedSet
内置集合的受检测版本。
类签名
类 sqlalchemy.orm.collections.InstrumentedSet
(builtins.set
, typing.Generic
)
function sqlalchemy.orm.collections.prepare_instrumentation(factory: Type[Collection[Any]] | Callable[[], _AdaptedCollectionProtocol]) → Callable[[], _AdaptedCollectionProtocol]
准备一个可调用对象,以便将来用作集合类工厂。
给定一个集合类工厂(类型或无参数可调用对象),返回另一个工厂,当调用时将产生兼容的实例。
此函数负责将 collection_class=list 转换为 collection_class=InstrumentedList 的运行时行为。
自定义集合访问
映射一对多或多对多的关系会导致通过父实例上的属性访问的值集合。这两种常见的集合类型是 list
和 set
,在使用 Mapped
的 声明 映射中,通过在 Mapped
容器内使用集合类型来建立,如下面 Parent.children
集合中所示,其中使用了 list
:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class Parent(Base):
__tablename__ = "parent"
parent_id: Mapped[int] = mapped_column(primary_key=True)
# use a list
children: Mapped[List["Child"]] = relationship()
class Child(Base):
__tablename__ = "child"
child_id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))
或者对于 set
,在同样的 Parent.children
集合中示例:
from typing import Set
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class Parent(Base):
__tablename__ = "parent"
parent_id: Mapped[int] = mapped_column(primary_key=True)
# use a set
children: Mapped[Set["Child"]] = relationship()
class Child(Base):
__tablename__ = "child"
child_id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))
注意
如果使用 Python 3.7 或 3.8,集合的注释需要使用 typing.List
或 typing.Set
,例如 Mapped[List["Child"]]
或 Mapped[Set["Child"]]
;在这些 Python 版本中,list
和 set
内置的 Python 不支持泛型注释,例如:
from typing import List
class Parent(Base):
__tablename__ = "parent"
parent_id: Mapped[int] = mapped_column(primary_key=True)
# use a List, Python 3.8 and earlier
children: Mapped[List["Child"]] = relationship()
在不使用 Mapped
注解的映射中,比如在使用 命令式映射 或未类型化的 Python 代码时,以及在一些特殊情况下,总是可以直接指定 relationship()
的集合类,使用 relationship.collection_class
参数:
# non-annotated mapping
class Parent(Base):
__tablename__ = "parent"
parent_id = mapped_column(Integer, primary_key=True)
children = relationship("Child", collection_class=set)
class Child(Base):
__tablename__ = "child"
child_id = mapped_column(Integer, primary_key=True)
parent_id = mapped_column(ForeignKey("parent.id"))
在缺少 relationship.collection_class
或 Mapped
的情况下,默认的集合类型是 list
。
除了内置的 list
和 set
外,还支持两种字典的变体,下面在 字典集合 中描述。还支持将任何任意可变序列类型设置为目标集合,只需进行一些额外的配置步骤;这在 自定义集合实现 部分有描述。
字典集合
使用字典作为集合时需要一些额外的细节。这是因为对象总是以列表形式从数据库加载的,必须提供一种键生成策略以正确地填充字典。attribute_keyed_dict()
函数是实现简单字典集合的最常见方式。它生成一个字典类,将映射类的特定属性应用为键。在下面的示例中,我们映射了一个包含以 Note.keyword
属性为键的 Note
项字典的 Item
类。当使用 attribute_keyed_dict()
时,可能会使用 Mapped
注释使用 KeyFuncDict
或普通的 dict
,如下例所示。但是,在这种情况下,必须使用 relationship.collection_class
参数,以便适当地参数化 attribute_keyed_dict()
:
from typing import Dict
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import attribute_keyed_dict
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=attribute_keyed_dict("keyword"),
cascade="all, delete-orphan",
)
class Note(Base):
__tablename__ = "note"
id: Mapped[int] = mapped_column(primary_key=True)
item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
keyword: Mapped[str]
text: Mapped[Optional[str]]
def __init__(self, keyword: str, text: str):
self.keyword = keyword
self.text = text
Item.notes
现在是一个字典:
>>> item = Item()
>>> item.notes["a"] = Note("a", "atext")
>>> item.notes.items()
{'a': <__main__.Note object at 0x2eaaf0>}
attribute_keyed_dict()
会确保每个 Note
的 .keyword
属性与字典中的键一致。例如,当分配给 Item.notes
时,我们提供的字典键必须与实际 Note
对象的键匹配:
item = Item()
item.notes = {
"a": Note("a", "atext"),
"b": Note("b", "btext"),
}
attribute_keyed_dict()
用作键的属性根本不需要被映射!使用普通的 Python @property
允许几乎任何关于对象的细节或组合细节被用作键,就像下面我们将其建立为 Note.keyword
和 Note.text
字段的前十个字母的元组时那样:
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=attribute_keyed_dict("note_key"),
back_populates="item",
cascade="all, delete-orphan",
)
class Note(Base):
__tablename__ = "note"
id: Mapped[int] = mapped_column(primary_key=True)
item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
keyword: Mapped[str]
text: Mapped[str]
item: Mapped["Item"] = relationship()
@property
def note_key(self):
return (self.keyword, self.text[0:10])
def __init__(self, keyword: str, text: str):
self.keyword = keyword
self.text = text
在上面的示例中,我们添加了一个 Note.item
关系,具有双向的 relationship.back_populates
配置。将 Note
分配给这个反向关系时,Note
被添加到 Item.notes
字典中,键会自动为我们生成:
>>> item = Item()
>>> n1 = Note("a", "atext")
>>> n1.item = item
>>> item.notes
{('a', 'atext'): <__main__.Note object at 0x2eaaf0>}
其他内置字典类型包括 column_keyed_dict()
,它几乎和 attribute_keyed_dict()
类似,除了直接给出 Column
对象之外:
from sqlalchemy.orm import column_keyed_dict
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=column_keyed_dict(Note.__table__.c.keyword),
cascade="all, delete-orphan",
)
以及传递任何可调用函数的 mapped_collection()
。请注意,通常更容易使用 attribute_keyed_dict()
以及前面提到的 @property
:
from sqlalchemy.orm import mapped_collection
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=mapped_collection(lambda note: note.text[0:10]),
cascade="all, delete-orphan",
)
字典映射通常与“Association Proxy”扩展结合使用以生成简化的字典视图。有关示例,请参见代理到基于字典的集合 和 复合关联代理。
处理键的突变和字典集合的反向填充
当使用 attribute_keyed_dict()
时,字典的“键”来自目标对象上的属性。不会跟踪此键的更改。这意味着必须在首次使用时分配键,如果键更改,则集合将不会发生变化。可能出现问题的典型示例是依赖 backrefs 填充属性映射集合。给定以下情况:
class A(Base):
__tablename__ = "a"
id: Mapped[int] = mapped_column(primary_key=True)
bs: Mapped[Dict[str, "B"]] = relationship(
collection_class=attribute_keyed_dict("data"),
back_populates="a",
)
class B(Base):
__tablename__ = "b"
id: Mapped[int] = mapped_column(primary_key=True)
a_id: Mapped[int] = mapped_column(ForeignKey("a.id"))
data: Mapped[str]
a: Mapped["A"] = relationship(back_populates="bs")
如果我们创建一个引用特定 A()
的 B()
,那么 back populates 将把 B()
添加到 A.bs
集合中,但是如果 B.data
的值尚未设置,则键将为 None
:
>>> a1 = A()
>>> b1 = B(a=a1)
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}
事后设置 b1.data
并不会更新集合:
>>> b1.data = "the key"
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}
如果尝试在构造函数中设置 B()
,也会出现这种情况。参数的顺序改变了结果:
>>> B(a=a1, data="the key")
<test3.B object at 0x7f7b10114280>
>>> a1.bs
{None: <test3.B object at 0x7f7b10114280>}
对比:
>>> B(data="the key", a=a1)
<test3.B object at 0x7f7b10114340>
>>> a1.bs
{'the key': <test3.B object at 0x7f7b10114340>}
如果 backrefs 被这样使用,请确保使用 __init__
方法按正确顺序填充属性。
事件处理程序可以用来跟踪集合中的更改,例如以下示例:
from sqlalchemy import event
from sqlalchemy.orm import attributes
@event.listens_for(B.data, "set")
def set_item(obj, value, previous, initiator):
if obj.a is not None:
previous = None if previous == attributes.NO_VALUE else previous
obj.a.bs[value] = obj
obj.a.bs.pop(previous)
``` ### 字典集合
使用字典作为集合时需要一些额外的细节。这是因为对象总是以列表形式从数据库加载,必须提供一种键生成策略来正确填充字典。`attribute_keyed_dict()` 函数是实现简单字典集合的最常见方式。它生成一个字典类,该类将应用映射类的特定属性作为键。下面我们映射了一个包含以`Note.keyword`属性为键的`Note`项目字典的`Item`类。在使用`attribute_keyed_dict()`时,可以使用`Mapped`注释使用`KeyFuncDict`或普通的`dict`,如下例所示。然而,在这种情况下,必须使用`relationship.collection_class`参数,以便适当地对`attribute_keyed_dict()`进行参数化:
```py
from typing import Dict
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import attribute_keyed_dict
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=attribute_keyed_dict("keyword"),
cascade="all, delete-orphan",
)
class Note(Base):
__tablename__ = "note"
id: Mapped[int] = mapped_column(primary_key=True)
item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
keyword: Mapped[str]
text: Mapped[Optional[str]]
def __init__(self, keyword: str, text: str):
self.keyword = keyword
self.text = text
然后,Item.notes
是一个字典:
>>> item = Item()
>>> item.notes["a"] = Note("a", "atext")
>>> item.notes.items()
{'a': <__main__.Note object at 0x2eaaf0>}
attribute_keyed_dict()
将确保每个 Note
的 .keyword
属性与字典中的键相符合。例如,当分配给 Item.notes
时,我们提供的字典键必须与实际 Note
对象的键相匹配:
item = Item()
item.notes = {
"a": Note("a", "atext"),
"b": Note("b", "btext"),
}
attribute_keyed_dict()
用作键的属性根本不需要被映射!使用普通的 Python @property
允许使用对象的几乎任何细节或细节组合作为键,如下所示,当我们将其建立为 Note.keyword
的元组和 Note.text
字段的前十个字母时:
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=attribute_keyed_dict("note_key"),
back_populates="item",
cascade="all, delete-orphan",
)
class Note(Base):
__tablename__ = "note"
id: Mapped[int] = mapped_column(primary_key=True)
item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
keyword: Mapped[str]
text: Mapped[str]
item: Mapped["Item"] = relationship()
@property
def note_key(self):
return (self.keyword, self.text[0:10])
def __init__(self, keyword: str, text: str):
self.keyword = keyword
self.text = text
上面我们添加了一个具有双向 relationship.back_populates
配置的 Note.item
关系。给这个反向关系赋值时,Note
被添加到 Item.notes
字典中,并且键会自动生成:
>>> item = Item()
>>> n1 = Note("a", "atext")
>>> n1.item = item
>>> item.notes
{('a', 'atext'): <__main__.Note object at 0x2eaaf0>}
其他内置的字典类型包括 column_keyed_dict()
,它几乎类似于 attribute_keyed_dict()
,只是直接给出了 Column
对象:
from sqlalchemy.orm import column_keyed_dict
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=column_keyed_dict(Note.__table__.c.keyword),
cascade="all, delete-orphan",
)
以及mapped_collection()
,它将传递任何可调用函数。请注意,通常最好与前面提到的@property
一起使用attribute_keyed_dict()
更容易:
from sqlalchemy.orm import mapped_collection
class Item(Base):
__tablename__ = "item"
id: Mapped[int] = mapped_column(primary_key=True)
notes: Mapped[Dict[str, "Note"]] = relationship(
collection_class=mapped_collection(lambda note: note.text[0:10]),
cascade="all, delete-orphan",
)
字典映射经常与“关联代理”扩展组合以产生流畅的字典视图。参见基于字典的集合的代理和复合关联代理以获取示例。
处理键变化和字典集合的反向填充
当使用attribute_keyed_dict()
时,字典的“键”来自目标对象上的属性。对此键的更改不会被跟踪。这意味着键必须在首次使用时被分配,并且如果键发生更改,则集合将不会发生变化。一个典型的例子是当依赖反向引用来填充属性映射集合时可能会出现问题。给定以下内容:
class A(Base):
__tablename__ = "a"
id: Mapped[int] = mapped_column(primary_key=True)
bs: Mapped[Dict[str, "B"]] = relationship(
collection_class=attribute_keyed_dict("data"),
back_populates="a",
)
class B(Base):
__tablename__ = "b"
id: Mapped[int] = mapped_column(primary_key=True)
a_id: Mapped[int] = mapped_column(ForeignKey("a.id"))
data: Mapped[str]
a: Mapped["A"] = relationship(back_populates="bs")
如果我们创建引用特定A()
的B()
,那么反向填充将把B()
添加到A.bs
集合中,但是如果B.data
的值尚未设置,则键将为None
:
>>> a1 = A()
>>> b1 = B(a=a1)
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}
在事后设置b1.data
不会更新集合:
>>> b1.data = "the key"
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}
如果尝试在构造函数中设置 B()
,也可以看到这一点。参数的顺序会改变结果:
>>> B(a=a1, data="the key")
<test3.B object at 0x7f7b10114280>
>>> a1.bs
{None: <test3.B object at 0x7f7b10114280>}
对比:
>>> B(data="the key", a=a1)
<test3.B object at 0x7f7b10114340>
>>> a1.bs
{'the key': <test3.B object at 0x7f7b10114340>}
如果以这种方式使用反向引用,请确保使用__init__
方法以正确的顺序填充属性。
还可以使用以下事件处理程序来跟踪集合中的更改:
from sqlalchemy import event
from sqlalchemy.orm import attributes
@event.listens_for(B.data, "set")
def set_item(obj, value, previous, initiator):
if obj.a is not None:
previous = None if previous == attributes.NO_VALUE else previous
obj.a.bs[value] = obj
obj.a.bs.pop(previous)
``` #### 处理键变化和字典集合的反向填充
当使用`attribute_keyed_dict()`时,字典的“键”来自目标对象上的属性。**对此键的更改不会被跟踪**。这意味着键必须在首次使用时被分配,并且如果键发生更改,则集合将不会发生变化。一个典型的例子是当依赖反向引用来填充属性映射集合时可能会出现问题。给定以下内容:
```py
class A(Base):
__tablename__ = "a"
id: Mapped[int] = mapped_column(primary_key=True)
bs: Mapped[Dict[str, "B"]] = relationship(
collection_class=attribute_keyed_dict("data"),
back_populates="a",
)
class B(Base):
__tablename__ = "b"
id: Mapped[int] = mapped_column(primary_key=True)
a_id: Mapped[int] = mapped_column(ForeignKey("a.id"))
data: Mapped[str]
a: Mapped["A"] = relationship(back_populates="bs")
如果我们创建引用特定A()
的B()
,那么反向填充将把B()
添加到A.bs
集合中,但是如果B.data
的值尚未设置,则键将为None
:
>>> a1 = A()
>>> b1 = B(a=a1)
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}
在事后设置b1.data
不会更新集合:
>>> b1.data = "the key"
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}
如果尝试在构造函数中设置 B()
,也可以看到这一点。参数的顺序会改变结果:
>>> B(a=a1, data="the key")
<test3.B object at 0x7f7b10114280>
>>> a1.bs
{None: <test3.B object at 0x7f7b10114280>}
对比:
>>> B(data="the key", a=a1)
<test3.B object at 0x7f7b10114340>
>>> a1.bs
{'the key': <test3.B object at 0x7f7b10114340>}
如果以这种方式使用反向引用,请确保使用__init__
方法以正确的顺序填充属性。
还可以使用以下事件处理程序来跟踪集合中的更改:
from sqlalchemy import event
from sqlalchemy.orm import attributes
@event.listens_for(B.data, "set")
def set_item(obj, value, previous, initiator):
if obj.a is not None:
previous = None if previous == attributes.NO_VALUE else previous
obj.a.bs[value] = obj
obj.a.bs.pop(previous)
自定义集合实现
您也可以为集合使用自己的类型。在简单情况下,继承自 list
或 set
,添加自定义行为即可。在其他情况下,需要特殊的装饰器来告诉 SQLAlchemy 关于集合操作的更多细节。
SQLAlchemy 中的集合是透明的 instrumented。仪器化意味着对集合的常规操作将被跟踪,并在刷新时写入数据库中。此外,集合操作可以触发 事件,指示必须进行某些次要操作。次要操作的示例包括在父项的Session
(即 save-update
级联)中保存子项,以及同步双向关系的状态(即 backref()
)。
集合包理解列表、集合和字典的基本接口,并将自动将仪器应用于这些内置类型及其子类。通过鸭子类型检测实现基本集合接口的对象派生类型,以进行仪器化:
class ListLike:
def __init__(self):
self.data = []
def append(self, item):
self.data.append(item)
def remove(self, item):
self.data.remove(item)
def extend(self, items):
self.data.extend(items)
def __iter__(self):
return iter(self.data)
def foo(self):
return "foo"
append
、remove
和 extend
是 list
的已知成员,并将自动进行仪器化。__iter__
不是一个修改器方法,不会被仪器化,foo
也不会被仪器化。
当然,鸭子类型(即猜测)并不是百分之百可靠的,所以您可以通过提供一个 __emulates__
类属性来明确您正在实现的接口:
class SetLike:
__emulates__ = set
def __init__(self):
self.data = set()
def append(self, item):
self.data.add(item)
def remove(self, item):
self.data.remove(item)
def __iter__(self):
return iter(self.data)
此类似于 Python list
(即 “类似列表”),因为它有一个 append
方法,但 __emulates__
属性强制将其视为 set
。 remove
已知是集合接口的一部分,并将被仪器化。
但是,此类暂时无法正常工作:需要一点粘合剂来使其适应 SQLAlchemy 的使用。ORM 需要知道用于附加、删除和迭代集合成员的方法。当使用类似于 list
或 set
的类型时,当存在时,适当的方法是众所周知的并且会自动使用。然而,上面的类,虽然只大致类似于 set
,但并未提供预期的 add
方法,因此我们必须告诉 ORM 替代 add
方法的方法,在这种情况下使用装饰器 @collection.appender
;这在下一节中进行了说明。
通过装饰器注释自定义集合
当您的类不完全符合其容器类型的常规接口时,或者您希望以其他方式使用不同的方法来完成工作时,可以使用装饰器标记 ORM 需要管理集合的各个方法。
from sqlalchemy.orm.collections import collection
class SetLike:
__emulates__ = set
def __init__(self):
self.data = set()
@collection.appender
def append(self, item):
self.data.add(item)
def remove(self, item):
self.data.remove(item)
def __iter__(self):
return iter(self.data)
而这就是完成示例所需的全部。SQLAlchemy 将通过append
方法添加实例。remove
和__iter__
是集合的默认方法,将用于移除和迭代。默认方法也可以更改:
from sqlalchemy.orm.collections import collection
class MyList(list):
@collection.remover
def zark(self, item):
# do something special...
...
@collection.iterator
def hey_use_this_instead_for_iteration(self): ...
完全不需要“类似于列表”或“类似于集合”。集合类可以是任何形状,只要它们有被标记为 SQLAlchemy 使用的 append、remove 和 iterate 接口。append 和 remove 方法将以映射实体作为唯一参数调用,迭代器方法将以无参数调用,并且必须返回迭代器。
自定义基于字典的集合
KeyFuncDict
类可以用作自定义类型的基类,也可以作为混合类快速为其他类添加dict
集合支持。它使用一个键函数来委托__setitem__
和__delitem__
:
from sqlalchemy.orm.collections import KeyFuncDict
class MyNodeMap(KeyFuncDict):
"""Holds 'Node' objects, keyed by the 'name' attribute."""
def __init__(self, *args, **kw):
super().__init__(keyfunc=lambda node: node.name)
dict.__init__(self, *args, **kw)
当子类化KeyFuncDict
时,如果用户定义了__setitem__()
或__delitem__()
的版本,并且它们调用了相同的方法KeyFuncDict
上的方法,则应使用collection.internally_instrumented()
进行装饰,如果它们调用了相同的方法KeyFuncDict
上的方法。这是因为KeyFuncDict
上的方法已经被仪器化了 - 从已经仪器化的调用中调用它们可能会导致事件被重复触发,或者不适当地触发,在极少数情况下会导致内部状态损坏:
from sqlalchemy.orm.collections import KeyFuncDict, collection
class MyKeyFuncDict(KeyFuncDict):
"""Use @internally_instrumented when your methods
call down to already-instrumented methods.
"""
@collection.internally_instrumented
def __setitem__(self, key, value, _sa_initiator=None):
# do something with key, value
super(MyKeyFuncDict, self).__setitem__(key, value, _sa_initiator)
@collection.internally_instrumented
def __delitem__(self, key, _sa_initiator=None):
# do something with key
super(MyKeyFuncDict, self).__delitem__(key, _sa_initiator)
ORM 与dict
接口的理解方式与列表和集合一样,并且如果选择对dict
进行子类化或在鸭式类型的类中提供类似于字典的集合行为,则会自动对所有“类似于字典”的方法进行仪器化。但是,必须装饰追加和删除方法 - 基本字典接口中没有兼容的方法供 SQLAlchemy 默认使用。迭代将通过values()
进行,除非另有装饰。
仪器化和自定义类型
许多自定义类型和现有库类可以直接使用作为实体集合类型,无需额外操作。但是,重要的是要注意,仪器化过程将修改类型,自动在方法周围添加装饰器。
装饰很轻量级,在关系之外不起作用,但是当在其他地方触发时会增加不必要的开销。当将库类用作集合时,最好使用“微不足道的子类”技巧将装饰限制为关系中的使用。例如:
class MyAwesomeList(some.great.library.AwesomeList):
pass
# ... relationship(..., collection_class=MyAwesomeList)
ORM 使用这种方法进行内置,当直接使用list
、set
或dict
时,会悄悄地替换为一个微不足道的子类。
通过装饰器注释自定义集合
可以使用装饰器标记 ORM 需要管理集合的各个方法。当您的类不完全符合其容器类型的常规接口时,或者当您希望以不同的方法完成工作时,请使用它们。
from sqlalchemy.orm.collections import collection
class SetLike:
__emulates__ = set
def __init__(self):
self.data = set()
@collection.appender
def append(self, item):
self.data.add(item)
def remove(self, item):
self.data.remove(item)
def __iter__(self):
return iter(self.data)
这就是完成示例所需的全部内容。SQLAlchemy 将通过append
方法添加实例。remove
和__iter__
是集合的默认方法,将用于删除和迭代。默认方法也可以更改:
from sqlalchemy.orm.collections import collection
class MyList(list):
@collection.remover
def zark(self, item):
# do something special...
...
@collection.iterator
def hey_use_this_instead_for_iteration(self): ...
完全不需要“类似于列表”或“类似于集合”。集合类可以是任何形状,只要它们具有标记为 SQLAlchemy 使用的追加、移除和迭代接口即可。追加和移除方法将以映射实体作为单个参数调用,并且迭代方法将不带参数调用,并且必须返回迭代器。
自定义基于字典的集合
KeyFuncDict
类可以作为自定义类型的基类,也可以作为混合类快速将dict
集合支持添加到其他类中。它使用键函数来委托__setitem__
和__delitem__
:
from sqlalchemy.orm.collections import KeyFuncDict
class MyNodeMap(KeyFuncDict):
"""Holds 'Node' objects, keyed by the 'name' attribute."""
def __init__(self, *args, **kw):
super().__init__(keyfunc=lambda node: node.name)
dict.__init__(self, *args, **kw)
当子类化KeyFuncDict
时,用户定义的__setitem__()
或__delitem__()
版本应该用collection.internally_instrumented()
进行修饰,如果它们调用同样的方法。这是因为KeyFuncDict
上的方法已经被仪器化-在已经仪器化的调用中调用它们可能会导致事件重复触发,或者在罕见情况下导致内部状态损坏:
from sqlalchemy.orm.collections import KeyFuncDict, collection
class MyKeyFuncDict(KeyFuncDict):
"""Use @internally_instrumented when your methods
call down to already-instrumented methods.
"""
@collection.internally_instrumented
def __setitem__(self, key, value, _sa_initiator=None):
# do something with key, value
super(MyKeyFuncDict, self).__setitem__(key, value, _sa_initiator)
@collection.internally_instrumented
def __delitem__(self, key, _sa_initiator=None):
# do something with key
super(MyKeyFuncDict, self).__delitem__(key, _sa_initiator)
ORM 理解dict
接口就像理解列表和集合一样,并且如果您选择子类化dict
或在鸭子类型的类中提供类似于 dict 的集合行为,则会自动仪器化所有“类似于 dict”的方法。但是,您必须修饰追加和移除方法-默认情况下,基本字典接口中没有兼容的方法供 SQLAlchemy 使用。迭代将通过values()
进行,除非另有修饰。
仪器化和自定义类型
许多自定义类型和现有的库类可以直接用作实体集合类型,无需进一步操作。但是,需要注意的是,仪器化过程将修改类型,自动在方法周围添加修饰符。
装饰是轻量级的,并且在关系之外不起作用,但是当在其他地方触发时它们会增加不必要的开销。当将库类用作集合时,使用“trivial subclass”技巧将装饰限制为仅在关系中使用的情况可能是一个好习惯。例如:
class MyAwesomeList(some.great.library.AwesomeList):
pass
# ... relationship(..., collection_class=MyAwesomeList)
ORM 使用此方法处理内置功能,当直接使用 list
、set
或 dict
时,会静默地替换为一个微不足道的子类。
集合 API
对象名称 | 描述 |
---|---|
attribute_keyed_dict(attr_name, *, [ignore_unpopulated_attribute]) | 基于属性的键的字典类型集合。 |
attribute_mapped_collection | 基于属性的键的字典类型集合。 |
column_keyed_dict(mapping_spec, *, [ignore_unpopulated_attribute]) | 基于列的键的字典类型集合。 |
column_mapped_collection | 基于列的键的字典类型集合。 |
keyfunc_mapping(keyfunc, *, [ignore_unpopulated_attribute]) | 具有任意键的基于字典的集合类型。 |
KeyFuncDict | 用于 ORM 映射字典类的基础。 |
mapped_collection | 具有任意键的基于字典的集合类型。 |
MappedCollection | 用于 ORM 映射字典类的基础。 |
function sqlalchemy.orm.attribute_keyed_dict(attr_name: str, *, ignore_unpopulated_attribute: bool = False) → Type[KeyFuncDict[Any, Any]]
基于属性的键的字典类型集合。
2.0 版本更改:将attribute_mapped_collection
重命名为attribute_keyed_dict()
。
返回一个KeyFuncDict
工厂,它将根据要添加到字典中的 ORM 映射实例上的特定命名属性的值产生新的字典键。
注意
目标属性的值必须在对象添加到字典集合时被赋值。此外,不会跟踪关键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关更多详细信息,请参阅处理键变化和反向填充字典集合。
另请参阅
字典集合 - 使用背景
参数:
-
attr_name
– 映射类上的 ORM 映射属性的字符串名称,该属性的值将作为该实例的新字典条目的键值。 -
ignore_unpopulated_attribute
–如果为 True,并且对象上的目标属性根本未填充,则该操作将被静默跳过。默认情况下,将引发错误。
新版 2.0 中:如果确定用于字典键的属性从未填充任何值,则默认情况下会引发错误。
attribute_keyed_dict.ignore_unpopulated_attribute
参数可以设置,表示应忽略此条件,并且静默跳过追加操作。这与 1.x 系列的行为形成对比,后者会错误地用任意键值None
填充字典中的值。
function sqlalchemy.orm.column_keyed_dict(mapping_spec: Type[_KT] | Callable[[_KT], _VT], *, ignore_unpopulated_attribute: bool = False) → Type[KeyFuncDict[_KT, _KT]]
基于列的键控字典集合类型。
2.0 中更改:将column_mapped_collection
更名为 column_keyed_dict
。
返回一个KeyFuncDict
工厂,它将根据 ORM 映射实例上的特定Column
-映射属性的值生成新的字典键,以添加到字典中。
注意
目标属性的值必须在将对象添加到字典集合时分配其值。此外,不跟踪键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关详细信息,请参阅处理键变化和向字典集合回填。
也可参阅
字典集合 - 使用背景
参数:
-
mapping_spec
– 预期由目标映射器映射到映射类上的特定属性的Column
对象,该属性的值在特定实例上将用作该实例的新字典条目的键。 -
ignore_unpopulated_attribute
–如果为 True,并且对象上由给定
Column
目标属性指示的映射属性根本未填充,则操作将被静默跳过。默认情况下,会引发错误。新版 2.0 中:如果确定用于字典键的属性从未填充任何值,则默认情况下会引发错误。
column_keyed_dict.ignore_unpopulated_attribute
参数可以设置,表示应忽略此条件,并且静默跳过追加操作。这与 1.x 系列的行为形成对比,后者会错误地用任意键值None
填充字典中的值。
function sqlalchemy.orm.keyfunc_mapping(keyfunc: _F, *, ignore_unpopulated_attribute: bool = False) → Type[KeyFuncDict[_KT, Any]]
一个具有任意键的基于字典的集合类型。
从版本 2.0 开始更改:将 mapped_collection
重命名为 keyfunc_mapping()
。
返回一个从 keyfunc
生成的键函数的 KeyFuncDict
工厂,keyfunc
是一个可调用对象,接受一个实体并返回一个键值。
注意
给定的 keyfunc
只在将目标对象添加到集合时调用一次。不跟踪对函数返回的有效值的更改。
另请参见
字典集合 - 使用背景
参数:
-
keyfunc
– 应传递 ORM 映射实例的可调用对象,然后生成一个新的用于字典的键。如果返回的值是LoaderCallableStatus.NO_VALUE
,则会引发错误。 -
ignore_unpopulated_attribute
–如果为 True,并且可调用对象对于特定实例返回
LoaderCallableStatus.NO_VALUE
,则操作将被静默跳过。默认情况下,会引发错误。从版本 2.0 开始:如果用于字典键的可调用对象返回
LoaderCallableStatus.NO_VALUE
,默认情况下会引发错误,这在 ORM 属性上下文中表示一个从未填充任何值的属性。可以设置mapped_collection.ignore_unpopulated_attribute
参数,该参数将指示忽略此条件,并且追加操作将被静默跳过。这与 1.x 系列的行为形成对比,后者会错误地用任意的键值None
填充字典中的值。
sqlalchemy.orm.attribute_mapped_collection = <function attribute_keyed_dict>
一个基于字典的集合类型,具有基于属性的键。
从版本 2.0 开始更改:将 attribute_mapped_collection
重命名为 attribute_keyed_dict()
。
返回一个根据要添加到字典中的 ORM 映射实例的特定命名属性的值生成新字典键的 KeyFuncDict
工厂。
注意
目标属性的值必须在将对象添加到字典集合时分配其值。此外,不跟踪键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关详细信息,请参见处理键变异和向字典集合反填充。
另请参见
字典集合 - 使用背景
参数:
-
attr_name
– 映射类上的 ORM 映射属性的字符串名称,在特定实例上的值将用作该实例的新字典条目的键。 -
ignore_unpopulated_attribute
–如果为 True,并且对象上的目标属性根本未填充,则操作将被静默跳过。默认情况下,将引发错误。
新版本 2.0 中:如果确定用于字典键的属性从未填充任何值,则默认会引发错误。
attribute_keyed_dict.ignore_unpopulated_attribute
参数可以设置,以指示应忽略此条件,并且附加操作将被静默跳过。这与 1.x 系列的行为相反,后者会错误地使用任意键值为None
填充字典中的值。
sqlalchemy.orm.column_mapped_collection = <function column_keyed_dict>
基于字典的集合类型,以列为键。
2.0 版更改:将column_mapped_collection
重命名为column_keyed_dict
。
返回一个KeyFuncDict
工厂,它将根据 ORM 映射实例上的特定Column
属性的值生成新的字典键,以添加到字典中。
注意
目标属性的值必须在将对象添加到字典集合时分配其值。此外,不跟踪键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关详细信息,请参见处理键变异和向字典集合反填充。
另请参见
字典集合 - 使用背景
参数:
-
mapping_spec
– 预期由目标映射器映射到映射类上的特定属性的Column
对象,在特定实例上的值将用作该实例的新字典条目的键。 -
ignore_unpopulated_attribute
–如果为 True,并且对象上的给定
Column
目标属性指示的映射属性根本未填充,则操作将被静默跳过。默认情况下,会引发错误。版本 2.0 中的新功能:如果确定用于字典键的属性从未填充任何值,则默认情况下会引发错误。可以设置
column_keyed_dict.ignore_unpopulated_attribute
参数,该参数将指示忽略此条件,并静默跳过追加操作。这与 1.x 系列的行为相反,后者将错误地用任意键值None
填充字典中的值。
sqlalchemy.orm.mapped_collection = <function keyfunc_mapping>
具有任意键的基于字典的集合类型。
自版本 2.0 起更改:将mapped_collection
重命名为keyfunc_mapping()
。
返回一个由 keyfunc 生成的键函数的KeyFuncDict
工厂,keyfunc 是一个可调用的函数,它接受一个实体并返回一个键值。
注意
给定的 keyfunc 仅在将目标对象添加到集合时调用一次。不跟踪函数返回的有效值的更改。
另请参阅
字典集合 - 使用背景
参数:
-
keyfunc
– 一个可调用的函数,将传递 ORM 映射实例,然后生成一个新的键用于字典中。如果返回的值为LoaderCallableStatus.NO_VALUE
,则会引发错误。 -
ignore_unpopulated_attribute
–如果为 True,并且可调用函数对于特定实例返回
LoaderCallableStatus.NO_VALUE
,则操作将被静默跳过。默认情况下,会引发错误。新版本 2.0 中,默认情况下,如果用于字典键的可调用函数返回
LoaderCallableStatus.NO_VALUE
,则会引发错误。在 ORM 属性上下文中,这表示从未填充任何值的属性。可以设置mapped_collection.ignore_unpopulated_attribute
参数,该参数将表示应忽略此条件,并且附加操作会被静默跳过。这与 1.x 系列的行为形成对比,后者会错误地使用任意键值None
填充字典中的值。
class sqlalchemy.orm.KeyFuncDict
ORM 映射字典类的基类。
通过向 SQLAlchemy ORM 集合类添加所需的附加方法来扩展dict
类型。最直接使用attribute_keyed_dict()
或column_keyed_dict()
类工厂来使用KeyFuncDict
。KeyFuncDict
也可以用作用户定义的自定义字典类的基类。
2.0 中的变更:将MappedCollection
重命名为KeyFuncDict
。
另请参见
attribute_keyed_dict()
column_keyed_dict()
字典集合
自定义集合实现
成员
init(), clear(), pop(), popitem(), remove(), set(), setdefault(), update()
类签名
class sqlalchemy.orm.KeyFuncDict
(builtins.dict
, typing.Generic
)
method __init__(keyfunc: _F, *dict_args: Any, ignore_unpopulated_attribute: bool = False) → None
使用 keyfunc 提供的键制作新集合。
keyfunc 可以是任何接受对象并返回对象以用作字典键的可调用函数。
每当 ORM 需要通过仅基于值的方式添加成员(例如从数据库加载实例时)或删除成员时,都会调用 keyfunc。通常的字典键的注意事项也适用 - keyfunc(object)
应该在集合的生命周期内返回相同的输出。基于可变属性的键值可能导致集合中“丢失”的不可达实例。
method clear() → None. Remove all items from D.
method pop(k[, d]) → v, remove specified key and return the corresponding value.
如果找不到键,则如果给定默认值,则返回默认值;否则,引发 KeyError。
method popitem()
移除并返回一个 (key, value) 对作为 2-元组。
以 LIFO(后进先出)顺序返回对。如果字典为空,则引发 KeyError。
method remove(value: _KT, _sa_initiator: AttributeEventToken | Literal[None, False] = None) → None
通过值删除项目,并查询键的键函数。
method set(value: _KT, _sa_initiator: AttributeEventToken | Literal[None, False] = None) → None
通过值添加项目,并查询键的键函数。
method setdefault(key, default=None)
插入具有默认值的键,如果键不在字典中。
如果键在字典中,则返回键的值,否则返回默认值。
method update([E, ]**F) → None. Update D from dict/iterable E and F.
如果 E 存在且具有 .keys() 方法,则执行以下操作:for k in E: D[k] = E[k] 如果 E 存在且缺少 .keys() 方法,则执行以下操作:for k, v in E: D[k] = v 在任何一种情况下,都会执行以下操作:for k in F: D[k] = F[k]
sqlalchemy.orm.MappedCollection = <class 'sqlalchemy.orm.mapped_collection.KeyFuncDict'>
ORM 映射字典类的基础。
扩展了 dict
类型,以包含 SQLAlchemy ORM 集合类所需的附加方法。最直接使用 attribute_keyed_dict()
或 column_keyed_dict()
类工厂来使用 KeyFuncDict
。KeyFuncDict
也可以作为用户定义的自定义字典类的基础。
在 2.0 版本中更改:将 MappedCollection
重命名为 KeyFuncDict
。
另请参阅
attribute_keyed_dict()
column_keyed_dict()
字典集合
自定义集合实现
集合内部
Object Name | 描述 |
---|---|
bulk_replace(values, existing_adapter, new_adapter[, initiator]) | 加载一个新的集合,并根据先前的相似成员资格触发事件。 |
collection | 实体集合类的装饰器。 |
collection_adapter | attrgetter(attr, …) –> attrgetter 对象 |
CollectionAdapter | 在 ORM 和任意 Python 集合之间建立桥梁。 |
InstrumentedDict | 内置字典的受控版本。 |
InstrumentedList | 内置列表的受控版本。 |
InstrumentedSet | 内置集合的受控版本。 |
prepare_instrumentation(factory) | 准备一个可调用对象,以便将来用作集合类工厂。 |
function sqlalchemy.orm.collections.bulk_replace(values, existing_adapter, new_adapter, initiator=None)
加载一个新的集合,并根据先前的相似成员资格触发事件。
将values
中的实例追加到new_adapter
中。对于existing_adapter
中不存在的任何实例,将触发事件。existing_adapter
中存在但在values
中不存在的任何实例将触发删除事件。
参数:
-
values
– 一个包含集合成员实例的可迭代对象 -
existing_adapter
– 一个要替换的实例的CollectionAdapter
-
new_adapter
– 一个空的CollectionAdapter
,用于加载values
class sqlalchemy.orm.collections.collection
用于实体集合类的装饰器。
装饰器分为两组:注解和拦截配方。
注解装饰器(appender、remover、iterator、converter、internally_instrumented)指示方法的目的并且不带参数。它们不带括号写成:
@collection.appender
def append(self, append): ...
所有的装饰器都需要括号,即使没有参数:
成员
adds(), appender(), converter(), internally_instrumented(), iterator(), remover(), removes(), removes_return(), replaces()
@collection.adds('entity')
def insert(self, position, entity): ...
@collection.removes_return()
def popitem(self): ...
method static adds(arg)
将方法标记为向集合添加实体。
将“添加到集合”处理添加到方法中。装饰器参数指示哪个方法参数保存了与 SQLAlchemy 相关的值。参数可以按位置指定(即整数)或按名称指定:
@collection.adds(1)
def push(self, item): ...
@collection.adds('entity')
def do_stuff(self, thing, entity=None): ...
method static appender(fn)
将方法标记为集合追加器。
如果追加器方法调用时带有一个位置参数:要追加的值。如果尚未装饰,则该方法将自动装饰为‘adds(1)’:
@collection.appender
def add(self, append): ...
# or, equivalently
@collection.appender
@collection.adds(1)
def add(self, append): ...
# for mapping type, an 'append' may kick out a previous value
# that occupies that slot. consider d['a'] = 'foo'- any previous
# value in d['a'] is discarded.
@collection.appender
@collection.replaces(1)
def add(self, entity):
key = some_key_func(entity)
previous = None
if key in self:
previous = self[key]
self[key] = entity
return previous
如果要追加的值不允许在集合中,您可以引发异常。需要记住的是,追加器将针对数据库查询映射的每个对象调用。如果数据库包含违反集合语义的行,则您需要有创意地解决问题,因为通过集合访问将无法工作。
如果追加器方法在内部被检测到,您还必须接收关键字参数‘_sa_initiator’并确保将其传播到集合事件。
method static converter(fn)
将方法标记为集合转换器。
自版本 1.3 弃用:collection.converter()
处理程序已弃用,并将在未来的版本中移除。请参考 listen()
函数结合 bulk_replace
监听器接口。
当集合被完全替换时,将调用此可选方法,如下所示:
myobj.acollection = [newvalue1, newvalue2]
转换器方法将接收到要分配的对象,并应返回适用于 appender
方法使用的值的可迭代对象。转换器不得分配值或更改集合,它的唯一任务是将用户提供的值适应为 ORM 使用的值的可迭代对象。
默认的转换器实现将使用鸭子类型进行转换。类似字典的集合将被转换为字典值的可迭代对象,而其他类型将简单地进行迭代:
@collection.converter
def convert(self, other): ...
如果对象的鸭子类型与此集合的类型不匹配,则会引发 TypeError。
如果您希望扩展可以批量分配的可能类型的范围或对即将分配的值进行验证,请提供此方法的实现。
method static internally_instrumented(fn)
将该方法标记为受检测的。
此标记将阻止对该方法应用任何装饰。如果您正在 orchestrating 在基本的 SQLAlchemy 接口方法之一中调用 collection_adapter()
,或者要防止自动 ABC 方法装饰器包装您的实现,请使用此标记:
# normally an 'extend' method on a list-like class would be
# automatically intercepted and re-implemented in terms of
# SQLAlchemy events and append(). your implementation will
# never be called, unless:
@collection.internally_instrumented
def extend(self, items): ...
method static iterator(fn)
将该方法标记为集合移除器。
迭代器方法无需参数调用。它应返回所有集合成员的迭代器:
@collection.iterator
def __iter__(self): ...
method static remover(fn)
将该方法标记为集合移除器。
移除器方法使用一个位置参数调用:要移除的值。如果尚未被其他装饰器装饰,该方法将自动使用 removes_return()
进行装饰:
@collection.remover
def zap(self, entity): ...
# or, equivalently
@collection.remover
@collection.removes_return()
def zap(self, ): ...
如果要移除的值不存在于集合中,则可以引发异常或返回 None 以忽略错误。
如果移除方法在内部进行了检测,请确保也接收关键字参数 ‘_sa_initiator’ 并确保其在集合事件中传播。
method static removes(arg)
将该方法标记为从集合中移除实体。
为方法添加“从集合中移除”的处理。修饰器参数指示哪个方法参数包含要移除的与 SQLAlchemy 相关的值。参数可以按位置指定(即整数),也可以按名称指定:
@collection.removes(1)
def zap(self, item): ...
对于在调用时未知要移除的值的方法,请使用 collection.removes_return。
method static removes_return()
将该方法标记为从集合中移除实体。
为方法添加“从集合中移除”的处理。如果没有被其他装饰器装饰,该方法的返回值(如果有)将被视为要移除的值。不会检查方法参数:
@collection.removes_return()
def pop(self): ...
对于在调用时已知要移除的值的方法,请使用 collection.remove。
method static replaces(arg)
标记该方法用于替换集合中的实体。
为方法添加“添加到集合”和“从集合中移除”处理。装饰器参数指示哪个方法参数保存了要添加的与 SQLAlchemy 相关的值,以及返回值(如果有)将被视为要移除的值。
参数可以通过位置(即整数)或名称指定:
@collection.replaces(2)
def __setitem__(self, index, item): ...
sqlalchemy.orm.collections.collection_adapter = operator.attrgetter('_sa_adapter')
attrgetter(attr, …) –> attrgetter 对象
返回一个可调用对象,从其操作数中提取给定的属性。在 f = attrgetter(‘name’) 后,调用 f(r) 返回 r.name。在 g = attrgetter(‘name’, ‘date’) 后,调用 g(r) 返回 (r.name, r.date)。在 h = attrgetter(‘name.first’, ‘name.last’) 后,调用 h(r) 返回 (r.name.first, r.name.last)。
class sqlalchemy.orm.collections.CollectionAdapter
在 ORM 和任意 Python 集合之间建立桥梁。
将基本级别的集合操作(追加、删除、迭代)代理给底层的 Python 集合,并为进入或离开集合的实体发出添加/删除事件。
ORM 专门使用CollectionAdapter
与实体集合进行交互。
class sqlalchemy.orm.collections.InstrumentedDict
内置字典的受检版本。
类签名
类sqlalchemy.orm.collections.InstrumentedDict
(builtins.dict
, typing.Generic
)
class sqlalchemy.orm.collections.InstrumentedList
内置列表的受检版本。
类签名
类sqlalchemy.orm.collections.InstrumentedList
(builtins.list
, typing.Generic
)
class sqlalchemy.orm.collections.InstrumentedSet
内置集合的受检版本。
类签名
类sqlalchemy.orm.collections.InstrumentedSet
(builtins.set
, typing.Generic
)
function sqlalchemy.orm.collections.prepare_instrumentation(factory: Type[Collection[Any]] | Callable[[], _AdaptedCollectionProtocol]) → Callable[[], _AdaptedCollectionProtocol]
准备一个可调用对象,以便将来用作集合类工厂。
给定一个集合类工厂(类型或无参数可调用对象),返回另一个工厂,当调用时将生成兼容的实例。
该函数负责将 collection_class=list 转换为 collection_class=InstrumentedList 的运行时行为。
特殊的关系持久性模式
原文:
docs.sqlalchemy.org/en/20/orm/relationship_persistence.html
指向自身的行 / 相互依赖的行
这是一种非常特殊的情况,其中 relationship()必须执行一个 INSERT 和一个第二个 UPDATE,以正确填充一行(反之亦然,为了删除而执行一个 UPDATE 和 DELETE,而不违反外键约束)。这两种用例是:
-
一个表包含对自身的外键,而且单个行将具有指向其自身主键的外键值。
-
两个表都包含对另一个表的外键引用,每个表中的一行引用另一个表中的另一行。
例如:
user
---------------------------------
user_id name related_user_id
1 'ed' 1
或:
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
构造:
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”列(目前每次一行):
>>> 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
引用的是也引用此Widget
的Entry
。我们可以使用复合外键,如下所示:
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_id
和favorite_entry_id
列。为了确保Widget.widget_id
保持为“自动增量”列,我们在Column
上的autoincrement
参数上指定值"ignore_fk"
,并且在每个relationship()
上,我们必须限制那些被视为外键的列,以用于连接和交叉填充。##可变主键/更新级联
当实体的主键更改时,引用主键的相关项也必须更新。对于强制实施引用完整性的数据库,最佳策略是使用数据库的ON UPDATE CASCADE
功能,以便将主键更改传播到引用的外键 - 在事务完成之前,值不能不同步,除非约束标记为“可延迟”。
强烈建议一个希望使用可变值的自然主键的应用程序使用数据库的ON UPDATE CASCADE
功能。一个说明此功能的示例映射是:
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
的映射如下:
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 以删除而不违反外键约束)。这两种用例是:
-
一张表包含一个指向自身的外键,而且一行将具有指向自己主键的外键值。
-
两个表分别包含一个外键引用另一个表,每个表中的一行引用另一个表。
例如:
user
---------------------------------
user_id name related_user_id
1 'ed' 1
或者:
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
构造:
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 语句(目前一次一行)填充:
>>> 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
指向也指向此 Widget
的 Entry
。我们可以使用复合外键,如下所示:
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_id
和 favorite_entry_id
列。为了确保 Widget.widget_id
仍然是一个“自增”的列,我们在Column
上指定了 Column.autoincrement
的值为 "ignore_fk"
,并且在每个relationship()
上,我们必须限制那些被视为外键的列以进行连接和交叉填充。
可变主键 / 更新级联
当实体的主键发生变化时,引用该主键的相关项也必须进行更新。对于强制执行引用完整性的数据库,最佳策略是使用数据库的 ON UPDATE CASCADE 功能,以便将主键更改传播到引用的外键 - 除非约束被标记为“可延迟”,即不执行直到事务完成,否则值不能在任何时刻不同步。
强烈建议希望使用可变值的自然主键的应用程序使用数据库的 ON UPDATE CASCADE
功能。一个示例映射如下:
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
的映射如下:
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
的映射如下所示:
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
支持。
使用遗留的 ‘backref’ 关系参数
注意
应考虑使用遗留的 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()
构造相互引用。
以以下示例开始:
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.addresses
的Address
对象集合。它还在Address
上建立了一个.user
属性,该属性将指向父User
对象。使用relationship.back_populates
等效于以下操作:
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.addresses
和Address.user
关系的行为是以双向方式进行的,表示关系的一侧发生变化会影响另一侧。有关此行为的示例和讨论,请参阅 SQLAlchemy 统一教程的使用 ORM 相关对象部分。
反向引用默认参数
由于relationship.backref
会生成一个全新的relationship()
,默认情况下,生成过程将尝试在新的relationship()
中包含对应于原始参数的相应参数。举例说明,下面是一个包含自定义连接条件的relationship()
,该条件还包括relationship.backref
关键字:
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()
中:
>>> 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.primaryjoin
和relationship.secondaryjoin
;“backref”足够智能,知道在生成相反的一侧时这两个参数也应该被“翻转”。
指定反向引用参数
很多其他用于“backref”的参数都不是隐含的,包括像relationship.lazy
、relationship.remote_side
、relationship.cascade
和relationship.cascade_backrefs
等参数。对于这种情况,我们使用backref()
函数代替字符串;这将存储一组特定的参数,这些参数将在生成新的relationship()
时传递:
# <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
关键字:
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()
中:
>>> 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.primaryjoin
和relationship.secondaryjoin
;“反向引用”足够智能,可以知道在生成相反方向时这两个参数也应该“反转”。
指定反向引用参数
“反向引用”的许多其他参数都不是隐式的,包括像relationship.lazy
、relationship.remote_side
、relationship.cascade
和relationship.cascade_backrefs
等参数。对于这种情况,我们使用backref()
函数来代替字符串;这将存储一组特定的参数,这些参数在生成新的relationship()
时将被传递:
# <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
对象名称 | 描述 |
---|---|
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’注释对主要连接表达式的部分进行注释。 |
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
中解析类,以便找到该类,例如:class SomeClass(Base): # ... related = relationship("RelatedClass")
relationship.argument
也可以完全省略不在relationship()
构造中传递,而是放置在左侧的Mapped
注释中,如果关系预期为集合,则应包含 Python 集合类型,例如:class SomeClass(Base): # ... related_items: Mapped[List["RelatedItem"]] = relationship()
或者对于多对一或一对一关系:
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-update
、merge
、expunge
、delete
、delete-orphan
和refresh-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()
将根据那些指定了ForeignKey
的Column
对象或以其他方式列在ForeignKeyConstraint
构造中的引用列的那些列自动确定在relationship.primaryjoin
条件中应被视为“外键”列。只有在以下情况下才需要relationship.foreign_keys
参数:- 从本地表到远程表的连接可以有多种构造方式,因为存在多个外键引用。设置
foreign_keys
将限制relationship()
仅考虑此处指定的列作为“外键”。 - 被映射的
Table
实际上没有ForeignKey
或ForeignKeyConstraint
构造存在,通常是因为该表是从不支持外键反射的数据库(MySQL MyISAM)反射而来。 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 时,SQLAlchemyrelationship()
构造将尝试发出自己的 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()
是否处理集合或标量属性:>>> User.addresses.property.uselist True
另请参阅
一对一关系 - 介绍了“一对一”关系模式,通常涉及
relationship.uselist
的备用设置。 -
viewonly=False
–当设置为
True
时,该关系仅用于加载对象,而不用于任何持久性操作。指定了relationship.viewonly
的relationship()
可以在relationship.primaryjoin
条件内与更广泛的 SQL 操作一起使用,其中包括使用各种比较运算符以及 SQL 函数,如cast()
。relationship.viewonly
标志在定义任何不代表完整相关对象集的relationship()
时也是一般用途,以防止对集合的修改导致持久性操作。另见
关于使用视图关系参数的注意事项 - 使用
relationship.viewonly
时的最佳实践的更多细节。 -
sync_backref
-一个布尔值,用于在此关系是
relationship.backref
或relationship.back_populates
的目标时启用用于同步 Python 属性的事件。默认为
None
,表示应根据relationship.viewonly
标志的值选择自动值。在其默认状态下,只有在关系的任一方都不是视图时状态变化才会被回填。版本 1.3.17 中新增。
从版本 1.4 开始:- 指定了
relationship.viewonly
的关系自动意味着relationship.sync_backref
为False
。另见
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__()
时此字段是否应标记为关键字参数。
function sqlalchemy.orm.backref(name: str, **kwargs: Any) → ORMBackrefArgument
使用relationship.backref
参数时,提供要在生成新的relationship()
时使用的特定参数。
例如:
'items':relationship(
SomeItem, backref=backref('parent', lazy='subquery'))
一般认为relationship.backref
参数是遗留的;对于现代应用程序,应优先使用显式的relationship()
构造,使用relationship.back_populates
参数进行链接。
另请参阅
使用传统的‘backref’关系参数的背景信息,请参阅使用传统的‘backref’关系参数。
function sqlalchemy.orm.dynamic_loader(argument: _RelationshipArgumentType[Any] | None = None, **kw: Any) → RelationshipProperty[Any]
构造一个动态加载的映射器属性。
这与使用relationship()
的lazy='dynamic'
参数基本相同:
dynamic_loader(SomeClass)
# is the same as
relationship(SomeClass, lazy="dynamic")
更多关于动态加载的详细信息,请参阅动态关系加载器一节。
function sqlalchemy.orm.foreign(expr: _CEA) → _CEA
使用“foreign”注解注释主要联接表达式的一部分。
请参阅创建自定义外键条件一节,了解其用法描述。
另请参阅
创建自定义外键条件
remote()
function sqlalchemy.orm.remote(expr: _CEA) → _CEA
使用“remote”注解注释主要联接表达式的一部分。
参见章节创建自定义外键条件以了解其用法描述。
请参阅也
创建自定义外键条件
foreign()
ORM 查询指南
本节概述了使用 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 语句
关于本文档
本节利用了首次在 SQLAlchemy 统一教程中展示的 ORM 映射,显示在声明映射类一节中。
查看此页面的 ORM 设置。
SELECT 语句由 select()
函数生成,该函数返回一个 Select
对象。要返回的实体和/或 SQL 表达式(即“columns”子句)按位置传递给该函数。然后,使用其他方法生成完整的语句,例如下面所示的 Select.where()
方法:
>>> from sqlalchemy import select
>>> stmt = select(User).where(User.name == "spongebob")
给定一个完成的 Select
对象,为了在 ORM 中执行并获取行,对象被传递给 Session.execute()
,然后返回一个 Result
对象:
>>> 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 注解的 FromClause
和 ColumnElement
元素。
包含 ORM 注解实体的 Select
对象通常使用 Session
对象执行,而不是 Connection
对象,以便 ORM 相关功能生效,包括可以返回 ORM 映射对象的实例。直接使用 Connection
时,结果行仅包含列级数据。
选择 ORM 实体
下面我们从User
实体中进行选择,生成一个从User
映射到的Table
中进行选择的Select
:
>>> 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
对象:
>>> 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
对象,该对象产生单个元素而不是行:
>>> 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 类中选择时,它们在每个结果行中根据其类名命名。在下面的示例中,针对User
和Address
进行 SELECT 的结果行将以User
和Address
的名称引用它们:
>>> 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
参数将它们别名为显式名称:
>>> 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()
方法。我们也可以使用这种形式来生成上述语句:
>>> 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.name
和Address.email_address
,当传递给select()
时,可以像Column
或其他 SQL 表达式对象一样使用。针对特定列创建一个select()
将返回Row
对象,而不是像User
或Address
对象那样的实体。每个Row
将单独表示每一列:
>>> 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
[...] ()
上述语句返回具有name
和email_address
列的Row
对象,如下所示的运行时演示:
>>> 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 的构造,允许将列表达式集合分组在结果行中:
>>> 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()
构造实现:
>>> 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
参数:
>>> 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 语句,我们希望从中加载:
>>> 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
类上映射的属性:
>>> textual_sql = textual_sql.columns(User.id, User.name, User.fullname)
现在我们有了一个经过 ORM 配置的 SQL 构造,按照给定的方式,可以单独加载“id”、“name”和“fullname”列。要将此 SELECT 语句用作完整User
实体的源,则可以使用Select.from_statement()
方法将这些列链接到常规的 ORM 启用的Select
构造中:
>>> 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
实体,方式与下面讨论的从子查询中选择实体类似:
>>> 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')
直接使用TextualSelect
与Select.from_statement()
相比,使用aliased()
的区别在于,在前一种情况下,生成的 SQL 中不会产生子查询。在某些情景下,这样做从性能或复杂性的角度来看可能是有利的。 ### 从子查询中选择实体
在前一节讨论的aliased()
构造中,可以与任何来自诸如Select.subquery()
之类的方法的Subuqery
构造一起使用,以将 ORM 实体链接到该子查询返回的列;子查询返回的列与实体映射的列之间必须存在列对应关系,这意味着子查询最终需要来自这些实体,就像下面的示例中一样:
>>> 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()
之后添加额外的条件:
>>> 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()
构造中使用,包括我们可以基于其导出的列添加过滤和排序条件:
>>> 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()
方法的用法相似。
简单的关系连接
考虑两个类User
和Address
之间的映射,其中关系User.addresses
表示与每个User
关联的Address
对象的集合。 Select.join()
的最常见用法是沿着这个关系创建 JOIN,使用User.addresses
属性作为指示器来指示这应该如何发生:
>>> stmt = select(User).join(User.addresses)
在上文中,对User.addresses
的Select.join()
调用将导致 SQL 大致等效于:
>>> 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
实体的行。要同时从User
和Address
选择列/实体,必须在select()
函数中命名Address
实体,或者使用Select.add_columns()
方法在之后将其添加到Select
构造中。请参阅 同时选择多个 ORM 实体 部分以了解这两种形式的示例。
链式多重连接
要构建一系列连接,可以使用多个Select.join()
调用。关系绑定属性一次暗示了连接的左侧和右侧。考虑额外的实体Order
和Item
,其中User.orders
关系引用了Order
实体,而Order.items
关系通过关联表order_items
引用了Item
实体。两个Select.join()
调用将首先从User
到Order
进行连接,然后从Order
到Item
进行第二次连接。但是,由于Order.items
是多对多关系,它导致两个单独的 JOIN 元素,总共在生成的 SQL 中有三个 JOIN 元素:
>>> 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
关系:
>>> 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 子句,使用两个实体之间的自然外键关系:
>>> 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 子句的示例如下:
>>> 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()
-绑定属性,就像在简单关系连接中使用的那样:
>>> 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 条件为特定的电子邮件地址:
>>> 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
实体:
>>> 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 语句:
>>> 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()
构造中获取属性即可:
>>> 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"
,这样我们就可以在结果行中按名称引用它:
>>> 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
对象的。
>>> 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()
构造引用不同的返回值和对象属性。
例如,给定同时引用User
和Address
的子查询:
>>> 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()
... )
我们可以针对User
和Address
分别创建对同一对象的aliased()
构造:
>>> user_alias = aliased(User, user_address_subq, name="user")
>>> address_alias = aliased(Address, user_address_subq, name="address")
从两个实体中选择的Select
构造将一次渲染子查询,但在结果行上下文中可以同时返回User
和Address
类的对象:
>>> 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()
方法:
>>> 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>])
:
>>> 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()
方法:
>>> 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()
的内容被覆盖:
>>> 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)
方法调用,该语句最终等同于以下内容:
>>> 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 条件来限制子查询匹配的行数:
>>> 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
实体:
>>> 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
对象。
>>> 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
对象:
>>> 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
对象,该对象产生单个元素而不是行:
>>> 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 类中选择时,它们在每个结果行中根据其类名命名。在下面的示例中,对User
和Address
进行 SELECT 的结果行将以User
和Address
的名称引用它们:
>>> 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
参数将它们别名为一个明确的名称:
>>> 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 类和/或列表达式添加到其列子句中。我们也可以使用这种形式生成与上述相同的语句:
>>> 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.name
和Address.email_address
,可以像传递给select()
的Column
或其他 SQL 表达式对象一样使用。创建针对特定列的select()
将返回Row
对象,而不是像User
或Address
对象那样的实体。每个Row
将分别表示每个列:
>>> 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
对象,具有name
和email_address
列,如下所示的运行时演示:
>>> 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 构造,允许将列表达式集合分组在结果行中:
>>> 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()
构造实现的:
>>> 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
参数:
>>> 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 语句,我们希望从中加载:
>>> 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 映射属性:
>>> textual_sql = textual_sql.columns(User.id, User.name, User.fullname)
现在我们有一个经过 ORM 配置的 SQL 构造,可以分别加载“id”、“name”和“fullname”列。要将此 SELECT 语句作为完整User
实体的来源,我们可以使用Select.from_statement()
方法将这些列链接到常规的 ORM 启用的Select
构造:
>>> 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
实体中,方式与下文中从子查询中选择实体中所讨论的类似:
>>> 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')
直接使用TextualSelect
和Select.from_statement()
与使用aliased()
之间的区别在于,在前一种情况下,结果 SQL 中不会生成子查询。在某些情况下,从性能或复杂性的角度来看,这可能是有利的。### 从子查询中选择实体
前一节讨论的aliased()
构造可以与任何Subquery
构造一起使用,该构造来自诸如Select.subquery()
之类的方法,以将 ORM 实体链接到该子查询返回的列;子查询返回的列与实体映射的列之间必须存在列对应关系,这意味着子查询最终需要源自这些实体,就像下面的示例中所示:
>>> 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()
后添加额外的条件:
>>> 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()
构造中使用,包括我们可以基于其导出列添加过滤和排序条件:
>>> 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
:
>>> 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
对象:
>>> 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
对象,该对象产生单个元素而不是行:
>>> 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 时,它们在每个结果行中基于其类名命名。在下面的示例中,对 User
和 Address
进行 SELECT 的结果行将以 User
和 Address
为名称进行引用:
>>> 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
参数将它们别名为具有显式名称的实体:
>>> 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 类和/或列表达式添加到其列子句中。我们也可以使用这种形式来生成与上面相同的语句:
>>> 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.name
和 Address.email_address
,当传递给 select()
时,可以像 Column
或其他 SQL 表达式对象一样使用。创建针对特定列的 select()
将返回 Row
对象,而不是像 User
或 Address
对象那样的实体。每个 Row
将分别表示每个列:
>>> 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
[...] ()
上面的语句返回具有 name
和 email_address
列的 Row
对象,如下运行时演示所示:
>>> 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 的构造,允许将列表达式集合分组在结果行中:
>>> 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()
构造完成的:
>>> 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
参数:
>>> 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 语句:
>>> 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 映射属性:
>>> textual_sql = textual_sql.columns(User.id, User.name, User.fullname)
现在,我们有一个经过 ORM 配置的 SQL 构造,可以分别加载“id”、“name”和“fullname”列。要将此 SELECT 语句用作完整User
实体的来源,我们可以使用Select.from_statement()
方法将这些列链接到常规的 ORM 启用的Select
构造中:
>>> 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
实体,方式与下面讨论的从子查询中选择实体类似:
>>> 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')
直接使用TextualSelect
与Select.from_statement()
相比,使用aliased()
的区别在于,在前一种情况下,生成的 SQL 中不会产生子查询。在某些情况下,这可能有利于性能或复杂性方面。
从子查询中选择实体
在前一节讨论的aliased()
构造中,可以与任何Subuqery
构造一起使用,该构造来自诸如Select.subquery()
之类的方法,以将 ORM 实体链接到该子查询返回的列;子查询返回的列与实体映射的列之间必须存在列对应关系,这意味着子查询最终需要源自这些实体,例如下面的示例:
>>> 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()
后添加额外的条件:
>>> 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()
构造中,包括我们可以根据其导出的列添加过滤和排序条件:
>>> 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()
方法的用法。
简单的关系连接
考虑两个类User
和Address
之间的映射,其中关系User.addresses
表示与每个User
关联的Address
对象的集合。Select.join()
最常见的用法是沿着这种关系创建一个 JOIN,使用User.addresses
属性作为指示器来指示应该如何发生这种情况:
>>> stmt = select(User).join(User.addresses)
在上面的例子中,对Select.join()
和User.addresses
的调用将导致大致等效的 SQL 语句:
>>> 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
实体的行。要同时从User
和Address
中选择列/实体,必须在select()
函数中也命名Address
实体,或者在使用Select.add_columns()
方法后将其添加到Select
构造中。有关这两种形式的示例,请参阅同时选择多个 ORM 实体部分。
链式多重连接
要构建连接链,可以使用多个Select.join()
调用。关联属性同时涵盖连接的左侧和右侧。考虑额外的实体Order
和Item
,其中User.orders
关系指向Order
实体,而Order.items
关系指向Item
实体,通过一个关联表order_items
。两个Select.join()
调用将导致第一个 JOIN 从User
到Order
,第二个从Order
到Item
。然而,由于Order.items
是多对多关系,它会导致两个独立的 JOIN 元素,总共有三个 JOIN 元素在结果 SQL 中:
>>> 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
关系:
>>> 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 子句,使用两个实体之间的自然外键关系:
>>> 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 子句的示例如下:
>>> 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()
绑定属性,就像在简单关系连接中使用的方式一样:
>>> 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 条件仅适用于某些电子邮件地址:
>>> 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
实体:
>>> 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 语句:
>>> 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()
构造中可用:
>>> 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"
,这样我们就可以在结果行中通过名称引用它:
>>> 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()
构造:
>>> 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()
构造来引用不同的返回值和对象属性。
例如,给定一个同时引用User
和Address
的子查询:
>>> 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()
... )
我们可以针对User
和Address
分别创建aliased()
构造,它们各自指向相同的对象:
>>> user_alias = aliased(User, user_address_subq, name="user")
>>> address_alias = aliased(Address, user_address_subq, name="address")
从两个实体中进行选择的Select
构造将会渲染子查询一次,但在结果行上下文中可以同时返回User
和Address
类的对象:
>>> 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()
方法:
>>> 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>])
形式:
>>> 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()
方法:
>>> 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()
的内容被覆盖了:
>>> 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)
方法调用,该语句最终等同于以下内容:
>>> 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()
列表中的另一个条目,它取代了先前的条目。 ### 简单的关系连接
考虑两个类 User
和 Address
之间的映射,其中关系 User.addresses
表示与每个 User
关联的 Address
对象的集合。Select.join()
的最常见用法是沿着这种关系创建 JOIN,使用 User.addresses
属性作为指示器指示应该如何进行连接:
>>> stmt = select(User).join(User.addresses)
在上面的例子中,对 User.addresses
使用 Select.join()
的调用将导致大致等效于以下 SQL:
>>> 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
实体的行。要同时从User
和Address
中 SELECT 列/实体,必须在select()
函数中也命名Address
实体,或者使用Select.add_columns()
方法在之后将其添加到Select
构造中。有关这两种形式的示例,请参见同时选择多个 ORM 实体部分。
链接多个表
要构建一系列 JOIN,可以使用多个Select.join()
调用。关系绑定属性同时暗示 JOIN 的左侧和右侧。考虑额外的实体Order
和Item
,其中User.orders
关系指向Order
实体,而Order.items
关系通过关联表order_items
指向Item
实体。两个Select.join()
调用将导致从User
到Order
的第一个 JOIN,以及从Order
到Item
的第二个 JOIN。然而,由于Order.items
是多对多关系,它会导致两个独立的 JOIN 元素,总共在生成的 SQL 中有三个 JOIN 元素:
>>> 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
关系:
>>> 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 子句,使用两个实体之间的自然外键关系:
>>> 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 子句的示例如下:
>>> 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 加入 中使用的方式一样:
>>> 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 条件到某些电子邮件地址:
>>> 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
实体:
>>> 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 语句:
>>> 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()
构造中使用属性:
>>> 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()
方法的目标:
>>> 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"
,以便我们可以通过名称在结果行中引用它:
>>> 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
对象:
>>> 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()
构造来引用不同的返回值和对象属性。
给定一个同时引用User
和Address
的子查询,例如:
>>> 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()
... )
我们可以创建针对User
和Address
的aliased()
构造,它们各自都引用相同的对象:
>>> user_alias = aliased(User, user_address_subq, name="user")
>>> address_alias = aliased(Address, user_address_subq, name="address")
从两个实体中选择的Select
构造将只渲染子查询一次,但在结果行上下文中可以同时返回User
和Address
类的对象:
>>> 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()
方法:
>>> 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>])
:
>>> 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()
方法:
>>> 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()
的内容被覆盖:
>>> 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)
方法调用,语句最终等效于以下内容:
>>> 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 条件来限制子查询匹配的行:
>>> 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
实体:
>>> 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
对象:
>>> 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
实体:
>>> 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
对象:
>>> 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 条件。
-
多对一等于比较 - 一个特定的对象实例可以与多对一关系进行比较,以选择外键与目标实体的主键值匹配的行:
>>> 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
-
多对一不等于比较 - 也可以使用不等于运算符:
>>> 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
-
对象包含在一对多集合中 - 这基本上是“等于”比较的一对多版本,选择主键等于相关对象中外键值的行:
>>> 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()
函数生成一个比较,返回由给定父对象引用的行,这与使用==
运算符与多对一方面基本相同:>>> 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了