本文主要关注在 SQLAlchemy 中实现基本的关系模型,一对多,多对一,多对多等等。
一对多关系
通常一对多的关系,使用外键,用最常见的用户,地址来举例,每个地址只有一个住户,但是一个住户可以拥有多个地产。所以在 Address 中有一个外键指向 user.id 的主键。而 User 表则是使用一个 relationship
来表示多个地址。
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
addresses = relationship("Address", backref="user", lazy='dynamic')
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey('user.id'))
上面这段代码会使得 User 拥有一个 .addresses
的属性,包含一系列的地址,同时,注意这个使用场景下的 backref
,这个关键字会使得 Address 实例拥有一个 .user
的指向 User 的对象,可以通过 address.user
来引用。
而事实上,backref
关键字只是为了省略写两个 relationship()
而引入的缩写方式。其实下面的写法和上面是一致的。
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
addresses = relationship("Address", back_populates="user")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship("User", back_populates="addresses")
只是这种写法需要使用两个 relationship,并且使用了 back_populates
。
以上两种方式建立了两个对象之间的关联,在 SQLAlchemy 中使用时,SQLAlchemy 会自动填充字段。一旦用户增加了 address
u1 = User()
a1 = Address()
u1.addresses.append(a1)
那么此时,a1 中的 a1.user
同样也被填充了 u1 实例。 backref/back_populates 方法使得所有的 SQL 操作对使用者都隐藏了,调用者不需要关注 SQL 的具体实现,只需要关注 Python 对象的逻辑即可。
记住,使用 backref 单向 ref,和使用 back_populates 双向实现,是完全一致的。
relationship 方法使用
relationship,第一个参数是类名,backref 参数是添加一个属性,第一个用户地址的例子中,就是给地址增加一个 user 属性。
relationship 中 lazy 是加载方式,默认是 select,在查询时自动查询所有数据。lazy 属性根据需求决定,如果每次查询 User 都需要获取 Address,那么 select 可以使用。如果两个表互相有外键指向对方,则 relationship 中 lazy 不能为默认值,需要 dynamic 动态加载。
数据库中,一对多关系是最常用的关系类型,它可以把一个记录和一组相关的记录联系在一起,实现这种关系,只要在多一侧加入一个外键,指向一这侧关联的记录。
多对一关系
多对一,其实和一对多本质上是一样的,参考上面,一对多,一个用户可以有多个地址,而多对一其实就是多个地址对应于一个用户。大部分的其他关系都可以从一对多关系中衍生。
一对一关系
在一对一的双向关系中,使用 uselist 来表示,比如“计划生育”下,父母只能有一个小孩,孩子也只能有一对父母,所以使用 uselist=False
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child = relationship("Child", uselist=False, back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="child")
多对多关系
一对多,多对一关系和一对一关系至少有一侧是单个实体,所以记录之间的联系通过外键实现,让外键指向这个单一实体。但是实现多对多显然不行。以典型的学生选课为例子,学生表和课程表,显然不能在学生表中加入一个指向课程的外键,以为一个学生可以选择多个课程,一个外键不够用,同样,也不能在课程表中加入一个指向学生的外键,因为一个课程会有很多学生选择,两侧都需要一组外键。解决这种问题的方法就是添加第三张表,这个表称为关联表。这样,多对多关系就可以分解为原表和关联表之前的两个一对多关系。
多对多关系会在两个类之间增加一个关联的表,使用 relationship() 方法中的 secondary
参数。
实现多对多主要可以分为三个步骤:
- 定义关联表,保存两个表主键
- 定义多对多表模型
- 给每个模型添加一个访问对方属性
比如:
registrations = db.Table(
'registrations',
db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
db.Column('class_id', db.Integer, db.ForeignKey('classes.id'))
)
class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
classes = db.relationship('Class',
secondary=registrations,
backref=db.backref('students', lazy='dynamic'),
lazy='dynamic')
class Class(db.Model):
__tablename__ = 'classes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
或者使用 Association Object
class Association(Base):
__tablename__ = 'association'
left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
child = relationship("Child", back_populates="parents")
parent = relationship("Parent", back_populates="children")
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Association", back_populates="child")