在关系型数据库中,定义表与表之间的联系需要两步:第一步是创建外键,外键是用来在A表存放B表的主键值以便和B表建立联系的字段,并且外键只能存放单一数据(标量),总是在“多”这一侧定义;第二步是创建关系属性,使用关系函数进行在更复杂的多对多关系中,还需要建立第三方关键表来管理关系。下面将介绍一对多、多对一、一对一、多对多关系的原理及实际操作。
一对多关系可用作者和文章的关系来比喻,一个作者发表多篇文章,如下图所示:
定义关系第一步是创建外键db.ForeignKey()
定义关系第二步是使用关系函数db.relationship()定义关系,在db.relationship()中使用backref()函数接收第一个参数作为在关系另一侧添加的关系属性名,其他关键字参数会作为关系另一侧关系函数的参数传入
代码如下:
class Author(db.model):
__tablename__ = 'author'
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(70),unique=True)
phone = db.Column(db.String(20))
# 反向引用backref,Article实例调用属性字段articles连接Author,关系属性的名称没有限制
author = db.relationship('Article',backref = db.backref(' articles '))
class Article(db.model):
__tablename__ = 'article'
id = db.Column(db.Integer,primary_key=True)
title= db.Column(db.String(50),index=True)
body = db.Column(db.Text)
# 将Article表的author_id值用来存放Author表的主键id值
author_id = db.Coiumn((db.Integer, db.ForeignKey('author.id'))
Article表实例对象关联Author表,使用外键字段Article().author_id,被调用时会返回对应的作者记录,调用一次返回单一记录,author_id也叫标量关系属性。
Author表实例对象关联Article表,使用backref反向定义关系属性articles,Author().articles
拓展:
建立双向关系:在两个表都添加关系属性获取对方记录的关系
仍然是在多的一侧设外键,两侧都使用db.relationship()函数定义关系,在关系函数中,使用back_populates参数来连接对方,back_populates参数的值需要设为另一侧的关系属性名
代码举例:
class Writer(db.model):
books = db.relationship('Book', back_populates='writer')
class Book(db.Model):
writer_id = db.Column(db.Integer,db.ForeignKey('writer.id'))
writer = db.relationship('Writer',back_populates='booksr')
多对一关系可用市民和城市来比喻,多个市民在一座城市居住,如下图所示:
在Citizen类中创建一个标量关系属性,调用它可以获取单个City对象,外键仍然创建在”多“的一侧。
class Citizen(db.Model):
city_id = db.Column(db.Integer, db.ForeignKey('city.id'))
ciry = db.relationship('City')
class City(db.Model):
id = db.Column(db.Integer,primary_key=True)
当 Citizen.city被调用时,SQLAlchemy会根据外键字段city_id存放的值查找对应City对象并返回,即居民记录对应的城市记录。
一对一关系可用国家和首都来比喻,一个国家只有一个首都,一个城市也只能作为一个国家的首都。,用Country表示国家,Capital表示首都。在Country类中创建一个标量关系属性capital,调用它会获取单个Capital对象,同样地,在Capital类中创建一个标量关系属性country,调用它会获取单个Country对象,需要注意的是确保两侧的关系属性都是标量属性,返回一个值,在‘一’这一侧要将userlist参数设为False。
代码如下:
class Country(db.Model):
capital = db.relationship('Capital',userlist=False)
class Capital(db.Model):
country_id = db.Column(db.Integer, db.ForeignKey('country.id'))
country = db.relationship('Country')
Capital().country_id 被调用时会返回一个Country()对象
Country().capital 被调用时会返回一个Capital().对象
多对多关系用老师和学生来比喻,每个老师有多个学生,每个学生有多个老师。
在一对多关系中,我们需要在“多”的一侧添加外键指向“一”这一侧,外键只能存储一个记录,但是在多对多关系中,需要在两侧都设置外键,这时可以创建一个关联表association_table,在该表添加两侧的外键,关联表不存储数据,仅用来存储关系两侧模型的外键对应关系,只需要一侧定义属性关系时db.relationship()函数指向关联表就行。
代码如下:
association_table = db.Table(
'association',
db.Column('student_id',db.Integr, db.ForeignKey('student.id')),
db.Column('teacher_id',db.Integr, db.ForeignKey('teacher.id'))
)
class Student(db.Model):
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(70),unique=True)
grade = db.Column(db.String(20))
teachers = db.relationship('Teacher', secondary =association_table, back_populates = 'students' )
class Teacher(db.Model):
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(70),unique=True)
office = db.Column(db.String(70))
关联表使用db.Table类定义,传入的第一个参数是关联表的名称,在关联表外键字段‘teacher_id’存储Teacher类的主键,'student_id’存储Student类的主键,借助关联表这个中间人存储的外键对,把多对多关系化成两个一对多关系,如下图所示:
当我们需要查询某个学生记录的多个老师时,我们先通过学生和关联表的一对多关系查找所有包含该学生的关联表记录,然后就可以从这些记录中再进一步获取每个关联表记录包含的老师记录。以上图中的随机数据为例,假设学生记录的id为1,那么通过查找关联表中student_id字段为1的记录,就可以获取到对应的teacher_id值(分别为3和4),通过外键值就可以在teacher表里获取id为3和4的记录,最终,我们就获取到id为1的学生记录相关联的所有老师记录。
【注意】:关联表由SQLAlchem接管,会管理关联表,我们只需要想往常一样通过操作关系属性来建立和接触关系,不用手动操作关联表。在多对多关系中,我们只需要在一侧操作就行,例如当学生A的teachers添加了老师B后,当调用老师B的students属性时返回的学生记录也会包含学生A,反之亦同。