一文搞懂Django数据库查询操作
本文略长,读完约需十分钟。当做复习笔记效果更佳。
查询操作:
数据查询是数据库操作中一个非常重要的技术。查询一般就是使用filter
、exclude
以及get
三个方法来实现。我们可以在调用这些方法的时候传递不同的参数来实现查询需求。在ORM层面,这些查询条件都是使用field
+__
+condition
的方式来使用的。以下将那些常用的查询条件来一一解释。
1.exact
:在底层会被翻译成=
。使用精确的=进行查找。如果提供的是一个None,那么在SQL层面就是被解释为NULL。示例代码如下:
article = Article.objects.get(id__exact=14)
article = Article.objects.get(id__exact=None)
以上的两个查找在翻译为SQL
语句为如下:
select ... from article where id=14;
select ... from article where id IS NULL;
2.iexact
:在底层会被翻译成LIKE
。示例代码如下:
article = Article.objects.filter(title__iexact='hello world')
那么以上的查询就等价于以下的SQL语句:
select ... from article where title like 'hello world';
注意上面这个sql
语句,因为在MySQL
中,没有一个叫做ilike
的。所以exact
和iexact
的区别实际上就是LIKE和=的区别,在大部分collation=utf8_general_ci
情况下都是一样的(collation
是用来对字符串比较的)。
-
注意:
1.
LIKE
和=
:大部分情况下都是等价的,只有少数情况下是不等价的。2.
exict
和iexact
:他们的区别其实就是LIKE
和=
的区别,因为exact
会被翻译成=
,而iexact
会被翻译成LIKE
。3.因为
field__exact=xxx
其实等价于filed=xxx
,因此我们直接使用filed=xxx
就可以了,并且因为大部分情况exact
和iexact
又是等价的,因此我们以后直接使用field=xxx
就可以了。
-
QuerySet.query
:query
可以用来查看这个ORM
查询语句最终被翻译成的SQL
语句。但是query
只能被用在QuerySet
对象上,不能用在普通的ORM模型
上。因此如果你的查询语句是通过get
来获取数据的,那么就不能使用query
,因为get
返回的是满足条件的ORM
模型,而不是QuerySet
。如果你是通过filter
等其他返回QuerySet
的方法查询的,那么就可以使用query
。 -
contains
:使用大小写敏感的判断,某个字符串是否在指定的字段中。这个判断条件会使用大小敏感,因此在被翻译成SQL
语句的时候,会使用like binary
,而like binary
就是使用大小写敏感的。 -
icontains
:使用大小写不敏感的判断,某个字符串是否被包含在指定的字段中。这个查询语句在被翻译成SQL
的时候,使用的是like
,而like
在MySQL
层面就是不区分大小写的。 -
contains
和icontains
:在被翻译成SQL
的时候使用的是%hello%
,就是只要整个字符串中出现了hello
都能过够被找到,而iexact
没有百分号,那么意味着只有完全相等的时候才会被匹配到。 -
in
:可以直接指定某个字段的是否在某个集合中。示例代码如下:
articles = Article.objects.filter(id__in=[1,2,3])
也可以通过其他的表的字段来判断是否在某个集合中。示例代码如下:
categories = Category.objects.filter(article__id__in=[1,2,3])
如果要判断相关联的表的字段,那么也是通过__
来连接。并且在做关联查询的时候,不需要写models_set
,直接使用模型的名字的小写化
就可以了。比如通过分类去查找相应的文章,那么通过article__id__in
就可以了,而不是写成article_set__id__in
的形式。当然如果你不想使用默认的形式,可以在外键定义的时候传递related_query_name
来指定反向查询的名字。示例代码如下:
class Category(models.Model):
name = models.CharField(max_length=100)
class Meta:
db_table = 'category'
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
cateogry = models.ForeignKey("Category",on_delete=models.CASCADE,null=True,related_query_name='articles')
class Meta:
db_table = 'article'
因为在cateogry
的ForeignKey
中指定了related_query_name
为articles
,因此你不能再使用article
来进行反向查询了。这时候就需要通过articles__id__in
来进行反向查询。
反向查询是将模型名字小写化。比如article__in
。可以通过related_query_name
来指定自己的方式,而不使用默认的方式。反向引用是将模型名字小写化,然后再加上_set
,比如article_set
,可以通过related_name
来指定自己的方式,而不是用默认的方式。
并且,如果在做反向查询的时候,如果查询的字段就是模型的主键,那么可以省略掉这个字段,直接写成article__in
就可以了,不需要这个id
了。
in
不仅仅可以指定列表/元组,还可以为QuerySet
。比如要查询“文章标题中包含有hello的所有分类”,那么可以通过以下代码来实现:
articles = Article.objects.filter(title__icontains='hello')
categories = Category.objects.filter(articles__in=articles)
for cateogry in categories:
print(cateogry)
8.gt
、gte
、lt
、lte
:代表的是大于、大于等于、小于、小于等于的条件。示例代码如下:
articles = Article.objects.filter(id__lte=3)
9.startswith
、istartswith
、endswith
、iendswith
:表示以某个值开始,不区分大小写的以某个值开始、以某个值结束、不区分大小写的以某个值结束。示例代码如下:
articles = Article.objects.filter(title__endswith="hello")
10.关于时间的查询条件:
-
range
:可以指定一个时间段。并且时间应该标记为aware
时间,不然django会报警告。示例代码如下:
start_time = make_aware(datetime(year=2020,month=4,day=4,hour=17,minute=0,second=0))
end_time = make_aware(datetime(year=2020,month=4,day=4,hour=18,minute=0,second=0))
articles = Article.objects.filter(create_time__range=(start_time,end_time))
print(articles.query)
print(articles)
-
date
:用年月日来进行过滤。如果想要使用这个过滤条件,那么前提必须要在MySQL
中添加好那些时区文件。如何添加呢?参考教案。示例代码如下:
articles = Article.objects.filter(create_time__date=datetime(year=2020,month=4,day=4))
-
year/month/day
:表示根据年/月/日
进行查找。示例代码如下:
articles = Article.objects.filter(create_time__year__gte=2020)
-
week_day
:根据星期来进行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。比如要查找星期三的所有文章,那么可以通过以下代码来实现:
articles = Article.objects.filter(create_time__week_day=4)
-
time
:根据分时秒来进行查找。如果要具体到秒,一般比较难匹配到,可以使用区间的方式来进行查找。区间使用range
条件。比如想要获取17时/10分/27-28秒之间的文章,那么可以通过以下代码来实现:
start_time = time(hour=17,minute=10,second=27)
end_time = time(hour=17,minute=10,second=28)
articles = Article.objects.filter(create_time__time__range=(start_time,end_time))
聚合函数:
如果你用原生SQL
,则可以使用聚合函数来提取数据。比如提取某个商品销售的数量,那么可以使用Count
,如果想要知道商品销售的平均价格,那么可以使用Avg
。聚合函数是通过aggregate
方法来实现的。1.所有的聚合函数都是放在django.db.models
下面。2.聚合函数不能够单独的执行,需要放在一些可以执行聚合函数的方法下面中去执行。比如aggregate
。示例代码如下:
result = Book.objects.aggregate(Avg("price"))
3.聚合函数执行完成后,给这个聚合函数的值取个名字。取名字的规则,默认是filed+__+聚合函数名字
形成的。比如以上代码形成的名字叫做price__avg
。如果不想使用默认的名字,那么可以在使用聚合函数的时候传递关键字参数进去,参数的名字就是聚合函数执行完成的名字。实示例代码如下:
result = Book.objects.aggregate(avg=Avg("price"))
以上传递了关键字参数avg=Avg("price")
,那么以后Avg
聚合函数执行完成的名字就叫做avg
。4.aggregate
:这个方法不会返回一个QuerySet
对象,而是返回一个字典。这个字典中的key就是聚合函数的名字,值就是聚合函数执行后的结果。5.aggregate
和annotate
的相同和不同:
-
aggregate
返回的是一个字典,在这个字典中存储的是这个聚合函数执行的结果。 而annotate
返回的是一个QuerySet
对象,并且会在查找的模型上添加一个聚合函数的属性。 -
aggregate
不会做分组,而annotate
会使用group by
子句进行分组,只有调用了group by
子句,才能对每一条数据求聚合函数的值。
6.Count
:用来求某个数据的个数。比如要求所有图书的数量,那么可以使用以下代码:
result = Book.objects.aggregate(book_nums=Count("id"))
并且Count
可以传递distinct=True
参数,用来剔除那些重复的值,只保留一个。比如要获取作者表中,不同邮箱的个数,那么这时候可以使用distinct=True
。示例代码如下:
result = Author.objects.aggregate(email_nums=Count('email',distinct=True))
7.Max
和Min
:求指定字段的最大值和最小值。示例代码如下:
result = Author.objects.aggregate(max=Max("age"),min=Min("age"))
8.Sum
:求某个字段值的总和。示例代码如下:
result = BookOrder.objects.aggregate(total=Sum('price'))
aggregate
和annotate
方法可以在任何的QuerySet
对象上调用。因此只要是返回了QuerySet
对象,那么就可以进行链式调用。比如要获取2018年度的销售总额,那么可以先过滤年份,再求聚合函数。示例代码如下:
BookOrder.objects.filter(create_time__year=2020).aggregate(total=Sum('price'))
7.F表达式
:动态的获取某个字段上的值。并且这个F表达式,不会真正的去数据库中查询数据,他相当于只是起一个标识的作用。比如想要将原来每本图书的价格都在原来的基础之上增加10元,那么可以使用以下代码来实现:
from django.db.models import F
Book.objects.update(price=F("price")+10)
8.Q表达式
:使用Q
表达式包裹查询条件,可以在条件之间进行多种操作。与/或非等,从而实现一些复杂的查询操作。例子如下:
-
查找价格大于100,并且评分达到4.85以上的图书:
# 不使用Q表达式的
books = Book.objects.filter(price__gte=100,rating__gte=4.85)
# 使用Q表达式的
books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
-
查找价格低于100元,或者评分低于4分的图书:
books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
-
获取价格大于100,并且图书名字中不包含”传“字的图书:
books = Book.objects.filter(Q(price__gte=100)&~Q(name__icontains='传'))