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的使用相关推荐

  1. java 框架 Dao层 Mapper层 controller层 service层 model层 entity层 简介

    目录 简介 entity层 mapper层 service层 controller层 简介 SSM是sping+springMVC+mybatis集成的框架. MVC即model view contr ...

  2. view,control,service,dao,model层的关系

    view层:    结合control层,显示前台页面. control层:业务模块流程控制,调用service层接口. service层:业务操作实现类,调用dao层接口. dao层:     数据 ...

  3. Model层的两种写法

    Model层的两种写法 第一种写法 namespace MyMVC.Models {public class Child{ //属性private int id;public int Id{get { ...

  4. java的model层实例_Struts 2.1.6 精简实例系列教程(3):新闻管理Model层的开发(整合iBatis)...

    本期开始讲Model层的开发,整合iBatis框架,iBatis是Apache旗下Java数据持久层的框架,跟Hibernate是同一类型的框架.大家可到它的官方网站去下载http://ibatis. ...

  5. koa --- [MVC实现之五]Model层的实现

    说明 上一篇: MVC实现之四 这一篇主要介绍: 项目中用到的Sequelize库中的一些方法,参考使用Sequelize连接mysql 将Model层加入Mar类中 Service层 还是从业务出发 ...

  6. php model层怎么写逻辑,目前用php框架的话,大家会把逻辑写到model中吗?

    目前用php框架的话,大家会把逻辑写到model中吗? 还是model只做数据的添加删除 修改操作? 如果说是简单 mvc框架 你们把逻辑写在哪里?controller? 还是说自己弄了个逻辑层? 回 ...

  7. yii model层操作总结

    yii model层操作属性和方法总结. tableName – 设置Model所对应的表名,例如: public function tableName(){return 'gshop_order_e ...

  8. android model 设计,Android model层设计

    model层 在开发app的过程中,不管是使用了mvp还是mvc甚至mvvm模式,model层的设计基本都是一样的,model层可以被称为数据层,它的主要任务就是为上层提供各种的数据服务,上层完全不需 ...

  9. php 实现 model层,Thinkhphp5控制器调用的Model层的方法总结

    控制器器里: /** * Created by PhpStorm. * User: Haima * Date: 2018/7/8 * Time: 15:58 */ namespace app\api\ ...

最新文章

  1. 在Oracle中exception关键字,Oracle表字段有Oracle关键字出现异常解决方案
  2. Android4.0 Launcher 源码分析系列(二)
  3. 关于camera 构架设计的一点看法
  4. 离线轻量级大数据平台Spark之MLib机器学习库Word2Vec实例
  5. 算法与数据结构(python):分治与归并排序
  6. matlab全域基函数,多项式函数插值:全域多项式插值(一)单项式基插值、拉格朗日插值、牛顿插值 [MATLAB]...
  7. leetcode43. 字符串相乘 经典大数+和*
  8. 技术管理者必备管理模板
  9. 项目中有出现过缓存击穿,简单说说怎么回事?
  10. html输出json对象属性值,用javascript中的HTML元素值构建JSON对象
  11. java单例模式_java设计模式-单例模式
  12. 《C和C++游戏趣味编程》 第8章 十步万度
  13. 如果要在mFC客户区添加控件怎么办
  14. Centos 7.9系统安装网卡驱动
  15. 使用3g模块SIM5360E收发短信
  16. Level/levelup-1-简介
  17. 经典光流估计算法和光流对齐方法
  18. matlab某奶制品加工厂,奶制品加工问题 - 数学建模.doc
  19. PyCharm许可证过期解决方案
  20. 日记:2011年6月深圳2周找工作总结

热门文章

  1. 从「八个雅鹿」谈搜索技巧
  2. 真正的老实人是不会被欺负的,懦弱才是
  3. WPF聚光灯光源学习
  4. 牛客NC21477 御坂美琴
  5. Android必须知道的Java内存结构及堆栈区别
  6. Webservice应用
  7. 《谁动了我的奶酪》总结
  8. windows ping命令详解
  9. OpenShift集群完善及创建应用CakePHP
  10. cloneNode(true)和cloneNode()的区别?(克隆节点)