文章目錄
查詢方法
查找是數據庫操作中一個非常重要的技術。查詢一般就是使用filter、exclude以及get三個方法來實現。我們可以在調用這些方法的時候傳遞不同的參數來實現查詢需求。在ORM層面,這些查詢條件都是使用字段__查詢條件
的方式來使用的。
filter可以查詢多個數據。get只能查詢一條數據,如果查詢結果爲多條會報錯。exclude下方在詳細介紹。
查看執行的SQL語句
在介紹查詢方法前,我先來介紹兩種可以查看ORM模型底層將我們的Python代碼轉換成的SQL語句是什麼樣的。
- 如果我們查詢返回爲QuerySet對象,我們可以直接調用此對象的
.queries
屬性,此屬性會返回當前對象執行的SQL語句。
obj = Obj.objects.all()
print(obj.query)
- 如果我們執行的對象並非爲QuerySet對象(比如.get()方法返回值就並不是QuerySet對象),我們需要先引入
from django.db import connection
,然後在調用connection.queries
可以查看當時所有執行的SQL語句。
第二種查詢SQL執行語句的方式的前提條件是當前命令Django已經轉換爲SQL語句並且執行纔有效。我們生成的QuerySet對象並不一定馬上就會轉換爲SQL語句去執行,只有QuerySet對象會被使用的時候纔會去調用,比如說進行迭代,切片,判斷,或者調用函數的時候纔會正常觸發QuerySet對象生成SQL語句。
# 當前print(connection.queries)輸出爲空,(`.all()`爲查詢所有數據)
from django.db import connection
obj = Obj.objects.all()
print(connection.queries)
# 當前print(connection.queries)輸出爲執行了的SQL語句,(`.all()`爲查詢所有數據)
from django.db import connection
for obj in Obj.objects.all():
print(obj)
print(connection.queries)
簡單來說上述兩種方法的區別是:
第一種方法查詢的爲當前QuerySet對象對應的SQL語句,不管QuerySet對象是否執行,都會有對應的SQL語句可以查詢。
第二種方法多用於查詢出來的對象並不是QuerySet對象,不是QuerySet對象就沒有.query屬性,我們就需要查詢當前以及執行的所以SQL語句從中查找我們想要看到的查詢命令所對應的SQL語句。
查詢條件
上述也提到過查詢方式爲字段__查詢條件
,下列就來演示一下常用的查詢條件
精準與模糊查詢
exact
與iexact
: 精準查詢,其中exact相當於等號=
,寫不寫效果一樣,而iexact爲使用like進行查找。
# 下列兩種方法的SQL語句完全相同
角色信息 = Query.objects.filter(角色='蘿莉')
角色信息 = Query.objects.filter(角色__exact='蘿莉')
# iexact和exact的區別就是like和=的區別,查詢結果在大部分情況下都是相同的。
角色信息 = Query.objects.filter(角色__iexact='蘿莉')
# SELECT * FROM `query_query` WHERE `query_query`.`角色` LIKE 蘿莉
contains
與icontains
: 模糊查詢,其中contains
爲大小寫敏感的模糊查詢,而icontains
爲大小寫不敏感的模糊查詢
角色信息 = Query.objects.filter(角色__contains='蘿')
# SELECT * FROM `query_query` WHERE `query_query`.`角色` LIKE BINARY %蘿%
角色信息 = Query.objects.filter(角色__icontains='蘿')
# SELECT * FROM `query_query` WHERE `query_query`.`角色` LIKE %蘿%
in
: 提取那些給定的字段的值是否在給定的容器中。容器可以爲list、tuple或者任何一個可以迭代的對象,包括QuerySet對象。
角色們 = Query.objects.filter(技能_id__in=[3, 4])
技能 = Skill.objects.filter(技能號__in=角色們)
for i in 技能:
print(i.技能名)
print(技能.query)
比較條件
比較運算符 | 作用 |
---|---|
gt | 大於 |
gte | 大於等於 |
lt | 小於 |
lte | 小於等於 |
傷害 = Skill.objects.filter(攻擊力__gte="30")
print(傷害.query)
print(傷害)
# SELECT * FROM `技能外鍵` WHERE `技能外鍵`.`攻擊力` >= 30 ORDER BY `技能外鍵`.`技能號` DESC
# <QuerySet [<Skill: Skill object (11)>, <Skill: Skill object (3)>]>
比較條件除了大於小於外,還會出現一些範圍區間的需求,這時候我們就可以使用range
進行範圍查找,range條件需要一個列表或者元組傳入一個範圍區間進行篩選。
防禦 = Skill.objects.filter(防禦力__range=['0', '10'])
print(防禦.query)
print(防禦)
start = date(year=2020, month=3, day=25)
end = date(year=2020, month=3, day=26)
時間 = Query.objects.filter(創建時間__range=(start, end))
print(時間.query)
print(時間)
時間條件
時間條件有date
日期,year
年份,time
時間,day
天等,精準查詢時間使用較少,大部分爲使用range
進行範圍查詢,因爲精準查詢的時候需要寫微秒很難達成
其中date可以處理存日期和日期時間兩張形式。
年份 = Query.objects.filter(創建時間__year=2020)
# SELECT * WHERE `query_query`.`創建時間` BETWEEN 2019-12-31 16:00:00 AND 2020-12-31 15:59:59.999999
聚合函數
聚合函數同樣也是SQL攜帶的一種快速查詢的方式,因爲聚合函數在數據庫層面進行數據篩選,效率會比傳入Django後在做篩選高不少。
聚合函數主要通過aggregate
和annotate
方法進行查找。
- aggregate:返回使用聚合函數後的字段和值。(如果需要整體字段的聚合函數則使用此方法)
- annotate:在原來模型字段的基礎之上添加一個使用了聚合函數的字段,並且在使用聚合函數的時候,會使用當前這個模型的主鍵進行分組(group by)。(如果需要局部字段聚合,則使用此方法)
下列我們用聚合函數來區分aggregate
與annotate
的不同
這是我用到的數據庫,聚合函數需要從django.db.models
中導入,如下
from django.db.models import Avg, Count, Max, Min, Sum
查詢法語爲:聚合函數(查詢條件)
Avg:求平均值。
當前我們來求所有角色的等級平均值,使用aggregate即可,默認情況下會返回一個字典,其中鍵值默認爲查詢條件__聚合函數
,如何我們想要指定鍵名,可以寫成鍵名=聚合函數(查詢條件)
比如我們想要查看平均等級可以寫爲
lever_avg = Member.objects.aggregate(Avg('等級'))
SQL:
SELECT AVG( `query_member`.`等級` ) AS `等級__avg` FROM `query_member`
聚合函數還一個特性是可以從其外鍵中添加條件,比如下面我們從Query表中求平均等級
lever_avgs = Query.objects.aggregate(平均值=Avg('member__等級'))
當如果我們想要求每個角色的等級平均值,這時候就需要用到的annotate
lever_avgs = Query.objects.annotate(平均值=Avg('member__等級'))
想必看到此,你對aggregate
與annotate
還是有比較大的疑惑,我們下面來分析一下這個方法產生的SQL不同之處
其中aggregate的SQL語句爲連接表後直接求其平均值,求出的平均值爲總平均值
而annotate方法的SQL語句會在其添加分組,利用分組,我們可以求出單獨每個角色的平均值
Count:獲取指定的對象的個數。
求指定個數的聚合函數和將數據查詢出來後使用len()方法求長度獲取的值相同,不同於len()方法的是,count可以在查詢個數時候傳入distinct
可以去掉重複的數據。
query_len = len(Query.objects.all())
query_cou = Query.objects.aggregate(角色數量=Count('角色'))
query_cou_dis = Query.objects.aggregate(角色數量=Count('角色', distinct=True))
print(query_len, query_cou, query_cou_dis)
其中query_len與query_cou查詢出的數量都爲4,而query_cou_dis則爲3
Count查詢個數時,指定表頭中的數據如果爲空值,算個數,如果爲Null,這不算一個數,爲了防止出現Null,大多數時候我們都會把其指定爲主鍵進行個數計算。
Max和Min:獲取指定對象的最大值和最小值。
如果我們想要一次查詢多個聚合函數,可以直接在方法內以逗號將多個方法隔開即可
max_min = Query.objects.aggregate(最大值=Max('member__等級'), 最小值=Min('member__等級'))
print(max_min)
Sum:求指定對象的總和。
統計時我們還能指定統計一個範圍內的數據,比如我們想統計蘿莉的等級總和
add = Query.objects.aggregate(等級總和=Sum('member__等級'))
print(add)
# 統計蘿莉的等級總和
add = Query.objects.filter(角色='蘿莉').aggregate(蘿莉等級=Sum('member__等級'))
print(add)
從輸出的SQL語句可以看出,ORM模型會將我們的多個方法自動轉換爲一句SQL語句執行,增強效率
F、Q表達式
F與Q表達式需要導入的庫和聚合函數相似爲from django.db.models import F, Q
F表達式
F表達式是用來優化ORM操作數據庫的,他可以動態獲取數據庫中的內容,簡化數據庫操作
比如我們想要給數據庫中所有玩家的等級都提高,雖然不用F表達式也能做到。
member_lever = Member.objects.all()
for lever in member_lever:
lever.等級 += 1
lever.save()
但我們發現不僅我們寫的時候比較麻煩,在轉換成的SQL也是很長一段,這時候F表達式就起到了作用,這時候我們需要配合,這時候我們需要用到一個.update()
這個更新方法
member_lever = Member.objects.update(等級=F("等級")+10)
# UPDATE `query_member` SET `等級` = (`query_member`.`等級` + 10)
只需要一句SQL就可以執行完成,F表達式可以動態從當前數據庫中取出指定的值
Q表達式
默認我們可以在一個方法中傳入多個查詢條件,每個查詢條件以逗號,
隔開,這種查詢時候默認使用的爲邏輯與&
,我們如果想要使用邏輯或|
,或者非~
,這時候就需要使用到Q表達式。
# 獲得等級小於等於110級的蘿莉
Young_min = Member.objects.filter(角色__角色='蘿莉', 等級__lte=110)
# Young_min = Member.objects.filter(Q(角色__角色='蘿莉') & Q(等級__lte=110))
# 獲取角色爲蘿莉或者等級大於一百的成員
hegemony = Member.objects.filter(Q(角色__角色='蘿莉') | Q(等級__gte=100))
# 獲取所以角色不爲蘿莉的成員
old_man = Member.objects.filter(~Q(角色__角色='蘿莉'))
QuerySet API
我們通常做查詢操作的時候,都是通過模型名字.objects的方式進行操作。其實模型名字.objects是一個django.db.models.manager.Manager對象,而Manager這個類是一個“空殼”的類,他本身是沒有任何的屬性和方法的。他的方法全部都是通過Python動態添加的方式,從QuerySet類中拷貝過來的
我們上述介紹了一些QuerySet類只是其中一小部分的方法,下面我們會介紹一些常用的方法
必用QuerySet
常用QuerySet | 作用 |
---|---|
.get() |
查詢單條數據, 返回查詢數據 |
.filter() |
查詢所有滿足條件的數據, 返回一個新的QuerySet |
.exclude() |
排除滿足條件的數據, 查詢不滿足條件的所有數據,返回一個新的QuerySet |
.all() |
查詢此ORM模型中所有數據 |
.aggregate() |
返回使用聚合函數後的字段和值。 |
.annotate() |
在原來模型字段的基礎之上添加一個使用了聚合函數的字段,並且在使用聚合函數的時候,會使用當前這個模型的主鍵進行分組(group by)。 |
.first() |
返回滿足條件的首條數據 |
.last() |
返回滿足條件的最後一條數據 |
.update() |
執行更新操作,在SQL底層走的也是update命令。 |
上述QuerySet在之前查詢數據時大部分都使用過,下面使用一些我們沒有使用過的進行展示
# 下述兩種寫法生成的SQL語句完全相同
old_man = Member.objects.filter(~Q(角色__角色='蘿莉'))
old_man = Member.objects.exclude(角色__角色='蘿莉')
# 使用update進行常規更新
up = Query.objects.filter(角色='歐洲人').update(角色='歐皇')
常用QuerySet
排序規則(order_by)
排序有多重方法,之前我也介紹過在ORM模型中models.py中如何設定取出數據的排序,不過除此之外,我們使用order_by可以更加靈活的排序,只需在查詢出的條件中使用.order_by(表頭)
即可,默認爲正序(從小到大)如果需要倒敘,表頭前加入負號即可。
old_man = Member.objects.exclude(角色__角色='蘿莉').order_by('-等級')
取出內容(values, values_list)
之前我們都是先將所以內容取出後在獲取需要的內容,這樣無疑會造成性能浪費,這時候我們就可以使用.values(表頭,表頭)
即可指定取出內容,其中values返回值爲字典而values_list返回值爲列表
old_man = Member.objects.exclude(角色__角色='蘿莉').values('暱稱', '等級')
old_man_list = Member.objects.exclude(角色__角色='蘿莉').values_list('暱稱', '等級')
print(old_man)
print(old_man.query)
print(old_man_list)
print(old_man_list.query)
從上述代碼中可以看出values與values_list在SQL語句上完全相同,只是在Django中進行了不同的處理
關聯查詢(select_related, prefetch_related)
關聯查詢主要用於優化查詢,用更少的查詢次數來完成查詢,
select_related
:在提取某個模型的數據的同時,也提前將相關聯的數據提取出來。主要針對一對一和一對多的問題。
prefetch_related
: 訪問多個表中的數據的時候,減少查詢的次數。主要針對多對一和多對多的問題。
select_related常用於如果一個模型有外鍵的情況下用其指定外鍵所代表的的表頭時,會將其外鍵連接查詢
Young_min = Member.objects.get(pk=1)
print(Young_min.角色)
Young_min = Member.objects.select_related('角色').get(pk=1)
print(Young_min.角色)
select_related將兩個表連接後在進行查詢,可以減少數據庫的查詢次數。
prefetch_related常用於被外鍵指定的表中的數據,可以查詢被那些外鍵所指定,如果我們使用常規的方式寫
querys = Query.objects.all()
for query in querys:
print('\n角色:', query.角色)
members = query.member_set.all()
for member in members:
print('暱稱:', member.暱稱)
其SQL語句會比較複雜繁瑣
有多少數據,就要查詢多少次
但如果我們把all改成prefetch_related如下
querys = Query.objects.prefetch_related('member_set')
for query in querys:
print('\n角色:', query.角色)
members = query.member_set.all()
for member in members:
print('暱稱:', member.暱稱)
這時候再來查看我們使用的SQL會發現只調用了兩條
創建數據(create, get_or_create)
我在之前的博客中提到過如何保存數據,此處保存數據和之前博客提到的保存數據略有不同,之前保存的數據需要使用.save()
方法來確定保存,但是使用QuerySet中的create保存數據並不需要在調用.save()
方法
如果使用get_or_create,則會只保存數據庫中不存在的信息,此方法的返回值爲一個元組:(創建\查詢QuerySet對象, 是否創建)
retain = Member.objects.create(暱稱='大佬', 等級='50', 角色_id=1)
get_retain = Member.objects.get_or_create(暱稱='萌新', 等級='107', 角色_id=2)
上述代碼執行兩遍後數據庫爲
切片操作 [::]
此切片用法和Python中一致,但其實在數據庫層面的切片(實際爲數據庫中的分組,但步長會在Python中進行),會有更高的效率
比如說下列的
Member.objects.filter(等級__lte=100).values('暱稱', '等級')[1:3]
在數據庫中執行爲
SELECT `query_member`.`暱稱`, `query_member`.`等級` FROM `query_member` WHERE `query_member`.`等級` <= 100 LIMIT 2 OFFSET 1
QuerySet
QuerySet | 作用 |
---|---|
.exists() |
判斷某個條件的數據是否存在。此方法的效率非常高 |
.db |
返回現在執行此查詢時使用的數據庫。 |
.reverse() |
反轉QuerySet的順序。 |
.extra() |
手動添加SQL語句 |
.bulk_update() |
更新數據庫中每個給定對象中的給定字段。 |
更多的QuerySet
可以自行查詢,大部分會使用到的QuerySet方法上述都已介紹。使用率極地的QuerySet如果需要可以自行查看