多对多关系
一对多关系、多对一关系和一对一关系至少都有一侧是单个实体,所以记录之间的联系通过外键实现,让外键指向这个实体。但是,你要如何实现两侧都是“多”的关系呢?
下面以一个典型的多对多关系为例,即一个记录学生和他们所选课程的数据库。很显然,你不能在学生表中加入一个指向课程的外键,因为一个学生可以选择多个课程,一个外键不够用。同样,你也不能在课程表中加入一个指向学生的外键,因为一个课程有多个学生选择。两侧都需要一组外键。这种问题的解决方法是添加第三张表, 这个表称为关联表。现在,多对多关系可以分解成原表和关联表之间的两个一对多关系。
下图 描绘了学生和课程之间的多对多关系。
这个例子中的关联表是 registrations,表中的每一行都表示一个学生注册的一个课程。查询多对多关系要分成两步。 若想知道某位学生选择了哪些课程,你要先从学生和注册之间的一对多关系开始, 获取这位学生在 registrations 表中的所有记录,然后再按照多到一的方向遍历课程和注册之间的一对多关系, 找到这位学生在 registrations 表中各记录 所对应的课程。 同样,若想找到选择了某门课程的所有学生,你要先从课程表中开始,获取其在 registrations 表中的记录,再获取这些记录联接的学生。通过遍历两个关系来获取查询结果的做法听起来有难度, 不过像前例这种简单关系,SQLAlchemy 就可以完成大部分操作。上图中的多对多关系使用的代码表示如下:
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):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
classes = db.relationship('Class',secondary=registrations,
backref=db.backref('students', lazy='dynamic'),
lazy='dynamic')
class Class(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String)
下面来看一个实际的例子:因为在设计中学生会转学院,所以,学生与学院是多对多的关系
1.定义模型
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True, index=True)
.............省略其他字段
departments=db.relationship('Department', secondary=user_department, backref=db.backref('users',lazy='dynamic'), lazy='dynamic')
class Department(db.Model):
__tablename__ = 'departments'
id = db.Column(db.Integer, primary_key=True)
department = db.Column(db.String(100))
user_department = db.Table('user_department',
db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True),
db.Column('department_id', db.Integer, db.ForeignKey('departments.id'), primary_key=True)
)
2.定义表单:
class SmForm(Form):
name = StringField('真实姓名', validators=[Length(0, 64)])
....................省略其他字段
is_departmentChange = BooleanField('是否转过学院')
pre_department = SelectField('原学院:', coerce=int)
cut_department = SelectField('现学院:', coerce=int)
submit = SubmitField('Submit')
#下拉菜单初始化
def __init__(self, user, *args, **kwargs):
super(SmForm, self).__init__(*args, **kwargs)
self.pre_department.choices = [(pre_department.id, pre_department.department)
for pre_department in Department.query.order_by(Department.department).all()]
self.cut_department.choices = [(cut_department.id, cut_department.department)
for cut_department in Department.query.order_by(Department.department).all()]
self.user = user
3.定义路由:
@main.route('/sm', methods=['GET', 'POST'])
@login_required
@main.errorhandler(404)
def sm():
user = User.query.filter_by(email=current_user.email).first()
form = SmForm(user)
if user.is_realname ==False:
if form.validate_on_submit():
# User的学院更新 删除旧的数据,联合删除
usr = current_user._get_current_object()
deparment = user.departments.all()
for de in deparment:
de.users.remove(usr)
........................省略其他
user.is_departmentChange = form.is_departmentChange.data
#向关系表中添加
user.departments.append(Department.query.get(form.pre_department.data))
user.departments.append(Department.query.get(form.cut_department.data))
db.session.add(user)
db.session.commit()
return redirect(url_for('.sm_success'))
return render_template('sm.html', form=form)
4.渲染模板(省略)