非传统映射
根据多个表映射类
映射器可以针对任意关系单元(调用 可选择的 )除了普通的桌子。例如, 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将只向其映射了主键的表中发出一个插入。
注解
几乎不需要映射到任意的select语句,尤其是如上所述的复杂语句;它必然倾向于生成比直接查询构造生成的查询效率低的复杂查询。这种做法在某种程度上是基于早期的sqlacalchemy历史,其中 mapper()
构造的目的是表示主查询接口;在现代用法中, Query
对象可以用来构造几乎所有的select语句,包括复杂的复合语句,并且应该优于“映射到可选”方法。
一个类的多个映射器
在现代的SQLAlchemy中,一个特定的类只被一个所谓的 初级的 一次一次。这个映射器涉及三个主要功能领域:查询、持久性和映射类的检测。基本原理与地图绘制者有关 mapper()
修改类本身,不仅将其持久化到特定的 Table
,而且 instrumenting 类上的属性,这些属性是根据表元数据专门构造的。不可能有多个映射器以相同的度量与类关联,因为实际上只有一个映射器可以对类进行检测。
“非主”映射器的概念已经存在于许多版本的SQLAlchemy中,但是从版本1.3开始,这个特性就被弃用了。这种非主映射器很有用的一种情况是,在构建与类的关系时,可以使用另一个可选的映射器。这个用例现在适合使用 aliased
构造并在 与别名类的关系 .
至于可以在不同场景下完全持久化到不同表的类的用例,SQLAlChemy的非常早的版本为此提供了一个改编自Hibernate的特性,称为“实体名称”特性。然而,一旦映射的类本身成为SQL表达式构造的来源,这个用例在SQLAlChemy中就变得不可行了;也就是说,类的属性本身直接链接到映射的表列。该特性被移除,取而代之的是一种简单的面向配方的方法来完成这项任务,而不会有任何含糊的插装-创建新的子类,每个子类都被单独映射。此模式现已作为食谱提供,网址为 Entity Name 。