Django(四)数据库
一、數(shù)據(jù)庫(kù)框架
數(shù)據(jù)庫(kù)框架是數(shù)據(jù)庫(kù)的抽象層,也稱(chēng)為對(duì)象關(guān)系映射(Object-Relational Mapper, ORM),它將高層的面向?qū)ο蟛僮鬓D(zhuǎn)換成低層的數(shù)據(jù)庫(kù)指令,比起直接操作數(shù)據(jù)庫(kù)引擎,ORM極大的提高了易用性。這種轉(zhuǎn)換會(huì)帶來(lái)一定的性能損耗,但ORM對(duì)生產(chǎn)效率的提升遠(yuǎn)遠(yuǎn)超過(guò)這一丁點(diǎn)兒性能降低。
Django中內(nèi)置的SQLAlchemy ORM就是一個(gè)很好的數(shù)據(jù)庫(kù)框架,它為多種關(guān)系型數(shù)據(jù)庫(kù)引擎提供抽象層,比如MySQL, Postgres,SQLite,并且使用相同的面向?qū)ο蠼涌凇R虼?#xff0c;使用SQLAlchemy ORM,不僅能極大的提高生產(chǎn)力,而且可以方便的在多種數(shù)據(jù)庫(kù)之間遷移。
二、配置數(shù)據(jù)庫(kù)
我們可以在項(xiàng)目文件夾的settins.py中配置數(shù)據(jù)庫(kù)引擎。
Django默認(rèn)使用sqlite:
如果要要使用mysql, 需要進(jìn)行如下配置:
1 編輯項(xiàng)目文件夾下的settings.py :
2 編輯項(xiàng)目文件夾下的__init__.py :
由于mysql在Django中默認(rèn)驅(qū)動(dòng)是MySQLdb, 而該驅(qū)動(dòng)不適用于python3, 因此,我們需要更改驅(qū)動(dòng)為PyMySQL
3 顯示SQL語(yǔ)句
前面我們說(shuō)了ORM將高層的面向?qū)ο蟮牟僮?#xff0c;轉(zhuǎn)換為低層的SQL語(yǔ)句,如果想在終端打印對(duì)應(yīng)的SQL語(yǔ)句,可以在setting.py中加上日志記錄:
三、模型
在ORM中,用模型(Model)表示數(shù)據(jù)庫(kù)中一張表。模型的具體實(shí)現(xiàn)是一個(gè)Python類(lèi),類(lèi)中的屬性對(duì)應(yīng)數(shù)據(jù)庫(kù)表中的字段,這個(gè)類(lèi)的實(shí)例化對(duì)象,對(duì)應(yīng)表中的一條記錄。
總結(jié):類(lèi) –> 表; 類(lèi)屬性 –> 表字段; 類(lèi)實(shí)例 –> 表記錄
定義模型
定義模型就是定義一個(gè)python類(lèi),以創(chuàng)建一個(gè)圖書(shū)管理系統(tǒng)為例,基本形式如下:
from django.db import modelsclass Publish(models.Model):name = models.CharField(max_length=60)addr = models.CharField(max_length=60)def __str__(self):return self.nameclass Author(models.Model):name = models.CharField(max_length=30)def __str__(self):return self.nameclass Book(models.Model):name = models.CharField(max_length=60)price = models.DecimalField(max_digits=6, decimal_places=2)publish = models.ForeignKey(Publish)# 定義書(shū)與出版社的多對(duì)一關(guān)系# 默認(rèn)綁定到Publish表中的主鍵字段authors = models.ManyToManyField(Author)# 定義書(shū)與作者的多對(duì)多關(guān)系,ORM將自動(dòng)創(chuàng)建多對(duì)多關(guān)系的第三張表說(shuō)明:
1. 定義完模型后,或者修改了模型后,要執(zhí)行數(shù)據(jù)庫(kù)遷移操作:
python manage.py makemigrations
python manage.py migrate
執(zhí)行完命令后,查看數(shù)據(jù)庫(kù)的表目錄,可以看到上述表格成功創(chuàng)建:
2. 上述模型中都沒(méi)有設(shè)置主鍵,在完成上遷移操作后,orm會(huì)自動(dòng)創(chuàng)建主鍵。
3. orm會(huì)自動(dòng)將Book表中的關(guān)聯(lián)字段publish, 在數(shù)據(jù)庫(kù)中存為publish_id, 所以不要畫(huà)蛇添足自己命名為publish_id,否則你在數(shù)據(jù)庫(kù)中看到的是publish_id_id
4. 外鍵引用的主表要么在子表前創(chuàng)建,要么使用字符串形式指定,否則子表找不到主表。
5. 如果我們實(shí)例化一個(gè)Book對(duì)象,book_obj, 那么通過(guò)book_obj.publish得到的是publish_id對(duì)應(yīng)的那個(gè)Publish對(duì)象。這是ORM作的設(shè)定,原因很簡(jiǎn)單,如果通過(guò)book_obj.publish得到只是一個(gè)publish_id,對(duì)我們并沒(méi)有多大用。
6. 雖然不是強(qiáng)制的,但是建議在每個(gè)類(lèi)中定義__str__方法(或__repr__方法),這樣當(dāng)我們打印對(duì)象時(shí),可以顯示具有可讀性的字符串信息,方便調(diào)試。
7. 只要在一張表中定義了多對(duì)多關(guān)系,orm會(huì)自動(dòng)創(chuàng)建實(shí)現(xiàn)多對(duì)多關(guān)系的第三張表。當(dāng)然,你也可以手動(dòng)創(chuàng)建,如下:
還是不建議手動(dòng)創(chuàng)建,一是麻煩,而是后面我在執(zhí)行刪除記錄的操作時(shí),提示找不到第三關(guān)聯(lián)表,可能是我表名命名問(wèn)題,猜測(cè)應(yīng)該將第三張表命名為Book_authors的格式,這樣才能和orm自動(dòng)創(chuàng)建的第三張表同名,未驗(yàn)證。。。
字段類(lèi)型
| IntegerField | int | 普通整數(shù),通常是32位,-2147483648 to 2147483647 |
| SmallIntegerField | int | 小整數(shù),一般是16位,-32768 to 32767 |
| BigIntegerField | int/long | 64位的整數(shù),-9223372036854775808 to 9223372036854775807 |
| FloatField | float | 浮點(diǎn)數(shù) |
| DecimalField(max_digits=None, decimal_places=None | decimal.Decimal | 定點(diǎn)數(shù),精度更高;要求指定位數(shù)和小數(shù)點(diǎn)精度。 |
| CharField(max_length=None) | str | 字符串;要求指定最大長(zhǎng)度 |
| TextField | str | 變長(zhǎng)字符串 |
| BooleanField | bool | 布爾值 |
| DateField | datetime.date | 日期,比如:2017-08-25 |
| DateTimeField | datetime.datetime | 日期和時(shí)間 |
| BinaryField | str | 二進(jìn)制 |
更多類(lèi)型請(qǐng)參考官網(wǎng) field types
關(guān)系字段
| ForeignKey(othermodel) | 多對(duì)一關(guān)系,需要指定關(guān)系表 |
| ManyToManyField(othermodel) | 多對(duì)多關(guān)系,需要指定關(guān)系表 |
| OneToOneField(othermodel) | 一對(duì)一關(guān)系,需要指定關(guān)系表 |
字段選項(xiàng)
| primary_key | 如果設(shè)置primary_key=True, 這列就是表的主鍵;如果不指定,Django會(huì)自動(dòng)添加一個(gè)AutoField字段來(lái)盛放主鍵,所以我們一般無(wú)需設(shè)定主鍵。 |
| unique | 如果設(shè)置unique=True, 這列不允許出現(xiàn)重復(fù)的值 |
| db_index | 如果設(shè)置db_index=True, 為這列創(chuàng)建索引,提升查詢(xún)效率 |
| null | 如果設(shè)置null=True, 這列允許使用空值;如果新增了字段,建議設(shè)置該選項(xiàng),因?yàn)樾略鲎侄沃暗挠涗洓](méi)有該字段 |
| default | 為這列定義默認(rèn)值;如果新增了字段,建議設(shè)置該選項(xiàng),因?yàn)樾略鲎侄沃暗挠涗洓](méi)有該字段 |
| related_name | 在一對(duì)多關(guān)系多所在的表中定義反向引用;這樣在一所在的表中反向查詢(xún)多所在的表時(shí),直接用這個(gè)字段就行了,可以替代下面要講到的_set反向查詢(xún) |
更多選項(xiàng)請(qǐng)參考官網(wǎng) filed options
四、數(shù)據(jù)庫(kù)的操作
下面通過(guò)python shell來(lái)演示數(shù)據(jù)庫(kù)的操作。在終端切換到項(xiàng)目根目錄下,輸入命令python manage.py shell進(jìn)入shell操作環(huán)境:
增刪改查
1 創(chuàng)建記錄
方式一:實(shí)例化
>>> from app.models import Author #導(dǎo)入模型 >>> a = Author(name="張三") # 實(shí)例化 >>> a.save() # 插入記錄 >>> print(a) 張三方式二:create()工廠函數(shù)
>>> Author.objects.create(name='李四') <Author: 李四>通過(guò)get_or_create()創(chuàng)建記錄,這種方法可以防止重復(fù)(速度稍慢,因?yàn)闀?huì)先查詢(xún)數(shù)據(jù)庫(kù)),它返回一個(gè)元組,第一個(gè)是實(shí)例對(duì)象(記錄),創(chuàng)建成功返回True,已存在則返回False,不執(zhí)行創(chuàng)建。
>>> Author.objects.get_or_create(name='李四') (<Author: 李四>, False)2 查詢(xún)記錄
1 Author.objects.all()查詢(xún)所有
>>> Author.objects.all() <QuerySet [<Author: 張三>, <Author: 李四>]>我們也可以對(duì)查詢(xún)結(jié)果進(jìn)行切片索引操作:Author.objects.all()[start:end:step],注意,不支持負(fù)索引:
>>> Author.objects.all()[-1] AssertionError: Negative indexing is not supported.2 Author.objects.filter(name='李四') 過(guò)濾查詢(xún)
3 萬(wàn)能的雙下劃線查詢(xún)__,對(duì)應(yīng)SQL的where語(yǔ)句
__contains, __regex, __gt, __th, 多個(gè)條件之間以逗號(hào)分隔
__in判斷字段在列表內(nèi)。另外通常用pk指主鍵,不限于id,適用性更好。
models.Server.objects.filter(pk__in=id_list).delete()其它還有: __startswith(), __istartswith(), __endswith(), __iendswith()
4 Author.objects.get() 只能得到一個(gè)對(duì)象,多了少了都報(bào)錯(cuò)
5 first(), last()獲取查詢(xún)結(jié)果中的單個(gè)對(duì)象
>>> Author.objects.filter(id__gt=2).first() # 獲取第一個(gè) <Author: 李四> >>> Author.objects.filter(id__gt=2).last() # 獲取最后一個(gè) <Author: Martin>6 values(*field) 用字典形式,返回指定字段的查詢(xún)結(jié)果;多個(gè)字段間以逗號(hào)分隔
>>> Author.objects.values('name') <QuerySet [{'name': '李白'}, {'name': '光緒'}]> # values()方法前可以加過(guò)濾條件,如果不加,相當(dāng)于Author.objects.all().values()7 values_list(*field),同上,用元組形式
<QuerySet [('李白',), ('光緒',)]>8 exclude(**kwargs)反向過(guò)濾
>>> Author.objects.exclude(name__contains='魯迅') # 過(guò)濾所有姓名不包含‘魯迅的’ <QuerySet [<Author: 李白>, <Author: 光緒>, <Author: Martin>]>9 order_by(*field) 根據(jù)字段排序
10 reverse() 反向排序,用在·order_by后面
11 distinct() 剔除重復(fù)
12 count() 統(tǒng)計(jì)數(shù)量
13 exists() QuerySet包含數(shù)據(jù)返回True, 否則返回False
3 修改記錄
方式一:QuerySet.update(field=var)
修改的前提是先查找,然后調(diào)用update(field=val)方法,只有QuerySet集合對(duì)象才能調(diào)用該方法,也就是說(shuō)通過(guò)get(), first(), last()獲取的對(duì)象沒(méi)有該方法。
方式二:對(duì)象賦值,不推薦,效率低
>>> obj = Author.objects.filter(name='李小白').first() >>> obj.name='李白' >>> obj.save() # SQL語(yǔ)句: UPDATE `app_author` SET `name` = '李白' WHERE `app_author`.`id` = 3; args=('李白', 3) 從SQL語(yǔ)句可以看出,通過(guò)對(duì)象賦值的方式,會(huì)將該對(duì)象的所有字段重新賦值,故而效率低。4 刪除記錄
刪除的前提是先查找,然后調(diào)用delete()方法;不同于update()方法,delete()支持QuerySet集合對(duì)象的刪除,也支持單個(gè)對(duì)象的刪除。
delete()默認(rèn)就是級(jí)聯(lián)刪除:刪除一條記錄后,在多對(duì)多關(guān)系的關(guān)聯(lián)表中與該記錄有關(guān)的記錄也會(huì)刪除。
QuerySet
從數(shù)據(jù)庫(kù)從數(shù)據(jù)庫(kù)查詢(xún)出來(lái)的結(jié)果一般是一個(gè)集合,哪怕只有一個(gè)對(duì)象,這個(gè)集合叫QuerySet。
QuerySet特性:
1 支持切片操作
2 可迭代:for循環(huán)
3 惰性機(jī)制:只有使用QuerySet時(shí),才會(huì)走數(shù)據(jù)庫(kù),比如執(zhí)行res = Author.objects.all()時(shí),并不會(huì)真正執(zhí)行數(shù)據(jù)庫(kù)查詢(xún),只是翻譯為SQL語(yǔ)句。而當(dāng)我們執(zhí)行if res, print res, for obj in res這些操作時(shí),才會(huì)執(zhí)行SQL語(yǔ)句,進(jìn)行數(shù)據(jù)庫(kù)查詢(xún)。這一點(diǎn)可以通過(guò)在setting.py中加上日志記錄顯示SQL語(yǔ)句得到證實(shí)。
4 緩存機(jī)制:每次執(zhí)行了數(shù)據(jù)庫(kù)查詢(xún)后,會(huì)將結(jié)果放在QuerySet的緩存中,下次再使用QuerySet時(shí),不走數(shù)據(jù)庫(kù),直接從緩存中拿數(shù)據(jù)。緩存機(jī)制減少了對(duì)數(shù)據(jù)庫(kù)的訪問(wèn),有利于提高性能。但是一旦數(shù)據(jù)庫(kù)數(shù)據(jù)更新,除非重新訪問(wèn)數(shù)據(jù)庫(kù),否則緩存也不會(huì)更新,下面我們來(lái)證實(shí)這一點(diǎn):
提高數(shù)據(jù)庫(kù)性能
iterator()迭代器
如果我們查詢(xún)出的數(shù)據(jù)很大,QeurySet的緩存肯定會(huì)崩。解決方案:對(duì)QeurySet應(yīng)用.iterator()方法,將查詢(xún)結(jié)果轉(zhuǎn)化為迭代器。
>>> g = Author.objects.all().iterator() >>> for item in g: ... print(item.name) ... 李白 光緒 魯迅 Martin Susan >>> for item in g: ... print(item.name) ... >>> # 第一次for循環(huán)迭代器迭代完了,所以第二次不會(huì)打印出來(lái)盡管轉(zhuǎn)化為迭代器會(huì)節(jié)省內(nèi)存,但是這也意味著,會(huì)造成額外的數(shù)據(jù)庫(kù)查詢(xún)。
exists()
比如我們拿到一個(gè)QuerySet對(duì)象,res = Book.objects.all(),想確定記錄是否存在,如果用if res,將會(huì)查詢(xún)數(shù)據(jù)庫(kù)中的所有記錄,這會(huì)極大的影響性能,解決方案:if res.exists() 這樣會(huì)限定只查詢(xún)一條記錄(低層轉(zhuǎn)化為SQL語(yǔ)句中的limit 1)
select_related主動(dòng)連表查詢(xún)
提高數(shù)據(jù)庫(kù)性能的關(guān)鍵一點(diǎn)是減少對(duì)數(shù)據(jù)庫(kù)的查詢(xún),我們來(lái)看一個(gè)栗子:
1. 創(chuàng)建一張Role角色表,和UserInfo表,建立一對(duì)多關(guān)系:
2.往UserInfo表中插入3個(gè)用戶(hù),并指定角色:略
3.在視圖中通過(guò)如下方式查詢(xún)用戶(hù)名和用戶(hù)的角色名:
4.在settings.py中配置打印SQL命令;通過(guò)瀏覽器訪問(wèn)http://127.0.0.1:8000/index.html/執(zhí)行index視圖函數(shù),查看SQL命令的執(zhí)行結(jié)果:
(0.000) SELECT "app01_userinfo"."id", "app01_userinfo"."name", "app01_userinfo"."pwd", "app01_userinfo"."role_id" FROM "app01_userinfo"; args=() (0.000) SELECT "app01_role"."id", "app01_role"."title" FROM "app01_role" WHERE "app01_role"."id" = 1; args=(1,) (0.000) SELECT "app01_role"."id", "app01_role"."title" FROM "app01_role" WHERE "app01_role"."id" = 2; args=(2,) (0.000) SELECT "app01_role"."id", "app01_role"."title" FROM "app01_role" WHERE "app01_role"."id" = 3; args=(3,)SQL語(yǔ)句顯示一共執(zhí)行了4次數(shù)據(jù)庫(kù)查詢(xún),第一次對(duì)應(yīng)user_list = UserInfo.objects.all(),剩余三次是for user in user_list: print(user.name, user.role.title) 循環(huán)時(shí),針對(duì)三個(gè)用戶(hù),查詢(xún)了三次角色表。如果用戶(hù)數(shù)量很多,這樣一次次的查詢(xún)數(shù)據(jù)庫(kù),將極大影響數(shù)據(jù)庫(kù)性能。
下面我們通過(guò)select_related執(zhí)行查詢(xún):
查看這次的SQL語(yǔ)句:只執(zhí)行了一次數(shù)據(jù)庫(kù)查詢(xún)
select_related('FK')取當(dāng)前表數(shù)據(jù)和表外鍵關(guān)聯(lián)字段,因此,在一次查詢(xún)中獲得了所有需要的信息。
如果要連多個(gè)表,通過(guò)雙下劃線連接更多外鍵字段即可:
select_related('FK1__FK2')
prefetch_related
我們將上面的栗子中的select_related改為prefetch_related
def index(request):user_list = UserInfo.objects.all().prefetch_related("role")for user in user_list:print(user.name, user.role.title)return HttpResponse('ok')查看SQL語(yǔ)句:
(0.000) SELECT "app01_userinfo"."id", "app01_userinfo"."name", "app01_userinfo"."role_id" FROM "app01_userinfo"; args=() (0.001) SELECT "app01_role"."id", "app01_role"."title" FROM "app01_role" WHERE "app01_role"."id" IN (1, 2, 3); args=(1, 2, 3)執(zhí)行了兩次查詢(xún),第二次查詢(xún)是通過(guò)判斷用戶(hù)角色是否在角色表,并將關(guān)聯(lián)的角色取出來(lái)。因?yàn)橥ǔS脩?hù)數(shù)量很多,但是角色相對(duì)會(huì)少很多,因此,這種方式也減少了對(duì)數(shù)據(jù)庫(kù)的訪問(wèn)。
only
UserInfo.objects.all().only("name")only()方法只取某個(gè)字段,因此,如果需要只是需要用到指定的字段,通過(guò)這種方式可以提供性能。區(qū)別于values(),only()的查詢(xún)結(jié)果還是對(duì)象,而values()的查詢(xún)結(jié)果是字典。
defer
與only()相反,排除某個(gè)字段。
關(guān)聯(lián)關(guān)系的處理
在視圖函數(shù)中操作數(shù)據(jù)庫(kù)的語(yǔ)法與在python shell中是一樣的
添加一對(duì)多關(guān)系
from django.shortcuts import render, HttpResponse from .models import *def add(request):# 方式一,通過(guò)真實(shí)字段賦值Book.objects.get_or_create(title = 'chinese',price = 10.00,publish_id = 1, # Book的publish字段在數(shù)據(jù)庫(kù)中真實(shí)表示是publish_id)# 方式二, 通過(guò)對(duì)象賦值publish_obj = Publish.objects.get(id=2)Book.objects.create(title ='English',price = 18.88,publish = publish_obj, #通過(guò)對(duì)象賦值)return HttpResponse('OK')添加/解除多對(duì)多關(guān)系
from django.shortcuts import render, HttpResponse from .models import *def add(request):# 添加多對(duì)多關(guān)系的前提是記錄已經(jīng)創(chuàng)建好,無(wú)法在創(chuàng)建記錄的同時(shí)添加多對(duì)多關(guān)系# 逐個(gè)添加 add(obj)author_obj1 = Author.objects.get(id=1)author_obj2 = Author.objects.get(id=2)book_obj = Book.objects.get(id='8')book_obj.authors.add(author_obj2, author_obj1)# 批量添加 add(queryset)author_list = Author.objects.all()book_obj = Book.objects.get(id='1')book_obj.authors.add(*author_list)# * + 列表,將列表傳給函數(shù)# * + 字典,將字典傳給函數(shù)# 打印authors --> 對(duì)象集合book_obj = Book.objects.get(id='8')print(book_obj.authors.all())# 打印結(jié)果:<QuerySet [<Author: Egon>, <Author: Alex>, <Author: 魯迅>, <Author: 光緒>]># 解除部分綁定 remove(obj)book_obj = Book.objects.get(id='8')author = Author.objects.get(id=2)book_obj.authors.remove(author)# 如果要解除多個(gè):# * + 列表,將列表傳給函數(shù)# * + 字典,將字典傳給函數(shù)# 解除所有綁定 clear()book_obj = Book.objects.get(id='8')book_obj.authors.clear()return HttpResponse('OK')多表查詢(xún)
正向查詢(xún):通過(guò)當(dāng)前表中存在的字段查詢(xún)
例1:一對(duì)多:查詢(xún)一本書(shū)出版社的名字
>>> b = Book.objects.filter(name__contains='現(xiàn)代').first() >>> b.publish.name # b.publish 是一個(gè)對(duì)象,對(duì)應(yīng)主表Publish中的一條記錄 '復(fù)旦出版' # 通過(guò)publish拿到對(duì)應(yīng)主表中的對(duì)象,訪問(wèn)其屬性例2:多對(duì)多:查詢(xún)一本書(shū)的作者
>>> b = Book.objects.get(name='linux') >>> author_list = b.authors.all() # 拿到某本書(shū)的所有author對(duì)象 >>> print(author_list) <QuerySet [<Author: 李白>, <Author: 光緒>]>以上兩例是基于對(duì)象屬性的正向查詢(xún)。
例3:查詢(xún)某出版社出版了哪些書(shū):
反向查詢(xún)
Publish表中沒(méi)有book相關(guān)的字段,但是可以通過(guò)反向查詢(xún)來(lái)做:book_set(用關(guān)聯(lián)的表名小寫(xiě),下劃線加set)來(lái)找到與出版社關(guān)聯(lián)的書(shū)籍的對(duì)象的集合
還是例3,如果用反向查詢(xún):
book_set : 關(guān)聯(lián)表名,set集合;all()取出所有數(shù)據(jù)。
注意,如果是一對(duì)一關(guān)聯(lián),那么就不用加_set。
基于反向查詢(xún)的語(yǔ)法,我們也可以執(zhí)行反向綁定關(guān)系:
偽代碼形式:
基于values(), filter(), 雙下劃線的多表查詢(xún)
以上幾種多表查詢(xún)方式都略顯麻煩,現(xiàn)在我們通過(guò)values(), filter(), 雙下劃線,來(lái)簡(jiǎn)化一下:
例1:查詢(xún)一本書(shū)出版社的名字(正向思路):
例2: 查詢(xún)出版了某本書(shū)的的出版社名字(反向思路):
>>> Publish.objects.filter(book__name='linux').values('name') <QuerySet [{'name': '人民郵電'}]> # book(子表名) + __(雙下劃線) + name(子表中的字段) # 對(duì)應(yīng)的低層SQL語(yǔ)句:filter(book__title="linux")應(yīng)用了表聯(lián)結(jié) SELECT `app_publish`.`name` FROM `app_publish` INNER JOIN `app_book` ON (`app_publish`.`id ` = `app_book`.`publish_id`) WHERE `app_book`.`name` = 'linux' LIMIT 21; args=('linux',)例3:查詢(xún)價(jià)格大于10的書(shū)籍的作者姓名:
正向: Book.objects.filter(price__gt=10).values("authors__name") # authors(子表與主表關(guān)聯(lián)字段) + __(雙下劃線) + name(主表目標(biāo)字段) 反向: Author.objects.filter(book__price__gt=10).values("name") # book(子表名) + __(雙下劃線) + price__gt=10(子表字段,條件)聚合&分組查詢(xún)
SQL語(yǔ)言中有聚合函數(shù):Avg, Min, Max, Sum, Count,可以方便進(jìn)行數(shù)據(jù)統(tǒng)計(jì);在ORM中,QuerySet的aggregate()方法對(duì)此提供了支持,它返回一個(gè)統(tǒng)計(jì)結(jié)果的鍵值對(duì)。下面我們看看如何使用,
基本格式:QuerySet.aggregate(func(field))
例1 查詢(xún)某作家出版書(shū)籍的價(jià)格總和
如果要統(tǒng)計(jì)多個(gè)作者,那就要用到分組查詢(xún),QuerySet的anotate()方法對(duì)此提供了支持。
例2 每個(gè)作者出版過(guò)的書(shū)的平均價(jià)格
F&Q查詢(xún)
很多時(shí)候單一的關(guān)鍵字查詢(xún)無(wú)法滿(mǎn)足查詢(xún)要求,可以使用F&和Q查詢(xún),使用前請(qǐng)先導(dǎo)入:
from django.db.models import F, Q
F對(duì)字段取值
F用于取字段取值,我們來(lái)看一個(gè)例子:
對(duì)數(shù)據(jù)庫(kù)中每本書(shū)的價(jià)格加10元:
Book.objects.all.update(price=price+10)
直接報(bào)錯(cuò) NameError: name ‘price’ is not defined,提示price+10中的price未定義,取不到值。下面我們通過(guò)F對(duì)price字段取值:
Q組合多個(gè)查詢(xún)條件
假設(shè)我們要查詢(xún)某個(gè)作家,價(jià)格大于10元的書(shū),那么filter()函數(shù)中通過(guò)逗號(hào),放兩個(gè)過(guò)濾條件可以實(shí)現(xiàn):
>>> Book.objects.filter(authors__name='光緒', price__gt=10) <QuerySet [<Book: linux>, <Book: 現(xiàn)代編程方法>]>上面這個(gè)情況,逗號(hào)就是處理邏輯與。那如果要處理邏輯非,邏輯或,這些過(guò)濾條件呢?這時(shí)Q查詢(xún)就可以很靈活處理:
1 將查詢(xún)條件用Q包起來(lái)
2 通過(guò):, & | ~ 且,或,非,運(yùn)算符來(lái)連接多個(gè)過(guò)濾條件
下面我們看栗子:
例1 查詢(xún)某個(gè)作家的,或者價(jià)格大于10的書(shū)
例2 查詢(xún)非莫個(gè)作家寫(xiě)的,并且是某個(gè)出版社的書(shū)
>>> Book.objects.filter(~Q(authors__name='李白') & Q(publish__name='機(jī)械工業(yè)')) <QuerySet [<Book: 蘇菲的世界>, <Book: 水滸傳>]> # 不是李白寫(xiě)的,并且是由機(jī)械工業(yè)出版社出版的書(shū)'ID': [1, 2], 'hostname': ['c1.com', 'c2.com']}
分析查詢(xún)邏輯:
字典中每一個(gè)元素下鍵對(duì)應(yīng)的列表中的元素:OR
Q('ID'=1) | Q('ID'=2) Q('hostname'='c1.com') | Q('hostname'='c2.com')字典中ID與hostname – AND, 最終組合查詢(xún)條件如下:
Q((Q('ID'=1) | Q('ID'=2)) & (Q('hostname'='c1.com') | Q('hostname'='c2.com')))下面我們用Q查詢(xún)的面向?qū)ο蠓绞?#xff1a;
from django.db.models import Qquery = Q()temp1 = Q() temp1.connector = 'OR' temp1.children.append(('ID', 1)) temp1.children.append(('ID', 2)) # 相當(dāng)于: # Q('ID'=1) | Q('ID'=2)temp2 = Q() temp2.connector = 'OR' temp2.children.append(('hostname', 'c1.com')) temp2.children.append(('hostname', 'c2.com')) # 相當(dāng)于: # Q('hostname'='c1.com') | Q('hostname'='c2.com')query.add(temp1, 'AND') query.add(temp2, 'ADN') # 相當(dāng)于: # Q((Q('ID'=1) | Q('ID'=2)) & (Q('hostname'='c1.com') | Q('hostname'='c2.com')))當(dāng)查詢(xún)條件長(zhǎng)度不確定時(shí),顯然我們無(wú)法通過(guò)簡(jiǎn)單的對(duì)Q進(jìn)行組合來(lái)查詢(xún),那么Q查詢(xún)的面向?qū)ο蠓绞骄涂梢园l(fā)揮用處:
from django.db.models import Qquery = Q()for k, v in search_condictions.items():# k: AND; for i in v: ORtemp = Q()temp.connector = 'OR'for i in v:temp.children.append((k, i))query.add(temp, 'AND')res = models.Server.objects.filter(query).all()多表查詢(xún)和表創(chuàng)建總結(jié)
多表查詢(xún):正向查詢(xún)用字段,反向查詢(xún)用表名(小寫(xiě))
一對(duì)一關(guān)系:
# 正向:b_obj = a_obj.field# 反向:因?yàn)槭且粚?duì)一,所有查詢(xún)出來(lái)只有一個(gè),不需要_seta_obj = b_obj.model一對(duì)多關(guān)系:
# 正向:b_obj = a_obj.field# 反向:_set取到集合QuerySet_obj = b_obj.model_set.all()多對(duì)多關(guān)系:
# 正向:QuerySet_obj = a_obj.field.all()# 反向:QuerySet_obj = b_obj.model_set.all()創(chuàng)建表:
多表關(guān)系的創(chuàng)建
class Article(models.Model):# 自定義主鍵;一般不需要定義,默認(rèn)會(huì)自己創(chuàng)建。nid = models.BigAutoField(primary_key=True)title = models.CharField(max_length=50, verbose_name='文章標(biāo)題')# 一對(duì)一關(guān)系;to_field屬性一般不用定義,orm會(huì)自動(dòng)找到關(guān)聯(lián)表的主鍵字段body = models.OneToOneField(verbose_name='文章內(nèi)容', to='ArticleDetail', to_field='nid')# 一對(duì)多關(guān)系blog = models.ForeignKey(verbose_name='所屬博客', to='Blog', to_field='nid')# 多對(duì)多關(guān)系;默認(rèn)自動(dòng)創(chuàng)建第三張表,通過(guò)定義through和through_fields屬性,來(lái)手動(dòng)定義多對(duì)多關(guān)系。如果需要操作第三張表,選擇手動(dòng)定義。tags = models.ManyToManyField(to="Tag",through='Article2Tag',through_fields=('article', 'tag'),)# 靜態(tài)字段type_choices = [(1, "Python"),(2, "Linux"),(3, "OpenStack"),(4, "GoLang"),]article_type_id = models.IntegerField(choices=type_choices, default=None)# 手動(dòng)創(chuàng)建多對(duì)多關(guān)聯(lián)表 class Article2Tag(models.Model):nid = models.AutoField(primary_key=True)article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid')tag = models.ForeignKey(verbose_name='標(biāo)簽', to="Tag", to_field='nid')class Meta:unique_together = [('article', 'tag'),]說(shuō)明:表中出現(xiàn)靜態(tài)字段作為choices源的字段,存的值是Integer,如果想獲取對(duì)應(yīng)的文本,使用:
obj.get_field_display()即可顯示,省去自己寫(xiě)循環(huán)判斷的麻煩。對(duì)于這里來(lái)說(shuō),field是article_type_id
本表和本表的關(guān)系
自引用一對(duì)多
class Menu(models.Model):"""菜單"""title = models.CharField(verbose_name='菜單名稱(chēng)', max_length=32, unique=True)parent = models.ForeignKey(verbose_name='父級(jí)菜單', to="Menu", null=True, blank=True)# 定義本表的自引用一對(duì)多關(guān)系# blank=True 意味著在后臺(tái)管理中填寫(xiě)可以為空,根菜單沒(méi)有父級(jí)菜單 class Customer(models.Model):"""客戶(hù)表"""name = models.CharField(verbose_name='姓名', max_length=16)gender_choices = ((1, '男'), (2, '女'))gender = models.SmallIntegerField(verbose_name='性別', choices=gender_choices)referral_from = models.ForeignKey('self', # 與本表的自引用一對(duì)多blank=True,null=True,verbose_name="轉(zhuǎn)介紹自客戶(hù)",help_text="若此客戶(hù)是轉(zhuǎn)介紹自?xún)?nèi)部會(huì)員,請(qǐng)?jiān)诖颂庍x擇會(huì)員姓名",related_name="internal_referral")# related_name定義反向引用關(guān)系,通過(guò)該字段直接查找,而不用反向查找。自引用多對(duì)多,比如用戶(hù)互相關(guān)注
class UserInfo(AbstractUser):"""用戶(hù)信息"""nid = models.BigAutoField(primary_key=True)nickname = models.CharField(verbose_name='昵稱(chēng)', max_length=32)fans = models.ManyToManyField(verbose_name='粉絲們',to='UserInfo',through='UserFans',through_fields=('user', 'follower'))class UserFans(models.Model):"""互粉關(guān)系表"""nid = models.AutoField(primary_key=True)user = models.ForeignKey(verbose_name='用戶(hù)', to='UserInfo', to_field='nid', related_name='users')follower = models.ForeignKey(verbose_name='粉絲', to='UserInfo', to_field='nid', related_name='followers')class Meta:unique_together = [('user', 'follower'),]繼承自帶用戶(hù)表
Django自帶一張用戶(hù)表,其中提供了很多字段,包括密文密碼。而用戶(hù)自定義的用戶(hù)表密碼是明文的,如果需要使用Django自帶用戶(hù)表的特性。可以繼承自帶的用戶(hù)表。
配置settings.py
AUTH_USER_MODEL='app.UserInfo' # app名 加 表名繼承AbstractUser表后,自帶用戶(hù)表中的所有字段可用,并且可以定義其它字段。
from django.contrib.auth.models import AbstractUserclass UserInfo(AbstractUser):"""用戶(hù)信息"""pass總結(jié)
以上是生活随笔為你收集整理的Django(四)数据库的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Netiler annotation 用
- 下一篇: 进程池和线程池