【Flask从入门到精通:第十课:数据基本查询、关联查询】

news/2024/9/23 9:24:46/

基本查询

SQLAlchemy常用的查询过滤器

过滤器说明
filter()把过滤器添加到原查询上,返回一个新查询
filter_by()把等值过滤器添加到原查询上,返回一个新查询
limit()使用指定的值限定原查询返回的结果数量
offset()设置结果范围的开始位置,偏移原查询返回的结果,返回一个新查询
order_by()根据指定条件对原查询结果进行排序,返回一个新查询
group_by()根据指定条件对原查询结果进行分组,返回一个新查询

SQLAlchemy常用的查询结果方法

方法说明
all()列表形式返回查询的所有结果
first()返回查询的第一个结果,模型对象,如果未查到,返回None
first_or_404()返回查询的第一个结果,模型对象,如果未查到,通过abort 返回404异常
get()返回指定主键对应的模型对象,如不存在,返回None
get_or_404()返回指定主键对应的行,如不存在,abort 返回404
count()返回查询结果的数量
paginate()返回一个Paginate分页器对象,它包含指定范围内的结果
having()返回结果中符合条件的数据,必须跟在group by后面,其他地方无法使用。

get():参数为主键,表示根据主键查询数据,如果主键不存在返回None

python">@app.route("/get")
def get():"""按主键获取一条"""# student = Student.query.get({"id": 5})# student = Student.query.get((5,))# student = db.session.query(Student).get(5)student = Student.query.get(5)print(student)return "ok"

课堂代码:

python"># 前面代码省略@app.route("/")
def index():return "ok"@app.route("/create")
def create_table():db.create_all() # 为项目中被识别的所有模型创建数据表return "ok"@app.route("/drop")
def drop_table():db.drop_all()   # 为项目中被识别的所有模型删除数据表return "ok"@app.route("/query")
def query():query1 = Student.query   # 简写操作query2 = db.session.query(Student)print(type(query1), query1)print(type(query2), query2)return "ok"@app.route("/get")
def get():"""get根据主键获取数据"""    # student1 = Student.query.get({"id":10})student1 = Student.query.get(30)# student2 = db.session.query(Student).get({"id":10})student2 = db.session.query(Student).get(30)# 结果是模型对象print(type(student1), student1)print(type(student2), student2)# 查询不到结果,则返回值为Noneif student1:print(student1.name, student1.age)if student2:print(student2.name, student2.age)return "ok"if __name__ == '__main__':app.run()

all()返回查询到的所有对象

python">模型类.query.all()"""获取多个数据"""
student = Student.query.all()
print(student) # [dong<Student>, 小红<Student>, wang<Student>, chen<Student>, zhou<Student>, tang<Student>, wu<Student>, qian<Student>, liu<Student>, li<Student>, sun<Student>]student = Student.query.filter(Student.id<5).all()  # 没有结果返回空列表[]
print(student) # [dong<Student>, 小红<Student>, wang<Student>]# all()的返回值是一个python列表,可以直接使用切片,与django的QuerySet完全不是一回事。
student = Student.query.filter(Student.id < 5).all()[:-1]  # 没有结果返回空列表[]
print(student) # [dong<Student>, 小红<Student>]

count 返回结果的数量

python"># 返回结果的数量
ret = Student.query.filter(Student.id < 5).count()
print(f"ret={ret}")

first()返回查询到的第一个对象【first获取一条数据,all获取多条数据】

python">模型类.query.first()"""获取第一个数据"""
student = Student.query.first()
print(student)student = Student.query.filter(Student.id==5).first() # 没有结果返回None
print(student)

filter条件查询,支持各种运算符和查询方法或者模糊查询方法。

返回名字结尾字符为g的所有数据。

python"># 模糊查询
# 使用163邮箱的所有用户
student_list = Student.query.filter(Student.email.endswith("@163.com")).all()
print(student_list)# 姓名以"zh"开头的
student_list = Student.query.filter(Student.name.startswith("zh")).all()
print(student_list)# 名字中带有"a"字母的数据
student_list = Student.query.filter(Student.name.contains("a")).all()
print(student_list)"""单条件比较"""
# 则需要指定条件格式为: filter(模型.字段 比较运算符 值)。
# 运算符可以是: ==表示相等, !=不相等,> 表示大于  < 表示小于,>=大于等于,<=小于等于
# student_list = Student.query.filter(Student.age > 18).all()
# print(student_list) # [wang<Student>, chen<Student>, zhou<Student>,...]"""多条件比较"""
# 要求多个条件都要满足,相当于逻辑查询中的 并且(and)!!
student_list = Student.query.filter(Student.age > 18, Student.sex == True).all()
print(student_list) # [wang<Student>, chen<Student>, qian<Student>, liu<Student>]

filter_by精确条件查询

filter_by 只支持字段的值是否相等的情况,对于大于、等于、等等其他条件是不支持的。

例如:返回age等于22的学生

python"># 单条件
student_list = Student.query.filter_by(age=22).all()  # 字段添加不需要附带模型类
print(student_list)# 多条件
student_list = Student.query.filter_by(age=22,sex=True).all()
print(student_list)

练习:

python">查询所有男生[Student.sex==True]数据查询id4的学生[3种方式]查询年龄等于22的所有学生数据查询name为小白的学生数据查询20岁的男生

代码:

python">from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()
app = Flask(__name__, template_folder="templates", static_folder="static")# 配置
app.config.update({"DEBUG": True,"SQLALCHEMY_DATABASE_URI": "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4",# 如果使用pymysql,则需要在连接时指定pymysql# "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 动态追踪修改设置,如未设置只会提示警告,设置False即可"SQLALCHEMY_TRACK_MODIFICATIONS": False,# ORM执行SQL查询时是哦否显示原始SQL语句,debug模式下可以开启"SQLALCHEMY_ECHO": True,
})db.init_app(app)class Student(db.Model):"""学生管理"""__tablename__ = "db_student" # 表名# 属性名 = db.Column(字段类型, 字段列约束选项)# 如果SQL语句中的字段名在python中是关键字/保留字,则建议改写绑定字段名# 属性名 = db.Column("字段名", 字段类型, 字段列约束选项)id = db.Column("student_id", db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(15), index=True, comment="姓名")age = db.Column(db.SmallInteger, comment="年龄")sex = db.Column(db.SmallInteger, comment="性别")email = db.Column(db.String(255), unique=True, comment="邮箱地址")money = db.Column(db.Numeric(10,2), default=0.0, comment="钱包")# repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息def __repr__(self):return f"<{self.__class__.__name__} {self.name}>"# 所有的模型必须直接或间接继承于db.Model
class Course(db.Model):"""课程数据模型"""__tablename__ = "db_course""""# 原生SQL语句create table db_course (id int primary key auto_increment comment "主键",name varchar(64) comment "课程",price NUMERIC(7,2) comment "价格",unique (name));# 字段根据SQL语句来声明"""id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(64), unique=True, comment="课程")price = db.Column(db.Numeric(7, 2), comment="价格")# repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"class Teacher(db.Model):"""老师数据模型"""__tablename__ = "db_teacher""""# 原生SQL语句create table db_teacher (id int primary key auto_increment comment "主键",name varchar(64) comment "姓名",option enum("讲师", "助教", "班主任") comment "职位",unique (`name`));# 字段根据SQL语句来声明"""id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(64), unique=True, comment="姓名")option = db.Column(db.Enum("讲师", "助教", "班主任"), default="讲师")def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"@app.route("/")
def index():return "ok"@app.route("/create")
def create_table():db.create_all() # 为项目中被识别的所有模型创建数据表return "ok"@app.route("/drop")
def drop_table():db.drop_all()   # 为项目中被识别的所有模型删除数据表return "ok"@app.route("/exam")
def exam():# 查询所有男生[Student.sex == True]数据# student_list = Student.query.filter(Student.sex == True).all()# print(student_list)# 查询id为4的学生[3种方式]# 1. get# student = Student.query.get(4)# print(student)# 2. filter+first# student = Student.query.filter(Student.id == 4).first()# print(student)# 3. filter_by + first# student = Student.query.filter_by(id=4).first()# print(student)# 查询年龄等于20的所有学生数据# student_list = Student.query.filter(Student.age == 20).all()# print(student_list)# 查询name为小白的学生数据# student = Student.query.filter(Student.name == "小白").first()# print(student)# 查询20岁的男生student_list = Student.query.filter(Student.age == 20, Student.sex == True).all()print(student_list)return "ok"if __name__ == '__main__':app.run()

多条件查询

逻辑与,需要导入and_,返回and_()条件满足的所有数据

python">from sqlalchemy import and_
Student.query.filter(and_(Student.name!='wang',Student.email.endswith('163.com'))).all()# # and_(条件1,条件2,....)  等价于  filter(条件1,条件2,.....)
# # age > 18 and email like "%163.com"
# # student_list = Student.query.filter(Student.age > 18, Student.email.endswith("163.com")).all()
# 
# student_list = Student.query.filter(
#     and_(
#         Student.age > 18,
#         Student.email.endswith("163.com")
#     )
# ).all()

逻辑或,需要导入or_

python">from sqlalchemy import or_
Student.query.filter(or_(Student.name!='wang',Student.email.endswith('163.com'))).all()# 查询年龄在20岁,使用的邮箱是qq或者163邮箱的
student_list = Student.query.filter(Student.age == 20,or_(Student.email.endswith("qq.com"),Student.email.endswith("163.com"))
).all()# 复合条件的查询情况
# 查询年龄>17岁的女生或者年龄>18岁的男生
student_list = Student.query.filter(or_(and_(Student.age > 17, Student.sex == False),and_(Student.age > 18, Student.sex == True),)
).all()
print(student_list)print(student_list)

逻辑非,返回名字不等于"小白"的所有数据

python">Student.query.filter(Student.name!='小白').all()

not_ 相当于取反

python">from sqlalchemy import not_
Student.query.filter(not_(Student.name=='小白')).all()# # 查询年龄不等于22
# student_list = Student.query.filter(Student.age != 22).all()
# print(student_list)
# student_list = Student.query.filter(not_(Student.age==22)).all()
# print(student_list)

in_范围查询

python"># 查询id是 5, 7, 10 的学生信息
student_list = Student.query.filter(Student.id.in_([5, 7, 10])).all()
print(student_list)# 查询id不是 1 3 5 的学生信息
student_list = Student.query.filter(not_(Student.id.in_([1, 3, 5]))).all()
print( student_list )

is_判断值查询

python">    """判断值查询"""student_list = Student.query.filter(Student.email.is_(None)).all()print(student_list)student_list = Student.query.filter(Student.email == None).all()print(student_list)

order_by 排序

python"># 倒序[值从大到小]
student_list = Student.query.order_by(Student.id.desc()).all()
# 升序[值从小到大]
student_list = Student.query.order_by(Student.id.asc()).all()# 多字段排序[第一个字段值一样时,比较第二个字段,进行排序]
student_list = Student.query.order_by(Student.money.asc(), Student.age.asc(), Student.id.asc()).all()
print(student_list)

count统计

python"># 查询age>=19的男生的数量
from sqlalchemy import and_
# ret = Student.query.filter( and_(Student.age>=19,Student.sex==True) ).count()
ret = Student.query.filter( Student.age>=19, Student.sex==True ).count()

对结果进行偏移量和数量的限制

python"># 查询年龄最大的3个学生
student_list = Student.query.order_by(Student.age.desc()).limit(3).all()
print(student_list)# 查询年龄排第4到第7名的学生
student_list = Student.query.order_by(Student.age.desc()).offset(3).limit(4).all()
print(student_list)# 查询年龄最小的3个人
student_list = Student.query.order_by(Student.age.asc()).limit(3).all()
print(student_list)

SQL

# 查询年龄最大的3个学生
# select * from db_student order by age desc limit 3;# 查询年龄排第4到第7名的学生
select * from db_student order by age desc limit 3, 4;
# select * from db_student order by age desc limit 4 offset 3;# 查询年龄最小的3个人
# select * from db_student order by age asc limit 3;

练习

python"># 查询age是18 或者 使用163邮箱的所有学生
Student.query.filter(or_(Student.age==18,Student.email.endswith("163.com"))).all()
# 查询id为 [1, 3, 5, 7, 9] 的学生列表
student_list = Student.query.filter(Student.id.in_([1, 3, 5, 7, 9])).all()
print(student_list)

分页器

manage.py,代码:

python">from flask import Flask
from flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)
class Config(object):DEBUG = True# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 动态追踪修改设置,如未设置只会提示警告SQLALCHEMY_TRACK_MODIFICATIONS = False# ORM运行时会显示ORM生成的原始SQL语句[调试]SQLALCHEMY_ECHO = Trueapp.config.from_object(Config)"""模型类定义"""
db = SQLAlchemy(app=app)class Student(db.Model):"""学生信息模型"""# 声明与当前模型绑定的数据表名称__tablename__ = "db_student"id = db.Column(db.Integer, primary_key=True,comment="主键")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}<{self.__class__.__name__}>"# 所有的模型必须直接或间接继承于db.Model
class Course(db.Model):"""课程数据模型"""__tablename__ = "db_course"id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(64), unique=True, comment="课程")price = db.Column(db.Numeric(7, 2), comment="价格")def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"class Teacher(db.Model):"""老师数据模型"""__tablename__ = "db_teacher"id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(64), unique=True, comment="姓名")option = db.Column(db.Enum("讲师", "助教", "班主任"), default="讲师")def __repr__(self):return f"{self.name}<{self.__class__.__name__}>"from flask import request,render_template@app.route("/")
def index():# 分页器page  = int(request.args.get("page", 1))size = int(request.args.get("size", 3))pagination = Student.query.paginate(page, size)print(pagination)"""from flask_sqlalchemy import Pagination# 常用属性total  总数据量items  每一页数据项列表pages  总页码===> math.ceil( total/per_page )# 常用方法prev      上一页分页对象prev_num  上一页页码has_prev  是否有上一页next      下一页分页对象next_num  下一页页码has_next  是否有下一页"""print(pagination.items)  # 当前页的数据项列表print(pagination.total)  # 总数据量print(pagination.pages)  # 总页码数量print(pagination.prev_num)  # 上一页页码print(pagination.next_num)  # 下一页页码print(pagination.has_prev)  # 是否有上一页print(pagination.has_next)  # 是否有下一页print(pagination.prev())    # 上一页的分页对象print(pagination.next())    # 下一页的分页对象# """前后端分离"""# data = {#     "page": pagination.page, # 当前页码#     "pages": pagination.pages, # 总页码#     "has_prev": pagination.has_prev, # 是否有上一页#     "prev_num": pagination.prev_num, # 上一页页码#     "has_next": pagination.has_next, # 是否有下一页#     "next_num": pagination.next_num, # 下一页页码#     "items": [{#         "id": item.id,#         "name": item.name,#         "age": item.age,#         "sex": item.sex,#         "money": item.money,#     } for item in pagination.items]# }# return data"""前后端不分离"""return render_template("list.html",**locals())if __name__ == '__main__':with app.app_context():db.create_all()app.run()

list.html,代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>.page a,.page span{padding: 2px 6px;color: #fff;background: #6666ff;text-decoration: none;}.page span{color: #fff;background: orange;}</style>
</head>
<body><table border="1" align="center" width="600"><tr><th>ID</th><th>age</th><th>name</th><th>sex</th><th>money</th></tr>{% for student in pagination.items %}<tr><td>{{ student.id }}</td><td>{{ student.age }}</td><td>{{ student.name }}</td><td>{{ "男" if student.sex else "女" }}</td><td>{{ student.money }}</td></tr>{% endfor %}<tr align="center"><td colspan="5" class="page">{% if pagination.has_prev %}<a href="?page=1">首  页</a><a href="?page={{ pagination.page-1 }}">上一页</a><a href="?page={{ pagination.page-1 }}">{{ pagination.page-1 }}</a>{% endif %}<span>{{ pagination.page }}</span>{% if pagination.has_next %}<a href="?page={{ pagination.page+1 }}">{{ pagination.page+1 }}</a><a href="?page={{ pagination.page+1 }}">下一页</a><a href="?page={{ pagination.pages }}">尾  页</a>{% endif %}</td></tr></table>
</body>
</html>

聚合分组

分组查询和分组查询结果过滤

一般分组都会结合聚合函数来一起使用。SQLAlchemy中所有的聚合函数都在func模块中声明的。

python">from sqlalchemy import func
函数名说明
func.count统计总数
func.avg平均值
func.min最小值
func.max最大值
func.sum求和

代码:

python">"""聚合函数"""
from sqlalchemy import func
# 获取所有学生的money总数
# SELECT sum(db_student.money) AS sum_1 FROM db_student LIMIT %s
# ret = db.session.query(func.sum(Student.money)).first()[0]
# print(ret) # 3998.0
# # 查询女生的数量
# ret = db.session.query(func.count(Student.id)).filter(Student.sex==False).first()[0]
# print(ret) # 7
# # 查询所有学生的平均年龄
# ret = db.session.query(func.avg(Student.age)).first()[0]
# print(ret) # 18.2727"""
聚合分组
在聚合分组的情况下,db.session.query中的参数只能是被分组的字段或者是被聚合的数据
"""
# # 查询当前所有男生女生的数量
# ret = db.session.query(Student.sex,func.count(Student.id)).group_by(Student.sex).all()
# print(ret) # [(False, 7), (True, 4)]# # 查询各个年龄段的学生数量
# ret = db.session.query(Student.age, func.count(Student.id)).group_by(Student.age).all()
# print(ret) # [(15, 2), (13, 1), (22, 4), (19, 1), (18, 1), (16, 1), (17, 1)]
#
# # 查看当前男生女生的平均年龄
# ret = db.session.query(Student.sex, func.avg(Student.age)).group_by(Student.sex).all()
# ret = [{"sex":"男" if item[0] else "女","age":float(item[1])} for item in ret]
# print(ret) # [{'sex': '女', 'age': 18.0}, {'sex': '男', 'age': 18.75}]# # 分组后的过滤操作 having
# # 在所有学生中,找出各个年龄中拥有最多钱的同学,并在这些同学里面筛选出money > 500的数据
# subquery = func.max(Student.money)
# print(subquery) # max(db_student.money)
# ret = db.session.query(Student.age, subquery).group_by(Student.age).having(subquery > 500).all()
# print(ret)  # [(18, Decimal('1000.00')), (22, Decimal('26000.00')), (23, Decimal('1998.00'))]"""
多字段分组字段1   字段21      32      4分组如下:13142324
"""
# 各个年龄里,男生和女生的money总数
subquery = func.sum(Student.money)
ret = db.session.query(Student.sex, Student.age, subquery).group_by(Student.sex, Student.age).all()
print(ret) # [(False, 15, 1000.0), (False, 13, 600.0), (True, 15, 0.0), (True, 22, 1998.0), (False, 19, 0.0), (False, 22, 400.0), (False, 18, 0.0), (True, 16, 0.0), (False, 17, 0.0)]

SQL方法中的关键字顺序:

python">模型.query.   // db.session.query.
filter/ filter_by
group by
having
order_by
limit  offset
all / get / first / count / paginate

执行原生SQL语句

python">"""执行原生SQL语句"""
# # 查询多条数据
# ret = db.session.execute("select * from db_student").fetchall()
# print(ret)
# # 查询一条数据
# ret = db.session.execute("select * from db_student").fetchone()
# print(ret)"""name  age  achievement80小明   17   8183group_concat 逗号合并小明   17   80,81,83concat  字符串拼接小明   17   808183"""# # 添加数据
# db.session.execute("insert db_student (name,age,sex,email,money) select name,age,sex,concat(now(),email),money from db_student")
# db.session.commit()# # # 更新/删除
# db.session.execute("UPDATE db_student SET money=(db_student.money + %s) WHERE db_student.age = %s" % (200, 22))
# db.session.commit()"""分组合并"""
# 统计各个年龄段的学生人数,并记录对应年龄段的学生ID
ret = db.session.execute("select age,count(id),group_concat(id) from db_student group by age").fetchall()
print(ret)
return "ok"

关联查询

常用的SQLAlchemy关系选项

选项名说明
backref在关系的另一模型中添加反向引用,用于设置外键名称,在1查多的
primary join明确指定两个模型之间使用的连表条件, 用于1对1 或者1对多连表中
lazy指定如何加载关联模型数据的方式,用于1对1或1对多链表中。参数值:
select(立即加载,查询所有相关数据显示,相当于lazy=True)
subquery(立即加载,但使用子查询)
dynamic(不立即加载,但提供加载记录的查询对象)
uselist指定1对1或1对多连表时,返回的数据结果是模型对象还是模型列表,如果为False,不使用列表,而使用模型对象。
1对1或多对1关系中,需要设置relationship中的uselist=Flase,1对多或多对多关系中,需要设置relationshio中的uselist=True。
secondary指定多对多关系中关系表的名字。
多对多关系中,需建立关系表,设置 secondary=关系表
secondary join在SQLAlchemy中无法自行决定时,指定多对多关系中的二级连表条件,绑定主外键。

范式理论:一套提供给数据库开发者设置标准、规范的数据库的理论。

1NF. 数据不可再分,必须保证原子性。数据的值保证可以方便存储,不可再分。
2NF. 数据不能重复,必须保证唯一性。必须使用主键来进行区分每一行数据。
3NF. 数据不能冗余,必须保证关联性。冗余的数据必须使用另外的数据表存放,并与当前表进行关联。基于实际业务的角度出发,设计出违背了范式理论的表结构。
逆范式:以空间换时间class table:id   name      teacher      1    301班      李老师       2    302班      王老师      student table:id   name        class_id1    xiaoming    12    xiaohong    23    xiaohui     3

三范式+逆范式

第三范式:数据不能冗余,把关联性不强的数据可以移除到另一个表中。使用外键进行管理。

python">11:把主表的主键放到附加表中作为外键存在。商品信息表id   name        price   image    描述    售后   配置    包装1    PC-1-gr      100    1.png    2    PC-1-re      100    1.png3    PC-1-he      100    1.png4    PC-1-bu      100    1.png1对多:把主表(1) 的主键放到附加表()作为外键存在。订单信息表        订单详情表1个订单     ----> 多个商品多对多:把主表()的主键和附加表的()主键,放到第三方表(关系表)中作为外键。用户表            课程表1  xiaoming      1   python2  xiaohong      2   django3  xiaolong      3   flask用户与课程的购买关系表user_id   course1          11          21          3

模型之间的关联

一对一

常见的业务:主表和详情表(用户、会员、学生、商品、文章、主机)

python">class Student(db.Model):"""个人信息主表"""....# 关联属性,这个不会被视作表字段,只是模型对象的属性。# 因为StudentInfo和Student是一对一的关系,所以uselist=False表示关联一个数据info = db.relationship("StudentInfo", uselist=False, backref="own")class StudentInfo(db.Model):"""个人信息附加表"""# 外键,# 如果是一对一,则外键放在附加表对应的模型中# 如果是一对多,则外键放在多的表对象的模型中# sid = db.Column(db.Integer, db.ForeignKey(Student.id),comment="外键")sid = db.Column(db.Integer, db.ForeignKey("student表名.主键"),comment="外键")
关联属性声明在主模型中【最常用】

代码:

python">import json
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import funcdb = SQLAlchemy()
app = Flask(__name__, template_folder="templates", static_folder="static")# 配置
app.config.update({"DEBUG": True,"SQLALCHEMY_DATABASE_URI": "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4",# 如果使用pymysql,则需要在连接时指定pymysql# "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 动态追踪修改设置,如未设置只会提示警告,设置False即可"SQLALCHEMY_TRACK_MODIFICATIONS": False,# ORM执行SQL查询时是哦否显示原始SQL语句,debug模式下可以开启"SQLALCHEMY_ECHO": True,
})db.init_app(app)class Student(db.Model):"""学生管理"""__tablename__ = "tb_student"  # 表名id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(15), index=True, comment="姓名")age = db.Column(db.SmallInteger, comment="年龄")sex = db.Column(db.SmallInteger, comment="性别")email = db.Column(db.String(255), unique=True, comment="邮箱地址")money = db.Column(db.Numeric(10,2), default=0.0, comment="钱包")# 模型的关联属性,不会在数据表创建字段# 因为StudentInfo和Student是一对一的关系,所以uselist=False表示关联一个数据info = db.relationship("StudentInfo", uselist=False, backref="student")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"name": self.name,"age": self.age,"sex": self.sex,"email": self.email,"money": float(self.money),}class StudentInfo(db.Model):__tablename__ = "tb_student_info"  # 表名id = db.Column(db.Integer, primary_key=True, comment="主键")# 附加表的外键,就是主表的主键# 如果是一对一,则外键放在附加表对应的模型中# 如果是一对多,则外键放在多的附加表对应的模型中sid = db.Column(db.Integer, db.ForeignKey("tb_student.id"), comment="外键")  # mysql字段# student = db.relationship("Student", uselist=False, backref="info")     # ORM关联属性mobile = db.Column(db.String(15), index=True, comment="手机号码")address = db.Column(db.String(255), nullable=True, comment="家庭地址")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"sid": self.sid,"mobile": self.mobile,"address": self.address,}@app.route("/create")
def create_table():db.create_all() # 为项目中被识别的所有模型创建数据表return "ok"@app.route("/drop")
def drop_table():db.drop_all()   # 为项目中被识别的所有模型删除数据表return "ok"@app.route("/a1")
def a1():"""添加操作"""# 添加主表信息的时候通过关联属性db.relationship同步添加附件表信息student = Student(name="xiaolan01",age=16,sex=False,money=10000,email="xiaolan01@qq.com",info=StudentInfo(address="北京市昌平区百沙路203号",mobile="13312345672"))db.session.add(student)db.session.commit()return "ok"@app.route("/a2")
def a2():"""添加操作"""# 假设已经存在主表信息,后面再补充附加表信息# student = Student(#     name="xiaolan03",#     age=16,#     sex=False,#     money=10000,#     email="xiaolan03@qq.com",# )## db.session.add(student)# db.session.commit()# 上面代码先执行了,现有1个学生没有附加信息的,我们可以在后续代码通过查询主表的主键,补充附加表数据student = Student.query.get(3)if not student.info:"""添加附加表数据""""""方式1"""# student.info = StudentInfo(mobile=13300010002, address="北京市昌平区百沙路205号",)# db.session.commit()"""方式2"""# info = StudentInfo(#     student=student,  # 关联属性#     mobile=13300010002,#     address="北京市昌平区百沙路205号",# )## db.session.add(info)# db.session.commit()"""方式3"""info = StudentInfo(sid=student.id,  # 外键mobile=13300010002,address="北京市昌平区百沙路205号",)db.session.add(info)db.session.commit()return "ok"@app.route("/a3")
def a3():"""添加操作"""# 添加附加模型数据的同时,把主模型也进行添加info = StudentInfo(mobile=13300010003,address="北京市昌平区百沙路206号",student=Student(name="xiaolan04",age=17,sex=False,money=10000,email="xiaolan04@qq.com",),)db.session.add(info)db.session.commit()return "ok"@app.route("/g1")
def g1():"""查询操作"""# # 正向关联----> 从主模型查询外键模型# student = Student.query.get(2)# print(student.name)          # xiaolan01# # 如果主模型没有调用关联属性查询附加模型对象,则ORM不会执行查询关联模型的SQL语句# print(student.info)          # {"id": 2, "sid": 2, "mobile": "13312345672", "address": "北京市昌平区百沙路203号"}# print(student.info.address)  # 北京市昌平区百沙路203号# 反向关联----> 从外键模型查询主模型student_info = StudentInfo.query.filter(StudentInfo.mobile == "15012345678").first()print(student_info.address)       # 北京市昌平区百沙路206号# 如果附加模型没有调用关联属性查询主模型对象,则ORM不会执行查询主模型的SQL语句print(student_info.student)       # {"id": 4, "name": "xiaolan04", "age": 17, "sex": 0, "email": "xiaolan04@qq.com", "money": 10000.0}print(student_info.student.name)  # xiaolan04print(student_info.sid)           # 4return "ok"@app.route("/u1")
def u1():"""修改数据"""# # 通过主模型使用关联属性修改附加模型的数据# student = Student.query.get(2)# student.info.address = "广州市天河区天河东路103号"# db.session.commit()# 也可以通过附加模型直接修改主模型的数据student_info = StudentInfo.query.filter(StudentInfo.mobile == "13312345678").first()# 如果要修改的数据,与数据表的没有改动,则不会执行更新的SQL语句student_info.student.age = 23db.session.commit()return "ok"@app.route("/d1")
def d1():"""删除数据"""# # 1. 如果删除主模型数据,则会先将附加模型的外键设置为null,然后才会删除主模型对象# student = Student.query.get(4)# db.session.delete(student)# db.session.commit()# 2. 如果删除附加模型数据,则直接删除,不会修改主模型数据StudentInfo.query.filter(StudentInfo.mobile == "13312345678").delete()db.session.commit()return "ok"if __name__ == '__main__':app.run()
在外键模型中声明关联属性

代码:

python">import json
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import backrefdb = SQLAlchemy()
app = Flask(__name__, template_folder="templates", static_folder="static")# 配置
app.config.update({"DEBUG": True,"SQLALCHEMY_DATABASE_URI": "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4",# 如果使用pymysql,则需要在连接时指定pymysql# "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 动态追踪修改设置,如未设置只会提示警告,设置False即可"SQLALCHEMY_TRACK_MODIFICATIONS": False,# ORM执行SQL查询时是哦否显示原始SQL语句,debug模式下可以开启"SQLALCHEMY_ECHO": True,
})db.init_app(app)class Student(db.Model):"""学生管理"""__tablename__ = "tb_student"  # 表名id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(15), index=True, comment="姓名")age = db.Column(db.SmallInteger, comment="年龄")sex = db.Column(db.SmallInteger, comment="性别")email = db.Column(db.String(255), unique=True, comment="邮箱地址")money = db.Column(db.Numeric(10, 2), default=0.0, comment="钱包")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"name": self.name,"age": self.age,"sex": self.sex,"email": self.email,"money": float(self.money),}class StudentInfo(db.Model):__tablename__ = "tb_student_info"  # 表名id = db.Column(db.Integer, primary_key=True, comment="主键")# 附加表的外键,就是主表的主键# 如果是一对一,则外键放在附加表对应的模型中# 如果是一对多,则外键放在多的附加表对应的模型中sid = db.Column(db.Integer, db.ForeignKey("tb_student.id"), comment="外键")  # mysql字段# 模型的关联属性,不会在数据表创建字段# 因为StudentInfo和Student是一对一的关系,所以uselist=False表示关联一个数据student = db.relationship("Student", uselist=False, backref=backref("info", uselist=False))  # ORM关联属性mobile = db.Column(db.String(15), index=True, comment="手机号码")address = db.Column(db.String(255), nullable=True, comment="家庭地址")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"sid": self.sid,"mobile": self.mobile,"address": self.address,}@app.route("/create")
def create_table():db.create_all()  # 为项目中被识别的所有模型创建数据表return "ok"@app.route("/drop")
def drop_table():db.drop_all()  # 为项目中被识别的所有模型删除数据表return "ok"@app.route("/a1")
def a1():"""添加操作"""# 添加主表信息的时候通过关联属性db.relationship同步添加附件表信息student = Student(name="xiaolan01",age=16,sex=False,money=10000,email="xiaolan01@qq.com",info=StudentInfo(address="北京市昌平区百沙路203号",mobile="13312345672"))db.session.add(student)db.session.commit()return "ok"@app.route("/a2")
def a2():"""添加操作"""# 假设已经存在主表信息,后面再补充附加表信息# student = Student(#     name="xiaolan03",#     age=16,#     sex=False,#     money=10000,#     email="xiaolan03@qq.com",# )## db.session.add(student)# db.session.commit()# 上面代码先执行了,现有1个学生没有附加信息的,我们可以在后续代码通过查询主表的主键,补充附加表数据student = Student.query.get(2)if student and not student.info:"""添加附加表数据""""""方式1"""# student.info = StudentInfo(mobile=13300010002, address="北京市昌平区百沙路205号",)# db.session.commit()"""方式2"""# info = StudentInfo(#     student=student,  # 关联属性#     mobile=13300010002,#     address="北京市昌平区百沙路205号",# )## db.session.add(info)# db.session.commit()"""方式3"""info = StudentInfo(sid=student.id,  # 外键mobile=13300010002,address="北京市昌平区百沙路205号",)db.session.add(info)db.session.commit()return "ok"@app.route("/a3")
def a3():"""添加操作"""# 添加附加模型数据的同时,把主模型也进行添加info = StudentInfo(mobile=13300010003,address="北京市昌平区百沙路206号",student=Student(name="xiaolan04",age=17,sex=False,money=10000,email="xiaolan04@qq.com",),)db.session.add(info)db.session.commit()return "ok"@app.route("/g1")
def g1():"""查询操作"""# # 正向关联----> 从主模型查询外键模型# student = Student.query.get(2)# print(student.name)          # xiaolan01# # 如果主模型没有调用关联属性查询附加模型对象,则ORM不会执行查询关联模型的SQL语句# print(student.info)          # {"id": 2, "sid": 2, "mobile": "13312345672", "address": "北京市昌平区百沙路203号"}# print(student.info.address)  # 北京市昌平区百沙路203号# 反向关联----> 从外键模型查询主模型student_info = StudentInfo.query.filter(StudentInfo.mobile == "13300010003").first()print(student_info.address)  # 北京市昌平区百沙路206号# 如果附加模型没有调用关联属性查询主模型对象,则ORM不会执行查询主模型的SQL语句print(student_info.student)  # {"id": 3, "name": "xiaolan04", "age": 17, "sex": 0, "email": "xiaolan04@qq.com", "money": 10000.0}print(student_info.student.name)  # xiaolan04print(student_info.sid)  # 3return "ok"@app.route("/u1")
def u1():"""修改数据"""# 通过主模型使用关联属性修改附加模型的数据student = Student.query.get(2)student.info.address = "广州市天河区天河东路103号"db.session.commit()# # 也可以通过附加模型直接修改主模型的数据# student_info = StudentInfo.query.filter(StudentInfo.mobile == "13312345672").first()# # 如果要修改的数据,与数据表的没有改动,则不会执行更新的SQL语句# student_info.student.age = 23# db.session.commit()return "ok"@app.route("/d1")
def d1():"""删除数据"""# # 1. 如果删除主模型数据,则会先将附加模型的外键设置为null,然后才会删除主模型对象student = Student.query.get(2)db.session.delete(student)db.session.commit()# 2. 如果删除附加模型数据,则直接删除,不会修改主模型数据# StudentInfo.query.filter(StudentInfo.mobile == "13312345672").delete()# db.session.commit()return "ok"if __name__ == '__main__':app.run()
一对多

常见业务:商品分类和商品、文章分类和文章、班级与学生、部门与员工、角色与会员、订单与订单详情、用户与收货地址。。。

python">class User(db.Model):...# 关联属性,一的一方添加模型关联属性address_list = db.relationship("UserAddress", uselist=True, backref="user", lazy='dynamic')class UsertAddress(db.Model):...# 外键,多的一方模型中添加外间user_id = db.Column(db.ForeignKey(User.id))
  • 其中realtionship描述了Student和StudentAddress的关系。第1个参数为对应参照的类"StudentAddress"
  • 第3个参数backref为类StudentAddress声明关联属性
  • 第4个参数lazy决定了什么时候SQLALchemy什么时候执行读取关联模型的SQL语句
    • lazy=‘subquery’,查询当前数据模型时,采用子查询(subquery),把外键模型的属性也同时查询出来了。
    • lazy=True或lazy=‘select’,查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性时,才进行连表查询数据[执行SQL]
    • lazy=‘dynamic’,查询当前数据模型时,不会把外键模型的数据立刻查询出来,只有操作到外键关联属性并操作外键模型具体字段时,才进行连表查询数据[执行SQL]
  • 常用的lazy选项:dynamic和select

课堂代码:

manage.py,代码:

python">import json
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import backrefdb = SQLAlchemy()
app = Flask(__name__, template_folder="templates", static_folder="static")# 配置
app.config.update({"DEBUG": True,"SQLALCHEMY_DATABASE_URI": "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4",# 如果使用pymysql,则需要在连接时指定pymysql# "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 动态追踪修改设置,如未设置只会提示警告,设置False即可"SQLALCHEMY_TRACK_MODIFICATIONS": False,# ORM执行SQL查询时是哦否显示原始SQL语句,debug模式下可以开启"SQLALCHEMY_ECHO": True,
})db.init_app(app)class User(db.Model):__tablename__ = "tb_user"id = db.Column(db.Integer, primary_key=True, comment="主键")username = db.Column(db.String(50), unique=True, comment="用户名")nickname = db.Column(db.String(50), index=True, comment="昵称")sex = db.Column(db.Boolean, default=True, comment="性别")money = db.Column(db.Numeric(8,2), default=0.0, comment="钱包余额")address_list = db.relationship("UserAddress", uselist=True, backref="user", lazy="dynamic")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"username": self.username,"nickname": self.nickname,"sex": self.sex,"money": float(self.money),}class UserAddress(db.Model):__tablename__ = "tb_user_address"id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(50), default="默认", comment="名称")province = db.Column(db.String(50), comment="省份")city = db.Column(db.String(50), comment="城市")area = db.Column(db.String(50), comment="地区")address = db.Column(db.String(500), comment="详细地址")mobile = db.Column(db.String(15), comment="收货人电话")user_id = db.Column(db.Integer, db.ForeignKey("tb_user.id"), comment="外键")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"name": self.name,"province": self.province,"city": self.city,"area": self.area,"address": self.address,"mobile": self.mobile,"user_id": self.user_id,}@app.route("/create")
def create_table():db.create_all()  # 为项目中被识别的所有模型创建数据表return "ok"@app.route("/drop")
def drop_table():db.drop_all()  # 为项目中被识别的所有模型删除数据表return "ok"@app.route("/")
def index():return "ok"@app.route("/a1")
def a1():"""添加数据"""# # 添加主模型的同时也会给附加模型添加数据,这种情况,附加模型的值可以以列表形式进行添加,一次性添加多个附加模型数据进去。# user = User(#     username="xiaolan001",#     nickname="xiaolan001",#     sex=False,#     money=10000,#     address_list=[#         UserAddress(name="公司", province="北京市", city="北京市", area="昌平区", address="百沙路201", mobile="13012345678"),#         UserAddress(name="门口小卖部", province="北京市", city="北京市", area="昌平区", address="百沙路202", mobile="13012345677"),#         UserAddress(name="小区门口", province="北京市", city="北京市", area="昌平区", address="百沙路203", mobile="13012345676"),#     ]# )# db.session.add(user)# db.session.commit()# 添加外键模型数据的同时,添加主模型数据address = UserAddress(province="天津市",city="天津市",area="静海区",address="静安路1103号",user=User(username="xiaolan02",nickname="xiaolan02",money=10000,sex=False,))db.session.add(address)db.session.commit()return "ok"@app.route("/q1")
def q1():# 正向关联----> 从主模型查询外键模型student = User.query.filter(User.username == "xiaolan001").first()# 观察连表SQL语句的执行""" lazy="select" || lazy=True """# print(student) # 没有查询附加表# print(student.address_list) # 查询了附加表""" lazy="subquery" """# print(student)  # 已经查询了附加表,如果没有使用到外键模型的数据,则本次查询存在资源浪费!!""" lazy="dynamic" """# print(student)  # 没有查询附加表# student.address_list  # 没有查询附加模型的SQL语句# print( student.address_list ) # 只要访问关联模型的具体字段才真正执行,如果海量数量查询,则本次查询会影响返回数据给客户端的时间print( student.address_list[0] ) # 获取返回列表的第1个成员return "ok"if __name__ == '__main__':app.run()
多对多

常见业务:用户收藏文章、用户与用户之间的好友关系、点赞、评论、关注、用户浏览商品的历史记录、订阅文章、专题与商品/文章的关系、活动与商品。。。。。

python"># 关系表[这种表,无法提供给python进行操作的,仅仅用于在数据库中记录两个模型之间的关系]
# 关系表[这种表,无法提供给python进行操作的,仅仅用于在数据库中记录两个模型之间的关系]
student_and_course = db.Table("table_student_course",db.Column("id", db.Integer, primary_key=True, comment="主键ID"),db.Column("sid", db.Integer, db.ForeignKey("table_student.id"), comment="学生"),db.Column("cid", db.Integer, db.ForeignKey("table_course.id"), comment="课程"),db.Column("created_time", db.DateTime, default=datetime.now, comment="购买时间"), # 当前字段无法操作
)class Student(db.Model):id = db.Column(db.Integer, primary_key=True,comment="主键")...course_list = db.relationship("Course", secondary=student_and_course, backref="student_list", lazy="dynamic")class Course(db.Model):...# 关系模型,[关系模型和关系表,任选其一]
class Achievement(db.Model):...
基于第三方关系表构建多对多

代码:

python">import json
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import backref
from datetime import datetimedb = SQLAlchemy()
app = Flask(__name__, template_folder="templates", static_folder="static")# 配置
app.config.update({"DEBUG": True,"SQLALCHEMY_DATABASE_URI": "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4",# 如果使用pymysql,则需要在连接时指定pymysql# "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 动态追踪修改设置,如未设置只会提示警告,设置False即可"SQLALCHEMY_TRACK_MODIFICATIONS": False,# ORM执行SQL查询时是哦否显示原始SQL语句,debug模式下可以开启"SQLALCHEMY_ECHO": True,
})db.init_app(app)# 关系表[这种表,无法提供给python进行操作的,仅仅用于在数据库中记录两个模型之间的关系]
student_and_course = db.Table("table_student_course",db.Column("id", db.Integer, primary_key=True, comment="主键ID"),db.Column("sid", db.Integer, db.ForeignKey("table_student.id"), comment="学生"),db.Column("cid", db.Integer, db.ForeignKey("table_course.id"), comment="课程"),db.Column("created_time", db.DateTime, default=datetime.now, comment="购买时间"), # 当前字段无法操作
)class Student(db.Model):"""学生信息模型"""# 声明与当前模型绑定的数据表名称__tablename__ = "table_student"id = db.Column(db.Integer, primary_key=True,comment="主键")name = db.Column(db.String(15), comment="姓名")age = db.Column(db.SmallInteger, comment="年龄")sex = db.Column(db.Boolean, default=True, comment="性别")email = db.Column(db.String(128), comment="邮箱地址")money = db.Column(db.Numeric(10, 2), default=0.0, comment="钱包")course_list = db.relationship("Course", secondary=student_and_course, backref="student_list", lazy="dynamic")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"name": self.name,"age": self.age,"sex": self.sex,"email": self.email,"money": float(self.money),}class Course(db.Model):"""课程数据模型"""__tablename__ = "table_course"id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(64), unique=True, comment="课程")price = db.Column(db.Numeric(7, 2), default=0.0, comment="价格")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"name": self.name,"price": self.price,}@app.route("/create")
def create_table():db.create_all()  # 为项目中被识别的所有模型创建数据表return "ok"@app.route("/drop")
def drop_table():db.drop_all()  # 为项目中被识别的所有模型删除数据表return "ok"@app.route("/")
def index():return "ok"@app.route("/a1")
def a1():"""添加数据""""""添加其中一个主模型数据时,同时绑定添加另外一个主模型的数据,这个过程中,关系表会自动写入2者的关系数据,绑定2个模型之间的关系"""# student = Student(#     name="xiaozhao",#     age=13,#     sex=False,#     money=30000,#     email="xiaozhao@qq.com",#     course_list=[#         Course(name="python入门", price=99.99),#         Course(name="python初级", price=199.99),#         Course(name="python进阶", price=299.99),#     ]# )# db.session.add(student)# db.session.commit()"""在学生报读课程的基础上,新增报读课程。"""# student = Student(#     name="xiaohong",#     age=14,#     sex=False,#     money=30000,#     email="300000@qq.com",# )# db.session.add(student)# db.session.commit()# student = Student.query.filter(Student.name=="xiaohong").first()# # 直接采用python内置的list方法操作# student.course_list.append(Course.query.get(3)) # 新增已经存在的课程# student.course_list.append(Course(name="python顶级", price=399.99))  # 已有课程,并让当前学生报读该课程# db.session.commit()"""添加学生报读课程的测试数据"""# student1 = Student.query.get(1)# course_list = Course.query.filter(Course.id.in_([1,2])).all()# student1.course_list.extend(course_list)# db.session.commit()student2 = Student.query.get(2)course_list = Course.query.filter(Course.id.in_([3,2])).all()student2.course_list.extend(course_list)db.session.commit()return "ok"@app.route("/q1")
def q1():"""查询操作"""# 查询ID=4的学生报读的课程列表# student = Student.query.get(2)# course_list = [{"name":item.name,"price":float(item.price)} for item in student.course_list]# print(course_list)# 查询出2号课程,都有那些学生在读?course = Course.query.get(2)student_list = [{"name":item.name,"age":item.age} for item in course.student_list]print(student_list)return "ok"@app.route("/u1")
def u1():"""更新数据"""# # 给报读了3号课程的同学,返现红包200块钱# course = Course.query.get(3)# for student in course.student_list:#     student.money+=200# db.session.commit()# db.Table的缺陷: 无法通过主模型直接操作db.Table中的外键之外的其他字段,例如:报读课程的时间course = Course.query.get(3)print(course.student_list)# 解决:在声明2个模型是多对多的关联关系时,把关联关系使用第三个模型来创建声明,# 就是不要使用db.Table改成模型来绑定关系,把模型的对多对拆分成2个1对多return "ok"if __name__ == '__main__':app.run()

多对多,也可以拆解成3个模型(2个主模型,1个关系模型,关系模型保存了2个主模型的外键),其中tb_achievement作为单独模型存在。

基于第三方关系模型构建多对多

在SQLAlchemy中,基于db.Table创建的关系表,如果需要新增除了外键以外其他字段,无法操作。所以将来实现多对多的时候,除了上面db.Table方案以外,还可以把关系表声明成模型的方法,如果声明成模型,则原来课程和学生之间的多对多的关系,就会变成远程的1对多了。

代码:

python">import json
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import backref
from datetime import datetimedb = SQLAlchemy()
app = Flask(__name__, template_folder="templates", static_folder="static")# 配置
app.config.update({"DEBUG": True,"SQLALCHEMY_DATABASE_URI": "mysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4",# 如果使用pymysql,则需要在连接时指定pymysql# "SQLALCHEMY_DATABASE_URI": "mysql+pymysql://root:123@127.0.0.1:3306/flaskdemo?charset=utf8mb4"# 动态追踪修改设置,如未设置只会提示警告,设置False即可"SQLALCHEMY_TRACK_MODIFICATIONS": False,# ORM执行SQL查询时是哦否显示原始SQL语句,debug模式下可以开启"SQLALCHEMY_ECHO": True,
})db.init_app(app)# 关系表[这种表,无法提供给python进行操作的,仅仅用于在数据库中记录两个模型之间的关系]
# student_and_course = db.Table(
#     "demo_student_course",
#     db.Column("id", db.Integer, primary_key=True, comment="主键ID"),
#     db.Column("sid", db.Integer, db.ForeignKey("demo_student.id"), comment="学生"),
#     db.Column("cid", db.Integer, db.ForeignKey("demo_course.id"), comment="课程"),
#     db.Column("created_time", db.DateTime, default=datetime.now, comment="购买时间"), # 当前字段无法操作
# )class StudentCourse(db.Model):__tablename__ = "demo_student_course"id = db.Column(db.Integer, primary_key=True, comment="主键")sid = db.Column(db.Integer, db.ForeignKey("demo_student.id"))cid = db.Column(db.Integer, db.ForeignKey("demo_course.id"))created_time = db.Column(db.DateTime, default=datetime.now, comment="购买时间")# 关联属性student = db.relationship("Student", uselist=False, backref=backref("to_relation", uselist=True))course = db.relationship("Course", uselist=False, backref=backref("to_relation", uselist=True))class Student(db.Model):"""学生信息模型"""# 声明与当前模型绑定的数据表名称__tablename__ = "demo_student"id = db.Column(db.Integer, primary_key=True,comment="主键")name = db.Column(db.String(15), comment="姓名")age = db.Column(db.SmallInteger, comment="年龄")sex = db.Column(db.Boolean, default=True, comment="性别")email = db.Column(db.String(128), comment="邮箱地址")money = db.Column(db.Numeric(10, 2), default=0.0, comment="钱包")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"name": self.name,"age": self.age,"sex": self.sex,"email": self.email,"money": float(self.money),}class Course(db.Model):"""课程数据模型"""__tablename__ = "demo_course"id = db.Column(db.Integer, primary_key=True, comment="主键")name = db.Column(db.String(64), unique=True, comment="课程")price = db.Column(db.Numeric(7, 2), default=0.0, comment="价格")def __repr__(self):return json.dumps(self.__to_dict__, ensure_ascii=False)@propertydef __to_dict__(self):return {"id": self.id,"name": self.name,"price": self.price,}@app.route("/create")
def create_table():db.create_all()  # 为项目中被识别的所有模型创建数据表return "ok"@app.route("/drop")
def drop_table():db.drop_all()  # 为项目中被识别的所有模型删除数据表return "ok"@app.route("/")
def index():return "ok"@app.route("/a1")
def a1():"""添加数据""""""添加其中一个主模型数据时,同时绑定添加另外一个主模型的数据"""# student = Student(#     name="xiaozhao",#     age=13,#     sex=False,#     money=30000,#     email="xiaozhao@qq.com",#     to_relation=[#         StudentCourse(#             course=Course(name="python基础", price=99.9)#         )#     ]# )# db.session.add(student)# db.session.commit()"""在学生报读课程的基础上,新增报读课程。"""# student = Student(#     name="xiaohong",#     age=14,#     sex=False,#     money=30000,#     email="300000@qq.com",# )# db.session.add(student)# db.session.commit()# student = Student.query.filter(Student.name == "xiaohong").first()# # 直接采用python内置的list方法操作# student.to_relation.extend([#     StudentCourse(#         course=Course.query.get(1) # 已经存在的课程,给学生报读#     ),#     StudentCourse(#         course=Course(name="python进阶", price=399.99)  # 新增课程,并让当前学生报读该课程#     )# ])## db.session.commit()"""添加学生报读课程的测试数据"""student1 = Student.query.get(1)course_list = Course.query.filter(Course.id.in_([1,2])).all()student1.to_relation.extend([StudentCourse(course=course) for course in course_list])db.session.commit()return "ok"@app.route("/q1")
def q1():"""查询操作"""# 查询ID=2的学生报读的课程列表# student = Student.query.get(2)# course_list = [{"name":item.course.name,"price":float(item.course.price)} for item in student.to_relation]# print(course_list)# 查询出2号课程,都有那些学生在读?course = Course.query.get(2)student_list = [{"name":item.student.name,"age":item.student.age} for item in course.to_relation]print(student_list)return "ok"@app.route("/u1")
def u1():"""更新数据"""# # 给报读了2号课程的同学,返现红包200块钱# course = Course.query.get(2)# for relation in course.to_relation:#     relation.student.money += 200# db.session.commit()# 获取中间的关系模型的字段course = Course.query.get(2)for relation in course.to_relation:print(relation.created_time)return "ok"if __name__ == '__main__':app.run()

relationship还有一个设置外键级联的属性:cascade=“all, delete, delete-orphan”

作业:
1. 我们现在学习的flask框架集成SQLAlchemy操作数据库使用的是flask-SQLAlchemy模块。如果原生的python下面应该如何使用SQLAlchemy进行初始化数据库连接和声明模型并实现模型的基本操作[增删查改,关联查询]?2. flask中的SQLAlchemy如何进行自关联查询? 这里自己写一个关于行政区划的自关联操作。

http://www.ppmy.cn/news/1476010.html

相关文章

算法篇 滑动窗口 leetCode 无重复字符的最长子串

无重复字符的最长子串 1.题目描述2.图解文字分析3.代码演示 1.题目描述 2.图解文字分析 3.代码演示

elasticsearch源码分析-05分片分配

分片分配 上面的集群状态和索引级状态已经恢复完成&#xff0c;开始分配索引分片&#xff0c;恢复分片级元数据&#xff0c;构建routingTable路由表 allocationService的创建在ClusterModule的构造函数中 public ClusterModule(Settings settings, ClusterService clusterSer…

ssh作用及原理

目录 什么是SSH ssh作用 ssh配置 Port UseDNS yes SyslogFacility AUTHPRIV LoginGraceTime 2m PermitRootLogin yes PasswordAuthentication yes PermitEmptyPasswords no PrintLastLog yes MaxAuthTries 6 什么是SSH 那天看到一篇关于SSH的博客,我想到,其实关…

QT--控件篇四

一、对话框 在软件开发中&#xff0c;对话框&#xff08;Dialog&#xff09;是一种常见的用户界面元素&#xff0c;用于与用户进行交互和获取信息。它通常以模态或非模态的形式出现&#xff0c;模态对话框会阻止用户与应用程序的其他部分交互&#xff0c;直到对话框关闭为止&a…

C++ :内联函数inline|nullptr

欢迎来到HarperLee的学习笔记&#xff01; 博主主页传送门&#xff1a;HarperLee博客主页&#xff01; 欢迎交流学习&#xff01; 一、inline关键字 1.1 什么是内联函数&#xff1f; 内联函数&#xff1a;用** inline 修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用…

[Linux+git+Gitee+Jenkins]持续集成实验安装配置详细

首先理解持续集成原理&#xff0c;看懂并理解图 1。 图 1 持续集成原理结构 图 1 中&#xff0c;版本控制服务器指远程代码仓库&#xff0c;本实验使用 GitEE 作为远程代码仓库&#xff1b;Jenkins 自动化部署服务器为虚拟机&#xff0c;操作系统为 Linux &#xff1b…

数仓工具—Hive基础之临时表及示例

Hive基础之临时表及示例 临时表是应用程序自动管理在大型或复杂查询执行期间生成的中间数据的一种便捷方式。Hive 0.14 及更高版本支持临时表。可以在用户会话中像使用普通表一样多次使用它们。在本文中,我们将介绍 Apache Hive 临时表,以及如何创建和使用限制的示例。 Hiv…

Windows与Ubuntu安装ffmpeg

文章目录 前言ffmpeg的简介安装ffmpegWindows下载设置环境变量 Ubuntu 总结 前言 FFmpeg是一款非常强大的开源音视频处理工具&#xff0c;它包含了众多的音视频编解码库&#xff0c;可以用于音视频的采集、编解码、转码、流化、过滤和播放等复杂的处理。在Windows系统上安装FF…