非传统映射

优质
小牛编辑
138浏览
2023-12-01

根据多个表映射类

映射器可以针对任意关系单元(调用 可选择的 )除了普通的桌子。例如, 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.idaddress.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) ,因为这些是 useraddress 桌子合并在一起。的标识 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_counthighest_ordercustomer_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