Flask-SQLALchemy

发布时间 2023-12-05 23:50:42作者: 一笔带过

SQLALchemy

orm,对象-关系映射,主要实现模型对象到关系型数据库的映射,orm提供一种面向对象的数据库的方式给开发者,不需要编写原生的sql语句也能操作数据库,实现了业务代码与底层数据的解耦
优势:
1.对数据库操作转为对类/对象的属性和方法的操作,字段--对象数据,sql关键字--操作方法
2.不用编写各种数据库的原生sql语句,也可以编写原生的sql
3.不再关注那类的数据库
4.通过简单的配置就能修改数据库,不需要修改业务代码


缺点:
1.相对于直接使用sql语句操作数据,而orm将操作转为sql,具有性能的损失
2.根据对象的操作转为sql,根据查询的结果转为模型试验对象,在映射过程中有性能的损失
3.不同的orm提供的操作不一样,增加的学习成本

网址:
http://www.pythondoc.com/flask-sqlalchemy/index.html

切记在使用数据库时创建数据库请设置好字符集

SQLALchemy安装

pip insatall -U Flask-SQLAlchemy (可以使用orm 也可以使用原生的sql)

使用数据库时需要安装对应的驱动(会出现源码报错的问题mysqlclient模块)
mysql :pip install flask-mysqldb  or pymysql
postgresql : pip install  psycopg2

conda install falsk-mysqldb -c conda-forge


SQLAlchemy 作用就是将orm的语句转变为sql语句,通过stoke方式链接数据库,进行执行

SQLALchemy的使用配置

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 配置链接数据的地址
# 如果使用不是mysqldb驱动链接的时候需要指定 mysql+pymysql
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123456@127.0.0.1:3306/flask?charset=utf8mb4'
#  将会追踪对象的修改并且发送信号(没有设置默认警告,线上False)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# 显示orm映射的sql语句
app.config['SQLALCHEMY_ECHO'] = True
# 设置连接池大小
app.config['SQLALCHEMY_POOL_SIZE'] = 10
# 链接超时时间
app.config['SQLALCHEMY_POOL_TIMEOUT'] = 15
# 连接回收时间(默认 情况下 MySQL 会自动移除闲置 8 小时,如果使用mysql那么设置的回收时间为2个小时)
app.config['SQLALCHEMY_POOL_RECYCLE'] = 10 * 60
# 连接池达到最大值后,额外创建的链接个数
app.config['SQLALCHEMY_MAX_OVERFLOW'] = 5
# 实例化sqlalchemy 注册到flask中
db = SQLAlchemy()
db.init_app(app)

if __name__ == '__main__':
    app.run()

# 不同数据库的链接规则
MySQL	mysql://username:password@hostname/database?charset=utf8mb4
Postgres	postgresql://username:password@hostname/database?charset=utf8mb4

SQLALchemy字段

# 常用字段:

Integer  int 普通字段,一般为32位数
SmallInteger int 取值范围小的整数 一般为16位
Biglnteger int 不限制精度整数
Float float 浮点数
Numeric decimal.Decimal 普通数值,一般为32位
String str 变长字符串
Text str 变长字符串,对较长或不限制长度做了优化
Boolean bool 布尔值
DateTime datatime.datetime 日期时间
Date datetime.date 日期
Time datetime.time 时间
LargeBinary bytes 二进制文件内容
Enum enum.Enum 枚举类型相当于django字典中的choices,但是没有那么强大

# 约束选项

primary_key 如果为True,代表当前表中的主键
unique 如果为True 代表唯一索引,当前这个字段列中不能存在重复
index 如果为Ture 为这个列创建的普通索引,提高查询效率
nullable 如果为Ture 允许有空值,如果为False 不能有空值
default 列的默认值
comment 描述

SQLALchemy基本操作

在SQLALchemy中 添加 修改 删除 均有数据库会话 sessionsm管理
	会话db.session表示,在准备写入数据库前,要现将数据库添加到会话中调用db.commit()

在SQLALchemy中 查看操作通过query对象操作的
	最基本的查询时返回表中的全部数据,也可以通过filter or fliter_by 进行过滤最精准的数据
    
# 创建一张表
class Student(db.Model):
    # __tablename__ 生成表名 就会映射到数据库中的表名
    __tablename__ = 'student'
    # db.Column 代表创建字段
    # db.Integer 字段中的属性
    '''
    	生成的sql
        CREATE TABLE student (
        id INTEGER NOT NULL AUTO_INCREMENT, 
        name VARCHAR(15) COMMENT '名称', 
        age SMALLINT COMMENT '年龄', 
        sex BOOL COMMENT '性别', 
        email VARCHAR(128) COMMENT '邮箱地址', 
        money NUMERIC(10, 2) COMMENT '钱包', 
        PRIMARY KEY (id), 
        UNIQUE (email)
        )
    '''
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(15), index=True, comment='名称')
    age = db.Column(db.SmallInteger, comment='年龄')
    sex = db.Column(db.Boolean, default=True, comment='性别')
    email = db.Column(db.String(128), unique=True, comment='邮箱地址')
    money = db.Column(db.Numeric(10, 2), default=0.0, comment='钱包')

    def __repr__(self):  # 相当于django中的str 方法显示一个可读字符串
        return f'{self.name}'
    of
    def __str__(self):  # 相当于django中的str
        return f'{self.name} - {self.__class__.__name__}' # 顺便将类名进行打印
    

if __name__ == '__main__':
	# 注意在视图以外调用当前的db.create_all()创建需要创建应用上下文下使用
    with app.app_context(): # 需要上下文
        db.create_all()  # 创建数据库
        # db.drop_all() # 删除数据与表
    app.run()    
    

数据操作基础(添加)

1.添加一条 .add(对象)
@app.route('/')
def index():
    # 数据添加
    # 1.需要获取表
    # 根据创建的表进行赋值新的列的内容
    student_obj = Student(name='wkx',age=18,sex=1,email='565151759@qq.com',money=2002.22)
    # 2.通过会话对象将内容添加
    db.session.add(student_obj)
    # 3.提交数据(commit 自带事务操作的,成功添加,失败回滚)
    db.session.commit()
    return 'ok'

2.批量添加 .add_all(对象列表)
@app.route('/')
def index():
    student_list = [
        Student(name='小白', age=18, sex=False, email='xiaobais@qq.com', money=6.22),
        Student(name='小李', age=15, sex=True, email='xiaolix@qq.com', money=85.10),
        Student(name='小赵', age=32, sex=False, email='xiaozhao@qq.com', money=99.21),
        Student(name='小花', age=20, sex=True, email='xioahua@qq.com', money=36.88),
    ]
    db.session.add_all(student_list)
    db.session.commit()
    return 'ok'

数据操作基础(更新)

1.更新一条 get
# 使用 get(主键)
student_obj = Student.query.get(1) # 获取对象
student_obj.age = 1089 # 对字段赋值
student_obj.money = 100028.22 
db.session.commit() # 提交数据

2.使用 filter 更新多条或者更新1条
# filter(条件(根据条件的具体范围定义更新1条还是多条)) 可以多条修改 也可以修改单条
# 使用的话需要 对象.字段进行指定 使用双=号对比  Student.sex == True
# 在原来的money的基础上+100{Student.money: Student.money + 100}
Student.query.filter(Student.sex == True).update({Student.money: Student.money + 100})
db.session.commit()

3.使用filter_by(条件)
# 查询到对象,对象的money-10
Student.query.filter_by(id=1).update({Student.money: Student.money - 10})
db.session.commit()

数据操作基础(删除)

删除
1.使用get
student_obj = Student.query.get(1)
db.session.delete(student_obj)
db.session.commit()

2.使用filter
student_obj = Student.query.filter(Student.id == 6).first() # 找到当前第一个对象
db.session.delete(student_obj)
db.session.commit()

3.使用filter_by
student_obj = Student.query.filter_by(id = 7).first() # 找到当前第一个对象
db.session.delete(student_obj)
db.session.commit()

4.使用delete删除 
Student.query.filter_by(id = 8).delete()# 获取对象直接提交delete 
db.session.commit() # 删除

使用删除多条就需要使用
filter_by 或者 filter 将条件写的范围化就可以和更新数据一个原理

注意悲观锁与乐观锁的区别

在删除多条和更新多条的时候使用了乐观锁
悲观锁: 是数据库中的一种互斥锁机制,但是乐观锁并非真正的数据库锁(悲观锁存在缺陷,乐观锁属于后者开发出来的)
两种锁都是数据库在应对并发操作,防止出现资源抢夺,基于不同人生观实现的2中解决方案

悲观锁基本使用过程:

数据库终端实现:
begin; 开启事务
select * from student where id=1 for update # 对当前的id=1的数据进行加锁
# 其他的不能进行修改当前的数据,处于一个阻塞状态 直到是事务提交后,其他链接才能访问修改
UPDATE student SET sex=1 WHERE student.id = 11
commit; 提交事务

悲观锁的问题:
    1.提前锁定数据,形成串行化,形成阻塞,不利于性能发挥。不适用于高并发场景
    2.悲观只能保证数据的唯一性,不能保证藏数据的出现
    
# 对商品进行抢购案例:
乐观锁: 解决悲观锁的并发问题
begin 开启事务
先查看库存 记录当前的库存 num=10
下单 买6件
付款
扣除库存 update from goods set num = num-6 where num = 等不等于当前记录的库存 and id = 6 # 判断当前库存是否是原来的库存
执行成功,表示没有人抢,购买成功
commit
执行失败,表示已经有人抢了
rollback 回滚

悲观锁:就会造成其他的链接无法访问当前商品进行修改,需要等待锁接触获取当前锁才能执行
begin 开启事务
给id=6的商品进行加锁
	select * from goods where id=6 for update
进行下单 买6件
付款
update from goods set num = num-6 where id=6
执行成功解锁
commit 提交

数据操作基础(查询)

单表查询
# 查询过滤器方法
filter 把过滤器添加到查询上返回一个新查询 # 支持运算符和查询方法或者模糊查询
filter_by() 把等职过滤器加到查询上返回一个新查询
注意:
	filter or filter_by 根据条件获取的一个值也是[obj对象,] 需要使用first方法获取第一个对象

limit() 指定范围的只返回结果输了
offset 设置结果查询的开始范围 偏移查询返回的记过,返回一个新的查询
order_by() 根据条件进行排序
group_by() 根据条件进行分组

# 查询结果方法
all 以'列表'形式返回全部的结果
first() 返回查询结果的第一个 模型对象 如果没有查到 返回none # 获取多个对象列表的第一个 
first_or_404() 返回查询的第一个结果 模型对象 如果没有查到 通过abort返回404异常
get('需要指定主键id') 指定主键返回模型对象 不存在返回none
count() 返回查询结果的数量
paginate('页码','每页显示的数量+') 返回一个paginate分页器对象 包含指定返回内的结果 
having() 返回结果中符合条件的数据 必须跟在group by后面 其他地方无法使用(分组在过滤)




# get
st = Student.query.get(11)

# all 查询全部
st = Student.query.all()  # [<Student 9>, <Student 10>, <Student 11>, <Student 12>, <Student 13>]
# all 设置过滤条件查询 查询年龄为Ture(男)的(查询不到为空)
st = Student.query.filter_by(Student.sex == 1).all()  # [<Student 9>, <Student 12>]
# all 进行切片操作只是在原查询数据中利用list将数据切出来,django会执行limit操作

# count 统计查询结果
st = Student.query.count()  # 不设置条件返回全部数量
# count 设置条件 只获取条件中的全部结果
st = Student.query.filter(Student.age < 16).count()

# first 获取查询结果的第一个结果对象
st = Student.query.first()
# first 设置条件后获取第一个结果对象 没有就是none 可以进行切片获取最后以个数据
st = Student.query.filter(Student.age > 16).first()

filter模糊查询高级使用方式

# filter(模型类.字段 比较运算符 值)的高级方式
# 1.模糊查询 contains(包含) 只要包含都会被查询到
st = Student.query.filter(Student.name.contains('王')).all()

# 2.模糊查询 contains(开头) 都会被查询到
st = Student.query.filter(Student.name.startswith('小')).all()

# 3.模糊查询 contains(结尾) 都会被查询到
st = Student.query.filter(Student.name.endswith('花')).all()

# 比较查询 == != > < >= <= 单条件
# 4.比较查询 大于 >
st = Student.query.filter(Student.age > 15).all()

# 5.比较查询 小于 <
st = Student.query.filter(Student.age < 15).all()

# 6.比较查询 等于 必须是两个==号
st = Student.query.filter(Student.age == 15).all()

# 7.比较查询 不等于 !=
st = Student.query.filter(Student.age != 15).all()

# 8.比较查询 大于等于 >=
st = Student.query.filter(Student.age >= 15).all()

# 9.比较查询 大于等于 <=
st = Student.query.filter(Student.age <= 15).all()

# 比较查询 多比较 都好(,)相当于sql中的and并且
st = Student.query.filter(Student.age <= 15, Student.sex == 1).all()



from sqlalchemy import or_, and_, not_
# 比较查询 多比较 or _or,需要导入模块 from sqlalchemy import or_, and_, not_, in_
st = Student.query.filter(or_(Student.age <= 15, Student.sex == 1)).all()  # 获取
st = Student.query.filter(and_(Student.age <= 15, Student.sex == 1)).all()  # 与
st = Student.query.filter(not_(Student.age <= 15)).all()  # 非
st = Student.query.filter(Student.age.in_([20,])).all()  # 数据库中是否右列表这些值的数据

filter_by精确查询

# filter_by(字段 = 值) 只支持是否相等的情况 不支持大于等于等等运算符号
# 1.filter_by 单条件查询
st = Student.query.filter_by(age=18).all()  # 数据库中是否右列表这些值的数据
# 2.filter_by 多条件 也可以使用  or_, and_, not_ in_
st = Student.query.filter_by(age=30,sex=1).all()


from sqlalchemy import or_, and_, not_
# 比较查询 多比较 or _or,需要导入模块 from sqlalchemy import or_, and_, not_, in_
st = Student.query.filter_by(or_(age <= 15, sex = 1)).all()  # 获取
st = Student.query.filter_by(and_(age <= 15, sex = 1)).all()  # 与
st = Student.query.filter_by(not_(age = 15)).all()  # 非
st = Student.query.filter_by(age.in_([20,])).all()  # in 数据库中是否右列表这些值的数据

排序,偏移量,限制查询数量,in,is,分页器对象

# 判断是否存在 exists
st = db.session.query(Student).filter(Student.name == '小花').exists()
ret = db.session.query(st).scalar()  # 使用 one 获取当前判断的结果(True,) 元组类型 scalar 拿到的就是True

# 范围查询 in_['小花','小王'] 查看是否在允许的范围内能查询到
st = Student.query.filter(Student.name.in_(['小花', '小王'])).all()

# is_ 判断查询 名字为空的
st = Student.query.filter(Student.name.is_(None)).all()

# order_by排序
st = Student.query.order_by(Student.age.desc()).all()  # desc() 倒叙
st = Student.query.order_by(Student.age.asc()).all()  # asc() 正序
# order_by排序 多条件排序 按年龄倒叙如如果年龄一样就按id正序
st = Student.query.order_by(Student.age.desc(), Student.id.asc()).all()

# 对查询结果进行偏移量,数量限制
# limit(数量)(查询数量的限制) 按照年龄倒叙 显示前2个人(年龄最大)
st = Student.query.order_by(Student.age.desc()).limit(2).all()

# 偏移量 offset(按照索引) 查询按照年龄倒叙中的2-末尾的全部人offset相当于列表的切片,获取指定的位置开始到最后的对象,也可以指定范围
st = Student.query.order_by(Student.age.desc()).offset(2).all()
# 查询按照年龄倒叙中的2-末尾的开始的第一个人
st = Student.query.order_by(Student.age.desc()).offset(2).limit(1).all()

# 分页查询
# 分页器对象 = 模型类.query.filter(过滤条件).paginate(page=页码,per_page=单页显示的数量, max_per_page=最大的分页数(默认100))
st = Student.query.paginate(page=1, per_page=2)
for i in st:
    print(i)
    print(st.total)  # 当前模型中的总数据量
    print(st.items)  # 每一页的数据列表(可以根据分页对象调用当前方法当前页的数据)
    print(st.pages)  # 总页码
    print(st.page)  # 当前页码

    print(st.prev())  # 上一页的分页对象
    print(st.prev_num)  # 上一页的页码
    print(st.has_prev)  # 是否有上一页
    print(st.prev().items)  # 获上一页的数据列表

    print(st.next())  # 下一页的分页对象
    print(st.next_num)  # 下一页的页码
    print(st.has_next)  # 是否有下一页
    print(st.next().items)  # 获下一页的数据列表
    # 前后端分离 通过分页器返回数据
    data = {
        'has_prev': st.has_prev,  # 是否有上一页
        'prev_num': st.prev_num,  # 上一页页码
        'has_next': st.has_next,  # 是否有下一页
        'next_num': st.next_num,  # 下一页页码
        'page': st.page,  # 当前页码
        "data": [  # 数据
            {
                "name": i.name,
                "age": i.age,
                "sex": i.sex,
                "email": i.email,
                'money': i.money
            } for i in st.items],
        'pages_num': st.pages,  # 页码总数量
        'total_num': st.total  # 数据总数量
    }
    # 前后端不分离只用返回页面就可以 将对象进行循环渲染页面就可以

聚合分组查询

通过分组查询和分组数据进行查询


from sqlalchemy import func  # 全部的聚合函数都声明在这个模块中


@app.route('/')
def index():
    # 聚合函数 scalar 只能获取单个条件的值
    # func.sum 求和(获取money的总数)
    # 第一个结果是(Decimal('339.96'),) 还需要获取第一个元素[0] 339.96
    st = db.session.query(func.sum(Student.money)).first()[0]

    # func.sum 求和中添加条件(获取男的money的总数)
    st = db.session.query(func.sum(Student.money)).filter(Student.sex == True).first()[0]

    #  func.count 统计总数 (统计女生的数量) 都是可以存在条件的
    st = db.session.query(func.count(Student.id)).filter(Student.sex == False).first()[0]
    st = db.session.query(func.count(Student.id)).filter(Student.sex == False).scalar()  # 直接返回结果不是元组

    # func.avg 获取平均值(获取平均年龄) 都是可以存在条件的
    st = db.session.query(func.avg(Student.age)).scalar()  # 28.0000

    # func.min 获取最小值 (获取最小的年龄) 都是可以存在条件的
    st = db.session.query(func.min(Student.age)).scalar()  # 20
    # func.max 获取最大值 (获取最大的年龄) 都是可以存在条件的
    st = db.session.query(func.max(Student.age)).scalar()  # 30

    '''分组查询group_by(分组条件(按照字段什么进行分组))'''
    # 根据分组查男女的平均年龄[(Decimal('30.0000'),), (Decimal('25.0000'),)]
    st = db.session.query(func.avg(Student.age)).group_by(Student.sex).all()

    # 根据不同年龄段查询有多少个人
    # 分组时db.session.query(要么是分组的字段,要么是聚合函数的结果) 其他字段会出现报错
    st = db.session.query(Student.age,func.count(Student.id)).group_by(Student.age).all()

    # 多字段分组
    # 查询各个年龄段的男生与女生数量
    st = db.session.query(Student.age, Student.sex,func.count(Student.id)).group_by(Student.sex,Student.age).all()


    # 分组后的过滤操作 having
    # 根据年龄分组后获当前年龄中金额大于99的存在几个
    st = db.session.query(Student.age, func.count(Student.id)).group_by(Student.age).having(func.max(Student.money) > 99).all()

    print(st)
    return '123456'


Sqlalchemy方法的顺序

1.模型.query or db.session.query
2.filter / filter_by
3.group_by .
4. having
5.order_by
6.limit offset
7.all / get / furst / count / paginate

原生sql操作

    '''原生操作'''
    # 查询全部
    ret = db.session.execute('select * from student').fetchall()
    # 查询单条(第一条)
    ret = db.session.execute('select * from student').fetchone()

    # 分组合并 (group_concat代表的就是合并,合并起来变为一个字段(将内容合并在一起))

    ret = db.session.execute('select age,count(id),group_concat(name) from student group by age').fetchall()

    # 插入一条新数据
    ret = db.session.execute(
        'insert into student (name,age,sex,email,money) values("zhouzhou",18,FALSE ,"565151759@qqqq.com",778899)')
    db.session.commit()

    # 更新一条数据
    ret = db.session.execute('update student set student.money = 79 where student.name = "zhouzhou" ')
    db.session.commit()
    
    # 删除一条数据
    ret = db.session.execute(' delete from student where id=5')
    db.session.commit()
    
    # 删除更新添加都需要使用commit进行提交(删除方法也是一样的)

SQLALchemy关联查询

常用的关系字段:
backref :在关系的另一种模型中添加'反向引用',用于设置外键名,用于1对多中使用

primary join : 用与指定两个模型之间的使用的连表条件,用于1对1或者1对多连表中

lazy :指定如果加载关联模型的数据方式,用于1对1或者1对多的都多连表中
	select(立即加载,查询所有的相关数据显示,相当于lazy=True)
	subquery(立即加载,使用与子查询)
	dynamic(不立即加载,但是提供加载的查询对象)
	
uselist : 指定1对1 or 1对多的关系,返回的数据是模型或者是模型列表,
	如果是False,不使用列表,而使用模型对象(1对1)
	如果使用True,那么就是1对多关系或多对多关系
	需要在relationship中进行指定
	
secondary :指定多对多中的关系表名
	多对多关系中,需要建立关系表,设置secondary=关系表

secondary join: 在sqlalchemy中无法自行决定时,指定多对多关系中的二级链表条件,绑定主外键

# 不会在数据库创建的 属于逻辑字段在sqlalchemy使用
relationship (关系) # 通过当前属性创建查询时使用的 作用作为一种引用声明,方便两张表或者多张表orm的调用关系的纽带作用
relationship的级联属性:
	# 是对save-update, merge, refresh-expire, expunge, delete几种的缩写。
	cascade='all'
    # 表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除掉(即子表中的某条数据没有了关联数据,则该条数据会被自动删除掉)
    cascade='delete-orphan'
    
    # 表示当删除某一个模型中的数据的时候,也删掉使用relationship和他关联的数据。
    cascade='delete'

1对1关系

表模型

from sqlalchemy.orm import backref  # 1.导包 

# 创建一张表 学生(主表)
class Student(db.Model):
    __tablename__ = 'student'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(15), index=True, comment='名称')
    age = db.Column(db.SmallInteger, comment='年龄')
    sex = db.Column(db.Boolean, default=True, comment='性别')
    email = db.Column(db.String(128), unique=True, comment='邮箱地址')
    money = db.Column(db.Numeric(10, 2), default=0.0, comment='钱包')

    def __repr__(self):
        return f'<{self.name} - {self.__class__.__name__}>'

    
# 创建关系表 学生详情表(外键表)
class StudentInfo(db.Model):
    __tablename__ = 'student_info'

    id = db.Column(db.Integer, primary_key=True)
    address = db.Column(db.String(255), comment='地址')
    mobile = db.Column(db.String(15), index=True, comment='手机号码')
    
    
    # 2.添加一个1对1的外键[提供给数据使用的(物理绑定,必须存在的)需要将表关系字段存放在外键表中]
    student_id = db.Column(db.Integer, db.ForeignKey('student.id'), comment='student表的外键关系1对1')
   
    # 3.添加一个属性[提供给sqlalchemy使用(在内部通过当前字段查询关联表信息),关联属性的可以存放在任意的模型中] ***
    # student 是学生详情表(外键表)使用  info学生(主表)使用
    student = db.relationship('Student', uselist=False, backref=backref('info', uselist=False))

    def __repr__(self):
        return f'<{self.student.name} - {self.__class__.__name__}>'

    
注意:
    当前字段 不会在表中创建,因为属于逻辑字段用于提供给sqlalchemy使用
	字段名 = db.relationship('绑定关联表名','1对多 or 1对1关系 or 多对多关系', '反向查询字段段名称')
    例如:
    student = db.relationship('Student', uselist=False, backref=backref('info', uselist=False))
    1.参数1:Student # 绑定学生表(可以根据学生详情表.student查询到主模型中的全部字段信息)
    2.参数2:uselist=False(1对1关系返回的是对象) # 确定两张表之间的关系 1对1 还是1对多
    3.backref=backref('反向查询字段名称', uselist=False) # 设置反向查询字段名称(当学生表查询学生详情表示需要使用)
   
为什么需要使用=backref('反向查询字段名称', uselist=False)函数?
	因为1对1时设置的relationship(关系)的属性uselist=False , 那么返回的是一个模型对象而不是模型列表,那么创建的relationship(关系)中设置的反向关系属性,也需要设置uselist=False属性
    
# 更新查询修改都会基于relationship的逻辑字段进行操作的

添加

# 1.添加主模型时也将,也将关联模型添加
注意:info属性时由StudentInfo类中的student字段通过属性backref提供逻辑字段
st = Student(
    name='小米',
    age=16,
    sex=True,
    email='565151759@qq.com',
    money=228,
    info=StudentInfo(
        address='我住在东北',
        mobile='11012011911'
    ))
db.session.add(st) # 存放
db.session.commit() # 提交

# 2.主模型存在,基于主模型添加关联模型(先有主才能有外)
st = Student(name='小编',age=19,sex=True,email='123@qq.com',money=228,)
db.session.add(st)
db.session.commit()
st.info = StudentInfo(address='增工作者', mobile='89988')
db.session.commit()

# 3.添加外键模型的同时,将主键进行添加(在sql创建时也是先执行主表的sql获取id后在创关联表的sql将关联id拿到) 不推荐使用
student 字段时StudentInfo表中逻辑字段
st = StudentInfo(
    address='nimami',
    mobile = 'sdxxx1231',
    student = Student(
        name = '小王',
        age = 18,
        sex= True,
        email='456@qq.com',
        money=888,
    )
)
db.session.add(st)
db.session.commit()

查询

# 1.通过主模型对象调用外键模型relationship(逻辑字段属性)获取外键模型对象信息
st = Student.query.filter(Student.id == 1).first()
print(st.info)

# 2.通过外键模型调用主模型数据(也是通过relationship逻辑字段)获取主模型信息
st = StudentInfo.query.filter(StudentInfo.id == 3).first()
print(st.student)

# 3.以外键模型的信息作为主键模型的查询条件 [获取的对应的主模型对象]
st = Student.query.filter(StudentInfo.id==3).first()


# 4.以主键模型的信息作为外键模型的查询条件 [获取对应的子模型对象]
st = StudentInfo.query.filter(Student.id == 1).first()

更新

# 1.根据主模型修改外键模型的数据
st = Student.query.filter(Student.id == 1).first()
st.age = 18 # 主键字段修改
st.info.address = '山东山东' # 修改关联外键模型数据
db.session.commit()

# 2. 通过外键模型修改主模型的数据
st = StudentInfo.query.filter(StudentInfo.id == 1).first()
st.mobile = '1308888888'  # 外键字段修改
st.student.age = 88 # 修改主键的字段年龄

db.session.commit()

删除

# 1.删除主模型数据,sqlalchemy会将外键模型关联的字段设为null
st = Student.query.filter(Student.id == 1).first()
db.session.delete(st)
db.session.commit()

# 2.删除外键模型数据,不会影响主模型数据
st = StudentInfo.query.filter(StudentInfo.id == 3).first()
db.session.delete(st)
db.session.commit()


如果要进行连贯删除,需要进行删主模型再删子模型

1对多关系

常见的业务:
	商品分类与商品,学生与班级,文章分类与文章等等...
	
逻辑字段 = relationship('表名',uselist=Ture,backref='xxx',lazy='dynamic')

关于relationship中属性lazy(决定sqlalchemy什么时候执行读取关联模型的sql语句):
lazy='dynamic' : [具体到字段]  uselist=Ture 才能使用
    查询当前字段存在的数据模型是,不会将关联的外键表模型给查询出来,只有操作到外键关联属性并操作外键'具体的字段',才进行链表查询数据【执行sql】

# 解释  如果设置 lazy='dynamic'
# 只会执行当前的 select * from student where id = 7 limit 1 不会执行与当前表关联表的查询
st = Student.query.filter(Student.id == 7).first()
print(st)
print(st.逻辑字段) # 才会执行当前的关联表的sql语句
    
lazy=True or lazy='select':[使用到关联属性]
    查询当前数据模型是,不会把外键模型的数据查询出来,只有操作到'外键关联属性'时,才会进行链表查询【执行sql】
    
lazy = subquery :[无论如何都会执行]
	查询当前数据模型是,采用的是子查询(subquery) 把外键模型的属性也同时查出来
    
'''
lazy='dynamic': (刚刚好合适)
    对象 只会执行当前对象表sql, 对象.逻辑字段 执行对象表和对象关联表sql,查询结果不会获取
    执行 对象.逻辑字段.all() 才会获取查询结果列表
    
lazy=True or lazy='select' :(太主动)
   对象 只会执行当前对象表sql, 对象.逻辑字段 执行对象表和对象关联表sql,直接获取查询结果列表

lazy = subquery :(消耗资源太大)
    当只查询某个表时,sqlalchemy会将当前查询的表全部关联的表join到一张虚拟表中,在获取查询结果
'''


lazy 这个属性只有在 uselist=Ture才能使用 

表模型

class Student(db.Model):
    __tablename__ = 'student'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(15), index=True, comment='学生名称')

    def __repr__(self):
        return f'<{self.name} - {self.__class__.__name__}>'


from sqlalchemy.orm import backref


class StudentAddress(db.Model):
    __tablename__ = 'student_address'
    id = db.Column(db.Integer, primary_key=True)
    address = db.Column(db.String(255), comment='学生的地址')

    # 绑定外键[物理字段真实存在的,记录到数据库中]
    student_id = db.Column(db.Integer, db.ForeignKey('student.id'), comment='1对多的关系')
    # 关联属性[sqlalchemy内部查询使用方便查询,不会进行创建]
    # uselist=False : 因为当前地址是多关系的表,1个地址只能关联一个学生,而一个学生可以绑定多个地址 uselist=False
    # backref=backref('address_list', uselist=True) :反向的关系就要 uselist=True ,因为一个学生可以存在多个关系
    # lazy='dynamic' 使用当前关联表字段时,才会执行sql,不然就不执行(动态) 只能在 uselist=True 才能使用
    # 外键模型 --- > 主模型  StudentAddress对象.student 返回模型对象
    # 主模型 --- > 外键模型 Student对象.address_list 返回模型对象列表
    student = db.relationship('Student', uselist=False, backref=backref('address_list', uselist=True, lazy='dynamic'))

    def __repr__(self):
        return f'<{self.student.name} - {self.__class__.__name__}>'
    
student : 逻辑字段内部sqlalchemy使用

添加

1.主模型存在,添加外键模型(1个学生有多个地址)
    添加主模型(1)
    st = Student(
        #     name='wkx1',
        # )
        # db.session.add(st)
        # db.session.commit()
    
    添加外键信息(多)[使用了创建的逻辑字段反向属性进行添加]
  
    # 1.1 append 只能添加一条,多条会出报错
    st.address_list.append(
        StudentAddress(address='北京朝阳'),
    )
    
    # 1.2指定一个列表添加多条
    st.address_list = [
        StudentAddress(address='北京朝阳'),
        StudentAddress(address='北京朝阳1'),
        StudentAddress(address='北京朝阳2')
    ]
    db.session.commit()  # 添加

2.添加主模型的同时,添加外键模型
st = Student(
            name='wkx333',
            address_list=[  # 地址列表主键模型创建的同时添加外键模型
                StudentAddress(address='河南'),
                StudentAddress(address='河北'),
                StudentAddress(address='新疆'),
            ]
)
db.session.add(st)
db.session.commit()

3.添加外键模型的同时添加主模型[执行sql顺序还是先执行主模型添加,在添加外键模型] # 不会使用
st = StudentAddress( # 添加外键模型
        address='河南',
        student=Student(name='wkx666778') # 添加住建模型信息
)
db.session.add(st)
db.session.commit()

查询

1.通过主模型查找外键模型

st = Student.query.filter(Student.id == 7).first()
print(st.address_list.all())  # 返回一个对象列表 因为时1对多 all() 因为使用了lazy='dynamic'

2.通过外键模型找主模型

st = StudentAddress.query.filter(StudentAddress.id == 7).first()
print(st.student)  # 返回一个对象 因为多对1 单一个地址对应的学生返回当前学生的对象

修改

1.根据主表对象找到关联表对象列表,根据下标索引进行修改

st = Student.query.filter(Student.id == 7).first()
st.address_list.all()[0].address = '6677899'
db.session.commit()

删除

'''删除操作想要关联删除,先根据主表信息找到关联表全部信息递归删除,在删除主表信息'''
# 1.根据主表对象删除,那么关联表对象关联字段就为null
st = Student.query.filter(Student.id == 7).first()
db.session.delete(st)
db.session.commit()

# 2.删除关联表信息,不会影响主表信息
st = StudentAddress.query.filter(StudentAddress.id == 7).first()
db.session.delete(st)
db.session.commit()

多对多关系

用户的收藏文章/商品(用户可以收藏多个文章/1个文章可以被多个商品收藏)

1.方式1 基于第三方关系表创建多对多
使用db.Table() 创建一张表(不会在数据库中物理显示),但是这张表是不能被操作的
使用其他表创建逻辑字段进行操控
    
2.方式2 
创建关系模型表进行关联相当于创建一张中间表关联两张表的id

1.基于db.Table创建多对多

# 关系表[这种表,无法提供给flask进行数据库操作,仅仅用于在数据库记录两个模型之间的关系,flask根本不知道当前表与表之间的关系到底是什么](需要在关联表中设置关联属性逻辑字段才能进行操作)


1.创建方式使用
	db.Table('表名','字段1','字段2',.....)
当创建后还需要在表中创建逻辑字段让falsk知道当前怎么操作orm

2.创建逻辑字段
db.relationship('绑定表','关系表','反向关系','sql查询方式')
course_list = db.relationship('Course', secondary=student_course_table, backref=backref('student_list',lazy='dynamic'),
                                  lazy='dynamic')


使用db.Table的缺陷:无法通过主模型进行操作db.Table外键之外的字段,例如(在db.Table中创建的其他字段 类似购买时间)

解决: 如果使用2个模型是多对多的关联关系,如果使用python操作关系表数据,则可以把关联关系使用的第三个模型来创建什么,不在使用db.Table创建表关系,将关系模型绑定的关系拆分为 2个1对多进行操作

表关系

# 关系表[这种表,无法提供给flask进行数据库操作,仅仅用于在数据库记录两个模型之间的关系](需要在关联表中设置关联属性逻辑字段)
student_course_table = db.Table(
    'student_course_table',  # 表名
    db.Column('id', db.Integer, primary_key=True, comment='主键id'),  # 主键字段
    db.Column('student_id', db.Integer, db.ForeignKey('student.id'), comment='学生表外键id'),  # 绑定关系
    db.Column('course_id', db.Integer, db.ForeignKey('course.id'), comment='课程表外键id'),  # 绑定关系
)

from sqlalchemy.orm import backref


# 创建一张表 学生
class Student(db.Model):
    __tablename__ = 'student'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(15), index=True, comment='学生名称')

    # 设置关联属性,可以让flask 进行操作(与之前1对1,1对多不同,需要设置uselist属性,而要设置第三张多对多的关系表名)
    # 参数1:当前表与另外一张主表绑定关系
    # 参数2:secondary= 设置多对多的第三张关系表,告诉flask这个学生与课程两张主表的关系从哪里获取
    # 参数3:backref = 需设置反向关系查询字段,方便课程表对象.反向查询字段,查询当前课程与多少学生存在关系
    # 参数4:lazy='dynamic'(被动读取) 设置sql的读取关系(主动还是被动) 需要主动all()执行sql,如果不适用,那么不需要使用all()执行sql
    course_list = db.relationship('Course', secondary=student_course_table, backref=backref('student_list',lazy='dynamic'),
                                  lazy='dynamic')

    def __repr__(self):
        return f'<{self.name} - {self.__class__.__name__}>'


# 创建一张表 课程
class Course(db.Model):
    __tablename__ = 'course'
    id = db.Column(db.Integer, primary_key=True)
    course_name = db.Column(db.String(15), index=True, comment='课程名称')

    def __repr__(self):
        return f'<{self.course_name} - {self.__class__.__name__}>'
    
'''
1.db.relationship() 属性设置的最为重要
2.作用只是为了让flask进行识别两个主表之间的关系,和关系表表的关系
3.查询,删除,修改,新增都会使用到db.relationship() 创建的字段
class A(db.Model):
	....
	a = db.relationship('B',backref=backref('b'))


class B(db.Model):
	....
1.会在A表中创建名称为a的查询字段
2.会在B表中创建名称为b的查询字段,因为backref=backref('b')才会创建
3.这两个字段都会在表中创建只是为了查询使用
4.那么可以通过: A对象.a字段查询到B表中的关系内容
5.也可以通过: B对象.b字段查询到A表中的关系内容
6.查询关系内容的前提是,存在数据库绑定物理关系
'''

添加

# 1.添加操作 添加主模型数据,同时绑定另一个主模型数据,这个过程关系表会主动的将两个主模型数据的id写入,绑定关系
'''
        执行流程:
            1.先创建学生表信息
            2.在创建课程表信息
            3.在将创建的内容id写入到关系表中进行绑定
'''
    
方式1: 添加学生同时添加课程(关系主动绑定)
st = Student(
    name='wkx',
    course_list=[  # 通过关系数据(字段)将另一张表(课程)进行写入
        Course(course_name='python'),
        Course(course_name='flask框架'),
    ]
)
db.session.add(st)
db.session.commit()

方式2: 在新增学生的基础上添加原有课程,和新增课程
# 1.添加小红 提交数据
st = Student(
    name='xiaohong'
)
db.session.add(st)
db.session.commit()
# 2.获取小红的对象
st = Student.query.filter(Student.name == 'xiaohong').first()
# 3.给新增的小红添加课程id为2(已有)
st.course_list.append(Course.query.get(2))
# 4.给小红添加(新增)的课程
st.course_list.append(Course(course_name='python高级'))
# 5.提交数据
db.session.commit()

方式3:对已有的学生与课程进行绑定
 # 1.查询当前的学生id为1的对象
st = Student.query.get(1)
# 2.查询当前课程表中id为1和2的课程
course_list_all = Course.query.filter(Course.id.in_([1, 2])).all()
# 3.进扩展 st.course_list.extend 对绑定关系进行扩展合并
st.course_list.extend(course_list_all)
# 4.进行提交
db.session.commit()

查询

'''查询:主要的关键使用的还是db.relationship创建的字段'''
# 查询当前学生id为1报名的课程
st = Student.query.get(1)
print(st.course_list.all()) # 使用查询字段进行查询会通过关联表获取全部的报名课程信息

# 查询课程id1有哪些学生报名
cu =Course.query.get(1)
print(cu.student_list.all())

更新

'''更新数据 查询当前绑定的课程并修改绑定课程的名称(根据绑定课程修改用户的信息也可以)'''
st = Student.query.get(2)
for i in st.course_list.all():
i.course_name = '6677'
db.session.commit()

删除

'''删除数据 删除学生id为2的数据,也会将关系表中的关联关系进行删除(课程表不受影响)'''
st = Student.query.get(2)
db.session.delete(st)
db.session.commit()

2.基于关系模型的多对多

变为两个一对多,基于第三个模型(中间模型),变2个1对多

表模型


# 因为没有绑定关系,所以三张表都没什么关系。单独存在的
import datetime
from sqlalchemy.orm import backref


# 创建一张表 学生
class Student(db.Model):
    __tablename__ = 'student'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(15), index=True, comment='学生名称')

    def __repr__(self):
        return f'<{self.name} - {self.__class__.__name__}>'


# 创建一张表 课程
class Course(db.Model):
    __tablename__ = 'course'
    id = db.Column(db.Integer, primary_key=True)
    course_name = db.Column(db.String(15), index=True, comment='课程名称')

    def __repr__(self):
        return f'<{self.course_name} - {self.__class__.__name__}>'


class StudentCourse(db.Model):
    __tablename__ = 'student_course'
    id = db.Column(db.Integer, primary_key=True)
    # 绑定外键关系
    sid = db.Column(db.Integer, db.ForeignKey('student.id'), comment='学生表外键字段')
    cid = db.Column(db.Integer, db.ForeignKey('course.id'), comment='课程表外键字段')
    create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='购买时间')

    # 关联属性[给flask用的]反向查找时course表与student表对象进行使用
    # 正向查 当前表对学生表和课程表时 1的关系
    # 反向查 学生表与课程表查当前表 时多的关系 索引 uselist=True
    student = db.relationship('Student', uselist=False, backref=backref('to_relation_course', uselist=True,lazy='dynamic'))
    course = db.relationship('Course', uselist=False, backref=backref('to_relation_student', uselist=True,lazy='dynamic'))

添加

# 1.添加,在添加学生模型,同时添加课程模型(添加主模型时,添加另一个主模型)需要手动添加第三张表的关系
st = Student(
    name='wkx',
    to_relation_course=[ # 关系表中反向关系
        # 通过第三张表进行创建 执行关联的课程表关联字段,指定就是课程表对象进行创建
        # 比db.Table不同的是,需要手动的将第三张表的关系进行添加
        StudentCourse(course=Course(course_name='python1')), # 添加第三张表关系
        StudentCourse(course=Course(course_name='python2')),

    ]
)
db.session.add(st)
db.session.commit()

# 2.在已有课程的基础上,新增课程
# 1.查出当存在的学生对象
st = Student.query.filter(Student.name == 'wkx').first()
# 2.进行添加 通过extend进行添加
st.to_relation_course.extend([
    StudentCourse(course=Course.query.get(1)),  # 添加已经有的
    StudentCourse(course=Course(course_name='Go'))  # 新增的
])
db.session.commit()

# 3.对已有的学生,添加已有课程的记录
# 1.找到学生对象
st = Student.query.get(2)
# 2.找到课程为id1 和id2的对象列表
course_list = Course.query.filter(Course.id.in_([1,2])).all()
st.to_relation_course.extend([StudentCourse(course=i) for i in course_list])
db.session.commit()

查询

# 查询学生购买的课程
# 1.查询学生对象
st = Student.query.get(1)
# 2.通过反向查询字段查询到与当前学生有关系的课程关系(返回的第三关系表对象)
student_course_list = st.to_relation_course.all()
# 3.在通过第三关系表对象循环获取,课程表对象
course_list = [student_course_obj.course for student_course_obj in student_course_list]
print(course_list)

# 查看课程有学生购买了
# 1.查到课程表对象
cu = Course.query.get(3)
# 2.通过课程标对象.反向关系字段获取第三张表关系的(课程与学生关系对象)
student_course_list = cu.to_relation_student.all()
# 3.在通过第三关系表对象循环获取,学生对象
student_list = [i.student for i in student_course_list]
print(student_list)

#查询学生2购买的课程时间
st = Student.query.get(2)
for i in st.to_relation_course.all(): # 拿到关系(学生表与第三张关系表的关系)
    print(i.create_time) # 购买时间

更新

# 修改 通过课程id3 修改报名课程3的学生对象名称修改
cu = Course.query.get(3)
for i in cu.to_relation_student.all():
	i.student.name = 'xiugaixiugai'
db.session.commit()

删除

# 删除关于课程id为3的全部的学生关系
cu = Course.query.get(3)
for i in cu.to_relation_student.all():
	db.session.delete(i)
db.session.commit()
# 那么学生表 中 不会在有和课程id为3的存在关系,同时不会影响 课程表与学生表

逻辑外键

也被称为虚拟外键,主要在开发中减少数据库的性能消耗提升,代码的运行效率一般在项目中数据太大[千万级别的]就不会使用数据本身维护的物理外键,而使用,orm或者逻辑代码进行查询的逻辑外键,不使用数据提供的物理外键,会给数据带来一致性带来风险.



sqlalchemy设置物理外键的两种方式
1.在查询数据时临时指定逻辑外键的逻辑关系
2.在声明模型是执行外键映射关系

在查询数据时临时指定逻辑外键的逻辑关系

表关系

import datetime
# 创建一张表 学生
class Student(db.Model):
    __tablename__ = 'student_1'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(15), index=True, comment='学生名称')

    def __repr__(self):
        return f'<{self.name} - {self.__class__.__name__}>'


# 创建一张表 课程
class Course(db.Model):
    __tablename__ = 'course_1'
    id = db.Column(db.Integer, primary_key=True)
    course_name = db.Column(db.String(15), index=True, comment='课程名称')

    def __repr__(self):
        return f'<{self.course_name} - {self.__class__.__name__}>'


class StudentCourse(db.Model):
    __tablename__ = 'student_course_1'
    id = db.Column(db.Integer, primary_key=True)
    sid = db.Column(db.Integer, index=True, comment='学生表id')
    cid = db.Column(db.Integer, index=True, comment='课程表id')
    create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='购买时间')


使用

# 1.添加测试数据 添加学生和课程对象
st = Student(name='wkx1')
st1 = Student(name='wkx2')
st2 = Student(name='wkx3')
st3 = Student(name='wkx4')
db.session.add_all([st, st1, st2, st3])
cu = Course(course_name='python1')
cu1 = Course(course_name='python2')
cu2 = Course(course_name='python3')
cu3 = Course(course_name='python4')
db.session.add_all([cu, cu3, cu2, cu1])

# 2.给添加学生与课程添加关系
data = [
    StudentCourse(cid=6, sid=5),
    StudentCourse(cid=7, sid=5),
    StudentCourse(cid=8, sid=5),
    StudentCourse(cid=9, sid=5),
    StudentCourse(cid=6, sid=6),
    StudentCourse(cid=7, sid=6),
    StudentCourse(cid=8, sid=6),
    StudentCourse(cid=9, sid=6),
    StudentCourse(cid=6, sid=7),
    StudentCourse(cid=7, sid=7),
    StudentCourse(cid=8, sid=8),
    StudentCourse(cid=9, sid=8)
]
db.session.add_all(data)
db.session.commit()


# 查询学生id为5的都报名了那些课程
# 1.手动关联关系
# 1.1 从第三方表中获取与学生id为5的关系的课程
course_student = StudentCourse.query.filter(StudentCourse.sid == 5).all()
# 1.2 获取课程id列表
course_id_list = [i.cid for i in course_student]
# 1.3 通过课程id 从课程表中 获取课程对象
course_obj_list = Course.query.filter(Course.id.in_(course_id_list)).all()
print(course_obj_list)

# 2.基于临时外键逻辑外键关联查询(多个模型通过join方式临时关联)
# 主模型.query.join(从模型,关系语句)
# 主模型.query.join(从模型,主模型.id==从模型.外键)
# 查询学生id为5的都报名了那些课程
# Student.query.join(StudentCourse, Student.id == StudentCourse.sid) 将学生表与StudentCourse进行join一起
# Student.id,Student.name,StudentCourse.cid 查询什么样的数据 查询的是学生id 学生的名字 课程id 这个方法与django orm中的 values一样显示一些需要的内容
data = Student.query.join(
    StudentCourse, Student.id == StudentCourse.sid).with_entities(
  Student.id,Student.name,StudentCourse.cid).filter(Student.id==5).all()
print(data)

# 再将课程join进来 获取课程名称
data = Student.query.join(
    StudentCourse, Student.id == StudentCourse.sid).join(
    Course,Course.id == StudentCourse.cid
).with_entities(
    Student.id,Student.name,StudentCourse.cid,Course.course_name).filter(Student.id==5).all()
print(data)

在声明模型是声明外键关系

表模型

import datetime
# 创建一张表 学生
class Student(db.Model):
    __tablename__ = 'student_1'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(15), index=True, comment='学生名称')

    def __repr__(self):
        return f'<{self.name} - {self.__class__.__name__}>'


# 创建一张表 课程
class Course(db.Model):
    __tablename__ = 'course_1'
    id = db.Column(db.Integer, primary_key=True)
    course_name = db.Column(db.String(15), index=True, comment='课程名称')

    def __repr__(self):
        return f'<{self.course_name} - {self.__class__.__name__}>'


class StudentCourse(db.Model):
    __tablename__ = 'student_course_1'
    id = db.Column(db.Integer, primary_key=True)
    sid = db.Column(db.Integer, index=True, comment='学生表id')
    cid = db.Column(db.Integer, index=True, comment='课程表id')
    create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='购买时间')

    # 设置关联属性[比原来物理外键设置多了两个属性 primaryjoin与foreign_keys]
    student = db.relationship('Student', uselist=False,
                              backref=backref('to_relation_course', uselist=True, lazy='dynamic'),
                              primaryjoin="Student.id == StudentCourse.sid ",  # 两表进行join
                              foreign_keys='StudentCourse.sid'  # 在确定当前这张表那个是外键
                              )
    course = db.relationship('Course', uselist=False,
                             backref=backref('to_relation_student', uselist=True, lazy='dynamic'),
                             primaryjoin="Course.id == StudentCourse.cid ",  # 两表进行join
                             foreign_keys='StudentCourse.cid'  # 在确定当前这张表那个是外键
                             )
# 这三张表还是不存物理外键关系

使用

# 1.添加测试数据 添加学生和课程对象
st = Student(name='wkx1')
st1 = Student(name='wkx2')
st2 = Student(name='wkx3')
st3 = Student(name='wkx4')
db.session.add_all([st, st1, st2, st3])
cu = Course(course_name='python1')
cu1 = Course(course_name='python2')
cu2 = Course(course_name='python3')
cu3 = Course(course_name='python4')
db.session.add_all([cu, cu3, cu2, cu1])

# 2.给添加学生与课程添加关系
data = [
    StudentCourse(cid=6, sid=5),
    StudentCourse(cid=7, sid=5),
    StudentCourse(cid=8, sid=5),
    StudentCourse(cid=9, sid=5),
    StudentCourse(cid=6, sid=6),
    StudentCourse(cid=7, sid=6),
    StudentCourse(cid=8, sid=6),
    StudentCourse(cid=9, sid=6),
    StudentCourse(cid=6, sid=7),
    StudentCourse(cid=7, sid=7),
    StudentCourse(cid=8, sid=8),
    StudentCourse(cid=9, sid=8)
]
db.session.add_all(data)
db.session.commit()


# 查询学生id为5的全部报考课程
# 1.获取对象
st = Student.query.get(5)
# 2.通过关联字段获取当前第三张表的对象
studentcourse_list = st.to_relation_course.all()
# 3.通过第三张表对象获取课程对象列表
course_obj_list = [i.course for i in studentcourse_list]
print(course_obj_list)


# 操作方式与存在物理外键的方式是相同的