sqlalchemy 关系映射

class Customer(Base):
    __tablename__ = 'customer'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    billing_address_id = Column(Integer, ForeignKey("address.id"))
    shipping_address = relationship(Address, backref='customers')

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    street = Column(String)
    city = Column(String)
    state = Column(String)
    zip = Column(String)

一般情况下,relationship可以定义在有foreingkey的一端,也就是多端,这样的话可以通过以下两种搜索来确定连接的外键,1 自身是否有外键连接到对方表的主键, 2 对方表是否有外键连接到自己的主键。

由此可知,relationship也可以定义到对方也就是多的那一端。

function sqlalchemy.orm.relationship(argument, secondary=None, primaryjoin=None, secondaryjoin=None, foreign_keys=None, uselist=None, order_by=False, backref=None, back_populates=None, overlaps=None, post_update=False, cascade=False, viewonly=False, lazy='select', collection_class=None, passive_deletes=False, passive_updates=True, remote_side=None, enable_typechecks=True, join_depth=None, comparator_factory=None, single_parent=False, innerjoin=False, distinct_target_key=None, doc=None, active_history=False, cascade_backrefs=True, load_on_pending=False, bake_queries=True, _local_remote_pairs=None, query_class=None, info=None, omit_join=None, sync_backref=None, _legacy_inactive_history_style=False)¶

backref:可以在另一端的model上添加一个属性,这里就是在Address上添加了名为customer的集合。

uselist:backref添加的属性默认是list,不过如果是一对一的映射,可以通过relactionship的=false来操作。

lazy:决定关系映射的查询方法。select默认值,每次使用关联属性时惰性通过select外键查询来加载关系表。joined 查询时通过join一次性加载所有关联项,默认使用left join,另外一个配置项innerjoin=True时使用 inner join。subquery,使用子查询一次性加载。selectin,使用附加的select id in (xx,xx,xx)类似的语句进行加载,会多一条sql语句。noload 只写属性,不加载。raise 使用到关联属性直接报错。 lazy的加载方式可以通过查询时的关系加载技术 options来改变,见后文。

如果同一个表进行了多次连接,那么显然relactionship省略的外键就无法启用了,此时需要显式指定外键:

class Customer(Base):
    __tablename__ = 'customer'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    billing_address_id = Column(Integer, ForeignKey("address.id"))
    shipping_address_id = Column(Integer, ForeignKey("address.id"))

    billing_address = relationship("Address", foreign_keys=[billing_address_id])
    shipping_address = relationship("Address", foreign_keys=[shipping_address_id])

另外,如果join是多条件的,那么可以通过primarykey来指定:

from sqlalchemy import and_

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    # 可以使用and_函数或者直接使用字符串表达式
    boston_addresses = relationship(Address,
                    primaryjoin=and_(User.id==Address.user_id,
                        Address.city=='Boston'))
    # 这里全都使用字符串表达式
    # boston_addresses = relationship("Address",
                    primaryjoin="and_(User.id==Address.user_id, "
                        "Address.city=='Boston')")

自连接时,需要还是通过primaryjoin来确定外键关联,但是需要确定那一个是自己的列,那一个是连接目标的列。

from sqlalchemy import cast, String, Column, Integer
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.postgresql import INET

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class HostEntry(Base):
    __tablename__ = 'host_entry'

    id = Column(Integer, primary_key=True)
    ip_address = Column(INET)
    content = Column(String(50))

    # relationship() using explicit foreign_keys, remote_side
    parent_host1 = relationship("HostEntry",
                        primaryjoin=ip_address == cast(content, INET),
                        foreign_keys=content,
                        remote_side=ip_address
                    )
    # relationship() using explicit foreign() and remote() annotations
    # in lieu of separate arguments
    parent_host2 = relationship("HostEntry",
                        primaryjoin=remote(ip_address) == \
                                cast(foreign(content), INET),
                    )

查询时通过options来指定关系加载技术选项:

# lazyload 使用select惰性加载,此时不发出sql,使用到属性时才发出
session.query(Parent).options(lazyload(Parent.children)).all()

# joinedload join连接一次性加载
session.query(Parent).options(joinedload(Parent.children)).all()

# 如果要深度加载其他关联属性,可以链式操作
session.query(Parent).options(
    joinedload(Parent.children).
    subqueryload(Child.subelements)).all()

# 也可以在原来的基础上,临时添加一个on的条件而不是使用 primarykey
session.query(A).options(lazyload(A.bs.and_(B.id > 5)))

joinedload使用起来很像join,但其实有所区别,它只为查询结果填充关联对象,但是不用在where或orderby中使用关联对象的列来查询。 join可以使用查询,但是并不填充对象到自身。那么有没有一种方法结合两种呢,这就是contains_eager:

q = session.query(User).\
        join(User.addresses).\
        filter(Address.email_address.like('%@aol.com')).\
        options(contains_eager(User.addresses)).\
        populate_existing()

它可以把join的关联表信息封装到对象中,非常方便。当然,User.address属性必须先存在,不能省略(可以是backref属性)。

以下是真实运用:

        units = Unit.query.outerjoin(
        TtilTarget,
        and_(Unit.plant_id == TtilTarget.plant_id,
             Unit.unit_id == TtilTarget.unit_id,
             TtilTarget.ttil_id == ttil_id)) \
        .join(Plant).join(Customer) \
        .options(contains_eager(Unit.plant).contains_eager(Plant.customer),
                 contains_eager(Unit.ttil_targets)) \
        .order_by(Customer.disp_order, Customer.customer_name,
                  Plant.disp_order, Plant.plant_name,
                  Unit.disp_order, Unit.unit_id) \
        .all()

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值