11_06、ORM多表操作
一、测试环境
1、什么是测试环境
在Python脚本中调用Django环境
测试环境是指不需要启动整个Django框架项目,而是在应用中的test.py文件中通过一系列的导入
实现整个项目可以在一个py文件中的简单启动方式,通过操作这个test.py文件,方便我们对整个Django项目的测试。
2、测试环境的准备
'''测试环境准备''' import os if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "orm操作数据库.settings") import django django.setup() '''测试环境准备完毕''' '''若想测试环境生效,新增加的代码必须写在下面''' # 举例 my_rest = 1 * 2 print(my_rest) # 2
二、ORM的其他查询方法
1、查询表纪录
查询API
<1> all(): 查询所有结果 <2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 <3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。 <4> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 <5> order_by(*field): 对查询结果排序('-id') <6> reverse(): 对查询结果反向排序 <8> count(): 返回数据库中匹配查询(QuerySet)的对象数量。 <9> first(): 返回第一条记录 <10> last(): 返回最后一条记录 <11> exists(): 如果QuerySet包含数据,就返回True,否则返回False <12> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列 <13> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 <14> distinct(): 从返回结果中剔除重复纪录
2、基于双下划线的模糊查询
Book.objects.filter(price__in=[100,200,300]) Book.objects.filter(price__gt=100) Book.objects.filter(price__lt=100) Book.objects.filter(price__gte=100) Book.objects.filter(price__lte=100) Book.objects.filter(price__range=[100,200]) Book.objects.filter(title__contains="python") Book.objects.filter(title__icontains="python") Book.objects.filter(title__startswith="py") Book.objects.filter(pub_date__year=2012)
3、外键字段的增删改查
我们依然以之前的4张表为例,探究ORM对外键字段的增删改查操作
# models.py文件
'''创建外键关系''' '''创建外键关系表的时候,先创建基础字段,最后再添加外键字段''' # 1. 图书表 class Book(models.Model): title = models.CharField(max_length=32, verbose_name='图书标题') # price int # price = models.IntegerField() # 创建出来int类型 # price decimal(8, 2) ''' max_digits=None, 代表存储的总长度 decimal_places=None, 代表的是存储的小数位 ''' price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格') # 可以存小数 ########################## 图书表——出版社表, 一对多外键关系 ########################## # publish_id = models.ForeignKey(to='Publish', to_field='id') ''' 如果关联的字段是主键,那么to_field可以省略不写,默认关联的就是主键 如果你关联的不是主键字段,那么这个参数就不能省略,必须显式指定 ''' # publish_id = models.ForeignKey(to='Publish') ''' 当我们创建一对多关系的时候,字段的后缀_id就不要再自己添加了 而是,自动帮我们添加_id字段 ''' publish = models.ForeignKey(to='Publish', null=True) # null=True字段记录可为空,若不设置,无法迁移 ########################## 图书表——作者表,多对多外键关系 ########################## ''' authors是一个虚拟字段,不会在Book表中创建出来authors字段,而是,会自定帮助我们创建出来第三张表 此时的第三张表就是图书与作者之间的关系表 ''' authors = models.ManyToManyField(to='Author', ) # 2. 出版社表 class Publish(models.Model): ''' verbose_name:对当前字段进行解释,每个数据类型都有这个参数 ''' title = models.CharField(max_length=64, verbose_name='出版社标题') addr = models.CharField(max_length=32) # 3. 作者表 class Author(models.Model): name = models.CharField(max_length=32) ############################ 作者表——作者详情表,一对一外键关系 ########################## ''' 当我们创建一对一关系的时候,字段的后缀id就不要再加了,而是默认添加_id的结尾 ''' author_detail = models.OneToOneField(to='AuthorDetail', null=True) # null=True字段记录可为空,若不设置,无法迁移 # 4. 作者详情表 class AuthorDetail(models.Model): phone = models.CharField(max_length=64) wx = models.CharField(max_length=32)
一对多,一对一,多对多
# test.py文件
'''外键字段的增删改查''' '''针对一对一,一对多的处理''' '''增加''' # 增加一本书 # 方式一:利用publish_id # 此处publish_id是外键字段,利用外键字段增加一本书 # res = models.Book.objects.create(title='三国', price=100, publish_id=1) # 方式二:利用publish=对象 # # 此处publish是一个对象,利用对象增加一本书 # publish_obj = models.Book.objects.filter(pk=2).first() # res = models.Book.objects.create(title='西游', price=200, publish=publish_obj) '''修改''' # 方式一: # 修改主键为1的外键字段为2 # res = models.Book.objects.filter(pk=1).update(publish_id=2) # 方式二: # 修改主键为2的外键字段为3 # publish_obj = models.Book.objects.filter(pk=3).first() # 生成外键为3的对象 # models.Book.objects.filter(pk=2).update(publish=publish_obj) # 找到主键为2的对象 '''针对多对多的处理''' '''增加add''' # 给主键为1的图书增加两个作者 # book_obj = models.Book.objects.filter(pk=1).first() # 先找到主键为1的图书 # 补充: # print(book_obj.authors) # authors是虚拟字段,连接图书和作者两张表(多对多) # 打印结果为app01.Author.None,它代表着我们已经处在这张虚拟表中了 # print(book_obj.authors.all()) # 打印结果是一个作者列表 # 方式一:根据主键添加 # book_obj.authors.add(1, 2) # ORM提供的方法,括号里写的是作者的id # 方式二:根据对象添加 # 根据主键创建两个作者对象 # author_obj1 = models.Author.objects.filter(pk=1).first() # author_obj2 = models.Author.objects.filter(pk=2).first() # 为主键为1的图书增加两个作者 # book_obj.authors.add(author_obj1, author_obj2) '''改set''' # 把pk=1的书籍的作者改为作者id=1的 # book_obj = models.Book.objects.filter(pk=1).first() # book_obj.authors.set([1]) # set括号内必须是一个可迭代对象,把pk=1的书籍的作者改为作者id=1的 # book_obj.authors.set([1, 2]) # 把pk=1的书籍的作者改为作者id=1和id=2的(多对多:两人合写一本书) # set的底层原理是:如果原来就有,则不变;多了删除;少了增加。 '''删除remove''' # 删除主键为1的图书 book_obj = models.Book.objects.filter(pk=1).first() book_obj.authors.remove(1) # 删除1个 book_obj.authors.remove(2, 3) # 删除多个 '''清空clear''' book_obj.authors.clear() # 把book中id=1的全部删除
三、终端中查看SQL语句的执行情况
在settings.py文件中配置下面一段代码
配置完成后,在test.py文件中运行py文件,可以把用ORM操作数据库的语句,通过print()自动转换为SQL语句
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
四、正反向的概念
1、什么是正反向查询
在多表查询的时候,存在正反向查询的概念
正向查询:A和B,外键字段在A,A查B,就是正向查询
反向查询:A和B,外键字段在B,A查B,就是反向查询
简而言之:
外键字段在我手上,我查你就是正向
外键字段不在我手上,我查你就反向
举例: 1. book >>> publish >>> 正向 2. publish >>> book >>> 反向 3. author >>> book >>> 反向 4. book >>> author >>> 正向
2、正反向查询的口诀
1. 正向查询按照字段(外键字段)
2. 反向查询按照表名小写或者表名小写_set.all()
注意:
表名小写:只有一种结果
表名小写_set.all():结果有不止一个的可能
3、应用方向
基于对象的跨表查询和基于双下划线的跨表查询需要用到
五、基于对象的跨表查询(子查询)
基于对象的跨表查询就是sql语句中的子查询,需要用到正反向查询的概念
SQL语句中的子查询:一个sql语句的执行结果是另一个sql语句的执行条件
子查询的特点是分布解决问题(套娃)
举例练习暂时搁置
六、基于双下划线的跨表查询
基于对象的跨表查询与基于双下划线的跨表查询,都是跨表查询的一种手段,是做事的两种方式,都能实现跨表查询
基于双下划线的跨表查询也需要用到正反向的概念
'''基于双下划线的跨表查询:适用正反向查询的概念''' # 1、查询书籍主键为1的图书标题、出版社名称 # 书籍 》 出版社 》 正向一对多查询 》 用字段 res = models.Book.objects.filter(pk=1).values('title', 'publish__title') print(res) # <QuerySet [{'title': '三国', 'publish__title': '东京出版社'}]> # 2、查询书籍主键为1的作者 # 书籍 》 作者 》 正向多对多查询 》 用字段 res1 = models.Book.objects.filter(pk=1).values('title', 'authors__name') print(res1) # <QuerySet [{'title': '三国', 'authors__name': '罗贯中'}, {'title': '三国', 'authors__name': '吴承恩'}]> # 3、查询曹雪芹的手机号和微信 # 作者 》 作者详情表 》 正向一对一查询 》 用字段 res2 = models.Author.objects.filter(name='曹雪芹').values('name', 'author_detail__phone', 'author_detail__wx') print(res2) # <QuerySet [{'name': '曹雪芹', 'author_detail__phone': '6666', 'author_detail__wx': 'caoxueqin'}]> # 4、查询东京出版社的所有书籍 # 出版社 》 书籍 》 反向一对多查询 》 用名小写或者表名小写_set.all() res3 = models.Publish.objects.filter(title='东京出版社').values('title', 'book__title') print(res3) # <QuerySet [{'title': '东京出版社', 'book__title': '三国'}, {'title': '东京出版社', 'book__title': '水浒'}]> # 5、查询作者罗贯中写过的书 # 作者 》 书籍 》 反向多对多查询 》 用名小写或者表名小写_set.all() res4 = models.Author.objects.filter(name='罗贯中').values('name', 'book__title') print(res4) # <QuerySet [{'name': '罗贯中', 'book__title': '三国'}, {'name': '罗贯中', 'book__title': '西游'}]> # 6、查询手机号是6666的作者的姓名 # 作者详情表 》 作者 》 反向一对一查询 》 用名小写或者表名小写_set.all() res5 = models.AuthorDetail.objects.filter(phone='6666').values('author__name', 'phone') print(res5) # <QuerySet [{'author__name': '曹雪芹', 'phone': '6666'}]> # 7、查询书籍主键为2的作者的手机号 # 书籍 》 作者 》 作者详情表 》 跨三张表的查询(正向多对多查询——正向一对一查询) 》 都用字段 res6 = models.Book.objects.filter(pk=2).values('title', 'authors__name', 'authors__author_detail__phone') print(res6) # <QuerySet [{'title': '西游', 'authors__name': '罗贯中', 'authors__author_detail__phone': '123'}]>
七、F与Q查询
1、 F查询
F是Django框架中db.models中sql语句一个F类,可以拿到数据库表字段的原数据,方便我们对数据库中数据的修改和查找
F查询可以操作数字和字符串
'''F查询''' from django.db.models import F # 1、把所有书籍提价100 # F查询默认操作的是数字 models.Book.objects.update(price=F('price') + 100) # 2、把所有的书籍标题后面都加上'经典版' # F查询要操作字符串需要导入两个模块 from django.db.models.functions import Concat from django.db.models import Value models.Book.objects.update(title=Concat(F('title'), Value('经典版')))
2、Q查询
Q查询主要用来为我们解决ORM框架操作sql语句中的(or)关系
'''Q查询''' # 1、查询书籍价格不低于300的或者标题为狂人日记的书籍 from django.db.models import Q res = models.Book.objects.filter(Q(price__gt=300) | Q(title='狂人日记')).values_list() print(res) # <QuerySet [(2, '西游经典版', Decimal('400.00'), 3), (3, '水浒经典版', Decimal('500.00'), 2), (5, '狂人日记', Decimal('250.00'), None)]> ''' 补充:用 ,分割是and关系,用 | 分割是or关系,用 ~ 分割是not关系 ''' res1 = models.Book.objects.filter(Q(price__gt=300), Q(title='狂人日记')).values_list() # 查询书籍价格不低于300的和标题为狂人日记的书籍 print(res1) # < QuerySet[] > 没有 res2 = models.Book.objects.filter(~Q(price__gt=300) | Q(title='狂人日记')).values_list() # 查询书籍价格低于300的和标题或狂人日记的书籍 print(res2) # <QuerySet [(1, '三国经典版', Decimal('300.00'), 2), (4, '红楼经典版', Decimal('202.00'), 4), (5, '狂人日记', Decimal('250.00'), None)]>
八、聚合查询和分组查询
1、聚合查询
聚合查询是根据sql语句中的五个聚合函数查询(max最大 min最小 sun求和 cont统计 avg求平均)
注意在Django语句中使用聚合函数,需要用到关键字‘aggregate’
'''聚合函数''' from django.db.models import Max, Min, Sum, Count, Avg # 查询书籍的最高价格,最低价格,总价格,统计个数,平均数 res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Count('price'), Avg('price')) print(res) # {'price__max': Decimal('500.00'), 'price__min': Decimal('202.00'), 'price__sum': Decimal('1652.00'), 'price__count': 5, 'price__avg': 330.4} # 还可以给聚合函数起别名 res1 = models.Book.objects.aggregate(max_price=Max('price'))
2、分组查询
对应sql语句中弄得group by,ORM使用分组需要用到关键字annotate
sql语句中的分组回顾
# 严格模式 set global sql_mode='STRICT_TRANS_TABLES,PAD_CHAR_TO_FULL_LENGTH,only_full_group_by'
mysql从5.7以后,默认开启group by的严格模式。
但是严格模式下不方便我们查看数据库中的具体数值对应的内容,为此,我们可以适当配置
方式一:更改my.cnf(windows下是my.ini)中的sql_mode参数,去掉:only_full_group_by。
方式二:分组拼接
ORM操作分组查询
'''分组查询''' # 分组查询需要用到annotate # 1、统计每一本书的作者个数 # 书籍 》 作者 》 正向多对多查询 》 用字段 res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num') # 给Count()起别名作values的键 print(res) # <QuerySet [{'title': '三国经典版', 'author_num': 2}, {'title': '西游经典版', 'author_num': 1}, {'title': '水浒经典版', 'author_num': 1}, {'title': '红楼经典版', 'author_num': 0}, {'title': '狂人日记', 'author_num': 0}]> # 2、统计每个出版社最便宜的书 # 出版社 》 书籍 》 方向一对多查询 》 表名小写 res1 = models.Publish.objects.annotate(min_price=Min('book__price')).values_list('title', 'book__title', 'min_price') print(res1) # <QuerySet [('东京出版社', '三国经典版', Decimal('300.00')), ('南京出版社', '西游经典版', Decimal('400.00')), ('西京出版社', '红楼经典版', Decimal('202.00')), ('北京出版社', None, None)]> # 3、统计不只一个作者的图书 # 翻译:统计图书的作者个数,且作者个数至少有两个 # 3.1 查询每个图书的作者个数 res2 = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num') print(res2) # <QuerySet [{'title': '三国经典版', 'author_num': 2}, {'title': '西游经典版', 'author_num': 1}, {'title': '水浒经典版', 'author_num': 1}, {'title': '红楼经典版', 'author_num': 0}, {'title': '狂人日记', 'author_num': 0}]> # 3.2 查询作者个数大于1的图书 # 利用filter过滤 res3 = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title', 'author_num') print(res3) # < QuerySet[{'title': '三国经典版', 'author_num': 2}] >
九、事务
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通