Pycharm開發Django專案查詢操作
查詢操作
查詢是資料庫操作中一個非常重要的技術。查詢一般就是使用filter
、exclude
以及get
三個方法來實現。我們可以在呼叫這些方法的時候傳遞不同的引數來實現查詢需求。在ORM
層面,這些查詢條件都是使用field
+__
+condition
的方式來使用的。以下將那些常用的查詢條件來一一解釋。
查詢條件
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;
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
是用來對字串比較的)。
contains:
大小寫敏感,判斷某個欄位是否包含了某個資料。示例程式碼如下:
articles = Article.objects.filter(title__contains='hello')
在翻譯成SQL
語句為如下:
select ... where title like binary '%hello%';
要注意的是,在使用contains
的時候,翻譯成的sql
語句左右兩邊是有百分號的,意味著使用的是模糊查詢。而exact
sql
語句左右兩邊是沒有百分號的,意味著使用的是精確的查詢。
icontains:
大小寫不敏感的匹配查詢。示例程式碼如下:
articles = Article.objects.filter(title__icontains='hello')
在翻譯成SQL
語句為如下:
select ... where title like '%hello%';
in:
提取那些給定的field
的值是否在給定的容器中。容器可以為list
、tuple
或者任何一個可以迭代的物件,包括QuerySet
物件。示例程式碼如下:
articles = Article.objects.filter(id__in=[1,2,3])
以上程式碼在翻譯成SQL
語句為如下:
select ... where id in (1,3,4)
當然也可以傳遞一個QuerySet
物件進去。示例程式碼如下:
inner_qs = Article.objects.filter(title__contains='hello')
categories = Category.objects.filter(article__in=inner_qs)
以上程式碼的意思是獲取那些文章標題包含hello
的所有分類。
將翻譯成以下SQL
語句,示例程式碼如下:
select ...from category where article.id in (select id from article where title like '%hello%');
gt:
某個field
的值要大於給定的值。示例程式碼如下:
articles = Article.objects.filter(id__gt=4)
以上程式碼的意思是將所有id
大於4的文章全部都找出來。
將翻譯成以下SQL
語句:
select ... where id > 4;
gte:
類似於gt
,是大於等於。
lt:
類似於gt
是小於。
lte:
類似於lt
,是小於等於。
startswith:
判斷某個欄位的值是否是以某個值開始的。大小寫敏感。示例程式碼如下:
articles = Article.objects.filter(title__startswith='hello')
以上程式碼的意思是提取所有標題以hello
字串開頭的文章。
將翻譯成以下SQL
語句:
select ... where title like 'hello%'
istartswith:
類似於startswith
,但是大小寫是不敏感的。
endswith:
判斷某個欄位的值是否以某個值結束。大小寫敏感。示例程式碼如下:
articles = Article.objects.filter(title__endswith='world')
以上程式碼的意思是提取所有標題以world
結尾的文章。
將翻譯成以下SQL
語句:
select ... where title like '%world';
iendswith:
類似於endswith
,只不過大小寫不敏感。
range:
判斷某個field
的值是否在給定的區間中。示例程式碼如下:
from django.utils.timezone import make_aware
from datetime import datetime
start_date = make_aware(datetime(year=2018,month=1,day=1))
end_date = make_aware(datetime(year=2018,month=3,day=29,hour=16))
articles = Article.objects.filter(pub_date__range=(start_date,end_date))
以上程式碼的意思是提取所有釋出時間在2018/1/1
到2018/12/12
之間的文章。
將翻譯成以下的SQL
語句:
select ... from article where pub_time between '2018-01-01' and '2018-12-12'。
需要注意的是,以上提取資料,不會包含最後一個值。也就是不會包含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
的範圍。並且這個時間過濾,還可以使用鏈式呼叫。示例程式碼如下:
articles = Article.objects.filter(pub_date__date=date(2018,3,29))
以上程式碼的意思是查詢時間為2018/3/29
這一天發表的所有文章。
將翻譯成以下的sql
語句:
select ... WHERE DATE(CONVERT_TZ(`front_article`.`pub_date`, 'UTC', 'Asia/Shanghai')) = 2018-03-29
注意,因為預設情況下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:
根據年份進行查詢。示例程式碼如下:
articles = Article.objects.filter(pub_date__year=2018)
articles = Article.objects.filter(pub_date__year__gte=2017)
以上的程式碼在翻譯成SQL
語句為如下:
select ... where pub_date between '2018-01-01' and '2018-12-31';
select ... where pub_date >= '2017-01-01';
month:
同year
,根據月份進行查詢。
day:
同year
,根據日期進行查詢。
week_day:
Django 1.11
新增的查詢方式。同year
,根據星期幾進行查詢。1表示星期天,7表示星期六,2-6
代表的是星期一到星期五。
time:
根據時間進行查詢。示例程式碼如下:
articles = Article.objects.filter(pub_date__time=datetime.time(12,12,12));
以上的程式碼是獲取每一天中12點12分12秒發表的所有文章。
更多的關於時間的過濾,請參考Django
官方文件:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#range
。
isnull:
根據值是否為空進行查詢。示例程式碼如下:
articles = Article.objects.filter(pub_date__isnull=False)
以上的程式碼的意思是獲取所有釋出日期不為空的文章。
將來翻譯成SQL
語句如下:
select ... where pub_date is not null;
regex和iregex:
大小寫敏感和大小寫不敏感的正則表示式。示例程式碼如下:
articles = Article.objects.filter(title__regex=r'^hello')
以上程式碼的意思是提取所有標題以hello
字串開頭的文章。
將翻譯成以下的SQL
語句:
select ... where title regexp binary '^hello';
iregex
是大小寫不敏感的。
根據關聯的表進行查詢:
假如現在有兩個ORM
模型,一個是Article
,一個是Category
。程式碼如下:
class Category(models.Model):
"""文章分類表"""
name = models.CharField(max_length=100)
class Article(models.Model):
"""文章表"""
title = models.CharField(max_length=100,null=True)
category = models.ForeignKey("Category",on_delete=models.CASCADE)
比如想要獲取文章標題中包含"hello"的所有的分類。那麼可以通過以下程式碼來實現:
categories = Category.object.filter(article__title__contains("hello"))
聚合函式:
如果你用原生SQL
,則可以使用聚合函式來提取資料。比如提取某個商品銷售的數量,那麼可以使用Count
,如果想要知道商品銷售的平均價格,那麼可以使用Avg
。
聚合函式是通過aggregate
方法來實現的。在講解這些聚合函式的用法的時候,都是基於以下的模型物件來實現的。
from django.db import models
class Author(models.Model):
"""作者模型"""
name = models.CharField(max_length=100)
age = models.IntegerField()
email = models.EmailField()
class Meta:
db_table = 'author'
class Publisher(models.Model):
"""出版社模型"""
name = models.CharField(max_length=300)
class Meta:
db_table = 'publisher'
class Book(models.Model):
"""圖書模型"""
name = models.CharField(max_length=300)
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)
class Meta:
db_table = 'book'
class BookOrder(models.Model):
"""圖書訂單模型"""
book = models.ForeignKey("Book",on_delete=models.CASCADE)
price = models.FloatField()
class Meta:
db_table = 'book_order'
-
Avg
:求平均值。比如想要獲取所有圖書的價格平均值。那麼可以使用以下程式碼實現。from django.db.models import Avg result = Book.objects.aggregate(Avg('price')) print(result)
以上的列印結果是:
{"price__avg":23.0}
其中
price__avg
的結構是根據field__avg
規則構成的。如果想要修改預設的名字,那麼可以將Avg
賦值給一個關鍵字引數。示例程式碼如下:from django.db.models import Avg result = Book.objects.aggregate(my_avg=Avg('price')) print(result)
那麼以上的結果列印為:
{"my_avg":23}
-
Count
:獲取指定的物件的個數。示例程式碼如下:from django.db.models import Count result = Book.objects.aggregate(book_num=Count('id'))
以上的
result
將返回Book
表中總共有多少本圖書。Count
類中,還有另外一個引數叫做distinct
,預設是等於False
,如果是等於True
,那麼將去掉那些重複的值。比如要獲取作者表中所有的不重複的郵箱總共有多少個,那麼可以通過以下程式碼來實現:from djang.db.models import Count result = Author.objects.aggregate(count=Count('email',distinct=True))
-
Max
和Min
:獲取指定物件的最大值和最小值。比如想要獲取Author
表中,最大的年齡和最小的年齡分別是多少。那麼可以通過以下程式碼來實現:from django.db.models import Max,Min result = Author.objects.aggregate(Max('age'),Min('age'))
如果最大的年齡是88,最小的年齡是18。那麼以上的result將為:
{"age__max":88,"age__min":18}
-
Sum
:求指定物件的總和。比如要求圖書的銷售總額。那麼可以使用以下程式碼實現:from djang.db.models import Sum result = Book.objects.annotate(total=Sum("bookstore__price")).values("name","total")
以上的程式碼
annotate
的意思是給Book
表在查詢的時候新增一個欄位叫做total
,這個欄位的資料來源是從BookStore
模型的price
的總和而來。values
方法是隻提取name
和total
兩個欄位的值。
更多的聚合函式請參考官方文件:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#aggregation-functions
aggregate和annotate的區別:
-
aggregate
:返回使用聚合函式後的欄位和值。 -
annotate
:在原來模型欄位的基礎之上新增一個使用了聚合函式的欄位,並且在使用聚合函式的時候,會使用當前這個模型的主鍵進行分組(group by)。
比如以上Sum
的例子,如果使用的是annotate
,那麼將在每條圖書的資料上都新增一個欄位叫做total
,計算這本書的銷售總額。
而如果使用的是aggregate
,那麼將求所有圖書的銷售總額。
F表示式和Q表示式:
F表示式:
F表示式
是用來優化ORM
操作資料庫的。比如我們要將公司所有員工的薪水都增加1000元,如果按照正常的流程,應該是先從資料庫中提取所有的員工工資到Python記憶體中,然後使用Python程式碼在員工工資的基礎之上增加1000元,最後再儲存到資料庫中。這裡面涉及的流程就是,首先從資料庫中提取資料到Python記憶體中,然後在Python記憶體中做完運算,之後再儲存到資料庫中。示例程式碼如下:
employees = Employee.objects.all()
for employee in employees:
employee.salary += 1000
employee.save()
而我們的F表示式
就可以優化這個流程,他可以不需要先把資料從資料庫中提取出來,計算完成後再儲存回去,他可以直接執行SQL語句
,就將員工的工資增加1000元。示例程式碼如下:
from djang.db.models import F
Employee.object.update(salary=F("salary")+1000)
F表示式
並不會馬上從資料庫中獲取資料,而是在生成SQL
語句的時候,動態的獲取傳給F表示式
的值。
比如如果想要獲取作者中,name
和email
相同的作者資料。如果不使用F表示式
,那麼需要使用以下程式碼來完成:
authors = Author.objects.all()
for author in authors:
if author.name == author.email:
print(author)
如果使用F表示式
,那麼一行程式碼就可以搞定。示例程式碼如下:
from django.db.models import F
authors = Author.objects.filter(name=F("email"))
Q表示式:
如果想要實現所有價格高於100元,並且評分達到9.0以上評分的圖書。那麼可以通過以下程式碼來實現:
books = Book.objects.filter(price__gte=100,rating__gte=9)
以上這個案例是一個並集查詢,可以簡單的通過傳遞多個條件進去來實現。
但是如果想要實現一些複雜的查詢語句,比如要查詢所有價格低於10元,或者是評分低於9分的圖書。那就沒有辦法通過傳遞多個條件進去實現了。這時候就需要使用Q表示式
來實現了。示例程式碼如下:
from django.db.models import Q
books = Book.objects.filter(Q(price__lte=10) | Q(rating__lte=9))
以上是進行或運算,當然還可以進行其他的運算,比如有&
和~(非)
等。一些用Q
表示式的例子如下:
from django.db.models import Q
# 獲取id等於3的圖書
books = Book.objects.filter(Q(id=3))
# 獲取id等於3,或者名字中包含文字"記"的圖書
books = Book.objects.filter(Q(id=3)|Q(name__contains("記")))
# 獲取價格大於100,並且書名中包含"記"的圖書
books = Book.objects.filter(Q(price__gte=100)&Q(name__contains("記")))
# 獲取書名包含“記”,但是id不等於3的圖書
books = Book.objects.filter(Q(name__contains='記') & ~Q(id=3))