基本关系模式
快速浏览基本关系模式。
以下各部分使用的导入如下:
from sqlalchemy import Table, Column, Integer, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base Base = declarative_base()
一对多
一对多关系在引用父表的子表上放置一个外键。 relationship()
然后在父级上指定,作为引用由子级表示的项集合:
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship("Child") class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id'))
要在一对多中建立双向关系,“反向”端是多对一,请指定一个附加的 relationship()
然后用 relationship.back_populates
参数::
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship("Child", back_populates="parent") class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id')) parent = relationship("Parent", back_populates="children")
Child
将得到一个 parent
具有多对一语义的属性。
或者, relationship.backref
选项可用于单个 relationship()
而不是使用 relationship.back_populates
::
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship("Child", backref="parent")
为一对多配置删除行为
通常情况下 Child
当对象拥有 Parent
已删除。若要配置此行为,则 delete
级联选项在 删除 被使用。另一个选择是 Child
对象与其父对象解除关联时,可以将其自身删除。此行为在中描述 删除孤儿 .
参见
多对一
多对一将外键放置在引用子表的父表中。 relationship()
在父级上声明,将在其中创建新的标量保持属性:
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) child_id = Column(Integer, ForeignKey('child.id')) child = relationship("Child") class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True)
双向行为是通过增加一秒钟来实现的。 relationship()
并应用 relationship.back_populates
双向参数:
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) child_id = Column(Integer, ForeignKey('child.id')) child = relationship("Child", back_populates="parents") class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parents = relationship("Parent", back_populates="child")
或者, relationship.backref
参数可以应用于单个 relationship()
,如 Parent.child
::
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) child_id = Column(Integer, ForeignKey('child.id')) child = relationship("Child", backref="parents")
一对一
一对一本质上是具有两端标量属性的双向关系。在ORM中,“一对一”被认为是一种约定,ORM期望任何父行都只有一个相关行。
“一对一”约定是通过应用 False
发送到 relationship.uselist
属性的参数 relationship()
构造,或者在某些情况下构造 backref()
构造,并将其应用于关系的“一对多”或“集合”端。
In the example below we present a bidirectional relationship that includes both one-to-many (Parent.children
) and a many-to-one (Child.parent
) relationships:
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) # one-to-many collection children = relationship("Child", back_populates="parent") class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id')) # many-to-one scalar parent = relationship("Parent", back_populates="children")
上图, Parent.children
是引用集合的“一对多”端,并且 Child.parent
是指单个对象的“多对一”方面。若要将其转换为“一对一”,“一对多”或“集合”端将使用 uselist=False
标志,重命名 Parent.children
至 Parent.child
为清楚起见:
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) # previously one-to-many Parent.children is now # one-to-one Parent.child child = relationship("Child", back_populates="parent", uselist=False) class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id')) # many-to-one side remains, see tip below parent = relationship("Parent", back_populates="child")
上面,当我们加载一个 Parent
对象,则 Parent.child
属性将引用单个 Child
对象而不是集合。如果我们将 Parent.child
使用新的 Child
对象时,ORM的工作进程单元将替换以前的 Child
与新的行一起,设置上一个 child.parent_id
列默认情况下设置为NULL,除非有特定的 cascade 行为设置。
小技巧
如前所述,ORM将“一对一”模式视为约定,其中它假设在加载 Parent.child
属性上的 Parent
对象,它将只返回一行。如果返回多行,ORM将发出警告。
然而, Child.parent
上述关系的一方仍然是“多对一”关系,并且没有变化,ORM本身中没有内部系统可以防止多对一关系 Child
要针对同一对象创建的 Parent
在持之以恒的过程中。取而代之的是,诸如 unique constraints 可以在实际数据库架构中使用,以强制执行此安排,其中对 Child.parent_id
列将确保只有一个 Child
行可以引用特定的 Parent
一次排一排。
在这种情况下, relationship.backref
参数用于定义“一对多”一方,则可以将其转换为“一对一”约定。 backref()
函数,该函数允许 relationship.backref
参数来接收自定义参数,在本例中为 uselist
参数::
from sqlalchemy.orm import backref class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id')) parent = relationship("Parent", backref=backref("child", uselist=False))
多对多
多对多在两个类之间添加一个关联表。关联表由 relationship.secondary
参数 relationship()
. 通常, Table
使用 MetaData
与声明性基类关联的对象,以便 ForeignKey
指令可以定位要链接的远程表:
association_table = Table('association', Base.metadata, Column('left_id', ForeignKey('left.id')), Column('right_id', ForeignKey('right.id')) ) class Parent(Base): __tablename__ = 'left' id = Column(Integer, primary_key=True) children = relationship("Child", secondary=association_table) class Child(Base): __tablename__ = 'right' id = Column(Integer, primary_key=True)
小技巧
上面的“关联表”建立了引用关系两端的两个实体表的外键约束。每个对象的数据类型 association.left_id
和 association.right_id
通常是从被引用的表中推断出来的,可以省略。它也是 推荐 ,尽管不是SQLAlChemy所要求的任何方式,但是引用这两个实体表的列是在 唯一约束 或者更常见的是 主键约束 ;这可确保无论应用程序端出现什么问题,表中都不会保留重复的行::
association_table = Table('association', Base.metadata, Column('left_id', ForeignKey('left.id'), primary_key=True), Column('right_id', ForeignKey('right.id'), primary_key=True) )
对于双向关系,关系的两边都包含一个集合。指定使用 relationship.back_populates
为每个 relationship()
指定公共关联表::
association_table = Table('association', Base.metadata, Column('left_id', ForeignKey('left.id'), primary_key=True), Column('right_id', ForeignKey('right.id'), primary_key=True) ) class Parent(Base): __tablename__ = 'left' id = Column(Integer, primary_key=True) children = relationship( "Child", secondary=association_table, back_populates="parents") class Child(Base): __tablename__ = 'right' id = Column(Integer, primary_key=True) parents = relationship( "Parent", secondary=association_table, back_populates="children")
当使用 relationship.backref
参数而不是 relationship.back_populates
,backref将自动使用相同的 relationship.secondary
反向关系的参数:
association_table = Table('association', Base.metadata, Column('left_id', ForeignKey('left.id'), primary_key=True), Column('right_id', ForeignKey('right.id'), primary_key=True) ) class Parent(Base): __tablename__ = 'left' id = Column(Integer, primary_key=True) children = relationship("Child", secondary=association_table, backref="parents") class Child(Base): __tablename__ = 'right' id = Column(Integer, primary_key=True)
这个 relationship.secondary
的参数 relationship()
还接受一个返回最终参数的可调用文件,该参数仅在首次使用映射器时计算。使用这个,我们可以定义 association_table
稍后,只要在所有模块初始化完成后可调用文件可用,则:
class Parent(Base): __tablename__ = 'left' id = Column(Integer, primary_key=True) children = relationship("Child", secondary=lambda: association_table, backref="parents")
使用声明性扩展时,也接受传统的“表的字符串名称”,与存储在 Base.metadata.tables
::
class Parent(Base): __tablename__ = 'left' id = Column(Integer, primary_key=True) children = relationship("Child", secondary="association", backref="parents")
警告
当作为Python可求值字符串传递时 relationship.secondary
参数使用Python的 eval()
功能。 不要将不受信任的输入传递到此字符串 . 见 关系论据的评估 有关声明性评估的详细信息 relationship()
争论。
从多对多表中删除行
一种独特的行为 relationship.secondary
参数 relationship()
那是 Table
当对象从集合中添加或删除时,此处指定的对象将自动服从INSERT和DELETE语句。有 无需手动从此表中删除 . 从集合中删除记录的操作将具有在刷新时删除的行的效果:
# row will be deleted from the "secondary" table # automatically myparent.children.remove(somechild)
经常出现的一个问题是,当直接将子对象交给“辅助”表时,如何删除“辅助”表中的行 Session.delete()
::
session.delete(somechild)
这里有几种可能性:
如果有一个
relationship()
从…Parent
至Child
,但是有 not 链接特定对象的反向关系Child
给每个人Parent
,SQLAlChemy将不会意识到在删除此特定的Child
对象时,它需要维护将其链接到Parent
。不会删除“辅助”表。如果有一个关系链接到一个特定的
Child
对每个Parent
,假设它被称为Child.parents
,默认情况下,SQLAlchemy将加载到Child.parents
集合以定位所有Parent
对象,并从建立此链接的“辅助”表中删除每一行。注意,这种关系不需要是双向的;sqlAlchemy严格地查看relationship()
与Child
正在删除的对象。这里的一个更高性能的选项是使用数据库使用的外键来删除CASCADE指令。假设数据库支持此功能,则可以使数据库本身自动删除“辅助”表中的行,因为“子”中的引用行将被删除。可以指示SQLAlchemy放弃在
Child.parents
在本例中,使用relationship.passive_deletes
指令relationship()
见 在具有ORM关系的DELETE cascade中使用外键 有关此的详细信息。
再次注意,这些行为是 only 有关的 relationship.secondary
与一起使用的选项 relationship()
. 如果处理显式映射的关联表, not 存在于 relationship.secondary
相关选项 relationship()
,可以使用级联规则来自动删除实体,以响应被删除的相关实体-请参见 级联 有关此功能的信息。
参见
关联对象
关联对象模式是多对多的变体:当关联表包含的列超出了左表和右表的外键之外的其他列时,就会使用它。而不是使用 relationship.secondary
参数,将新类直接映射到关联表。关系的左侧通过一对多引用关联对象,关联类通过多对一引用右侧。下面我们演示了一个映射到 Association
类,其中包含一个名为 extra_data
,它是一个字符串值,与 Parent
和 Child
::
class Association(Base): __tablename__ = 'association' left_id = Column(ForeignKey('left.id'), primary_key=True) right_id = Column(ForeignKey('right.id'), primary_key=True) extra_data = Column(String(50)) child = relationship("Child") class Parent(Base): __tablename__ = 'left' id = Column(Integer, primary_key=True) children = relationship("Association") class Child(Base): __tablename__ = 'right' id = Column(Integer, primary_key=True)
和往常一样,双向版本使用 relationship.back_populates
或 relationship.backref
::
class Association(Base): __tablename__ = 'association' left_id = Column(ForeignKey('left.id'), primary_key=True) right_id = Column(ForeignKey('right.id'), primary_key=True) extra_data = Column(String(50)) child = relationship("Child", back_populates="parents") parent = relationship("Parent", back_populates="children") class Parent(Base): __tablename__ = 'left' id = Column(Integer, primary_key=True) children = relationship("Association", back_populates="parent") class Child(Base): __tablename__ = 'right' id = Column(Integer, primary_key=True) parents = relationship("Association", back_populates="child")
使用其直接形式的关联模式要求子对象在附加到父对象之前与关联实例关联;同样,父对象到子对象的访问通过关联对象进行:
# create parent, append a child via association p = Parent() a = Association(extra_data="some data") a.child = Child() p.children.append(a) # iterate through child objects via association, including association # attributes for assoc in p.children: print(assoc.extra_data) print(assoc.child)
增强关联对象模式,以便直接访问 Association
对象是可选的,SQLAlchemy提供 关联代理 延伸。此扩展允许配置属性,这些属性将通过一次访问访问两个“跃点”,一个“跃点”访问关联的对象,另一个“跃点”访问目标属性。
警告
关联对象模式 不与将关联表映射为“辅助”的单独关系协调更改 .
下面,对 Parent.children
不会与对 Parent.child_associations
或 Child.parent_associations
在python中;虽然所有这些关系都将继续正常运行,但是在 Session
过期,通常在 Session.commit()
::
class Association(Base): __tablename__ = 'association' left_id = Column(ForeignKey('left.id'), primary_key=True) right_id = Column(ForeignKey('right.id'), primary_key=True) extra_data = Column(String(50)) child = relationship("Child", backref="parent_associations") parent = relationship("Parent", backref="child_associations") class Parent(Base): __tablename__ = 'left' id = Column(Integer, primary_key=True) children = relationship("Child", secondary="association") class Child(Base): __tablename__ = 'right' id = Column(Integer, primary_key=True)
此外,正如对一个关系的更改不会自动反映在另一个关系中一样,向两个关系中写入相同的数据也会导致插入或删除语句冲突,例如下面我们在 Parent
和 Child
对象两次:
p1 = Parent() c1 = Child() p1.children.append(c1) # redundant, will cause a duplicate INSERT on Association p1.child_associations.append(Association(child=c1))
如果你知道自己在做什么,可以使用上面这样的映射,尽管应用 viewonly=True
参数设置为“次要”关系,以避免记录冗余更改的问题。然而,为了得到一个简单的两个对象的万无一失的模式 Parent->Child
关系在仍然使用关联对象模式时,使用关联代理扩展,如中所述 关联代理 .
关系论据的后期评估
前面几节中的许多示例说明了 relationship()
构造使用字符串名称而不是类本身引用目标类:
class Parent(Base): # ... children = relationship("Child", back_populates="parent") class Child(Base): # ... parent = relationship("Parent", back_populates="children")
这些字符串名在mapper resolution stage中被解析为类,mapper resolution stage是一个内部进程,通常在定义完所有映射之后发生,通常由首次使用映射本身触发。这个 registry
对象是存储这些名称并将其解析为它们所指向的映射类的容器。
除了 relationship()
,也可以将依赖于出现在尚未定义的类上的列的其他参数指定为Python函数,或者更常见的情况是指定为字符串。对于这些参数中的大多数(主参数除外),字符串输入为 evaluated as Python expressions using Python's built-in eval() function ,因为它们旨在接收完整的SQL表达式。
警告
就像 Python eval()
函数用于解释传递给 relationship()
映射器配置构造,这些参数应该 not 重新调整用途,使其能够接收不可信的用户输入; eval()
是 不安全 针对不可信的用户输入。
此计算中可用的完整命名空间包括为此声明性基映射的所有类,以及 sqlalchemy
包,包括表达式函数 desc()
和 sqlalchemy.sql.functions.func
::
class Parent(Base): # ... children = relationship( "Child", order_by="desc(Child.email_address)", primaryjoin="Parent.id == Child.parent_id" )
对于多个模块包含相同名称的类的情况,也可以将字符串类名称指定为这些字符串表达式中的模块限定路径::
class Parent(Base): # ... children = relationship( "myapp.mymodel.Child", order_by="desc(myapp.mymodel.Child.email_address)", primaryjoin="myapp.mymodel.Parent.id == myapp.mymodel.Child.parent_id" )
限定路径可以是消除名称之间不明确的任何部分路径。例如,消除 myapp.model1.Child
和 myapp.model2.Child
,我们可以指定 model1.Child
或 model2.Child
::
class Parent(Base): # ... children = relationship( "model1.Child", order_by="desc(mymodel1.Child.email_address)", primaryjoin="Parent.id == model1.Child.parent_id" )
这个 relationship()
construct还接受Python函数或lambdas作为这些参数的输入。这样做的好处是提供了更多的编译时安全性和对ide和 PEP 484 情节。
Python函数方法可能如下所示:
from sqlalchemy import desc def _resolve_child_model(): from myapplication import Child return Child class Parent(Base): # ... children = relationship( _resolve_child_model(), order_by=lambda: desc(_resolve_child_model().email_address), primaryjoin=lambda: Parent.id == _resolve_child_model().parent_id )
接受将传递给的Python函数/lambda或字符串的完整参数列表 eval()
是:
在 1.3.16 版更改: 在SQLAlChemy 1.3.16之前,主要 relationship.argument
至 relationship()
也是通过 eval()
从1.3.16开始,字符串名称直接从类解析器解析,不支持自定义Python表达式。
警告
如前所述,上述参数 relationship()
是 使用eval()计算为Python代码表达式。不要将不受信任的输入传递给这些参数。
还应注意的是,以类似于 添加新列 任何 MapperProperty
构造可以随时添加到声明性基映射中。如果我们想实现这个目标 relationship()
后 Address
有课,我们以后也可以申请:
# first, module A, where Child has not been created yet, # we create a Parent class which knows nothing about Child class Parent(Base): # ... #... later, in Module B, which is imported after module A: class Child(Base): # ... from module_a import Parent # assign the User.addresses relationship as a class variable. The # declarative base class will intercept this and map the relationship. Parent.children = relationship( Child, primaryjoin=Child.parent_id==Parent.id )
注解
只有在使用“声明性基”类时,将映射属性分配给声明性映射类才会正常工作,这也提供了元类驱动 __setattr__()
方法来截获这些操作。会的 not 如果 registry.mapped()
它也不适用于由映射的强制映射类 registry.map_imperatively()
.
多对多关系的延迟评估
多对多关系包括对附加的(通常是非映射的)的引用 Table
对象的 MetaData
由 registry
. 后期求值系统还支持将此属性指定为字符串参数,该参数将从中解析 MetaData
收藏。下面我们指定一个关联表 keyword_author
,共享 MetaData
与我们的声明基及其 registry
. 我们可以参考这个 Table
在 relationship.secondary
参数::
keyword_author = Table( 'keyword_author', Base.metadata, Column('author_id', Integer, ForeignKey('authors.id')), Column('keyword_id', Integer, ForeignKey('keywords.id')) ) class Author(Base): __tablename__ = 'authors' id = Column(Integer, primary_key=True) keywords = relationship("Keyword", secondary="keyword_author")
有关多对多关系的更多详细信息,请参阅部分 多对多 .