一对一和多对多关系

本文介绍如何使用SQLAlchemy ORM进行关系型数据库表之间的关系映射,包括一对一、多对多等关系的实现方法,并提供具体代码示例。

一对一关系

本节实验我们来学习创建一对一、多对多关系的数据表。为了方便演示,假设我们的每个课程只有一个实验,我们要创建映射类 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()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值