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()