django(ORM)
一 单表(增、删、改、查)
''' 只想测试django中的某一个py文件内容,那么可以不用书写前后端交互的形式 而是直接写一个测试脚本即可 ''' # 脚本代码无论是写在应用下的tests.py,还是自己单独开设py文件都可以 # 测试环境的准备,去manage.py中拷贝前四行代码,然后自己写两行 import os if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64project.settings") import django django.setup()
from app01 import models # 必须写到django.setup()下面,不能置顶,不然要报错,环境未准备好 # 在这个代码块的下面就可以测试django里面的py文件了
# django自带的sqlite3数据库对日期格式不是很敏感,处理的时候容易出错 # 增 models.User.objects.create(name='lq', age=18, register_time='2000-1-1') import datetime ctime = datetime.datetime.now() user_obj = models.User(name='zd', age='18', register_time=ctime) user_obj.save() # 删 res=models.User.objects.filter(pk=2).delete() print(res) ''' pk会自动查找到当前表的主键字段,指代的就是当前表的主键字段 用了pk之后,就不需要指代当前表的主键字段到底叫什么 uid pid sid ''' # 第二种删除方法 user_obj=models.User.objects.filter(pk=2).first() user_obj.delete() # 修改 models.User.objects.filter(pk=3).update(name='xiaobao') # 如果不想用filter 可以用get # user_obj = models.User.objects.get(pk=3) # user_obj.name='xiaobao' # user_obj.save() ''' get方法返回的就是当前数据对象 但是该方法不推荐使用 一旦数据不存在该方法会直接报错 而filter不会 所以还是用filter '''
# 方式一 res=models.User.objects.values_list('name','age') print(res) # print(res.query) # queryset对象才能够.query查看内部的sql语句 # 方式二:所有的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', }, } }
# 必知必会13条 # 1.all() 查询所有数据 # 2.filter() 带有过滤器条件的查询 # 3.get() 直接拿数据对象,但是条件不存在,直接报错 # 4.first() 拿querset里面第一个元素 # res = models.User.objects.all().first() # print(res) # 5.last() # res = models.User.objects.all().last() # print(res) # 6.values() 可以指定获取到数据字段 类似sql语句select name,age from... # res = models.User.objects.values('name', 'age') # print(res) # 返回结果是列表套字典<QuerySet [{'name': 'lq', 'age': 18}, {'name': 'xiaobao', 'age': 18}]> # print(res.query) # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user` # 7.values_list() 返回结果是列表套元组 res=models.User.objects.values_list('name','age') print(res) # 返回结果是列表套元组 print(res.query) #SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user` ''' 查看内部封装的sql语句 上述查看sql语句的方式,只能用于queryset对象 只有queryset对象才能能够点击query查看内部的sql语句 ''' # 8.distinct() 去重 res = models.User.objects.values('name', 'age').distinct() print(res) ''' 去重一定要一模一样的数据 如果带有主键那么肯定不一样,你在往后的查询中一定不要忽略主键 ''' # 9.order_by() res = models.User.objects.order_by('age') # 默认升序 res1 = models.User.objects.order_by('-age') # 降序 print(res) print(res1) # 10.reverse() 反转的前提是 数据已经排过序 order_by() res = models.User.objects.order_by('age').reverse() print(res) # 11.count() 统计当前数据的个数 res = models.User.objects.count() print(res) # 4 有4条数据 # 12.exclude() 排除在外 res = models.User.objects.exclude(name='xiaobao') print(res) # 13.exists() 基本用不到,因为数据本身就自带布尔值,返回的是布尔值 res = models.User.objects.filter(pk=4).exists() print(res)
# 1.年龄大于15岁的数据 res = models.User.objects.filter(age__gt=15) print(res) # 2.年龄小于15岁的数据 # res1 = models.User.objects.filter(age__lt=15) # print(res1) # 3.大于等于、小于等于 # res = models.User.objects.filter(age__gte=15) # print(res) # res1 = models.User.objects.filter(age__lte=15) # print(res1) # 年龄四18 或者14 或者6 # res = models.User.objects.filter(age__in=[18, 14, 6]) # print(res) # 年龄在18到40岁之间的 首尾都要 # res = models.User.objects.filter(age__range=[18, 40]) # print(res) # 查询出名字里面含有字母l的数据 模糊查询 默认区分大小写 # res = models.User.objects.filter(name__contains='l') # print(res) # 忽略大小写 # res = models.User.objects.filter(name__icontains='l') # print(res) # 类似的还有:startswith,istartswith, endswith, iendswith # 根据注册时间来查询 # date字段可以通过在其后加__year, __month, __day等来获取date的特点部分数据 res = models.User.objects.filter(register_time__month='2') res = models.User.objects.filter(register_time__year='2009') # date # # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) # Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) # year # # Entry.objects.filter(pub_date__year=2005) # Entry.objects.filter(pub_date__year__gte=2005) # month # # Entry.objects.filter(pub_date__month=12) # Entry.objects.filter(pub_date__month__gte=6) # day # # Entry.objects.filter(pub_date__day=3) # Entry.objects.filter(pub_date__day__gte=3) # week_day # # Entry.objects.filter(pub_date__week_day=2) # Entry.objects.filter(pub_date__week_day__gte=2) # 需要注意的是在表示一年的时间的时候,我们通常用52周来表示,因为天数是不确定的,老外就是按周来计算薪资的哦~
二 多表操作(增、删、改)
# 一对多外键增删改查 # 1.增 # 1)直接写实际字段 id # models.Book.objects.create(title='水浒传', price=99.6, publish_id=1) # models.Book.objects.create(title='西游记', price=44.6, publish_id=2) # models.Book.objects.create(title='论语', price=64.6, publish_id=1) # 2) 虚拟字段,对象 # publish_obj = models.Publish.objects.filter(pk=2).first() # models.Book.objects.create(title='红楼梦', price=88.888, publish=publish_obj) # 2.删 # models.Book.objects.filter(pk=9).delete() # 级联删除 # 3.修改 # models.Book.objects.filter(pk=1).update(price=50) # models.Book.objects.filter(pk=8).update(publish_id=1)
# 多对多 增删改查 就是在操作第三张表 # 第三张表是ORM自动创建的,models.py中找不第三张表的类,所以通过书的对象来找第三张表,book_obj.author 就是对应的第三张表 # 如何给书籍添加作者 # book_obj=models.Book.objects.filter(pk=1).first() # book_obj.authors.add(1) # 书籍id为1的书籍绑定一个主键为1的作者 # book_obj.authors.add(2,3) # book_obj=models.Book.objects.filter(pk=2).first() # book_obj.authors.add(1) # book_obj.authors.add(2,3) # .add(),不但可以放主键值,还可以放对应的书籍或作者对象,虚拟字段 ''' add给第三张表关系表添加数据 括号内既可以传数字也可以传对象,并且都支持多个 ''' # 删(也可以传对象) # book_obj = models.Book.objects.filter(pk=1).first() # id=1的书籍 # book_obj.authors.remove(2) # book_obj = models.Book.objects.filter(pk=2).first() # id=2的书籍 # book_obj.authors.remove(1,3) ''' remove 括号内既可以传数字也可以传对象,并且支持多个 ''' # 修改 set # book_obj = models.Book.objects.filter(pk=1).first() # id=2的书籍 # book_obj.authors.set([1,2]) # 括号内必须给一个可迭代对象,先删除,在修改 # book_obj.authors.set([3]) # author_obj = models.Author.objects.filter(pk=2).first() # author_obj1 = models.Author.objects.filter(pk=3).first() # book_obj.authors.set([author_obj, author_obj1]) ''' set 括号内必须传一个可迭代对象,对象内既可以传数字也可以传对象,并且都支持多个 先删除,后新增 ''' # 清空 # 在第三张关系表中清空某个书籍与作者的绑定关系 book_obj = models.Book.objects.filter(pk=2).first() book_obj.authors.clear() ''' clear 括号内不要加任何参数 '''
# 正向 # 反向 外键字段在我手上,我查你就是正向 外键字段不在我手上,我查你就是反向 book>>>>publish 正向(外键字段在book) publish>>>>book 反向 一对一和多对多正反向的判断也是如此 ''' 正向查询按字段 反向查询按表名小写 _set ... '''
# 基于对象的跨表查询(子查询,就是分步查询) # 1.查询书籍主键为1的出版社 # book_obj = models.Book.objects.filter(pk=1).first() # res = book_obj.publish # 正向查询,按字段,书查出版社,只有一个结果,不需要加.all() # print(res) # print(res.name) # print(res.addr) # res是object对象 # 2.查询书籍主键为1的作者 # book_obj = models.Book.objects.filter(pk=1).first() # 书查作者,正向,按字段查询 # res = book_obj.authors # res1 = book_obj.authors.all() # 多对多关系,查询结果有多个,需要.all() # print(res) # print(res1) # res1的类型是queryset对象 # 3.查询作者lq的电话号码 # author_obj = models.Author.objects.filter(name='lq').first() # res = author_obj.author_detail # 正向查询,按字段,只有一个结果,不用加.all() # print(res.phone) # res的类型是object ''' 在书写orm语句的时候跟写sql语句一样 不要企图一次性将orm语句写完,如果比较复杂,就写一点看一点 总结: 正向什么时候需要加.all() 当你的结果可能有多个的时候就需要加.all() 如果是一个则直接拿到数据对象 book_obj.publish book_obj.authors.all() author_obj.author_detail ''' # 4.查询出版社是中信出版的书(出版社查书,反向) # publish_obj=models.Publish.objects.filter(name='中信出版社').first() # res=publish_obj.book_set # app01.Book.None 有多个结果,需要加.all(),反向查询并有多个结果,小写表名_set.all() # res1=publish_obj.book_set.all() # print(res1) # 5.查询作者是cyz写过的书(作者查书,反向) # author_obj = models.Author.objects.filter(name='cyz').first() # res = author_obj.book_set.all() # print(res) # 6.手机号是130的作者姓名(作者详情查作者,反向) author_detail_obj=models.AuthorDetail.objects.filter(phone=130).first() # res=author_detail_obj.author # 一对一的关系,结果只有一个,不需要加_set.all(),只需要小写表名 # print(res) # print(res.name) ''' 基于对象 反向查询的时候 当你的查询结果可以有多个的 就必须加_set.all() 当你的结果只有一个的时候,不需要加_set.all() '''
# 基于双下划线的跨表查询(一行代码) # 1.查询作者姓名zd的手机号 # res = models.Author.objects.filter(name='zd').values('author_detail__phone') # print(res) # 正向查询,按字段,需要跨表的数据,__相应的表的字段名 # 反向 # res=models.AuthorDetail.objects.filter(author__name='zd').values('phone','author__name') # print(res) # 反向查询,filter中,小写表名,__字段名,去跨表的值,__字段名 # 2.查询书籍主键为1的出版社名称和书的名称 # res = models.Book.objects.filter(pk=1).values('title', 'publish__name') # print(res) # 反向 # res = models.Publish.objects.filter(book__pk=1).values('name', 'book__title') # print(res) # 3.查询书籍主键为1的作者姓名 # res = models.Book.objects.filter(pk=1).values('authors__name') # print(res) # 反向 # res = models.Author.objects.filter(book__pk=1).values('name') # print(res) # 4.查询书籍主键是1的作者的手机号(跨三张表),跨到第二张表,再__跨下一张表,再__字段,取值 res=models.Book.objects.filter(pk=1).values('authors__author_detail__phone') print(res) # 正向按字段名 # 反向 res1=models.AuthorDetail.objects.filter(author__book__pk=1).values('phone','author__name') print(res1) # 反向按小写表名 ''' 总结: 只要掌握了正反向的概念 以及双下划线 那么就可以无限制的跨表 authors --> authors__author_detail --> authors__author_detail__phone '''
# 聚合函数 aggregate ''' 聚合查询通常情况下都是配合分组一起使用 eg:只要跟数据库相关的模块 基本上都在django.db.models里面 如果上述没有那么应该再django.db里面 ''' from django.db.models import Max, Min, Sum, Count, Avg # 所有书的平均价格 # res = models.Book.objects.aggregate(Avg('price')) # print(res) # 上述方法一次性使用 res=models.Book.objects.aggregate(Max('price'),Min('price'),Sum('price'),Count('title'),Avg('price')) print(res)
# 分组查询 annotate ''' MySQL分组查询都有那些特点 分组之后默认只能获取到分组的依据,组内其他字段都无法直接获取 严格模式 ONLY_FULL_GROUP_BY ''' from django.db.models import Count, Avg, Max, Min, Sum # 1.统计每一本书的作者个数
# res=models.Book.objects.annotate() # models后面.什么 就是按什么分组 # res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('author_num') # author_num是起的别名 按书进行分组,通过书查作者,正向查询,按字段查询,涉及到跨表,__pk. # print(res) # 2.统计每个出版社卖的最便宜的书的价格 # res = models.Publish.objects.annotate(price_num=Min('book__price')).values('name', 'price_num') # print(res) # 按出版社分组,通过出版社查书,反向查询,按小写表名,涉及到跨表,book__price # 3.统计不止一个作者的图书 # 1)先按照图书分组,求没一本书对应的作者数 # 2)过滤出不止一个作者的图书 # res=models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=1).values('title','author_num') # print(res) ''' 只要orm语句得出的结果还是一个queryset对象 那么它可以继续无限制的点queryset对象封装的方法 ''' # 4.查询每个作者出的书的总价格 res = models.Author.objects.annotate(price_sum=Sum('book__price')).values('name', 'price_sum') print(res) ''' 如果想按照指定的字段分组该如何处理? models.Book.objects.values('price').annotate()
以书里的字典price进行分组 如果出现分组查询报错的情况 需要修改数据库严格模式 '''
多表查询总结:
MySQL中的多表操作
子查询
联表操作
inner join
left join
right join
union
Django ORM中
基于对象的跨表查询
子查询
先拿到一个数据对象
对象点点
(基于对象的子查询)多个结果 , 字段名.all(), 一个结果不用加
(基于对象的子查询)多个结果, 表名_set.all(),一个结果不用加
正向查询按外键字段,反向查询按表名小写
基于双下划线的跨表查询
联表操作
利用双下划线的跨表查询,__一次,跨一次表,取跨的值 ,__字段名
同样遵从,正向查询按外键字段,反向查询按表名小写
正反向的概念
正向
外键字段就在我手上
反向
外键字段不在我手中
小口诀
正向查询按外键字段
反向查询按表名小写
6
# F查询 ''' 我们构造的过滤器都只是将字段值与某个我们自己设定的常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢? Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。 能够帮助你直接获取到表中某个字段对应的数据 ''' from django.db.models import F # 1.查询卖出数大于库存数的书籍 # res = models.Book.objects.filter(sale__gt=F('inventory')).values('title') # print(res) # 2.将所有书籍的价格提升300元 # models.Book.objects.update(price=F('price')+500) # 3.将所有书的名称后面加上爆款两个字 ''' 在操作字符串类型的数据的时候,F不能直接做到字符串的拼接 ''' from django.db.models.functions import Concat from django.db.models import Value # models.Book.objects.update(title=Concat(F('title'),Value('(爆款)'))) # models.Book.objects.update(title=F('title')+'(爆款)') # 所有的名称会全部变成空白
# Q查询 # 1.查询卖出数大于100或者价格小于600的书籍 # res=models.Book.objects.filter(sale__gt=100,price__lt=400) '''filter括号内多个参数是and关系''' from django.db.models import Q # res=models.Book.objects.filter(Q(sale__gt=100),Q(price__lt=400)) # Q包裹逗号分割,还是and关系 # res = models.Book.objects.filter(Q(sale__gt=100)|Q(price__lt=400)).values('title') # | or关系 # print(res) # res = models.Book.objects.filter(~Q(sale__gt=100) | Q(price__lt=400)).values('title') # ~ not关系 # print(res) # Q的高阶用法,能够将查询条件的左边也变成字符串的形式(动态的指定查询条件) q=Q() q.connector='or' q.children.append(('sale__gt',100)) q.children.append(('price__lt',400)) res=models.Book.objects.filter(q) # 默认还是and关系 print(res)
''' 事务 ACID 原子性 不可分割的最小单位 一致性 跟原子性是相辅相成 隔离性 事务之间相互不干扰 持久性 事务一旦确认永久生效 事务的回滚 rollback 事务的确认 commit ''' 目前只需要掌握Django中如何简单的开启事务 from django.db import transaction with transaction.atomic(): sql1 sql2 # 在with代码块内书写的所有orm操作都是属于同一个事务 print('执行其他操作')
AutuField 主键字段 primary_key=True CharField 类似于MySQL中的varchar verbose_name 字段的注释 max_length 长度 IntegerField BigIntegerField DecimalField max_dights=8 decimal_places=2 8位,小数点后两位 EmailFiled varchar(254) DateField date DateTimeField datetime auto_now:每次修改数据的时候都会自动更新当前时间 auto_now_add:只有创建数据的时候记录创建时间后续不会自动修改 BooleanField(Field) 布尔值类型 该字段传布尔值(False/True) 数据库里面存0/1 TextField(Field) - 文本类型 该字段可以用来存大段内容(文章、博客...) 没有字数限制 后面的bbs作业 文章字段用的就是TextField FileField(Field) - 字符类型 upload_to="/data" 该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中 /data/a.txt 后面bbs作业也会涉及 # 更多字段,看博客 # django除了给你提供了很多字段类型之外,还支持自定义字段 class Myfield(models.Field): def __init__(self, max_length, *args, **kwargs): self.max_length = max_length # 改了父类方法,就需要调用父类的init方法 super().__init__(max_length=max_length, *args, **kwargs) def db_type(self, connection): ''' 返回真正的数据类型及各种约束条件 :param connection: :return: ''' return 'char({})'.format(self.max_length) my_char_field = Myfield(max_length=16, null=True) # 外键字段及参数 unique=True ForeignKey(unique=True) === OneToOneField() # 你在用前面字段创建一对一,Orm会有一个提示信息,orm推荐你使用后者但是前者也能用 db_index 如果db_index=True 则代表着为此字段设置索引 to_field 设置要关联的表的字段,默认不写关联的就是另外一张的主键字段 on_delete 当删除关联表中的数据时,当前表与其关联的行为 ''' django2.x及以上版本,需要你自己指定外键字段的级联更新级联删除 '''
only与defer ''' orm语句的特点: 惰性查询 如果你仅仅只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别,直接不执行 ''' # res=models.Book.objects.all() # print(res) # 要用数据,才会去数据库查 # 想要获取书籍表中所有数的名字 # res=models.Book.objects.values('title') # for i in res: # print(i.get('title')) # 实现获取到的是一个数据对象,然后点title就能够拿到书名,并且没有其他字段 # res=models.Book.objects.only('title') # print(res) # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>]> # for i in res: # print(i.title) # 点击only括号内的字段,不会走数据库 # print(i.price) # 点击only括号内没有的字段,会重新走数据库查询 # res=models.Book.objects.all() # for i in res: # print(i.price) # 不需要重新走数据库 res = models.Book.objects.defer('title') # 对象除了没有title属性之外其他的都有 for i in res: print(i.price) ''' defer和only刚好相反 defer括号内放的字段不在查询出来的对象里面,查询该字段需要重新走数据 而如果查询的是非括号内的字段,则不需要走数据库 '''
# res=models.Book.objects.all() # for i in res: # print(i.publish.name) # 每循环一次就要走一数据库查询 # res=models.Book.objects.select_related('publish') # INNER JOIN ''' select_related 内部直接先将Book与publish连起来,然后一次性将大表里面的所有数据 全部封装给查询出来的对象 这个时候对象无论是否点击book表的数据还是publish的数据都是无需再走数据库查询 select_related括号内只能放外键字段 一对多 一对一 ''' # for i in res: # print(i.publish.name) res=models.Book.objects.prefetch_related('publish') # 子查询 """ prefetch_related 该方法内部其实就是子查询 将子查询出来的所有结果也给你封装到对象中 给你感觉好像也是一次性搞定 """ for i in res: print(i.publish.name)
四 补充字段
''' 用户表 性别 学历 工作经验 是否结婚 是否生子 客户来源 针对某个可以列举完全的可能性字段,我们应该如何存储 只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choice参数 ''' class User(models.Model): username = models.CharField(max_length=32) age = models.IntegerField() # 性别 gender_choices = ( (1, '男'), (2, '女'), (3, '其他'), ) gender = models.IntegerField(choices=gender_choices) ''' 该gender字段存的还是数字,但是如果存的数字在上面元组列举的范围之内 那么可以非常轻松的获取到数字对应的真正的内容 1.gender字段存的数字不在上述元组列举的范围内容 2.如果在,如何获取对应的信息 ''' score_choices = ( ('A', '优秀'), ('B', '良好'), ('C', '及格'), ('D', '不及格'), ) score = models.CharField(choices=score_choices, null=True) # models.User.objects.create(username='lq', age=18, gender=1) # models.User.objects.create(username='zd', age=30, gender=2) # models.User.objects.create(username='cyz', age=6, gender=3) # 存的时候,没有列举出来的数字也能存(范围还是按照字段类型决定) # models.User.objects.create(username='cys', age=14, gender=4) # 取 user_obj = models.User.objects.filter(pk=1).first() print(user_obj.gender) # 只要有choices参数的字段,如果你想要获取对应的信息,固定写法 get_字段名_display() print(user_obj.get_gender_display()) # 男 user_obj = models.User.objects.filter(pk=4).first() print(user_obj.get_gender_display()) # 4 # 如果没有对应关系,那么字段是什么还是展示什么 # choices参数使用场景是非常广泛的
# MTV:Django号称是MTV模型 M:models T:templates V:views # MVC:其实Django本质也是MVC M:models V:views C:controller(控制器) # vue框架:MVVM模型
# 全自动 class Book(models.Model): name = models.CharField(max_length=32) authors = models.ManyToManyField(to='Author') class Author(models.Model): name = models.CharField(max_length=32) ''' 优点:非常的方便,还支持orm提供操作第三方关系表的方法 缺点:第三张表的扩展性极差(没有办法额外添加字段) '''
# 纯手动 class Book(models.Model): name = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') ''' 优点:第三张表完全取决于自己进行额外的扩展 缺点:需要写的代码较多,不能够再使用orm提供的简单方法add、remove 不建议使用该方式 '''
# 半自动 class Book(models.Model): name = models.CharField(max_length=32) authors = models.ManyToManyField(to='Author', through='Book2Author', through_fields=('book', 'author') ) class Author(models.Model): name = models.CharField(max_length=32) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') ''' through_fields字段先后顺序 判断的本质: 通过第三张表查询对应的表,需要用到那个字段就把那个字段放前面 也可以简化判断 当前表是谁,就把对应的关联字段放前面 半自动:可以使用orm的正反向查询,但是没法使用add,set,remove,clear这四个方法 ''' # 总结:需要掌握全自动和半自动,为了扩展性更高,一般采用半自动