ORM
簡介咱們在使用Django
框架開發web
應用的過程當中,不可避免地會涉及到數據的管理操做(如增、刪、改、查),而一旦談到數據的管理操做,就須要用到數據庫管理軟件,例如mysql
、oracle
、Microsoft SQL Server
等。python
若是應用程序須要操做數據(好比將用戶註冊信息永久存放起來),那麼咱們須要在應用程序中編寫原生sql語句,而後使用pymysql模塊遠程操做mysql數據庫,詳見圖一^①^mysql
可是直接編寫原生sql語句會存在兩方面的問題,嚴重影響開發效率,以下git
爲了解決上述問題,django
引入了ORM
的概念,ORM
全稱Object Relational Mapping
,即對象關係映射,是在pymysq
之上又進行了一層封裝,對於數據的操做,咱們無需再去編寫原生sql
,取代代之的是基於面向對象的思想去編寫類、對象、調用相應的方法等,ORM
會將其轉換/映射成原生SQL
而後交給pymysql
執行,詳見圖二^②^程序員
如此,開發人員既不用再去考慮原生SQL的優化問題,也不用考慮數據庫遷移的問題,ORM都幫咱們作了優化且支持多種數據庫,這極大地提高了咱們的開發效率,下面就讓咱們來詳細學習ORM的使用吧web
models.py
建立django
項目,新建名爲app01
的app
,在app01
的models.py
中建立模型sql
class Employee(models.Model): # 必須是models.Model的子類 id=models.AutoField(primary_key=True) name=models.CharField(max_length=16) gender=models.BooleanField(default=1) birth=models.DateField() department=models.CharField(max_length=30) salary=models.DecimalField(max_digits=10,decimal_places=1)
settings.py
django
的`orm
支持多種數據庫,若是想將上述模型轉爲mysql
數據庫中的表,須要settings.py
中數據庫
# 刪除\註釋掉原來的DATABASES配置項,新增下述配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 使用mysql數據庫 'NAME': 'db1', # 要鏈接的數據庫 'USER': 'root', # 連接數據庫的用於名 'PASSWORD': '', # 連接數據庫的用於名 'HOST': '127.0.0.1', # mysql服務監聽的ip 'PORT': 3306, # mysql服務監聽的端口 'ATOMIC_REQUEST': True, #設置爲True表明同一個http請求所對應的全部sql都放在一個事務中執行 #(要麼全部都成功,要麼全部都失敗),這是全局性的配置,若是要對某個 #http請求放水(而後自定義事務),能夠用non_atomic_requests修飾器 'OPTIONS': { "init_command": "SET storage_engine=INNODB", #設置建立表的存儲引擎爲INNODB } } }
在鏈接mysql
數據庫前,必須事先建立好數據庫django
mysql> create database db1; # 數據庫名必須與settings.py中指定的名字對應上
其實python
解釋器在運行django
程序時,django
的orm
底層操做數據庫的python
模塊默認是mysqldb
而非pymysql
,然而對於解釋器而言,python2.x
解釋器支持的操做數據庫的模塊是mysqldb
,而python3.x
解釋器支持的操做數據庫的模塊則是pymysql
,,毫無疑問,目前咱們的django
程序都是運行於python3.x
解釋器下,因而咱們須要修改django
的orm
默認操做數據庫的模塊爲pymysql
,具體作法以下python3.x
確保配置文件settings.py
中的INSTALLED_APPS
中添加咱們建立的app
名稱,django2.x
與django1.x
處理添加方式不一樣api
# django1.x版本,在下述列表中新增咱們的app名字便可 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01', # 'app02' # 如有新增的app,依次添加便可 ] # django2.x版本,可能會幫咱們自動添加app,只是換了一種添加方式 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', # 若是默認已經添加了,則無需重複添加 # 'app02.apps.App02Config', # 如有新增的app,按照規律依次添加便可 ]
若是想打印orm
轉換過程當中的sql
,須要在settings
中進行配置日誌:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
最後在命令行中執行兩條數據庫遷移命令,便可在指定的數據庫db1
中建立表 :
$ python manage.py makemigrations $ python manage.py migrate # 注意: # 一、makemigrations只是生成一個數據庫遷移記錄的文件,而migrate纔是將更改真正提交到數據庫執行 # 二、數據庫遷移記錄的文件存放於app01下的migrations文件夾裏 # 三、瞭解:使用命令python manage.py showmigrations能夠查看沒有執行migrate的文件
注意1:在使用的是django1.x
版本時,若是報以下錯誤
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None
那是由於MySQLclient
目前只支持到python3.4
,若是使用的更高版本的python
,須要找到文件C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql
這個路徑裏的文件
# 註釋下述兩行內容便可 if version < (1, 3, 3): raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
注意2:當咱們直接去數據庫裏查看生成的表時,會發現數據庫中的表與orm
規定的並不一致,這徹底是正常的,事實上,orm
的字段約束就是不會所有體如今數據庫的表中,好比咱們爲字段gender
設置的默認值default=1
,去數據庫中查看會發現該字段的default
部分爲null
mysql> desc app01_employee; # 數據庫中標籤前會帶有前綴app01_ +------------+---------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(16) | NO | | NULL | | | gender | tinyint(1) | NO | | NULL | | | birth | date | NO | | NULL | | | department | varchar(30) | NO | | NULL | | | salary | decimal(10,1) | NO | | NULL | | +------------+---------------+------+-----+---------+----------------+
雖然數據庫沒有增長默認值,可是咱們在使用orm
插入值時,徹底爲gender
字段插入空,orm
會按照本身的約束將空轉換成默認值後,再提交給數據庫執行
在表生成以後,若是須要增長、刪除、修改表中字段,須要這麼作
# 一:增長字段 # 1.一、在模型類Employee裏直接新增字段,強調:對於orm來講,新增的字段必須用default指定默認值 publish = models.CharField(max_length=12,default='人民出版社',null=True) #1.二、從新執行那兩條數據庫遷移命令 # 二:刪除字段 # 2.1 直接註釋掉字段 # 2.2 從新執行那兩條數據庫遷移命令 # 三:修改字段 # 3.1 將模型類中字段修改 # 3.2 從新執行那兩條數據庫遷移命令
# 一、用模型類建立一個對象,一個對象對應數據庫表中的一條記錄 obj = Employee(name="Egon", gender=0, birth='1997-01-27', department="財務部", salary=100.1) # 二、調用對象下的save方法,便可以將一條記錄插入數據庫 obj.save()
# 每一個模型表下都有一個objects管理器,用於對該表中的記錄進行增刪改查操做,其中增長操做以下所示 obj = Employee.objects.create(name="Egon", gender=0, birth='1997-01-27', department="財務部", salary=100.1)
API
模型Employee
對應表app01_employee
,表app01_employee
中的每條記錄都對應類Employee
的一個對象,咱們以該表爲例,來介紹查詢API
,讀者能夠自行添加下述記錄,而後配置url
、編寫視圖測試下述API
mysql> select * from app01_employee; +----+-------+--------+------------+------------+--------+ | id | name | gender | birth | department | salary | +----+-------+--------+------------+------------+--------+ | 1 | Egon | 0 | 1997-01-27 | 財務部 | 100.1 | | 2 | Kevin | 1 | 1998-02-27 | 技術部 | 10.1 | | 3 | Lili | 0 | 1990-02-27 | 運營部 | 20.1 | | 4 | Tom | 1 | 1991-02-27 | 運營部 | 30.1 | | 5 | Jack | 1 | 1992-02-27 | 技術部 | 11.2 | | 6 | Robin | 1 | 1988-02-27 | 技術部 | 200.3 | | 7 | Rose | 0 | 1989-02-27 | 財務部 | 35.1 | | 8 | Egon | 0 | 1997-01-27 | 財務部 | 100.1 | | 9 | Egon | 0 | 1997-01-27 | 財務部 | 100.1 | +----+-------+--------+------------+------------+--------+
每一個模型表下都有一個objects管理器,用於對該表中的記錄進行增刪改查操做,其中查詢操做以下所示
Part1
!!!強調!!!:下述方法(除了count外)的返回值都是一個模型類Employee的對象,爲了後續描述方便,咱們統一將模型類的對象稱爲「記錄對象」,每個」記錄對象「都惟一對應表中的一條記錄,
# 1. get(**kwargs) # 1.1: 有參,參數爲篩選條件 # 1.2: 返回值爲一個符合篩選條件的記錄對象(有且只有一個),若是符合篩選條件的對象超過一個或者沒有都會拋出錯誤。 obj=Employee.objects.get(id=1) print(obj.name,obj.birth,obj.salary) #輸出:Egon 1997-01-27 100.1 # 二、first() # 2.1:無參 # 2.2:返回查詢出的第一個記錄對象 obj=Employee.objects.first() # 在表全部記錄中取第一個 print(obj.id,obj.name) # 輸出:1 Egon # 三、last() # 3.1: 無參 # 3.2: 返回查詢出的最後一個記錄對象 obj = Employee.objects.last() # 在表全部記錄中取最後一個 print(obj.id, obj.name) # 輸出:9 Egon # 四、count(): # 4.1:無參 # 4.2:返回包含記錄對象的總數量 res = Employee.objects.count() # 統計表全部記錄的個數 print(res) # 輸出:9 # 注意:若是咱們直接打印Employee的對象將沒有任何有用的提示信息,咱們能夠在模型類中定義__str__來進行定製 class Employee(models.Model): ...... # 在原有的基礎上新增代碼以下 def __str__(self): return "<%s:%s>" %(self.id,self.name) # 此時咱們print(obj)顯示的結果就是: <本條記錄中id字段的值:本條記錄中name字段的值>
Part2
!!!強調!!!:下述方法查詢的結果都有可能包含多個記錄對象,爲了存放查詢出的多個記錄對象,django
的ORM
自定義了一種數據類型Queryeset
,因此下述方法的返回值均爲QuerySet
類型的對象,QuerySet
對象中包含了查詢出的多個記錄對象
# 一、filter(**kwargs): # 1.1:有參,參數爲過濾條件 # 1.2:返回值爲QuerySet對象,QuerySet對象中包含了符合過濾條件的多個記錄對象 queryset_res=Employee.objects.filter(department='技術部') # print(queryset_res) # 輸出: <QuerySet [<Employee: <2:Kevin>>, <Employee: <5:Jack>>, <Employee: <6:Robin>>]> # 二、exclude(**kwargs) # 2.1: 有參,參數爲過濾條件 # 2.2: 返回值爲QuerySet對象,QuerySet對象中包含了不符合過濾條件的多個記錄對象 queryset_res=Employee.objects.exclude(department='技術部') # 三、all() # 3.1:無參 # 3.2:返回值爲QuerySet對象,QuerySet對象中包含了查詢出的全部記錄對象 queryset_res = Employee.objects.all() # 查詢出表中全部的記錄對象 # 四、order_by(*field): # 4.1:有參,參數爲排序字段,能夠指定多個字段,在字段1相同的狀況下,能夠按照字段2進行排序,以此類推,默認升序排列,在字段前加橫杆表明降序排(如"-id") # 4.2:返回值爲QuerySet對象,QuerySet對象中包含了排序好的記錄對象 queryset_res = Employee.objects.order_by("salary","-id") # 先按照salary字段升序排,若是salary相同則按照id字段降序排 # 五、values(*field) # 5.1:有參,參數爲字段名,能夠指定多個字段 # 5.2:返回值爲QuerySet對象,QuerySet對象中包含的並非一個個的記錄對象,而上多個字典,字典的key即咱們傳入的字段名 queryset_res = Employee.objects.values('id','name') print(queryset_res) # 輸出:<QuerySet [{'id': 1, 'name': 'Egon'}, {'id': 2, 'name': 'Kevin'}, ......]> print(queryset_res[0]['name']) # 輸出:Egon # 六、values_list(*field): # 6.1:有參,參數爲字段名,能夠指定多個字段 # 6.2:返回值爲QuerySet對象,QuerySet對象中包含的並非一個個的記錄對象,而上多個小元組,字典的key即咱們傳入的字段名 queryset_res = Employee.objects.values_list('id','name') print(queryset_res) # 輸出:<QuerySet [(1, 'Egon'), (2, 'Kevin'),), ......]> print(queryset_res[0][1]) # 輸出:Egon
Part3
<u>Part2
中所示查詢API
的返回值都是QuerySet
類型的對象,QuerySet
類型是django ORM
自定義的一種數據類型,專門用來存放查詢出的多個記錄對象,該類型的特殊之處在於
queryset
類型相似於python
中的列表,支持索引操做# 過濾出符合條件的多個記錄對象,而後存放到QuerySet對象中 queryset_res=Employee.objects.filter(department='技術部') # 按照索引從QuerySet對象中取出第一個記錄對象 obj=queryset_res[0] print(obj.name,obj.birth,obj.salary)
objects
下的方法queryset
下一樣能夠調用,而且django
的ORM
支持鏈式操做,因而咱們能夠像下面這樣使用# 簡單示範: res=Employee.objects.filter(gender=1).order_by('-id').values_list('id','name') print(res) # 輸出:<QuerySet [(6, 'Robin'), (5, 'Jack'), (4, 'Tom'), (2, 'Kevin')]>
Part4
其餘查詢API
# 一、reverse(): # 1.1:無參 # 1.2:對排序的結果取反,返回值爲QuerySet對象 queryset_res = Employee.objects.order_by("salary", "-id").reverse() # 二、exists(): # 2.1:無參 # 2.2:返回值爲布爾值,若是QuerySet包含數據,就返回True,不然返回False res = Employee.objects.filter(id=100).exists() print(res) # 輸出:False # 三、distinct(): # 3.1:若是使用的是Mysql數據庫,那麼distinct()無需傳入任何參數 # 3.2:從values或values_list的返回結果中剔除重複的記錄對象,返回值爲QuerySet對象 res = Employee.objects.filter(name='Egon').values('name', 'salary').distinct() print(res) # 輸出:<QuerySet [{'name': 'Egon', 'salary': Decimal('100.1')}]> res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct() print(res1) # 輸出:<QuerySet [('Egon', Decimal('100.1'))]>
F
與Q
查詢F
查詢在上面全部的例子中,咱們在進行條件過濾時,都只是用某個字段與某個具體的值作比較。若是咱們要對兩個字段的值作比較,那該怎麼作呢?
Django
提供 F()
來作這樣的比較。F()
的實例能夠在查詢中引用字段,來比較兩個不一樣字段的值,以下
# 一張書籍表中包含字段:評論數commentNum、收藏數keepNum,要求查詢:評論數大於收藏數的書籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum'))
Django
支持 F()
對象之間以及F()
對象和常數之間的加減乘除和取模的操做
# 查詢評論數大於收藏數2倍的書籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum')*2)
修改操做也可使用F函數,好比將每一本書的價格提升30元:
Book.objects.all().update(price=F("price")+30)
filter()
等方法中逗號分隔開的多個關鍵字參數都是邏輯與(AND)
的關係。 若是咱們須要使用邏輯或(OR)
來鏈接多個條件,就用到了Django
的Q
對象
能夠將條件傳給類Q
來實例化出一個對象,Q
的對象可使用&
和|
操做符組合起來,&
等同於and
,|
等同於or
from django.db.models import Q Employee.objects.filter(Q(id__gt=5) | Q(name="Egon")) # 等同於sql:select * from app01_employee where id < 5 or name = 'Egon';
Q
查詢Q
對象可使用~
操做符取反,至關於NOT
from django.db.models import Q Employee.objects.filter(~Q(id__gt=5) | Q(name="Egon")) # 等同於sql:select * from app01_employee where not (id < 5) or name = 'Egon';
當咱們的過濾條件中既有or又有and,則須要混用Q對象與關鍵字參數,但Q
對象必須位於全部關鍵字參數的前面
from django.db.models import Q Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"),salary__lt=100) # 等同於sql:select * from app01_employee where (id < 5 or name = 'Egon') and salary < 100;
聚合查詢aggregate()
是把全部查詢出的記錄對象總體當作一個組,咱們能夠搭配聚合函數來對總體進行一個聚合操做
from django.db.models import Avg, Max, Sum, Min, Max, Count # 導入聚合函數 # 1. 調用objects下的aggregate()方法,會把表中全部記錄對象總體當作一組進行聚合 res1=Employee.objects.aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee; print(res1) # 輸出:{'salary__avg': 70.73} # 二、aggregate()會把QuerySet對象中包含的全部記錄對象當成一組進行聚合 res2=Employee.objects.all().aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee; print(res2) # 輸出:{'salary__avg': 70.73} res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee where id > 3; print(res3) # 輸出:{'salary__avg': 71.0}
aggregate()
的返回值爲字典類型,字典的key
是由聚合字段的名稱___聚合函數的名稱
合成的,例如
Avg("salary") 合成的名字爲 'salary__avg'
若咱們想定製字典的key名,咱們能夠指定關鍵參數,以下
res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) # select avg(salary) as avg_sal from app01_employee; print(res1) # 輸出:{'avg_sal': 70.73} # 關鍵字參數名就會被當作字典的key
若是咱們想獲得多個聚合結果,那就須要爲aggregate傳入多個參數
res1=Employee.objects.all().aggregate(nums=Count('id'),avg_sal=Avg('salary'),max_sal=Max('salary')) # 至關於SQL:select count(id) as nums,avg(salary) as avg_sal,max(salary) as max_sal from app01_employee; print(res1) # 輸出:{'nums': 10, 'avg_sal': 70.73, 'max_sal': Decimal('200.3')}
分組查詢annotate()
至關於sql
語句中的group by
,是在分組後,對每一個組進行單獨的聚合,須要強調的是,在進行單表查詢時,annotate()
必須搭配values()
使用:values
(「分組字段」).annotate
(聚合函數),以下
# 表中記錄 mysql> select * from app01_employee; +----+-------+--------+------------+------------+--------+ | id | name | gender | birth | department | salary | +----+-------+--------+------------+------------+--------+ | 1 | Egon | 0 | 1997-01-27 | 財務部 | 100.1 | | 2 | Kevin | 1 | 1998-02-27 | 技術部 | 10.1 | | 3 | Lili | 0 | 1990-02-27 | 運營部 | 20.1 | | 4 | Tom | 1 | 1991-02-27 | 運營部 | 30.1 | | 5 | Jack | 1 | 1992-02-27 | 技術部 | 11.2 | | 6 | Robin | 1 | 1988-02-27 | 技術部 | 200.3 | | 7 | Rose | 0 | 1989-02-27 | 財務部 | 35.1 | +----+-------+--------+------------+------------+--------+ # 查詢每一個部門下的員工數 res=Employee.objects.values('department').annotate(num=Count('id')) # 至關於sql: # select department,count(id) as num from app01_employee group by department; print(res) # 輸出:<QuerySet [{'department': '財務部', 'num': 2}, {'department': '技術部', 'num': 3}, {'department': '運營部', 'num': 2}]>
跟在annotate
前的values
方法,是用來指定分組字段,即group by
後的字段,而跟在annotate
後的values
方法,則是用來指定分組後要查詢的字段,即select
後跟的字段
res=Employee.objects.values('department').annotate(num=Count('id')).values('num') # 至關於sql: # select count(id) as num from app01_employee group by department; print(res) # 輸出:<QuerySet [{'num': 2}, {'num': 3}, {'num': 2}]>
跟在annotate
前的filter
方法表示where
條件,跟在annotate
後的filter
方法表示having
條件,以下
# 查詢男員工數超過2人的部門名 res=Employee.objects.filter(gender=1).values('department').annotate(male_count=Count("id")).filter(male_count__gt=2).values('department') print(res) # 輸出:<QuerySet [{'department': '技術部'}]> # 解析: # 一、跟在annotate前的filter(gender=1) 至關於 where gender = 1,先過濾出全部男員工信息 # 二、values('department').annotate(male_count=Count("id")) 至關於group by department,對過濾出的男員工按照部門分組,而後聚合出每一個部門內的男員工數賦值給字段male_count # 三、跟在annotate後的filter(male_count__gt=2) 至關於 having male_count > 2,會過濾出男員工數超過2人的部門 # 四、最後的values('department')表明從最終的結果中只取部門名
總結
一、values()在annotate()前表示group by的字段,在後表示取值 一、filter()在annotate()前表示where條件,在後表示having
須要注意的是,若是咱們在annotate前沒有指定values(),那默認用表中的id字段做爲分組依據,而id各不相同,如此分組是沒有意義的,以下
res=Employee.objects.annotate(Count('name')) # 每條記錄都是一個分組 res=Employee.objects.all().annotate(Count('name')) # 同上
能夠修改記錄對象屬性的值,而後執行save
方法從而完成對單條記錄的直接修改
# 一、獲取記錄對象 obj=Employee.objects.filter(name='Egon')[0] # 二、修改記錄對象屬性的值 obj.name='EGON' obj.gender=1 # 三、從新保存 obj.save()
QuerySet
中的全部記錄對象QuerySet
對象下的update()
方法能夠更QuerySet
中包含的全部對象,該方法會返回一個整型數值,表示受影響的記錄條數(至關於sql
語句執行結果的rows
)
queryset_obj=Employee.objects.filter(id__gt=5) rows=queryset_obj.update(name='EGON',gender=1)
能夠直接調用記錄對象下的delete
方法,該方法運行時當即刪除本條記錄而不返回任何值,以下
obj=Employee.objects.first() obj.delete()
QuerySet
中的全部記錄對象每一個 QuerySet
下也都有一個 delete()
方法,它一次性刪除 QuerySet
中全部的對象(若是QuerySet
對象中只有一個記錄對象,那也就只刪一條),以下
queryset_obj=Employee.objects.filter(id__gt=5) rows=queryset_obj.delete()
須要強調的是管理objects下並無delete
方法,這是一種保護機制,是爲了不意外地調用 Employee.objects.delete()
方法致使全部的記錄被誤刪除從而跑路。但若是你確認要刪除全部的記錄,那麼你必須顯式地調用管理器下的all方法,拿到一個QuerySet
對象後才能調用delete方法刪除全部
Employee.objects.all().delete()