Django中的查詢操作
查詢是資料庫操作中一個非常重要的技術。查詢一般就是使用filter、exclude以及get三個方法來實現。我們可以在呼叫這些方法的時候傳遞不同的引數來實現查詢需求。在ORM層面,這些查詢條件都是使用field+__+condition的方式來使用的。以下將那些常用的查詢條件來一一解釋。
準備工作:
新建一個專案,配置settings檔案連線至mysql資料庫,
新建一個front
的app,將app新增至settings中,然後在article
中新建一個模型
from django.db import models
# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
class Meta:
# 自定義資料表名稱
db_table = 'article'
然後makemigrations後在migrate,然後我們開啟navicat
,找到article
這個表,我們手動新增幾條資訊進去,以便後面測試。例如,我手動新增的4條資訊
查詢條件
1. exact:
使用方法,在屬性名後面
在front這個app中的views中新增一個函式:
from django.shortcuts import render
from django.http import HttpResponse
from . import models
# Create your views here.
def index(request):
article1 = models.Article.objects.filter(id__exact = 1)
article2 = models.Article.objects.get(id__exact=1)
print(article1)
print(article2)
return HttpResponse('success')
然後執行專案,我們可以發現打印出來的東西是不一樣的,用filter
方法返回的是一個QuerySet
的物件,get
返回的就是一個我們能直接使用的物件,
到這裡我們可以發現,以前我們使用這兩個方法獲取物件的時候,直接使用的時get(id=1)
或filter(id=1)
就直接得到了物件,為什麼還需要加一個__exact
呢。
起始__exact
是預設的選項,在我們使用get(id=1)
就是相當於get(id__exact=1)
,所以我們以後也不用寫__exact
,但是我們也需要了解一下。
我們可以通過query這個屬性來檢視翻譯為sql語言的原始碼
def index(request):
article1 = models.Article.objects.filter(id__exact = 1)
article2 = models.Article.objects.get(id__exact=1)
print(article1.query)
print(article2.query)
return HttpResponse('success')
然後執行專案會發現報錯了,是因為我們使用get()返回的物件是沒有query
這個屬性的,filter()
返回的才有這個屬性,所以我們只需要將print(article2.query)
這句程式碼註釋掉就行了。
然後我們就能看到在控制檯看到article1 = models.Article.objects.filter(id__exact = 1)
這句程式碼翻譯為sql原生語句的程式碼了
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`id` = 1
2. iexact:
iexact和exact的功能是差不多一樣的,我們可以使用query
檢視一下sql原始碼,修改views中的程式碼
def index(request):
article1 = models.Article.objects.filter(id__exact = 1)
article2 = models.Article.objects.filter(id__iexact = 1)
print(article1.query)
print(article2.query)
return HttpResponse('success')
執行專案檢視sql原始碼
可以看到只是後面的=
變成了LIKE
了,這就是iexact和exact
的區別,所以exact和iexact的區別實際上就是LIKE和=的區別,在大部分collation=utf8_general_ci情況下都是一樣的(collation是資料庫的排序規則)。所以我們可以預設iexact和exact一樣的。
總結:
- LIKE和=:大部分情況下都是等價的,只有少數情況下是不等價的。
- exict和iexact:他們的區別其實就是LIKE和=的區別,因為exact會被翻譯成=,而iexact會被翻譯成LIKE。
- 因為
field__exact=xxx
其實等價於filed=xxx
,因此我們直接使用filed=xxx
就可以了,並且因為大部分情況exact
和iexact
又是等價的,因此我們以後直接使用field=xxx
就可以了。 query
可以用來檢視這個ORM
查詢語句最終被翻譯成的SQL
語句。但是query
只能被用在QuerySet
物件上,不能用在普通的ORM模型
上。因此如果你的查詢語句是通過get
來獲取資料的,那麼就不能使用query
,因為get
返回的是滿足條件的ORM
模型,而不是QuerySet
。如果你是通過filter
等其他返回QuerySet
的方法查詢的,那麼就可以使用query
。
3. contains和icontains:
查詢某個字串是否在指定的欄位中
這兩個的用法99%的情況下都是一樣的,區別只在與
contains大小寫敏感,而icontains大小寫不敏感。
示例程式碼:修改index中的程式碼
def index(request):
# article1 = models.Article.objects.filter(id__exact = 1)
# article2 = models.Article.objects.filter(id__iexact = 1)
# print(article1.query)
# print(article2.query)
article1 = models.Article.objects.filter(id__contains=1)
article2 = models.Article.objects.filter(id__icontains = 1)
print(article1.query)
print(article2.query)
return HttpResponse('success')
然後執行我們就能檢視到sql語句了
contains:這個判斷條件會使用大小寫敏感,因此在被翻譯成SQL
語句的時候,會使用like binary
,而like binary
就是使用大小寫敏感的。
icontains:這個判斷條件會使用大小寫不敏感,因此在被翻譯成SQL
的時候,使用的是like
,而like
在MySQL
層面就是不區分大小寫的。
我們可以看到在1
的左右兩邊都有%
意思就是在1
的左右兩邊都可以與另外的字元,也就是123
,213
這些都拿呢個被查找出來,所以我們一般不對id這個欄位進行contains
或icontains
的判斷,而是對其他欄位。
contains和icontains:在被翻譯成SQL
的時候使用的是%1%
,就是隻要整個字串中出現了1
都能過夠被找到,而iexact
沒有百分號,那麼意味著只有完全相等的時候才會被匹配到。這就是contains
與exiact
的區別。
4. in:可以直接指定某個欄位的是否在某個集合中。
修改index中的程式碼:
def index(request):
# exact和iexact
# article1 = models.Article.objects.filter(id__exact = 1)
# article2 = models.Article.objects.filter(id__iexact = 1)
# print(article1.query)
# print(article2.query)
# contains和icontains
# article1 = models.Article.objects.filter(id__contains=1)
# article2 = models.Article.objects.filter(id__icontains = 1)
# print(article1.query)
# print(article2.query)
# in
articles = models.Article.objects.filter(id__in=[1,2,3])
for article in articles:
print(article)
return HttpResponse('success')
我們就能夠將id為1,2,3的資料查找出來,如果沒有的話,就返回一個空的QuerySet列表。
注意: 在這裡articles
擁有query屬性,而article
沒有。
為了接下來的測試,我們需要在models中新建一個模型,然後時喲個外來鍵。
from django.db import models
# Create your models here.
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()
category = models.ForeignKey('category',on_delete=models.CASCADE,null=True)
class Meta:
# 自定義資料表名稱
db_table = 'article'
然後執行makemigrations後再migrate。然後開啟navicat,在category
中新增一條資訊,article中的外來鍵也增加資訊。
接下來我們要根據已經知道的文章id來查詢所屬分類。
修改views中的程式碼
def index(request):
# exact和iexact
# article1 = models.Article.objects.filter(id__exact = 1)
# article2 = models.Article.objects.filter(id__iexact = 1)
# print(article1.query)
# print(article2.query)
# contains和icontains
# article1 = models.Article.objects.filter(id__contains=1)
# article2 = models.Article.objects.filter(id__icontains = 1)
# print(article1.query)
# print(article2.query)
# in
# articles = models.Article.objects.filter(id__in=[1,2,3])
# print(articles.query)
# for article in articles:
# print(article)
# 查詢id為1,2,3的文章的分類
categories = models.category.objects.filter(article__id__in = [1,2,3])
print(categories)
return HttpResponse('success')
這樣我們就將article的id為[1,2,3]的種類查找了出來,當然,在這個地方article__id
我們也是可以使用related_query_name來另外取一個名字的。這裡就不做演示了。
注意:
反向查詢
是將模型名字小寫化。比如article__in
。可以通過related_query_name
來指定自己的方式,而不使用預設的方式。反向引用
是將模型名字小寫化,然後再加上_set
,比如article_set
,可以通過related_name
來指定自己的方式,而不是用預設的方式。
我們也可以將article__id__in
中的__id
去掉,然後就會預設使用__id
來查詢。
in
不僅僅可以指定列表/元組,還可以為QuerySet
。
5. gt、gte、lt、lte:
代表的是大於、大於等於、小於、小於等於的條件。
示例程式碼:
修改views中的函式程式碼:
def index(request):
# exact和iexact
# article1 = models.Article.objects.filter(id__exact = 1)
# article2 = models.Article.objects.filter(id__iexact = 1)
# print(article1.query)
# print(article2.query)
# contains和icontains
# article1 = models.Article.objects.filter(id__contains=1)
# article2 = models.Article.objects.filter(id__icontains = 1)
# print(article1.query)
# print(article2.query)
# in
# articles = models.Article.objects.filter(id__in=[1,2,3])
# print(articles.query)
# for article in articles:
# print(article)
# 查詢id為1,2,3的文章的分類
# categories = models.category.objects.filter(articles__in = [1,2,3])
# print(categories)
articles = models.Article.objects.filter(id__gt=2)
print(articles)
return HttpResponse('success')
這樣我們就能取的article中id>2的文章了
其它幾個可自行嘗試。
6. startswith、istartswith、endswith、iendswith:
表示以某個值開始,不區分大小寫的以某個值開始、以某個值結束、不區分大小寫的以某個值結束。
例如:
articles = Article.objects.filter(title__startwith="hello")
articles = Article.objects.filter(title__iendswith="hello")
articles = Article.objects.filter(title__endswith="hello")
articles = Article.objects.filter(title__iendswith="hello")
接下來時對時間的查詢相關操作,所以首先我們應該先去修改一下我們的models,新增一個create_time
from django.db import models
# Create your models here.
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()
category = models.ForeignKey('category',on_delete=models.CASCADE,null=True,related_query_name='articles')
create_time = models.DateTimeField(auto_now_add=True,null=True)
class Meta:
# 自定義資料表名稱
db_table = 'article'
然後在makemigratios後在migrate,如果遇到報錯就將資料庫中的所有表都刪除了,然後再將app下的migrations下除了__init__.py
以外 的檔案全部刪除了,然後重新執行makemigrations後在migrate。
然後我們在views中新定義一個函式index1(),然後新增對映。
from datetime import datetime
def index1(request):
start_time = datetime(year=2018,month=11,day=2,hour=16,minute=0,second=0)
end_time = datetime(year=2018,month=11,day=2,hour=17,minute=0,second=0)
articles = models.Article.objects.filter(create_time__range=(start_time,end_time))
print(articles)
print(articles.query)
return HttpResponse('sucess')
這樣,我們就是用range條件查詢到了所需要的資訊。
但是,我們也收到了一個警告,它說我們的時間是一個navie型別的時間,所以我們需要將它轉化為aware
的時間。
所以修改程式碼:
from django.utils.timezone import make_aware
def index1(request):
start_time = make_aware(datetime(year=2018,month=11,day=2,hour=16,minute=0,second=0))
end_time = make_aware(datetime(year=2018,month=11,day=2,hour=17,minute=0,second=0))
articles = models.Article.objects.filter(create_time__range=(start_time,end_time))
print(articles)
print(articles.query)
return HttpResponse('sucess')
這樣,就不會有警告生成了。
7. date:
用年月日來進行過濾。
示例程式碼,新增一個index2的函式:
def index2(request):
articles = models.Article.objects.filter(create_time__date = datetime(year=2018,month=11,day=2))
print(articles.query)
print(articles)
return HttpResponse('success')
然後新增對映,然後我們發現並沒有查找出結果出來,但是我們是剛才新增的資訊,時間也是設定為剛才的,為什麼沒有資訊呢。
原因是因為MySQL預設是沒有儲存時區相關的資訊,但是在執行sql語句時我們發現對時區進行了裝換,所以時不會得到我們想要的結果的。因此我們需要下載一些時區表的檔案,然後新增到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中。
然後對上面的程式碼不需要進行改動,也能執行成功了
8. year/month/day:表示根據年/月/日進行查詢。
示例程式碼:
articles = models.Article.objects.filter(create_time__year=2018)
我們還可以對年進行更復雜的操作,例如
articles = models.Article.objects.filter(pub_date__year__gte=2017)
9. week_day:
根據星期來進行查詢。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。比如要查詢星期三的所有文章,那麼可以通過以下程式碼來實現:
articles = models.Article.objects.filter(create_time__week_day=4)
10.time:
根據時間進行查詢。
如果要具體到秒,一般比較難匹配到,因為資料庫中秒後面還有小數。可以使用區間的方式來進行查詢。區間使用range
條件。比如想要獲取17時/10分/27-28秒之間的文章,那麼可以通過以下程式碼來實現:
from datetime import datetime,time
start_time = time(hour=17,minute=10,second=27)
end_time = time(hour=17,minute=10,second=28)
articles = models.Article.objects.filter(create_time__time__range=(start_time,end_time))
更多的關於時間的過濾,請參考Django官方文件:https://docs.djangoproject.com/en/2.1/ref/models/querysets/#range
11.isnull:
根據值是否為空進行查詢。
示例程式碼:
# 查詢title為空的
articles = models.Article.objects.filter(create_time__isnull=True)
# 查詢title不為空的
articles = models.Article.objects.filter(create_time__isnull=False)
12.regex和iregex:
大小寫敏感和大小寫不敏感的正則表示式。示例程式碼如下
articles = models.Article.objects.filter(title__regex=r"^三國")
當然,我們還可以進行復雜的正則表示式,這裡就不細說了。