更多知识关注公众号:Python野路子
要检索数据库中的对象,就要为model类构造一个查询集QuerySet,一个QuerySet就代码数据库中的一组数据,它可以有一个或很多个,也可以通过filter根据给定的参数对数据集进行筛选。在SQL术语中,QuerySet相当于SELECT语句,filter相当于where或limit这样的限定从句。
查询对象
all()查询所有结果,返回QuerySet。
article_list = Article.objects.all()
article_list
<QuerySet [<Article: python数据类型>, <Article: Python快速入门>, <Article: MySQL快速入门>, <Article: MySQL快速入门>, <Article: 中间件通讯>, <Article: 最近,火遍朋友圈的地摊经济>, <Article: create>]>
我们可以使用for循环遍历一个个文章对象:
for article in article_list:
print(article, type(article))
python数据类型 <class 'article.models.Article'>
Python快速入门 <class 'article.models.Article'>
MySQL快速入门 <class 'article.models.Article'>
MySQL快速入门 <class 'article.models.Article'>
中间件通讯 <class 'article.models.Article'>
最近,火遍朋友圈的地摊经济 <class 'article.models.Article'>
create <class 'article.models.Article'>
上面打印出标题,是因为我们在model中通过__str__方法返回标题:
def __str__(self):
return self.title
get(**kwargs)查询单个数据,只会返回一个实例对象,如果所有条件都不满足或者是满足条件的有多个,将抛出DoesNotExist异常,所以一般情况下,我们会这么用:
try:
article = Article.objects.get(pk=1) # 在django 的ORM查询中,数据库的主键可以用PK代替, 官方推荐使用pk
article = Article.objects.get(id=1) # 等同于select * from hello_article where id=1;
except Article.DoesNotExist:
print('异常处理')
filter(**kwargs)返回满足筛选条件的QuerySet。
Article.objects.filter(status = 0) # select * from article where status=0
exclude(**kwargs)排除符合条件的数据,返回一个QuerySet。
Article.objects.exclude(status = 0) # select * from article where status<>0
*values(field)返回一个ValueQuerySet,一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列。
articles = Article.objects.all() # 获取所有Article对象
articles
<QuerySet [<Article: python数据类型>, <Article: Python快速入门>, <Article: MySQL快速入门>, <Article: MySQL快速入门>, <Article: 中间件通讯>, <Article: 最近,火遍朋友圈的地摊经济>, <Article: create>]>
articles_value = Article.objects.values('title', 'tags')
articles_value
<QuerySet [{'title': 'python数据类型', 'tags': None}, {'title': 'Python快速入门', 'tags': None}, {'title': 'MySQL快速入门', 'tags': None}, {'title': 'MySQL快速入门', 'tags': 4}, {'title': '中间件通讯', 'tags': 1}, {'title': '中间件通讯', 'tags': 2}, {'title': '最近,火遍朋友圈的地摊经济', 'tags': None}, {'title': 'create', 'tags': None}]>
同样我们可以使用for循环进行遍历:
for article_dict in articles_value:
print(article_dict)
{'title': 'python数据类型', 'tags': None}
{'title': 'Python快速入门', 'tags': None}
{'title': 'MySQL快速入门', 'tags': None}
{'title': 'MySQL快速入门', 'tags': 4}
{'title': '中间件通讯', 'tags': 1}
{'title': '中间件通讯', 'tags': 2}
{'title': '最近,火遍朋友圈的地摊经济', 'tags': None}
{'title': 'create', 'tags': None}
从上面可以看出遍历出来的是字典。
*values_list(field)它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列。
articles_values_list = Article.objects.values_list('title', 'tags')
# 输出结果:
<QuerySet [('python数据类型', None), ('Python快速入门', None), ('MySQL快速入门', None), ('MySQL快速入门', 4), ('中间件通讯', 1), ('中间件通讯', 2), ('最近,火遍朋友圈的地摊经济', None), ('create', None)]>
遍历获取的是元组:
for article_tuple in articles_values_list:
print(article_tuple)
('python数据类型', None)
('Python快速入门', None)
('MySQL快速入门', None)
('MySQL快速入门', 4)
('中间件通讯', 1)
('中间件通讯', 2)
('最近,火遍朋友圈的地摊经济', None)
('create', None)
*order_by(args)根据给定的参数进行排序。
# 正序
article_list = Article.objects.order_by('id')
# 输出结果:
<QuerySet [<Article: python数据类型>, <Article: Python快速入门>, <Article: MySQL快速入门>, <Article: MySQL快速入门>, <Article: 中间件通讯>, <Article: 最近,火遍朋友圈的地摊经济>, <Article: create>]>
# 倒序,字段前面加-
article_list = Article.objects.order_by('-id')
# 输出结果:
# <QuerySet [<Article: create>, <Article: 最近,火遍朋友圈的地摊经济>, <Article: 中间件通讯>, <Article: MySQL快速入门>, <Article: MySQL快速入门>, <Article: Python快速入门>, <Article: python数据类型>]>
# 可以根据多个
# 首先根据阅读量进行排序,如果阅读量相同,则根据点赞量进行排序
article_list = Article.objects.order_by('read_num', 'like_num')
一定要注意的一点是,多个order_by,会把前面排序的规则给打乱,而使用最后面的排序方式,例如:
article_list = Article.objects.order_by('read_num').order_by('like_num')
它会根据点赞数排序,而不是两者,因为前面一个排序得到一个queryset,然后又来排序了。
distinct()
给查询结果去重,一般都是在values和values_list方法后面,因为他们两个可以或取出指定字段数据,然后再去重。
这里需要注意的是,当模型类中指定了默认排序字段,那么当我们使用distinct方法进行去重时,默认会按照我们指定的排序字典进行去重,会导致去重结果不是我们想要的,所以要么我们在模型类中不指定排序字段,如果需要指定排序,则在查询集中,在distinct方法前面加上order_by方法,并且order_by方法中的字段就是我们要去重的字段数据。
count()将返回当前查询到的数据的总数。
article_count = Article.objects.count() # 7, 相当于select count(*) from tb_article
first()获取查询到的数据的第一条数据,如果使用用了order_by排序,那么将获取排序后的第一条,如果没有用order_by,那么将按主键进行默认排序。
article = Article.objects.first() # <Article: python数据类型>
last()与first方法一样,只不过是获取的是最后一条的数据。
article = Article.objects.last() # <Article: create>
latest(field_name=None)根据提供的参数field_name来进行排序,然后提取离现在最近的一条数据。
latest_article = Article.objects.latest('id') # <Article: create>
field_name通常是DateField、DateTimeField或IntegerField一起使用。应该避免和其他字段一起使用, 因为语义上是错误的。这两种方法主要是提供代码的方便性和可读性, 如果和非时间字段一起使用,会带来新的混淆。
还可以在模型的Meta类中先定义get_latest_by属性:
class Article(models.Model):
...
public_date = models.DateTimeField(blank=True, null=True)
class Meta:
db_table = 'tb_article'
get_latest_by = 'public_date' # 用来排序的字段
然后直接使用:
latest_article = Article.objects.latest()
earliest(field_name=None)用法和latest一样,只是这个是获取最久远的一个。
exists()返回 True或者 False ,在数据库层面执行select (1) as a from table_name limit 1的查询。判断QuerySet是否有数据,用这个接口是最合适的,不要用count或者len(queryset )这样的操作来判断是否存在 。 相反, 如果可以预期接下来会用到 Query 的方式来做判断 ,这样可以减少一次 查询请求。
Article.objects.exists() # True
update(**kwargs)更新数据方法,这个方法可以对查询出来的QuerySet里面的所有元素进行更新,并且更新参数的个数也是不限的。另外要注意的是,因为get方法返回的不是QuerySet对象,因此使用get方法提取出来的数据不能使用update方法。出于这种情况,建议应该使用filter(pk=xx)来替代get(pk=xxx)方法。并且,使用get出来的模型,修改数据后再save,会更新所有的数据,比update的效率更低。
Article.objects.filter(id=2).update(title="文章标题")
**delete():**删除QuerySet中的模型。
查询条件
我们可以在调用这些方法的时候传递不同的参数来实现查询需求。相当于是SQL语句中的where语句后面的条件,在ORM层面,这些查询条件都是使用field__condition方式使用。
exact使用精确的=进行查找。如果提供的是一个None,那么在SQL层面就是被解释为NULL。示例代码如下:
article = Article.objects.get(id=2) # <Article: python数据类型>
article = Article.objects.get(id__exact=2) # <Article: python数据类型>
article_list = Article.objects.filter(id=2) # <QuerySet [<Article: python数据类型>]>
article_list = Article.objects.filter(id__exact=2) # <QuerySet [<Article: python数据类型>]>
**iexact:**同exact,只是忽略大小写。
contains判断某个字段是否包含了某个数据。
articles = Article.objects.filter(title__contains='快速')
# <QuerySet [<Article: Python快速入门>, <Article: MySQL快速入门>, <Article: MySQL快速入门>]>
# 相当于select title from tb_article where title like '%快速%';
# 我们可以使用查看执行的SQL语句:查询结果集.query
要注意的是,在使用contains的时候,翻译成的sql语句左右两边是有百分号的,意味着使用的是模糊查询。而exact翻译成sql语句左右两边是没有百分号的,意味着使用的是精确的查询。
**icontains:**同contains,只是忽略大小写。
in提取那些给定的field的值是否在给定的容器中。容器可以为list、tuple或者任何一个可以迭代的对象,包括QuerySet对象。示例如下:
articles = Article.objects.filter(id__in=[2,3,4,5])
# <QuerySet [<Article: python数据类型>, <Article: Python快速入门>]>
# 相当于select title from tb_article where id in(2,3,4,5)
**gt:**某个field的值要大于给定的值。示例代码如下:
articles = Article.objects.filter(id__gt=4) # 将所有id大于4的文章全部都找出来。
# select ... where id > 4;
**gte:**类似于gt,是大于等于。
**lt:**小于。
**lte:**小于等于。
startswith字段的值以某个值开始,大小写敏感。
articles = Article.objects.filter(title__startswith = 'py')
# <QuerySet [<Article: python数据类型>]>
# 相当于select ... where title like 'py%'
**istartswith:**同startswith,但忽略大小写。
endswith以某个字符串结尾。
articles = Article.objects.filter(title__endswith = '入门')
# <QuerySet [<Article: Python快速入门>, <Article: MySQL快速入门>, <Article: MySQL快速入门>]>
# 相当于select ... where title like '%入门'
**iendswith:**同startswith,但忽略大小写。
range范围查询,多用于时间范围。
from article.models import Category, Article, Tag
from datetime import datetime
start_date = datetime(2020, 7, 4, 10)
end_date = datetime(2020, 7, 4, 11)
articles = Article.objects.filter(create_time__range=(start_date, end_date))
# <QuerySet [<Article: python数据类型>, <Article: Python快速入门>, <Article: MySQL快速入门>, <Article: MySQL快速入门>, <Article: 中间件通讯>, <Article: 最近,火遍朋友圈的地摊经济>, <Article: create>]>
start_date = datetime(2020, 7, 4, 2)
end_date = datetime(2020, 7, 4, 3)
articles = Article.objects.filter(update_time__range=(start_date, end_date))
# <QuerySet [<Article: python数据类型>, <Article: Python快速入门>, <Article: MySQL快速入门>, <Article: MySQL快速入门>, <Article: 中间件通讯>, <Article: 最近,火遍朋友圈的地摊经济>, <Article: create>]>
# print(create_time) # 2020-07-04 10:35:04.265011
# print(update_time) # 2020-07-04 02:25:53.175317
以上代码的意思是提取所有发布时间在start_date到end_date之间的文章。
需要注意的是,以上提取数据,不会包含最后一个值。也就是不会包含end_date的文章。
date针对某些date或者datetime类型的字段。可以指定date的范围。并且这个时间过滤,还可以使用链式调用。
from article.models import Category, Article, Tag
from datetime import date
articles = Article.objects.filter(create_time__date=date(2020,7,4))
# 相当于 select * from tb_article where date(create_time) = 2020 - 07 - 04
year根据年份进行查找,代码如下:
from article.models import Category, Article, Tag
from datetime import datetime
articles = Article.objects.filter(create_time__year=2020)
# <QuerySet [<Article: python数据类型>, <Article: Python快速入门>, <Article: MySQL快速入门>, <Article: MySQL快速入门>, <Article: 中间件通讯>, <Article: 最近,火遍朋友圈的地摊经济>, <Article: create>]>
# 相当于select * from tb_article where create_time between 2020-01-01 00:00:00 and 2020-12-31 23:59:59.999999
articles = Article.objects.filter(create_time__year__gte=2020)
# select * from tb_article where create_time >= 2020-01-01 00:00:00
关于日期类的查询还有很多 , 比如 month、day 等,具体等需要时查文档 即可 。
isnull判断是否为空。
articles = Article.objects.filter(create_time__isnull=True)
# 相当于select * from tb_article where create_time is null;
**切片:**可以用python的数组切片语法来限制你的QuerySet以得到一部分结果。它等价于SQL中的LIMIT和OFFSET。
articles = Article.objects.all()[:2] # 返回前2个对象。
# <QuerySet [<Article: python数据类型>, <Article: Python快速入门>]>
Django 不支持对查询集做负数索引。
切片可以用来实现翻页显示内容,比如每一页显示10条内容可以利用切片,第page页,如下:
articles = Article.objects.all() # 获取Article的QuerySet集
counts = articles.count() # 总记录数
per_page = 4 # 每页显示多少条记录
for page in range(counts/per_page + counts%per_page):
for article in articles[page*per_page: per_page(page+1)]:
print(article.title)
F和Q查询
F查询
之前的查询都是对象的属性与常量值比较,2个属性怎么比较呢?
Django中提供F来做这样的比较。F的实例可以在查询中引用字段,来比较同一个model 实例中两个不同字段的值。
# F语法形式
filter(字段名__运算符=F('字段名'))
例如,要查询阅读量大于等于点赞数的文章。可以这样实现:
articles = Article.objects.all()
for article in articles:
if article.read_num >= article.like_num:
print(article)
如果使用F表达式,那么一行代码就可以搞定。示例代码如下:
from django.db.models import F
# 要查询阅读量大于等于点赞数的文章
articles = Article.objects.filter(read_num__gte=F('like_num'))
# 要查询阅读量大于等于2倍点赞数的文章
articles = Article.objects.filter(read_num__gte=F('like_num')*2)
F对象还可以对自身属性进行运算。例如,将公司全体员工薪水都增加500元,如果按照一般流程,应该是先从数据库中提取所有的员工工资到内存中,然后进行增加500,最后保存数据库中。
employees = Employee.objects.all()
for employee in employees:
employee.salary += 1000
employee.save()
这个需要先将数据从数据库中提取出来,再进行计算,最后再保存到数据库。
而使用F对象,可以来优化ORM操作数据库的,F对象并不会马上从数据库中获取数据,而是在生成SQL语句的时候,动态的获取传给F表达式的值。
from djang.db.models import F
Employee.object.update(salary=F('salary')+1000)
Q查询
现在,我们要查询阅读量大于20,并且点赞大于10的文章。
# 使用链式调用,2个filter
articles = Article.objects.filter(read_num__gte=20).filter(like_num__gte=10)
# 使用一个filter
articles = Article.objects.filter(read_num__gte=20, like_num__gte=10)
以上是一个并集查询,可以简单的通过传递多个条件进去来实现。
但是,如果想要实现一些复杂的查询语句,比如要查询阅读量大于20,或者是点赞大于10的文章。那就没有办法通过传递多个条件进去实现了。这时候就需要使用Q表达式来实现了。
# Q语法形式:filter(Q(字段名__运算符=值))
from django.db.models import Q
articles = Article.objects.filter(Q(read_num__gte=20) | Q(like_num__gte=10))
以上是进行【或】运算,当然还可以进行其他的运算,比如有&和~(非)等。
'''
或 Q()|Q()
并且 Q()&Q()
非 ~Q()
'''
from django.db.models import Q
# 查询阅读量大于20,并且点赞大于10的文章
articles = Article.objects.filter(Q(read_num__gte=20) & Q(like_num__gte=10))
# 获取id不等于3的文章
articles = Article.objects.exclude(id=3)
# 或
articles = Article.objects.filter(~Q(id=3))
聚合查询aggregate
在SQL中,我们可以使用聚合函数来提取数据。比如提取某个商品销售的数量,那么可以使用Count,如果想要知道商品销售的平均价格,那么可以使用Avg。
在ORM中聚合函数是通过aggregate方法来实现的,返回的是一个字典。
'''
Avg, Max, Min, Count, Sum
语法形式:aggregate(Xxx('字段'))
'''
from django.db.models import Avg, Max, Min, Count, Sum
# Avg: 求平均值。 求文章平均阅读量
read_avg = Article.objects.aggregate(Avg('read_num'))
# {'read_num__avg': 20.0}
# 其中read_num__avg的结构是按照【字段名__聚合函数小写】构成的,如果想要修改默认的名字,那么可以将Avg赋值给一个关键字参数。
read_avg = Article.objects.aggregate(read_avg=Avg('read_num')) # {'read_avg': 20.0}
# Count: 统计格数。 统计总共有多少篇文章
article_num = Article.objects.aggregate(article_num=Count('id')) # {'article_num': 7}
# 如果需要去重,使用distinct=True,例如,获取用户表中所有的不重复的密码总共有多少个
pwd_num = Account.objects.aggregate(pwd_num=Count("password", distinct=True))
# 其他 Max, Min, Sum类似
# 多个聚合
result = Article.objects.aggregate(Count('id'), Sum('read_num'), Max('read_num'), Min('read_num'))
# {'id__count': 7, 'read_num__sum': 140, 'read_num__max': 20, 'read_num__min': 20}
分组查询annotate
我们有时候查看一些博客,侧边栏有分类列表,显示博客已有的全部文章分类。
现在想在分类名后显示该分类下有多少篇文章,即统计每个分类下有多少篇文章,该怎么做呢?
这个时候,我们就可以用到annotate,annotate为QuerySet中的每个对象添加一个独立的汇总值。先按类名进行分组,然后再统计每个组分别为多少数量即可。
from django.db.models import Avg, Max, Min, Count, Sum
# 统计每个分类下有多少篇文章。
# 这里先对Category进行分类,使用annotate分组,再使用Count统计每个分组下的数量,同时给每一个对象增加这个数量。
# 这相当于给Category动态增加了属性articles__count,而这个属性的值来源于Count("articles")
category_list=Category.objects.annotate(Count("articles")) # 类似反向查询,使用related_name定义的名称来查
# <QuerySet [<Category: Python基础>, <Category: 数据库知识>]>
'''
相当于SQL
select tb_category.id,tb_category.name,count(tb_article.id) as articles__count
from tb_category left join tb_article on tb_category.id=tb_article.category_id
group by tb_category.id
'''
for category in category_list:
# annotate为QuerySet中每一个对象都增加了一个独立的articles__count值。
print(category.name, category.articles__count)
# Python基础 2
# 数据库知识 1
# 统计每个分类阅读最多的文章
# 方式一:
category_list=Category.objects.annotate(article_read_max = Max("articles__read_num"))
# <QuerySet [<Category: Python基础>, <Category: 数据库知识>]>
'''
相当于SQL
select tb_category.id,tb_category.name,MAX(tb_article.read_num) as article_read_max
from tb_category left join tb_article on (tb_category.id = tb_article.category_id)
group by tb_category.id
'''
for category in category_list:
print(category.name, category.article_read_max)
# Python基础 22
# 数据库知识 25
# 方式2:
# values 就等同于group by,返回的是一个字典,values_list 就等同于group by,, 返回的是一个元祖
# 正向查询,外键属性__字段
Article.objects.values('category__name').annotate(article_read_max = Max("read_num"))
# <QuerySet [{'category__name': 'Python基础', 'article_read_max': 22}, {'category__name': '数据库知识', 'article_read_max': 25}, {'category__name': None, 'article_read_max': 33}]>
'''
相当于SQL
select tb_category.name, max(tb_article.read_num) as article_read_max
from tb_article left join tb_category on tb_article.category_id=tb_category.id
group by tb_category.name
'''
# 给每个文章对象实例,添加一个category__name属性,用来显示对应分类的名称,这里使用F取对应的分类名称。
articles = Article.objects.annotate(category_name=F("category__name"))
print(articles.query) # 输出对应的SQL语句
'''
select
tb_article.id,
tb_article.title,
tb_article.content,
tb_article.category_id,
tb_article.create_time,
tb_article.update_time,
tb_article.read_num,
tb_article.like_num,
tb_category.name as category_name
from
tb_article
left join tb_category ON (tb_article.category_id = tb_category.id)
'''
aggregate和annotate的区别:
- aggregate:返回使用聚合函数后的字段和值,返回的是一个字典,执行后就是最终结果。
- annotate:返回的是一个QuerySet,可以继续进行查询。annotate的聚合结果是针对每行数据的,而不是整个查询结果。在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组group by。
Django的aggregate和annotate方法属于高级查询方法,主要用于组合查询,可以大大提升数据库查询效率。当你需要对查询集(queryset)的某些字段进行聚合操作时(比如Sum, Avg, Max),请使用aggregate方法。如果你想要对数据集先进行分组(Group By)然后再进行某些聚合操作或排序时,请使用annotate方法。
value与annotate
value与annotate的顺序不同时,查询结果大相径庭。
1)value在annotate前面时,相当于group by。
ret=Student.objects.values('gender').annotate(count=Count('sid')).order_by('-count')
print(ret)
等价于以下sql语句:
select gender, COUNT(sid) AS count
from student
group by gender
order by count DESC
执行结果:
<QuerySet [{'gender': '女', 'count': 6}, {'gender': '男', 'count': 5}]>
可以看到结果是按性别分组的。
2)value放在annotate后面时,相当于select。
ret = Student.objects.annotate(count=Count('sid')).values('gender').order_by('-count')
print(ret)
等价于以下sql语句:
SELECT gender
FROM student
GROUP BY sid
ORDER BY COUNT(sid) DESC
执行结果:
<QuerySet [{'gender': '女'}, {'gender': '男'}, {'gender': '女'}, {'gender': '女'}, {'gender': '男'}, {'gender': '女'}, {'gender': '男'}, {'gender': '男'}, {'gender': '女'}, {'gender': '女'}, {'gender': '男'}]>
annotate前面没有value时,默认按当前表的主键分组。
可以看到结果并没有按性别分组,而是每个学生作为一组,说明上述结论是正确的
关联查询
关联查询有两种方式:一种是创建类对象,通过类对象查询。另外一种是通过模型类查询。
例如:
① 查询分类名字包含“Python”的所有文章信息;
② 查询文章标题中包含“python”的文章所属的分类信息;
③ 查询标签含有“快速入门”的文章;
④ 查询标签为“快速入门”的文章所属的分类信息;
我们分别使用2种方式处理。
方式一:类对象查询
① 通过 【分类】 查询 【文章】,即根据一类,查询多类的信息:
# 创建一类的对象
# 一类对象.多类类名小写_set.all() # 或者使用related_name设置的名字
② 通过 【文章】 查询 【分类】,即根据多类,查询一类的信息:
# 创建多类的对象
# 多类对象.关联属性(外键)
方式二:模型类查询
Django提供一种强大而又直观的方式来 处理 查询中的关联关系,它能自动处理join联系。要做跨关系查询,就使用双下划线来连接模型(model)间关联字段的名称,直到想要的model为止。
① 已知条件是一类信息,查询多类信息。(正向查询):
# 多类类名.objects.filter(多类中的外键属性__一类属性名__条件)
# 例如,查询分类名字包含‘Python’的所有文章信息
articles = Article.objects.filter(category__name__icontains='python')
# <QuerySet [<Article: python数据类型>, <Article: Python快速入门>]>
正向查询按字段,比如上面Article中的category。
② 已知条件为多类信息,查询一类信息。(反向查询):
# 一类类名.objects.filter(多类类名小写__多类属性名__条件) # 若使用related_name设置了名字,则需要使用设置的名字
# 查询文章标题中包含'python'的文章所属的分类信息
categories = Category.objects.filter(articles__title__icontains='python')
# <QuerySet [<Category: Python基础>, <Category: Python基础>]>
反向查询按子表的模型类名小写,如果设置了related_name,则使用related_name替换表名。
③ 多个表进行查询:
# 查询标签为‘快速入门’的文章所属的分类信息
categories = Category.objects.filter(articles__tags__name__icontains='快速入门')
# <QuerySet [<Category: Python基础>]>
多对多和一对多类似。
最后对模型类查询来个小总结:**
1、想查询哪个类中的信息,就以哪个类名.object.filter开头
2、多类类名开头,filter括号内 关联属性开头。一类类名开头,filter括号内 多类类名小写开头。后面都是条件判断(如上面的一类有条件判断或多类有条件判断)。
查询集QuerySet
Django的ORM中存在查询集的概念,也称查询结果集QuerySet,表示从数据库中获取的对象集合。
当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):
- all:返回所有数据;
- filter:返回满足条件的数据;
- exclude:返回满足条件之外的数据;
- order_by:对结果进行排序;
- and so on ....
对查询集可以再次调用过滤器进行过滤,即链式调用方式,如:
articles = Article.objects.filter(read_num__gte=20).filter(like_num__gte=10)
在每个方法的执行结果上可以继续调用同样的方法,因为每个方法的返回值都是它自己,也就是 QuerySet 。
两大特性
1) QuerySet是惰性的
Django中的QuerySet本质上是一个懒加载的对象,创建查询集QuerySet的动作不涉及任何数据库操作,只是返回一个 QuerySet对象,你可以一直添加过滤器,在这个过程中,Django不会执行任何数据库查询,除非QuerySet被执行,等真正调用它时才会执行查询。
from article.models import Category, Article, Tag
from django.db import connection
article_list = Article.objects.all() # 此时还未访问数据库操作
print(connection.queries) # [] 该方法会打印出所有执行过的sql语句
我们可以看到在打印connection.quries的时候打印的是一个空的列表。说明上面的QuerySet并没有真正的执行。
在以下情况下QuerySet会被转换为SQL语句执行:
# 1.遍历
for article in Article.objects.all(): # 第一次遍历的时候查询,后面再次遍历就不会查询了。
print(article)
'''
# 执行相关SQL:
select tb_article.id, tb_article.title, tb_article.content, tb_article.category_id, tb_article.create_time, tb_article.update_time, tb_article.read_num, tb_article.like_num
from tb_article
'''
for article in Article.objects.filter(title__icontains='快速'):
print(article)
for sql in connection.queries: # 第一次遍历的时候查询,后面再次遍历就不会查询了。
print('='*30)
print(sql)
'''
select tb_article.id, tb_article.title, tb_article.content, tb_article.category_id, tb_article.create_time, tb_article.update_time, tb_article.read_num, tb_article.like_num
from tb_article where tb_article.title like '%快速%'
'''
# 2. 使用步长做切片操作
# 3. 调用len函数用来获取QuerySet中总共有多少条数据
# 4. 调用list函数用来将一个QuerySet对象转换为list对象
# 5. 对某个QuerySet进行判断
2) 缓存机制
每个QuerySet都包含一个缓存,以减少对数据库的访问。要编写高效代码,就要理解缓存是如何工作的。
使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询时会使用缓存的数据,减少了数据库的查询次数。
场景一:如下是两个QuerySet,这2个是无法使用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。
[article.id for article in Article.objects.all()]
print(connection.queries)
# [{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}, {'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`content`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num` FROM `tb_article`', 'time': '0.000'}]
[article.id for article in Article.objects.all()]
print(connection.queries)
# [{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}, {'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`content`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num` FROM `tb_article`', 'time': '0.000'}, {'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`content`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num` FROM `tb_article`', 'time': '0.001'}]
通过上面输出所执行的SQL记录,我们可以看出第二次执行同样的语句,明显比第一次多了一条SQL语句,即第二次又进行数据库查询操作。
如何优化,减少对数据库的操作呢??
我们先来测试几个数据,看看效果:
articles = Article.objects.all()
print(connection.queries)
[]
[article.id for article in articles] # 访问数据库
# [2, 5, 6, 7, 8, 9, 10]
print(connection.queries)
#[{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}, {'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`content`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num` FROM `tb_article`', 'time': '0.000'}]
[article.id for article in articles] # 使用缓存
# [2, 5, 6, 7, 8, 9, 10]
print(connection.queries)
# [{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}, {'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`content`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num` FROM `tb_article`', 'time': '0.000'}]
通过上面测试可知,只要简单的保存QuerySet,然后重用即可,上面第二个就使用了缓存,只要查询一次数据库即可,提高了性能。
但是,查询集并不总是缓存结果,当查询集执行部分查询时,会先检查缓存,如果它没有被填充,部分查询返回的结果不会被缓存。这意味着,使用切片查询不会填充缓存。
articles = Article.objects.all() # 没有访问数据库
print(connection.queries) # []
# 重复的切片查询每次都会访问数据库,例如下面相当于只查询了一条记录,没有覆盖全部
articles[1] # 访问数据库
articles[2] # 再次访问数据库
# 但是,如果整个查询集已经被求值,切片查询会使用缓存
articles = Article.objects.all()
[article.id for article in articles] # 查询数据库
articles[1] # 使用缓存
articles[2] # 使用缓存
# 下面是一些会填充缓存的操作,因为会全部查询,即覆盖所有
[article.id for article in articles] # 列表推导式,会遍历queryset所有的
bool(articles)
article in articles
list(articles)
几个提升性能的接口
在优化Django项目,提高性能时,尤其要考虑这几种接口的用法 。
**defer接口:**把不需要展示的字段做延迟加载,比如我们在获取文章列表的时候,文章的内容我们是不需要的,因此这时候我们就可以使用defer来过滤掉一些字段,但是当我们需要用到这个字段时,在使用时回去加载。这个字段跟values有点类似,只不过defer返回的不是字典,而是模型。
from article.models import Category, Article, Tag
from django.db import connection
articles = Article.objects.all().defer('content') # 没有访问数据库
print(connection.queries) # []
for article in articles: # 这里遍历的时候会执行一次数据库查询
# 因为在上面提取的时候过滤了content
# 这个地方重新获取content,会执行数据库查询,for遍历多少次,即执行多少次数据库查询,加上上面一次,即要查询N+1次
print(article.content)
for sql in connection.queries:
print('='*30)
print(sql)
# 输出结果:
'''
[]
本篇将针对Python做一个简单入门,帮助小白迅速了解Python。
本篇将针对Mysql做一个简单入门,帮助小白迅速了解MySQL。
本篇将针对Mysql做一个简单入门,帮助小白迅速了解MySQL。
开始学习通讯领域,我们从CTI开始吧
相信,最近朋友圈都在讨论各种有关摆地摊的话题,你是否有种冲动也去试下呢?
添加不存在的数据,将数据直接存入数据库,创建一个新的对象,将它保存并放在新创建的对象。(一对一,多对多)
==============================
{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}
==============================
{'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}
============================== # 下面一条除了content字段,其他字段值都查出来,因为前面过滤此字段
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num` FROM `tb_article`', 'time': '0.000'}
============================== # 后面查询content字段和id字段
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 2', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 5', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 6', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 7', 'time': '0.000'}
==============================
'''
上面代码会产生N+1的查询问题,在实际使用时千万要注意!
defer虽然能过滤字段,但是有些字段是不能过滤的,比如id,即使你过滤了,也会提取出来。
**only接口:**与defer刚好相反,only提取指定的字段。如果只想获取文章标题title的内容。
from article.models import Category, Article, Tag
from django.db import connection
articles = Article.objects.all().only('title') # 没有访问数据库
print(connection.queries) # []
for article in articles: # 这里遍历的时候会执行一次数据库查询,只查询title字段值
# 因为在上面提取的时候过滤了title
# 这个地方重新获取title,会执行数据库查询,for遍历多少次,即执行多少次数据库查询,加上上面一次,即要查询N+1次
print(article.content)
for sql in connection.queries:
print('='*30)
print(sql)
#
'''
[]
本篇将针对Python做一个简单入门,帮助小白迅速了解Python。
本篇将针对Mysql做一个简单入门,帮助小白迅速了解MySQL。
本篇将针对Mysql做一个简单入门,帮助小白迅速了解MySQL。
开始学习通讯领域,我们从CTI开始吧
相信,最近朋友圈都在讨论各种有关摆地摊的话题,你是否有种冲动也去试下呢?
添加不存在的数据,将数据直接存入数据库,创建一个新的对象,将它保存并放在新创建的对象。(一对一,多对多)
==============================
{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}
==============================
{'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title` FROM `tb_article`', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 2', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 5', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 6', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 7', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 8', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 9', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`content` FROM `tb_article` WHERE `tb_article`.`id` = 10', 'time': '0.000'}
'''
**select_related:**在提取某个模型的数据的同时,也提前将相关联的数据提取出来。比如提取文章数据,可以使用select_related将category信息提取出来,以后再次使用article.category的时候就不需要再次去访问数据库了。可以减少数据库查询的次数。示例代码如下:
from article.models import Category, Article, Tag
from django.db import connection
articles = Article.objects.all() # 没有访问数据库
print(connection.queries) # []
for article in articles: # 查询数据库
print(article.category) # 再次查询数据库
'''
Python基础
Python基础
None
None
None
数据库知识
None
'''
for sql in connection.queries:
print('='*30)
print(sql)
'''
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`content`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num` FROM `tb_article`', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_category`.`id`, `tb_category`.`name` FROM `tb_category` WHERE `tb_category`.`id` = 2', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_category`.`id`, `tb_category`.`name` FROM `tb_category` WHERE `tb_category`.`id` = 2', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_category`.`id`, `tb_category`.`name` FROM `tb_category` WHERE `tb_category`.`id` = 4', 'time': '0.000'}
'''
上面外键产生了N+1查询问题,我们可以使用select_related来解决此类问题。
articles = Article.objects.all().select_related("category") # 没有访问数据库
print(connection.queries) # []
print(articles.query)
'''
select tb_article.id, tb_article.title, tb_article.content, tb_article.category_id, tb_article.create_time, tb_article.update_time, tb_article.read_num, tb_article.like_num, tb_category.id, tb_category.name
from tb_article left outer join tb_category on (tb_article.category_id = tb_category.id)
'''
for article in articles: # 查询数据库,category数据也会一次性查出来
print(article.category) # 使用缓存
for sql in connection.queries:
print('='*30)
print(sql)
'''
[]
==============================
{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}
==============================
{'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}
==============================
{'sql': 'SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`content`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num`, `tb_category`.`id`, `tb_category`.`name` FROM `tb_article` LEFT OUTER JOIN `tb_category` ON (`tb_article`.`category_id` = `tb_category`.`id`)', 'time': '0.000'}
'''
selected_related只能用在一对多或者一对一中,不能用在多对多或者多对一中。比如可以提前获取文章的作者,即正向获取关联对象时,关联对象会被缓存,后续根据外键访问时这个实例,就会从缓存中获得。
但是不能通过作者获取这个作者的文章(逆向关联),或者是通过某篇文章获取这个文章所有的标签(多对多的关系)。这个还得使用下面的接口解决。
**prefetch_related:**这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法可用于多对多关系字段,也可用于反向外键关系(related_name)的查询问题。例如:
# 获取标题中带有`python`字符串的文章以及它的所有标签,
articles = Article.objects.prefetch_related("tags").filter(title__icontains="python")
print(articles.query) # 通过这条命令查看在底层的SQL语句
'''
select tb_article.id, tb_article.title, tb_article.content, tb_article.category_id, tb_article.create_time, tb_article.update_time, tb_article.read_num, tb_article.like_num from tb_article
where tb_article.title like %python%
'''
for article in articles: # 查询article文章对象的同时提取tag标签对象
print(article.title) # 使用缓存
print(article.tags.all()) # 使用缓存
'''
python数据类型
<QuerySet [<Tag: MySQL>, <Tag: 快速入门>]>
Python快速入门
<QuerySet [<Tag: 快速入门>]>
'''
for sql in connection.queries:
print('='*30)
print(sql)
'''
触发的相关SQL,for循环时产生访问数据库的操作,查询article文章对象的同时提取tag标签对象
==============================
select tb_article.id, tb_article.title, tb_article.content,tb_article.category_id, tb_article.create_time, tb_article.update_time, tb_article.read_num,tb_article.like_num
from tb_article
where tb_article.title like '%python%'
==============================
select (tb_article_tags.article_id) as _prefetch_related_val_article_id, tb_tag.id, tb_tag.name
from tb_tag inner join tb_article_tags on(tb_tag.id = tb_article_tags.tag_id)
where tb_article_tags.article_id IN (2, 5)
'''
第一条SQL查询仅仅是获取标题中带有python字符串的Article对象,第二条比较关键,它选取中间表tb_article_tags中article_id带有python字符串的行,然后和tb_tag表内联(INNER JOIN)得到结果表。
现在问题来了,如果我们获取tags对象时只希望获取带有入门的tag对象怎么办呢?
articles = Article.objects.prefetch_related('tags').filter(title__icontains="python")
print(articles.query)
'''
select tb_article.id, tb_article.title, tb_article.content, tb_article.category_id, tb_article.create_time, tb_article.update_time, tb_article.read_num, tb_article.like_num
from tb_article
where tb_article.title like %python%
'''
for article in articles: #for循环时产生访问数据库的操作,查询article文章对象的同时提取tag标签对象
print(article.title) # 使用缓存
print(article.tags.filter(name__icontains='入门')) # 之前用prefetch_related缓存的数据将会被忽略掉
'''
python数据类型
<QuerySet [<Tag: 快速入门>]>
Python快速入门
<QuerySet [<Tag: 快速入门>]>
'''
for sql in connection.queries:
print('-'*30)
print(sql)
'''
触发的相关SQL,for循环时产生访问数据库的操作,查询article文章对象的同时提取tag标签对象
------------------------------
select tb_article.id, tb_article.title, tb_article.content, tb_article.category_id, tb_article.create_time, tb_article.update_time, tb_article.read_num, tb_article.like_num
from tb_article
where tb_article.title like '%python%'
------------------------------
select (tb_article_tags.article_id) as _prefetch_related_val_article_id, tb_tag.id, tb_tag.name
from tb_tag inner join tb_article_tags on (tb_tag.id = tb_article_tags.tag_id)
where tb_article_tags.article_id in (2, 5)
------------------------------
# 但是这里多了2条查询语句,一个为查询id=2对应的文章和id=5对应文章。
select tb_tag.id, tb_tag.name
from tb_tag inner join tb_article_tags on (tb_tag.id = tb_article_tags.tag_id)
where (tb_article_tags.article_id = 2 and tb_tag.name like '%入门%')
limit 21
------------------------------
select tb_tag.id, tb_tag.name
from tb_tag inner join tb_article_tags on (tb_tag.id = tb_article_tags.tag_id)
where (tb_article_tags.article_id = 5 and tb_tag.name like '%入门%')
limit 21
'''
注意:在使用QuerySet的时候,一旦在链式操作中改变了数据库请求,之前用prefetch_related缓存的数据将会被忽略掉,这会导致Django重新请求数据库来获得相应的数据,从而造成性能问题,这里提到的改变数据库请求指各种filter()、exclude()等等最终会改变SQL代码的操作。而all()并不会改变最终的数据库请求,因此是不会导致重新请求数据库的。
那如果确实是想要在查询的时候指定过滤条件该如何做呢,这时候我们可以使用django.db.models.Prefetch来实现,Prefetch这个可以提前定义好queryset,给prefect_related方法添加条件和属性,示例代码如下:
from django.db.models import Prefetch
articles = Article.objects.prefetch_related(Prefetch('tags', queryset=Tag.objects.filter(name__icontains='入门'))).filter(title__icontains="python")
print(articles.query) # 通过这条命令查看在底层的SQL语句
for article in articles: # 产生两条查询语句,分别查询article和tags
print(article.title) # 使用缓存
print(article.tags.filter(name__icontains='入门'))
'''
select tb_article.id, tb_article.title, tb_article.content, tb_article.category_id, tb_article.create_time, tb_article.update_time, tb_article.read_num, tb_article.like_num from tb_article where tb_article.title like %python%
'''
for article in articles:
print(article.title)
tags = article.tags.filter(name__icontains='入门')
for tag in tags:
print(tag)
'''
python数据类型
快速入门
Python快速入门
快速入门
'''
for sql in connection.queries:
print('='*30)
print(sql)
'''
==============================
{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}
==============================
{'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}
==============================
{'sql': "SELECT `tb_article`.`id`, `tb_article`.`title`, `tb_article`.`content`, `tb_article`.`category_id`, `tb_article`.`create_time`, `tb_article`.`update_time`, `tb_article`.`read_num`, `tb_article`.`like_num` FROM `tb_article` WHERE `tb_article`.`title` LIKE '%python%'", 'time': '0.000'}
==============================
{'sql': "SELECT (`tb_article_tags`.`article_id`) AS `_prefetch_related_val_article_id`, `tb_tag`.`id`, `tb_tag`.`name` FROM `tb_tag` INNER JOIN `tb_article_tags` ON (`tb_tag`.`id` = `tb_article_tags`.`tag_id`) WHERE (`tb_tag`.`name` LIKE '%入门%' AND `tb_article_tags`.`article_id` IN (2, 5))", 'time': '0.000'}
==============================
{'sql': "SELECT `tb_tag`.`id`, `tb_tag`.`name` FROM `tb_tag` INNER JOIN `tb_article_tags` ON (`tb_tag`.`id` = `tb_article_tags`.`tag_id`) WHERE (`tb_tag`.`name` LIKE '%入门%' AND `tb_article_tags`.`article_id` = 2 AND `tb_tag`.`name` LIKE '%入门%')", 'time': '0.000'}
==============================
{'sql': "SELECT `tb_tag`.`id`, `tb_tag`.`name` FROM `tb_tag` INNER JOIN `tb_article_tags` ON (`tb_tag`.`id` = `tb_article_tags`.`tag_id`) WHERE (`tb_tag`.`name` LIKE '%入门%' AND `tb_article_tags`.`article_id` = 5 AND `tb_tag`.`name` LIKE '%入门%')", 'time': '0.000'}
'''
#django#