映射器可以针对任意关系单元(调用 可选择的 )除了普通的桌子。例如, join()
函数创建一个由多个表组成的可选单元,并使用自己的复合主键完成,该主键可以与 Table
::
from sqlalchemy import Table, Column, Integer, \
String, MetaData, join, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import column_property
metadata_obj = MetaData()
# define two Table objects
user_table = Table('user', metadata_obj,
Column('id', Integer, primary_key=True),
Column('name', String),
)
address_table = Table('address', metadata_obj,
Column('id', Integer, primary_key=True),
Column('user_id', Integer, ForeignKey('user.id')),
Column('email_address', String)
)
# define a join between them. This
# takes place across the user.id and address.user_id
# columns.
user_address_join = join(user_table, address_table)
Base = declarative_base()
# map to it
class AddressUser(Base):
__table__ = user_address_join
id = column_property(user_table.c.id, address_table.c.user_id)
address_id = address_table.c.id
在上面的示例中,join表示 user
以及 address
表。这个 user.id
和 address.user_id
列与外键相等,因此在映射中它们被定义为一个属性, AddressUser.id
使用 column_property()
指示专用列映射。根据配置的这一部分,映射将从 user.id
进入 address.user_id
发生刷新时的列。
另外, address.id
列显式映射到名为 address_id
. 这是为了 消除歧义 的映射 address.id
来自相同名称的列 AddressUser.id
属性,此处已分配用于引用 user
表与 address.user_id
外键。
上述映射的自然主键是 (user.id, address.id)
,因为这些是 user
和 address
桌子合并在一起。的标识 AddressUser
对象将以这两个值表示,并由 AddressUser
对象AS (AddressUser.id, AddressUser.address_id)
.
当提到 AddressUser.id
列,大多数SQL表达式将只使用映射的列列表中的第一列,因为这两列是同义的。但是,对于诸如groupby表达式这样的特殊用例,在这种情况下,必须同时引用两个列,同时使用适当的上下文,也就是说,为了适应别名和类似的情况,访问器 Comparator.expressions
可用于:
q = session.query(AddressUser).group_by(*AddressUser.id.expressions)
1.3.17 新版功能: 增加了 Comparator.expressions
访问器。
注解
如上所示,针对多个表的映射支持持久性,即在目标表中插入、更新和删除行。但是,它不支持对一个记录同时更新一个表和对其他表执行INSERT或DELETE的操作。也就是说,如果一个记录PtoQ映射到表“p”和“q”,其中它有一个基于“p”和“q”的左外部联接的行,如果继续进行更新,即更改现有记录中“q”表中的数据,“q”中的行必须存在;如果主键标识已经存在,则不会发出插入。如果行不存在,对于大多数支持报告受更新影响的行数的DBAPI驱动程序,ORM将无法检测更新的行并引发错误;否则,数据将被忽略。
允许动态“插入”相关行的方法可以使用。MapperEvents.beforeu更新事件和外观:
from sqlalchemy import event
@event.listens_for(PtoQ, 'before_update')
def receive_before_update(mapper, connection, target):
if target.some_required_attr_on_q is None:
connection.execute(q_table.insert(), {"id": target.id})
在上面,一行插入到 q_table
通过创建插入构造 Table.insert()
,然后使用给定的 Connection
它是用于为刷新进程发出其他SQL的同一个。用户提供的逻辑必须检测到从“p”到“q”的左外部联接没有“q”侧的条目。
类似于针对连接的映射,普通 select()
对象也可以与映射器一起使用。下面的示例片段说明如何映射一个名为 Customer
到A select()
其中包括到子查询的联接:
from sqlalchemy import select, func
subq = select(
func.count(orders.c.id).label('order_count'),
func.max(orders.c.price).label('highest_order'),
orders.c.customer_id
).group_by(orders.c.customer_id).subquery()
customer_select = select(customers, subq).join_from(
customers, subq, customers.c.id == subq.c.customer_id
).subquery()
class Customer(Base):
__table__ = customer_select
上面的整行由 customer_select
将是所有列的 customers
表,除了 subq
子查询,即 order_count
, highest_order
和 customer_id
. 映射 Customer
然后创建一个包含这些属性的类。
当ORM保持新的 Customer
,只有 customers
表将实际接收一个插入。这是因为 orders
映射中不表示表;ORM将只向其映射了主键的表中发出一个插入。
在现代的SQLAlchemy中,一个特定的类只被一个所谓的 初级的 一次一次。这个映射器涉及三个主要功能领域:查询、持久性和映射类的检测。基本原理与地图绘制者有关 mapper()
修改类本身,不仅将其持久化到特定的 Table
,而且 instrumenting 类上的属性,这些属性是根据表元数据专门构造的。不可能有多个映射器以相同的度量与类关联,因为实际上只有一个映射器可以对类进行检测。
“非主”映射器的概念已经存在于许多版本的SQLAlchemy中,但是从版本1.3开始,这个特性就被弃用了。这种非主映射器很有用的一种情况是,在构建与类的关系时,可以使用另一个可选的映射器。这个用例现在适合使用 aliased
构造并在 与别名类的关系 .
至于可以在不同场景下完全持久化到不同表的类的用例,SQLAlChemy的非常早的版本为此提供了一个改编自Hibernate的特性,称为“实体名称”特性。然而,一旦映射的类本身成为SQL表达式构造的来源,这个用例在SQLAlChemy中就变得不可行了;也就是说,类的属性本身直接链接到映射的表列。该特性被移除,取而代之的是一种简单的面向配方的方法来完成这项任务,而不会有任何含糊的插装-创建新的子类,每个子类都被单独映射。此模式现已作为食谱提供,网址为 Entity Name 。
flambé! the dragon and The Alchemist image designs created and generously donated by Rotem Yaari.
Created using Sphinx 4.2.0.