一对一关系
本节实验我们来学习创建一对一、多对多关系的数据表。为了方便演示,假设我们的每个课程只有一个实验,我们要创建映射类 Lab 和实验表 lab,课程和实验就是一对一的关系,如何实现这种关系呢?把 lab 的主键 id 设置为外键关联到 course 的主键 id 即可,因为主键是自带唯一约束的,这样就实现了一对一关系。
创建映射类 Lab,将以下代码写入 db.py 文件:
class Lab(Base):
__tablename__ = 'lab'
# 设置主键为外键,关联 course 表的 id 字段
# 注意参数顺序,先定义外键,再定义主键
id = Column(Integer, ForeignKey('course.id'), primary_key=True)
name = Column(String(128))
# 设置查询接口,Lab 实例的 course 属性值为 Course 实例
# Course 实例的 lab 属性值默认为列表,列表中有一个 Lab 实例
# uselist 参数可以设置 Course 实例的 lab 属性值为 Lab 实例而非列表
course = relationship('Course', backref=backref('lab', uselist=False))
def __repr__(self):
return '<Lab: {}>'.format(self.name)
保存代码后,在终端运行文件即可生成数据表:
$ python3 db.py
查看数据库:
mysql> SHOW TABLES;
+-----------------+
| Tables_in_study |
+-----------------+
| course |
| lab |
| user |
+-----------------+
3 rows in set (0.00 sec)
接下来创建多对多关系的数据表,然后再一并生成测试数据。
多对多关系
一个课程可以有多个标签,每个标签可以贴在多个课程上,我们需要实现 course 课程表和 tag 标签表的多对多关系。
- 一对多关系:一个 User 实例(课程教师)对应多个 Course 实例,一个 Course 实例对应一个 User 实例。
- 一对一关系:一个 Course 实例对应一个 Lab 实例,一个 Lab 实例对应一个 Course 实例。
- 多对多关系:一个 Course 实例对应多个 Tag 实例,一个 Tag 实例对应多个 Course 实例。
满足上述需求的多对多关系,需要在创建 Tag 映射类之前,首先创建中间表的映射类,用 Table 这个特殊类创建,此类的实例就是映射类。
将以下代码写入 db.py 文件中:
# 引入 Table 类
from sqlalchemy import Table
# 创建 Table 类的实例,即中间表映射类,赋值给变量 Rela
# 该类在实例化时,接收 4 个参数:
# 1、数据表名字 2、Base.metadata
# 3 和 4、两个 Column(列名,数据类型,外键,主键)
Rela = Table('rela', Base.metadata,
Column('tag_id', Integer, ForeignKey('tag.id'), primary_key=True),
Column('course_id', Integer, ForeignKey('course.id'), primary_key=True)
)
有了中间表的映射类,就可以创建 tag 表的映射类 Tag 了,将以下代码写入 db.py 文件中:
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True)
# 设置查询接口,secondary 指定多对多关系的中间表,注意数据类型不是字符串
course = relationship('Course', secondary=Rela, backref='tag')
def __repr__(self):
return '<Tag: {}>'.format(self.name)
同样地,终端运行文件生成数据表:
$ python3 db.py
查看数据库中的数据表:
mysql> SHOW TABLES;
+-----------------+
| Tables_in_study |
+-----------------+
| course |
| lab |
| rela |
| tag |
| user |
+-----------------+
5 rows in set (0.00 sec)
注意中间表是真实存在的数据表,它有两个字段,当我们给课程添加标签时,该表会记录相关信息:
mysql> DESC rela;
+-----------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| tag_id | int(11) | NO | PRI | NULL | |
| course_id | int(11) | NO | PRI | NULL | |
+-----------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)
创建测试数据
下面对 create_data.py 文件补充一些代码,我们用 10 个随机中文汉字作为 lab 表的 name 字段的值,创建 20 个 Lab 类的实例,10 个 Tag 类的实例,补充代码如下:
from db import Lab, Tag
def create_labs():
for course in session.query(Course):
lab = Lab(name=''.join(fake.words(5)), id=course.id)
session.add(lab)
def create_tags():
for name in ['python', 'linux', 'java', 'mysql', 'lisp']:
tag = Tag(name=name)
session.add(tag)
启动命令行交互解释器 ipython,引入相关对象,执行创建数据的函数:
In [1]: from create_data import *
In [2]: create_labs()
In [3]: create_tags()
In [4]: session.commit()
查看数据库内数据:
mysql> SELECT * FROM lab;
+----+--------------------------------+
| id | name |
+----+--------------------------------+
| 1 | 北京联系其中准备技术 |
| 2 | 影响一样来源已经有些 |
| 3 | 建设研究工作那些我的 |
| 4 | 可是注册显示应该学生 |
| 5 | 起来决定不要企业资料 |
| 6 | 国内电子基本类型的人 |
| 9 | 方式一般的话具有系列 |
| 10 | 基本当然是否个人政府 |
| 11 | 其他教育你的类别更新 |
| 12 | 世界很多之后世界推荐 |
| 13 | 一直方法完全注意事情 |
| 14 | 大学不过认为世界应用 |
| 15 | 这么说明什么文化一个 |
| 16 | 状态阅读包括资料那么 |
| 17 | 作者继续作者人员对于 |
| 18 | 地区原因我的不断评论 |
| 19 | 基本环境表示我们情况 |
| 20 | 但是就是根据活动留言 |
+----+--------------------------------+
18 rows in set (0.00 sec)
mysql> SELECT * FROM tag;
+----+--------+
| id | name |
+----+--------+
| 1 | java |
| 2 | linux |
| 3 | lisp |
| 4 | mysql |
| 5 | python |
+----+--------+
5 rows in set (0.00 sec)
给课程添加标签
课程实例有 tag 属性,这是在映射类中设置的查询接口,标签实例也有对应的查询接口 course,它们的属性值均为空列表,如果要给课程添加标签,只需将标签实例添加到 tag 属性的列表中,给标签添加课程同理,举例如下:
# 通过查询将两个课程实例赋值给变量 c1 c2 ,将两个标签实例赋值给 t1 t2
In [6]: c1 = session.query(Course)[3]
In [7]: c2 = session.query(Course)[11]
In [8]: t1 = session.query(Tag)[1]
In [9]: t2 = session.query(Tag)[2]
# 课程的 tag 属性默认为空列表
In [10]: c1.tag
Out[10]: []
# 将标签实例添加到列表
In [11]: c1.tag.append(t1)
In [12]: c1.tag.append(t2)
# 标签的 course 属性里就有了对应的课程实例
In [13]: t1.course
Out[13]: [<Course: 次数通过评论等级>]
# 当然了,课程实例的 tag 属性里有了两个标签实例
In [14]: c1.tag
Out[14]: [<Tag: linux>, <Tag: lisp>]
In [15]: t2.course.append(c2)
In [16]: c2.tag
Out[16]: [<Tag: lisp>]
# 执行 session.commit() 即可将它们的关系传入数据库中
In [17]: session.commit()
In [18]:
查看数据库的中间表,可以看到给课程添加标签后,每组关系都被保存在该表中:
mysql> SHOW TABLES;
+-----------------+
| Tables_in_study |
+-----------------+
| course |
| lab |
| rela |
| tag |
| user |
+-----------------+
5 rows in set (0.00 sec)
mysql> SELECT * FROM rela;
+--------+-----------+
| tag_id | course_id |
+--------+-----------+
| 2 | 4 |
| 3 | 4 |
| 3 | 14 |
+--------+-----------+
3 rows in set (0.00 sec)
完整代码
以上课程中我们写了两个 Python 脚本。在 db.py 中创建了 User、Course、Lab、Tag 4 个映射类 + 1 个中间表的映射类 Rela,其中包含了数据表的一对多、一对一、多对多关系;在 create_data.py 脚本中编写了向数据表中添加测试数据的代码,为了方便演示,并未在终端使用 Python 解释器执行此文件。
下面是两个 Python 脚本的完整代码,供大家测试使用:
# File Name: db.py
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref
engine = create_engine('mysql://root@localhost/study?charset=utf8')
Base = declarative_base(engine)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True, nullable=False)
email = Column(String(64), unique=True)
def __repr__(self):
return '<User: {}>'.format(self.name)
class Course(Base):
__tablename__ = 'course'
id = Column(Integer, primary_key=True)
name = Column(String(64))
user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE'))
user = relationship('User',
backref=backref('course', cascade='all, delete-orphan'))
def __repr__(self):
return '<Course: {}>'.format(self.name)
class Lab(Base):
__tablename__ = 'lab'
id = Column(Integer, ForeignKey('course.id'), primary_key=True)
name = Column(String(128))
course = relationship('Course', backref=backref('lab', uselist=False))
def __repr__(self):
return '<Lab: {}>'.format(self.name)
Rela = Table('rela', Base.metadata,
Column('tag_id', Integer, ForeignKey('tag.id'), primary_key=True),
Column('course_id', Integer, ForeignKey('course.id'), primary_key=True)
)
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True)
course = relationship('Course', secondary=Rela, backref='tag')
def __repr__(self):
return '<Tag: {}>'.format(self.name)
if __name__ == '__main__':
Base.metadata.create_all()
# File Name: create_data.py
from sqlalchemy.orm import sessionmaker
from faker import Faker
from db import engine, User, Course, Lab, Tag
session = sessionmaker(engine)()
fake = Faker('zh-cn')
def create_users():
for i in range(10):
user = User(name=fake.name(), email=fake.email())
session.add(user)
def create_courses():
for user in session.query(User).all():
for i in range(2):
course = Course(name=''.join(fake.words(4)), user=user)
session.add(course)
def create_labs():
for course in session.query(Course):
lab = Lab(name=''.join(fake.words(5)), id=course.id)
session.add(lab)
def create_tags():
for name in ['python', 'linux', 'java', 'mysql', 'lisp']:
tag = Tag(name=name)
session.add(tag)
def main():
create_users()
create_courses()
create_labs()
create_tags()
session.commit()
if __name__ == '__main__':
main()
本文介绍如何使用SQLAlchemy ORM进行关系型数据库表之间的关系映射,包括一对一、多对多等关系的实现方法,并提供具体代码示例。

2595

被折叠的 条评论
为什么被折叠?



