Model层QuerySet的使用
Model层QuerySet的使用
1、QuerySet的概念
Django算是标准的MVC框架,虽然因为他的模板以及view的概念有时候被大家戏称“MTV”的开发模式,但是道理都是一样的。Model作为MVC模式中的基础层(也可以称为数据层),负责为整个系统提供数据。因此我们先了解下它是如何提供数据的:
在Model层中,Django通过给Model增加一个objects属性来提供数据操作的接口。
比如:我们以一个博客网站文章查询为例子,如果我们想要查询所有文章的数据,那我们可以这么写:
Post.objects.all()
这样我们就能拿到一个QuerySet的对象。这个对象包含了我们需要的数据,但是注意只有当我们用到它时才会去数据库查询
这时候可能会有点奇怪?为啥不是我在执行**Post.objects.all()**的时候就直接查询呢?
其原因是QuerySet对象要支持链式操作。如果每次执行都要查询数据库的话,会存在性能问题,因为有时候可能你就没用到你执行的代码。
举个例子:
posts = Post.objects.all()
result = posts.filter(status=1)
那么如果这条语句要立即执行的话就会产生两次的SQL查询并且两次查询存在重复数据。当然相信一般情况下大家也不会这么写。
因此,Django的QuerrySet其实在本质上就是一个懒加载对象,上面两句代码执行后不会产生数据库查询操作,只会返回一个QuerySet对象,等你真正用到他的时候它才会去执行查询。
posts = Post.objects.all() # 返回一个QuerySet对象并赋值给posts;
result = posts.filter(status=1) # 继续返回一个QuerySet对象并且赋值给result;print(result) # 此时会根据上面的两个条件执行数据查询操作,对应的SQL查询语句为:SELECT * FROM blog_post where status=1;
在我们理解了QuerySet对象的懒加载后,可以帮助我们在日常开发中提升我们的系统性能;
另外,上面我们也说到了链式调用,这个又是什么概念?
posts = Post.objects.filter(status=1).filter(category=2).filter(title_icontains='test')
相信看了以上的代码你就豁然开朗了,这就是链式调用,在每个函数(或者方法)的执行结果上可以继续调用同样的方法,因为每个函数的返回值都是他自己,也就是QuerySet。
2、常用的QuerySet接口
2.1、支持链式调用的接口
- all接口,相当于SELECT * FROM table_name 语句用于查询所有数据
- filter接口,顾名思义,根据条件顾虑,常用的条件基本上是字段等于、不等于、大于、小于。当然,还有其他的,比如能改成产生LIKE查询的:Model.objects.filter(content_contains=“条件”)
- exclude接口,同filter,只是相反的逻辑。
- reverse接口,把QuerySet中的结果倒序排列。
- distinct接口,用来进行去重查询,产生 SELECT DISTINCT 这样的SQL查询
- none接口,返回空的QuerySet。
2.2、不支持链式调用的接口
不支持链式调用,即返回值不是QuerySet的接口,具体如下:
- get接口,比如:Post.objects.get(id=1)这个语句就是来查询id为1的文章;如果文章存在则直接返回对应的post实例,如果不存在则抛出DoesNotExist异常。所一般情况下我们会这莫写:
try:post = Post.objetcs.get(id=1)
except Post.DoesNotExist:# 做异常情况处理
- create接口,用来直接创建一个model对象,eg:post = Post.objects.create(title=“django”)
- get_or_create接口,根据条件查找,如果没查找到就调用create创建。
- update_or_create接口,同get_or_create,只是用来做更新操作。
- count接口,用于返回QuerySet有多少条记录,相当于 SELECT COUNT(*) FROM table_name
- lastest接口,用于返回最新的一条记录,但是需要在Model中的Meta
- 中定义:get_latest_by = <用来排序的字段>
- earliest接口,同上,返回最早的一条记录
- first接口,从当前的QuerySet记录中获取第一条,
- last接口,同上,获取最后一条。
- exists接口,返回True或者False,在数据库层面执行 SELECT (1) AS “a” FROM table_name LIMIT 1的查询,如果只是需要判断QuerySet是否有数据,用这个接口最合适不过,不要用count或者len(QuerySet)的方式来做判断,这样可以减少一次DB查询请求。
- bulk_create 接口,同create,用来批量创建记录
- in_bluk 接口,批量查询,接受两个参数 id_list 和 filed_name。 可以通过Post.objects.in_bluk([1,2,3])查询出id为1,2,3的数据,返回结果是字典类型,字典类型的key为查询条件。返回结果示例:{1: <Post 实例 1>, 2:<Post案例2>, 3:<Post案例3>}.
- update接口,用来根据条件批量更新记录,比如:Post.objects.filter(owner_name=‘django’).update(title=‘测试更新’)。
- delete接口,同update,这个接口是用来根据条件批量删除记录。需要注意的是,update和delete都会出发django的signal
- values接口,当我们明确知道只需要返回某个字段的值,不需要Model实例时,可以使用它,用法如下:
title_list = Post.objects.filter(category_id=1).values('title')
# 返回结果包含dict的,QuerySet, 类似这样:<QuerySet[{'title':xxxx},]>
- values_list接口,同values,但是直接返回的是包含tuple的QuerySet:
title_list = Post.objects.filter(category=1).values_list('title')
#返回结果类似:<QuerySet[('标题',)]>
如果只是一个字段的话,可以通过增加 flat=True 参数,便于我们后面的处理:
title_list = Post.objects.filter(category=1).values_list('title',flat=True)
for title in title_list:print(title)
3、进阶接口
在优化django项目时,优先考虑这几种接口的用法。
- defer 接口,把不需要展示的字段做延时加载,比如说:需要获取文章中除了正文外的其他字段,就可以通过posts = Post.object.all().defer(‘content’),这样拿到的记录中就不会包含content部分,但是当我们需要用到这个字段时,在使用时会去加载。下面还是通过代码演示:
posts = Post.objects.all().defer('content')
for post in posts: # 此时会执行数据库查询print(post.content) # 此时会执行数据查询,获取到content
当不想某个过大的字段时(如text类型的字段),会使用defer,但是上面的演示代码会产生N+1查询问题,在实际使用一定要注意!
注意:上面的代码是一个不太典型的N+1查询问题,一般情况下,由外键查询产生的N+1查询问题比较多,即一条查询请求返回N条数据,当我们操作数据时,又会产生额外的请求。这就是N+1问题,所有的orm框架都存在这样的问题。
- only接口。同defer接口刚好相反,如果只是想获取到所有的title记录,就可以使用only,只获取title的内容,其他值在获取时会产生额外的查询。
- select_related接口,这就是用来解决外键产生的N+1解决方案,我们先来看看什么情况下会产生这个问题:
posts = Post.objects.all()
for post in posts: # 产生数据库查询print(post.owner) # 产生额外的数据库查询
代码同上类似,只是这里用的是owner
他的解决办法就是用select_related接口:
posts = Post.objects.all().select_related('category')
for post in posts: # 产生数据库查询,category一块跟着查询print(post.category) # 产生额外的数据库查询
- prefetch_related接口,针对多对多关系,比如,post和tag关系可以通过这种方式来避免:
posts = Post.objects.all().prefetch_related('tag')
for post in posts:print(post.tag.all())
4、常用的字段查询
这里我们把django常用的关键字查询列一下,更多的还是要查询django文档。
- contains:包含,用来进行相似查询
- icontains:同contains,只是忽略大小写
- exact:精确匹配
- iexact:同exact,忽略大小写
- in:指定某个集合,比如:Post.objetcs.filter(id_in=[1,2,3]),相当于SELECT * FROM blog_post WHERE IN (1,2,3);。
- gt:大于某个值
- gte:大于等于某个值
- lt:小于某个值
- lte:小于等于某个值
- startswith:以某个字符串开头,与contains类似,只是会产生LIKE '<关键字>%'这样的SQL
- istartswith:同startswith,忽略大小写
- endswith:以某个字符串结尾,
- iendswith:同上,忽略大小写
- range:范围查询,多用于时间范围,如:
Post.objects.filter(created_time_range=('2021-05-11','2022-09-21'))
会产生这样的查询:
SELECT ... WHERE created_time BETWEEN '2021-05-11' AND '2022-09-21';
关于日期查询的还有很多,比如:date、year、month等,具体等需要时查看文档即可。
5、进阶查询
- F,F表达式常用来执行数据库层面的计算,从而避免出现竞争状态。就比如需要处理每篇文章的访问量,假设存在post.pv这样的字段,当有用户访问时,我们对其加1:
post = Post.objects.get(id=1)
post.pv = post.pv + 1
post.save()
这在多线程情况下会出现问题,其执行逻辑是先获取到当前的pv值,然后将其加1后赋值给post.pv,最后保存。如果多个线程同时执行了post = Post.objects.get(id=1),那么每个线程里的post.pv值都是一样的,执行完加1和保存后,相当于只执行了一个加1,而不是多个。
其原因在于我们把数据拿到python中转了一圈,然后保存到数据库中。这时通过F表达式就可以方便的解决这个问题:
from django.db.models import F
post = Post.objetcs.get(id=1)
post.pv = F('pv') + 1
post.save()
这种方式最终会产生类似这样的SQL语句:UPDATE blog_post SET pv + 1 WHERE ID = 1。他在数据库层面执行原子性操作。
- Q,Q表达式就是用来解决OR查询的,可以这末用:
from django.db.models import Q
post.objects.filter(Q(id=1) | Q(id=2))
或者进行AND查询:
post.objects.filter(Q(id=1) & Q(id=2))
- Count,用来做聚合查询,比如想得到某个分类下有多少篇文章?简单的做法就是:
category = Category.objects.get(id=1)
posts_count = category.post_set.count()
- Sum, 同Count类似,只是它是用来做合计的。比如想要统计目前所有文章加起来的访问量有多少,可以这么做:
from django.db.models import Sum
Post.objects.aggregate(all_pv=Sum('pv'))
# 输出类似结果:{'all_pv':487}
上面演示了QuerySet的aggregate的用法,用来给QuerySet直接计算结果。
除了Count和Sum外,还有Avg,Min和Max等表达式,均用来满足我们对SQL查询的需求
6、总结
通过上面一系列的介绍,你应该对QuerrySet有了基本的了解。其实简单来说,就是django的ORM为了达到和SQL一样的表达能力,给我们提供了各种各样的接口!
因此,QuerrySet的作用其实就是帮助我们更好的和数据库打交道!
写了半天,真的累死,完全手敲,西巴!
各位看官看在折磨累的份上给个关注或者点赞吧!谢谢!
我的个人博客网站原链接:点击这里
里面还有其他好文,欢迎来访互相讨教。
Model层QuerySet的使用相关推荐
- java 框架 Dao层 Mapper层 controller层 service层 model层 entity层 简介
目录 简介 entity层 mapper层 service层 controller层 简介 SSM是sping+springMVC+mybatis集成的框架. MVC即model view contr ...
- view,control,service,dao,model层的关系
view层: 结合control层,显示前台页面. control层:业务模块流程控制,调用service层接口. service层:业务操作实现类,调用dao层接口. dao层: 数据 ...
- Model层的两种写法
Model层的两种写法 第一种写法 namespace MyMVC.Models {public class Child{ //属性private int id;public int Id{get { ...
- java的model层实例_Struts 2.1.6 精简实例系列教程(3):新闻管理Model层的开发(整合iBatis)...
本期开始讲Model层的开发,整合iBatis框架,iBatis是Apache旗下Java数据持久层的框架,跟Hibernate是同一类型的框架.大家可到它的官方网站去下载http://ibatis. ...
- koa --- [MVC实现之五]Model层的实现
说明 上一篇: MVC实现之四 这一篇主要介绍: 项目中用到的Sequelize库中的一些方法,参考使用Sequelize连接mysql 将Model层加入Mar类中 Service层 还是从业务出发 ...
- php model层怎么写逻辑,目前用php框架的话,大家会把逻辑写到model中吗?
目前用php框架的话,大家会把逻辑写到model中吗? 还是model只做数据的添加删除 修改操作? 如果说是简单 mvc框架 你们把逻辑写在哪里?controller? 还是说自己弄了个逻辑层? 回 ...
- yii model层操作总结
yii model层操作属性和方法总结. tableName – 设置Model所对应的表名,例如: public function tableName(){return 'gshop_order_e ...
- android model 设计,Android model层设计
model层 在开发app的过程中,不管是使用了mvp还是mvc甚至mvvm模式,model层的设计基本都是一样的,model层可以被称为数据层,它的主要任务就是为上层提供各种的数据服务,上层完全不需 ...
- php 实现 model层,Thinkhphp5控制器调用的Model层的方法总结
控制器器里: /** * Created by PhpStorm. * User: Haima * Date: 2018/7/8 * Time: 15:58 */ namespace app\api\ ...
最新文章
- 在Oracle中exception关键字,Oracle表字段有Oracle关键字出现异常解决方案
- Android4.0 Launcher 源码分析系列(二)
- 关于camera 构架设计的一点看法
- 离线轻量级大数据平台Spark之MLib机器学习库Word2Vec实例
- 算法与数据结构(python):分治与归并排序
- matlab全域基函数,多项式函数插值:全域多项式插值(一)单项式基插值、拉格朗日插值、牛顿插值 [MATLAB]...
- leetcode43. 字符串相乘 经典大数+和*
- 技术管理者必备管理模板
- 项目中有出现过缓存击穿,简单说说怎么回事?
- html输出json对象属性值,用javascript中的HTML元素值构建JSON对象
- java单例模式_java设计模式-单例模式
- 《C和C++游戏趣味编程》 第8章 十步万度
- 如果要在mFC客户区添加控件怎么办
- Centos 7.9系统安装网卡驱动
- 使用3g模块SIM5360E收发短信
- Level/levelup-1-简介
- 经典光流估计算法和光流对齐方法
- matlab某奶制品加工厂,奶制品加工问题 - 数学建模.doc
- PyCharm许可证过期解决方案
- 日记:2011年6月深圳2周找工作总结