合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 第七节:查询操作 # 查询操作 查找是数据库操作中一个非常重要的技术。查询一般就是使用`filter`、`exclude`以及`get`三个方法来实现。我们可以在调用这些方法的时候传递不同的参数来实现查询需求。在`ORM`层面,这些查询条件都是使用`field`+`__`+`condition`的方式来使用的。以下将那些常用的查询条件来一一解释。 ## 查询条件 ### exact: 使用精确的`=`进行查找。如果提供的是一个`None`,那么在`SQL`层面就是被解释为`NULL`。示例代码如下: ``` <pre class="calibre12">``` article = Article.objects.get(id__exact=<span class="hljs-params">14</span>) article = Article.objects.get(id__exact=<span class="hljs-keyword">None</span>) ``` ``` 以上的两个查找在翻译为`SQL`语句为如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">from</span> article <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span>=<span class="hljs-params">14</span>; <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">from</span> article <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">IS</span> <span class="hljs-params">NULL</span>; ``` ``` ### iexact: 使用`like`进行查找。示例代码如下: ``` <pre class="calibre12">``` article = Article.objects.filter(title__iexact='hello world') ``` ``` 那么以上的查询就等价于以下的`SQL`语句: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">from</span> article <span class="hljs-keyword">where</span> title <span class="hljs-keyword">like</span> <span class="hljs-string">'hello world'</span>; ``` ``` 注意上面这个`sql`语句,因为在`MySQL`中,没有一个叫做`ilike`的。所以`exact`和`iexact`的区别实际上就是`LIKE`和`=`的区别,在大部分`collation=utf8_general_ci`情况下都是一样的(`collation`是用来对字符串比较的)。 ### contains: 大小写敏感,判断某个字段是否包含了某个数据。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(title__contains=<span class="hljs-string">'hello'</span>) ``` ``` 在翻译成`SQL`语句为如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> title <span class="hljs-keyword">like</span> <span class="hljs-params">binary</span> <span class="hljs-string">'%hello%'</span>; ``` ``` 要注意的是,在使用`contains`的时候,翻译成的`sql`语句左右两边是有百分号的,意味着使用的是模糊查询。而`exact`翻译成`sql`语句左右两边是没有百分号的,意味着使用的是精确的查询。 ### icontains: 大小写不敏感的匹配查询。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(title__icontains=<span class="hljs-string">'hello'</span>) ``` ``` 在翻译成`SQL`语句为如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> title <span class="hljs-keyword">like</span> <span class="hljs-string">'%hello%'</span>; ``` ``` ### in: 提取那些给定的`field`的值是否在给定的容器中。容器可以为`list`、`tuple`或者任何一个可以迭代的对象,包括`QuerySet`对象。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(id__in=[<span class="hljs-params">1</span>,<span class="hljs-params">2</span>,<span class="hljs-params">3</span>]) ``` ``` 以上代码在翻译成`SQL`语句为如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">in</span> (<span class="hljs-params">1</span>,<span class="hljs-params">3</span>,<span class="hljs-params">4</span>) ``` ``` 当然也可以传递一个`QuerySet`对象进去。示例代码如下: ``` <pre class="calibre12">``` inner_qs = Article.objects.filter(title__contains=<span class="hljs-string">'hello'</span>) categories = Category.objects.filter(article__in=inner_qs) ``` ``` 以上代码的意思是获取那些文章标题包含`hello`的所有分类。 将翻译成以下`SQL`语句,示例代码如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ...<span class="hljs-keyword">from</span> <span class="hljs-keyword">category</span> <span class="hljs-keyword">where</span> article.<span class="hljs-keyword">id</span> <span class="hljs-keyword">in</span> (<span class="hljs-keyword">select</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">from</span> article <span class="hljs-keyword">where</span> title <span class="hljs-keyword">like</span> <span class="hljs-string">'%hello%'</span>); ``` ``` ### gt: 某个`field`的值要大于给定的值。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(id__gt=<span class="hljs-params">4</span>) ``` ``` 以上代码的意思是将所有`id`大于4的文章全部都找出来。 将翻译成以下`SQL`语句: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span> > <span class="hljs-params">4</span>; ``` ``` ### gte: 类似于`gt`,是大于等于。 ### lt: 类似于`gt`是小于。 ### lte: 类似于`lt`,是小于等于。 ### startswith: 判断某个字段的值是否是以某个值开始的。大小写敏感。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(title__startswith=<span class="hljs-string">'hello'</span>) ``` ``` 以上代码的意思是提取所有标题以`hello`字符串开头的文章。 将翻译成以下`SQL`语句: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> title <span class="hljs-keyword">like</span> <span class="hljs-string">'hello%'</span> ``` ``` ### istartswith: 类似于`startswith`,但是大小写是不敏感的。 ### endswith: 判断某个字段的值是否以某个值结束。大小写敏感。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(title__endswith=<span class="hljs-string">'world'</span>) ``` ``` 以上代码的意思是提取所有标题以`world`结尾的文章。 将翻译成以下`SQL`语句: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> title <span class="hljs-keyword">like</span> <span class="hljs-string">'%world'</span>; ``` ``` ### iendswith: 类似于`endswith`,只不过大小写不敏感。 ### range: 判断某个`field`的值是否在给定的区间中。示例代码如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.utils.timezone <span class="hljs-keyword">import</span> make_aware <span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime start_date = make_aware(datetime(year=<span class="hljs-params">2018</span>,month=<span class="hljs-params">1</span>,day=<span class="hljs-params">1</span>)) end_date = make_aware(datetime(year=<span class="hljs-params">2018</span>,month=<span class="hljs-params">3</span>,day=<span class="hljs-params">29</span>,hour=<span class="hljs-params">16</span>)) articles = Article.objects.filter(pub_date__range=(start_date,end_date)) ``` ``` 以上代码的意思是提取所有发布时间在`2018/1/1`到`2018/12/12`之间的文章。 将翻译成以下的`SQL`语句: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">from</span> article <span class="hljs-keyword">where</span> pub_time <span class="hljs-keyword">between</span> <span class="hljs-string">'2018-01-01'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'2018-12-12'</span>。 ``` ``` 需要注意的是,以上提取数据,不会包含最后一个值。也就是不会包含`2018/12/12`的文章。 而且另外一个重点,因为我们在`settings.py`中指定了`USE_TZ=True`,并且设置了`TIME_ZONE='Asia/Shanghai'`,因此我们在提取数据的时候要使用`django.utils.timezone.make_aware`先将`datetime.datetime`从`navie`时间转换为`aware`时间。`make_aware`会将指定的时间转换为`TIME_ZONE`中指定的时区的时间。 ### date: 针对某些`date`或者`datetime`类型的字段。可以指定`date`的范围。并且这个时间过滤,还可以使用链式调用。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(pub_date__date=date(<span class="hljs-params">2018</span>,<span class="hljs-params">3</span>,<span class="hljs-params">29</span>)) ``` ``` 以上代码的意思是查找时间为`2018/3/29`这一天发表的所有文章。 将翻译成以下的`sql`语句: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">WHERE</span> <span class="hljs-params">DATE</span>(<span class="hljs-keyword">CONVERT_TZ</span>(<span class="hljs-string">`front_article`</span>.<span class="hljs-string">`pub_date`</span>, <span class="hljs-string">'UTC'</span>, <span class="hljs-string">'Asia/Shanghai'</span>)) = <span class="hljs-params">2018</span><span class="hljs-params">-03</span><span class="hljs-params">-29</span> ``` ``` 注意,因为默认情况下`MySQL`的表中是没有存储时区相关的信息的。因此我们需要下载一些时区表的文件,然后添加到`Mysql`的配置路径中。如果你用的是`windows`操作系统。那么在`http://dev.mysql.com/downloads/timezones.html`下载`timezone_2018d_posix.zip - POSIX standard`。然后将下载下来的所有文件拷贝到`C:\ProgramData\MySQL\MySQL Server 5.7\Data\mysql`中,如果提示文件名重复,那么选择覆盖即可。 如果用的是`linux`或者`mac`系统,那么在命令行中执行以下命令:`mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p`,然后输入密码,从系统中加载时区文件更新到`mysql`中。 ### year: 根据年份进行查找。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(pub_date__year=<span class="hljs-params">2018</span>) articles = Article.objects.filter(pub_date__year__gte=<span class="hljs-params">2017</span>) ``` ``` 以上的代码在翻译成`SQL`语句为如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> pub_date <span class="hljs-keyword">between</span> <span class="hljs-string">'2018-01-01'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'2018-12-31'</span>; <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> pub_date >= <span class="hljs-string">'2017-01-01'</span>; ``` ``` ### month: 同`year`,根据月份进行查找。 ### day: 同`year`,根据日期进行查找。 ## week\_day: `Django 1.11`新增的查找方式。同`year`,根据星期几进行查找。1表示星期天,7表示星期六,`2-6`代表的是星期一到星期五。 ### time: 根据时间进行查找。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(pub_date__time=datetime.time(<span class="hljs-params">12</span>,<span class="hljs-params">12</span>,<span class="hljs-params">12</span>)); ``` ``` 以上的代码是获取每一天中12点12分12秒发表的所有文章。 更多的关于时间的过滤,请参考`Django`官方文档:`https://docs.djangoproject.com/en/2.0/ref/models/querysets/#range`。 ### isnull: 根据值是否为空进行查找。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(pub_date__isnull=<span class="hljs-keyword">False</span>) ``` ``` 以上的代码的意思是获取所有发布日期不为空的文章。 将来翻译成`SQL`语句如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> pub_date <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-params">null</span>; ``` ``` ### regex和iregex: 大小写敏感和大小写不敏感的正则表达式。示例代码如下: ``` <pre class="calibre12">``` articles = Article.objects.filter(title__regex=<span class="hljs-string">r'^hello'</span>) ``` ``` 以上代码的意思是提取所有标题以`hello`字符串开头的文章。 将翻译成以下的`SQL`语句: ``` <pre class="calibre12">``` <span class="hljs-keyword">select</span> ... <span class="hljs-keyword">where</span> title regexp <span class="hljs-params">binary</span> <span class="hljs-string">'^hello'</span>; ``` ``` `iregex`是大小写不敏感的。 ### 根据关联的表进行查询: 假如现在有两个`ORM`模型,一个是`Article`,一个是`Category`。代码如下: ``` <pre class="calibre12">``` <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Category</span><span class="hljs-params">(models.Model)</span>:</span> <span class="hljs-string">"""文章分类表"""</span> name = models.CharField(max_length=<span class="hljs-params">100</span>) <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span> <span class="hljs-string">"""文章表"""</span> title = models.CharField(max_length=<span class="hljs-params">100</span>,null=<span class="hljs-keyword">True</span>) category = models.ForeignKey(<span class="hljs-string">"Category"</span>,on_delete=models.CASCADE) ``` ``` 比如想要获取文章标题中包含"hello"的所有的分类。那么可以通过以下代码来实现: ``` <pre class="calibre12">``` categories = Category.object.filter(article__title__contains(<span class="hljs-string">"hello"</span>)) ``` ``` - - - - - - ## 聚合函数: 如果你用原生`SQL`,则可以使用聚合函数来提取数据。比如提取某个商品销售的数量,那么可以使用`Count`,如果想要知道商品销售的平均价格,那么可以使用`Avg`。 聚合函数是通过`aggregate`方法来实现的。在讲解这些聚合函数的用法的时候,都是基于以下的模型对象来实现的。 ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Author</span><span class="hljs-params">(models.Model)</span>:</span> <span class="hljs-string">"""作者模型"""</span> name = models.CharField(max_length=<span class="hljs-params">100</span>) age = models.IntegerField() email = models.EmailField() <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span> db_table = <span class="hljs-string">'author'</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Publisher</span><span class="hljs-params">(models.Model)</span>:</span> <span class="hljs-string">"""出版社模型"""</span> name = models.CharField(max_length=<span class="hljs-params">300</span>) <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span> db_table = <span class="hljs-string">'publisher'</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span><span class="hljs-params">(models.Model)</span>:</span> <span class="hljs-string">"""图书模型"""</span> name = models.CharField(max_length=<span class="hljs-params">300</span>) pages = models.IntegerField() price = models.FloatField() rating = models.FloatField() author = models.ForeignKey(Author,on_delete=models.CASCADE) publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE) <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span> db_table = <span class="hljs-string">'book'</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BookOrder</span><span class="hljs-params">(models.Model)</span>:</span> <span class="hljs-string">"""图书订单模型"""</span> book = models.ForeignKey(<span class="hljs-string">"Book"</span>,on_delete=models.CASCADE) price = models.FloatField() <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span> db_table = <span class="hljs-string">'book_order'</span> ``` ``` 1. `Avg`:求平均值。比如想要获取所有图书的价格平均值。那么可以使用以下代码实现。 ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Avg result = Book.objects.aggregate(Avg(<span class="hljs-string">'price'</span>)) print(result) ``` ``` 以上的打印结果是: ``` <pre class="calibre12">``` {"price__avg":23.0} ``` ``` 其中`price__avg`的结构是根据`field__avg`规则构成的。如果想要修改默认的名字,那么可以将`Avg`赋值给一个关键字参数。示例代码如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Avg result = Book.objects.aggregate(my_avg=Avg(<span class="hljs-string">'price'</span>)) print(result) ``` ``` 那么以上的结果打印为: ``` <pre class="calibre12">``` {"my_avg":23} ``` ``` 2. `Count`:获取指定的对象的个数。示例代码如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Count result = Book.objects.aggregate(book_num=Count(<span class="hljs-string">'id'</span>)) ``` ``` 以上的`result`将返回`Book`表中总共有多少本图书。 `Count`类中,还有另外一个参数叫做`distinct`,默认是等于`False`,如果是等于`True`,那么将去掉那些重复的值。比如要获取作者表中所有的不重复的邮箱总共有多少个,那么可以通过以下代码来实现: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> djang.db.models <span class="hljs-keyword">import</span> Count result = Author.objects.aggregate(count=Count(<span class="hljs-string">'email'</span>,distinct=<span class="hljs-keyword">True</span>)) ``` ``` 3. `Max`和`Min`:获取指定对象的最大值和最小值。比如想要获取`Author`表中,最大的年龄和最小的年龄分别是多少。那么可以通过以下代码来实现: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Max,Min result = Author.objects.aggregate(Max(<span class="hljs-string">'age'</span>),Min(<span class="hljs-string">'age'</span>)) ``` ``` 如果最大的年龄是88,最小的年龄是18。那么以上的result将为: ``` <pre class="calibre12">``` {"age__max":88,"age__min":18} ``` ``` 4. `Sum`:求指定对象的总和。比如要求图书的销售总额。那么可以使用以下代码实现: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> djang.db.models <span class="hljs-keyword">import</span> Sum result = Book.objects.annotate(total=Sum(<span class="hljs-string">"bookstore__price"</span>)).values(<span class="hljs-string">"name"</span>,<span class="hljs-string">"total"</span>) ``` ``` 以上的代码`annotate`的意思是给`Book`表在查询的时候添加一个字段叫做`total`,这个字段的数据来源是从`BookStore`模型的`price`的总和而来。`values`方法是只提取`name`和`total`两个字段的值。 更多的聚合函数请参考官方文档:<https://docs.djangoproject.com/en/2.0/ref/models/querysets/#aggregation-functions> ## aggregate和annotate的区别: 1. `aggregate`:返回使用聚合函数后的字段和值。 2. `annotate`:在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组(group by)。 比如以上`Sum`的例子,如果使用的是`annotate`,那么将在每条图书的数据上都添加一个字段叫做`total`,计算这本书的销售总额。 而如果使用的是`aggregate`,那么将求所有图书的销售总额。 ## F表达式和Q表达式: ### F表达式: `F表达式`是用来优化`ORM`操作数据库的。比如我们要将公司所有员工的薪水都增加1000元,如果按照正常的流程,应该是先从数据库中提取所有的员工工资到Python内存中,然后使用Python代码在员工工资的基础之上增加1000元,最后再保存到数据库中。这里面涉及的流程就是,首先从数据库中提取数据到Python内存中,然后在Python内存中做完运算,之后再保存到数据库中。示例代码如下: ``` <pre class="calibre12">``` employees = Employee.objects.all() <span class="hljs-keyword">for</span> employee <span class="hljs-keyword">in</span> employees: employee.salary += <span class="hljs-params">1000</span> employee.save() ``` ``` 而我们的`F表达式`就可以优化这个流程,他可以不需要先把数据从数据库中提取出来,计算完成后再保存回去,他可以直接执行`SQL语句`,就将员工的工资增加1000元。示例代码如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> djang.db.models <span class="hljs-keyword">import</span> F Employee.object.update(salary=F(<span class="hljs-string">"salary"</span>)+<span class="hljs-params">1000</span>) ``` ``` `F表达式`并不会马上从数据库中获取数据,而是在生成`SQL`语句的时候,动态的获取传给`F表达式`的值。 比如如果想要获取作者中,`name`和`email`相同的作者数据。如果不使用`F表达式`,那么需要使用以下代码来完成: ``` <pre class="calibre12">``` authors = Author.objects.all() <span class="hljs-keyword">for</span> author <span class="hljs-keyword">in</span> authors: <span class="hljs-keyword">if</span> author.name == author.email: print(author) ``` ``` 如果使用`F表达式`,那么一行代码就可以搞定。示例代码如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> F authors = Author.objects.filter(name=F(<span class="hljs-string">"email"</span>)) ``` ``` ### Q表达式: 如果想要实现所有价格高于100元,并且评分达到9.0以上评分的图书。那么可以通过以下代码来实现: ``` <pre class="calibre12">``` books = Book.objects.filter(price__gte=<span class="hljs-params">100</span>,rating__gte=<span class="hljs-params">9</span>) ``` ``` 以上这个案例是一个并集查询,可以简单的通过传递多个条件进去来实现。 但是如果想要实现一些复杂的查询语句,比如要查询所有价格低于10元,或者是评分低于9分的图书。那就没有办法通过传递多个条件进去实现了。这时候就需要使用`Q表达式`来实现了。示例代码如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Q books = Book.objects.filter(Q(price__lte=<span class="hljs-params">10</span>) | Q(rating__lte=<span class="hljs-params">9</span>)) ``` ``` 以上是进行或运算,当然还可以进行其他的运算,比如有`&`和`~(非)`等。一些用`Q`表达式的例子如下: ``` <pre class="calibre12">``` <span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Q <span class="hljs-title"># 获取id等于3的图书</span> books = Book.objects.filter(Q(id=<span class="hljs-params">3</span>)) <span class="hljs-title"># 获取id等于3,或者名字中包含文字"记"的图书</span> books = Book.objects.filter(Q(id=<span class="hljs-params">3</span>)|Q(name__contains(<span class="hljs-string">"记"</span>))) <span class="hljs-title"># 获取价格大于100,并且书名中包含"记"的图书</span> books = Book.objects.filter(Q(price__gte=<span class="hljs-params">100</span>)&Q(name__contains(<span class="hljs-string">"记"</span>))) <span class="hljs-title"># 获取书名包含“记”,但是id不等于3的图书</span> books = Book.objects.filter(Q(name__contains=<span class="hljs-string">'记'</span>) & ~Q(id=<span class="hljs-params">3</span>)) ``` ```