ORM 眾所周知有很多不同的資料庫系統,並且其中的大部分系統都包含Python介面,能夠讓我們更好的利用它們的功能,而這些系統唯一的缺點就是需要你瞭解SQL,如果你是一個更願意操縱Python對象,而不是SQL查詢的程式員,並且仍然希望使用關係資料庫作為你的數據後端,那麼我們可以使用ORM。 這些O ...
ORM
眾所周知有很多不同的資料庫系統,並且其中的大部分系統都包含Python介面,能夠讓我們更好的利用它們的功能,而這些系統唯一的缺點就是需要你瞭解SQL,如果你是一個更願意操縱Python對象,而不是SQL查詢的程式員,並且仍然希望使用關係資料庫作為你的數據後端,那麼我們可以使用ORM。
這些ORM系統的作者將純SQL語句進行了抽象化處理,將其實現為Python中的對象,這樣我們只操作對象就能完成與生成SQL語句相同的任務。就是用面向對象的方式去操作資料庫的創建表以及增刪改查等操作。
ORM優點:
1 ORM使得我們的通用資料庫交互變得簡單易行,而且完全不用考慮該死的SQL語句。快速開發,由此而來。
2 可以避免一些新手程式猿寫sql語句帶來的性能問題。
ORM缺點:
1 性能有所犧牲,不過現在的各種ORM框架都在嘗試各種方法,比如緩存,延遲載入登來減輕這個問題。效果很顯著。
2 對於個別複雜查詢,ORM仍然力不從心,為瞭解決這個問題,ORM一般也支持寫raw sql。
3 通過QuerySet的query屬性查詢對應操作的sql語句
Model
下麵要開始學習Django ORM語法了
單表的操作(增刪改查)
--------------------增
from blog.models import * # 首先導入應用里的models.py里的所有class(資料庫里的各個表) def base(request): # 增: # 方法一(推薦) # 固定結構: 類名(表名).objectes.create(**{"欄位名1":"插入的欄位內容","欄位名2":"插入的欄位內容"}) Author.objects.create(**{"name": "liu"}) # 方法二 # 固定結構: 類名(表名).objectes.create(欄位名1="插入的欄位內容",欄位名2="插入的欄位內容") # 註意:欄位名不加引號 Author.objects.create(name="liu2") # 方法三 author = Author(name="liu3") # 實例化類(要操作的表)的對象並直接賦值 author.save() # 保存表的內容 # 方法四 author = Author() # 實例化類(要操作的表)的對象 author.name = "liu4" # 逐個給對象的屬性賦值 author.save() # 最後保存
--------------------查
# User2是blog.models中的一個類(表) # 方法一 filter # 固定結構: 類名(表名).objectes.filter(判斷條件) user2_list = User2.objects.filter(name = "liu",sex="男") # filter括弧內添加查詢條件,多個條件用逗號隔開User2.objects.filter(name = "liu2",sex = "男") # 將所有滿足條件的對象集合成QuerySet對象返回:<QuerySet [<User2: User2 object>, <User2: User2 object>]> # 即使只有一個對象滿足條件也會返回QuerySet對象<QuerySet [<User2: User2 object>]> # 可以將QuerySet對象理解成一個list 可以通過索引獲取單個對象:user2_list[0] # 當沒有滿足條件的對象則會返回一個空的QuerySet對象:<QuerySet []> # 獲取到單個對象後可以通過 .欄位名 獲取該欄位對應的內容 user2_list[0].name # 方法二 get # 固定結構: 類名(表名).objectes.get(判斷條件) user2 = User2.objects.get(name = "liu2") # 只能獲取到單個滿足條件的對象,返回結果有且只有一個 # 如果符合篩選條件的對象超過一個,或者沒有都會拋出錯誤 # 直接通過 .欄位名 獲取該欄位對應的內容 # 方法三 all # 固定結構: 類名(表名).objectes.all() user2_list = User2.objects.all() # 將該表(User2)的所有數據集合成queryset對象返回 # 方法四 exclude # 固定結構: 類名(表名).objectes.exclude(判斷條件) user2_list = User2.objects.exclude(name = "liu2") # 與filter正好相反,返回的結果是所有不滿足括弧內條件的對象的集合,返回的是QuerySet對象 # -----------下麵的方法都是對查詢的結果進行處理再返回 # values("欄位名") User2.objects.filter(name = "liu2").values("sex") # QuerySet對象中只是想要獲取某個欄位,而不是全部的欄位,將該欄位放入 values("欄位名") # 獲取到QuerySet對象 列表包含字典的形式 < QuerySet[{'sex': '男'}, {'sex': '男'}]> # order_by("欄位名") # 對查詢結果按照 括弧內的欄位 從大到小排序,如果想要從小到大排序,則在引號內的欄位名前添加一個減號 User2.objects.filter(name="liu").order_by("-sex") # .reverse() User2.objects.filter(name="liu").reverse() # 對查詢結果進行倒序,可以配合order_by使用 # distinct() User2.objects.filter(name="liu").values("sex").distinct() # 剔除查詢結果中完全相同的數據 # count() User2.objects.filter(name="liu").count() # 返回查詢結果(QuerySet)中包含的對象數量 # first(): 返回查詢結果(QuerySet)中第一個對象 # last(): 返回查詢結果(QuerySet)中最後一個對象 # exists(): 如果QuerySet包含數據,就返回True,否則返回False。
--------------------刪
基於查的基礎上進行刪除,先查找到要刪除的數據,然後進行刪除
delete()
Author.objects.filter(name = "liu").delete()
刪除一條數據,那麼資料庫中所有與該條數據相關的數據都會被刪除,級聯刪除
--------------------改
基於查的基礎上進行修改,先查找到要修改的數據,然後進行修改
# update() 括弧內添加要修改的欄位及內容 sex = "aaa" 修改多個欄位用逗號分隔開 User2.objects.filter(name="liu2").update(sex = "女")
關聯表操作
實例:我們來假定下麵這些概念,欄位和關係
作者模型:一個作者有姓名。
作者詳細模型:把作者的詳情放到詳情表,包含性別,email地址和出生日期,作者詳情模型和作者模型之間是一對一的關係(one-to-one)(類似於每個人和他的身份證之間的關係),在大多數情況下我們沒有必要將他們拆分成兩張表,這裡只是引出一對一的概念。
出版商模型:出版商有名稱,地址,所在城市,省,國家和網站。
書籍模型:書籍有書名和出版日期,一本書可能會有多個作者,一個作者也可以寫多本書,所以作者和書籍的關係就是多對多的關聯關係(many-to-many),一本書只應該由一個出版商出版,所以出版商和書籍是一對多關聯關係(one-to-many),也被稱作外鍵。
書籍與作者:多對多關係,書籍與出版商:一對多關係,作者與出版商:無關係
創建表
from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30, verbose_name="名稱") address = models.CharField("地址", max_length=50) city = models.CharField('城市', max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() class Meta: verbose_name = '出版商' verbose_name_plural = verbose_name def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=30) def __str__(self): return self.name class AuthorDetail(models.Model): sex = models.BooleanField(max_length=1, choices=((0, '男'), (1, '女'),)) email = models.EmailField() address = models.CharField(max_length=50) birthday = models.DateField() author = models.OneToOneField(Author) # 一對一的關係 當表中存在兩個相同的author時會報錯 class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField("Author") # 創建多對多關係的第三章表 # models.ManyToManyField(Author) 括弧里放入那個與該表有“多對多”關係的表名 # 規定是一本書可以由多個作者共同完成,而一個作者又可以完成多本書,所以書與作者的關係是“多對多” # “多對多”的關係只有依靠第三章表才會完美體現兩個表中的關係,而ManyToManyField(Author)會自動幫我們創建第三張表 # 也可以在Author表裡寫 models.ManyToManyField(Book) 自動創建第三張表 # 外鍵中加引號則 外鍵相關聯的Publisher表不一定非要建在Book表之前,不加引號則必須建在該表之前 # 是根據反射找到的Publisher表 publishersss = models.ForeignKey("Publisher") # models.ForeignKey(Publisher) 括弧里放入你想建立主外鍵的另一個數據表的表名(對應的主鍵的表的名稱) # 規定是一本書只能由一家出版社出版,而一家出版社可以出版多本書,所以書與出版社之間的關係就是“多對一”的關係 # 多對一的關係中應該在“多”的那個表裡創建外鍵,所以這裡添加外鍵 # 我們寫的欄位是publisher 實際上django幫我們存入資料庫中時自動存儲成了publisher_id欄位, 這是models.ForeignKey()的特殊性 publication_date = models.DateField() price = models.DecimalField(max_digits=5, decimal_places=2, default=10) def __str__(self): return self.title class User2(models.Model): name = models.CharField(max_length=30) sex = models.CharField(max_length=30)
表中插入數據
from blog.models import * # 首先導入應用里的models.py里的所有class(資料庫里的各個表)
# Author表 Author.objects.create(**{ "name":"zhangsan" }) # AuthorDetail表 # 欄位:sex email address birthday author # 先獲取外鍵author所要綁定的Author對象author3 author3 = Author.objects.filter(name = "gaoer")[0] AuthorDetail.objects.create(**{ "sex":True, "email":"[email protected]", "address":"中國北京", "birthday":"1991-10-24", "author":author3 # 一對一關係中 author 賦值 Author對象author3 }) # Publisher表 # 欄位:name address city state_province country website Publisher.objects.create(**{ "name": "出版社002", "address": "地址002", "city": "城市002", "state_province": "省份002", "country": "國家002", "website": "網站002", }) # Book表 # 欄位:title authors publisher publication_date price # 先獲取要建立關係的Publisher對象 publisher2 = Publisher.objects.filter(id = 3)[0] Book.objects.create(**{ "title":"書籍005", "publishersss":publisher2, # 一對多關係中 直接在外鍵publishersss 賦值 publisher對象 "publication_date":"2016-08-09", "price":50 }) #多對多 建立關係 先獲取要綁定的Author對象 authors8 =Author.objects.filter(id = 5)[0] #先獲取要綁定的Book對象 book001 = Book.objects.filter(title="書籍005")[0] # Book對象.關係欄位.add(Author對象) book001.authors.add(authors8) # 由於我們是在Book表中與Author建立的ManyToManyField關係,所以是 Book對象.Book表中的關係欄位名.add(Author對象) # 如果是在Author表中與Book建立的ManyToManyField關係,那麼該是 Author對象.Author表中的關係欄位名.add(Book對象)
--------------------關聯表查詢
#----------------關聯查找之“一對多”的關係 # 雙下劃線可以理解為只是一個判斷條件,任何有關聯的表都可以查到 # 固定寫法:外鍵名__外鍵關聯表的欄位名 # Book.objects.filter(publishersss__name="出版社001")[0] # Publisher.objects.filter(book__title="書籍001") # 都是已知A表中的數據,查找與他相關的B表中的數據 # 當A表中的某行數據只能綁定B表中的一行數據時(一個Book只能對應一個Publisher) # 直接 .外鍵名稱 # book = Book.objects.filter(id = 1)[0] # publisher = book.publishersss # 由於是“一對多”的關係 所以publisher是一個對象 而不是QuerySet對象 # 當A表中的某行數據可以綁定多個B表中的數據(一個Publisher對應多個Book) # 需要用到 表名_set 固定寫法:表名_set # publisher =Publisher.objects.filter(id=1)[0] # book=publisher.book_set.all() # 獲取到QuerySit對象集合 # .book_set 跳轉到與Publisher對象關聯的book表 # Publisher.objects.filter(id=1)[0].book_set 相當於 Book.objects 只不過都是與id=1的publisher綁定的書的對象 # 之後可以根據查詢語法查詢了 .filter .all .get # ----------------關聯查找之“多對多”的關係 # 根據書的ID找到對應的作者 是個QuerySet對象,拿到第一個作者的作者信息.authordetail # author = Author.objects.filter(book__id=1)[0].authordetail # -----QuerySet對象.values("相關聯的表外鍵欄位名__相關聯的表的欄位名") # 取到與之相關聯對象的欄位,不加“__欄位名” 預設取到與之相關聯對象的主鍵 # Book.objects.filter(title='書籍001').values('publishersss__city')[0] # -----QuerySet對象.values("表名__外鍵欄位名") # A表和B表有關聯,B表和C表有關聯,A和C無關聯,已知A,查到與A有關聯的B再查到與B有關聯的C # A對象點.values('共同關聯B表名__B中關聯C的外鍵欄位名') 得到C表主鍵 # Author.objects.filter(id=1).values('book__publishersss')
--------------------聚合查詢:
通過對QuerySet進行計算,返回一個聚合值的字典。aggregate()中每一個參數都指定一個包含在字典中的返回值。即在查詢集上生成聚合。
首先導入模塊 from django.db.models import aggregates, Avg, Sum, Max, Min
# 從整個查詢集生成統計值。比如,你想要計算所有在售書的平均價錢。Django的查詢語法提供了一種方式描述所有圖書的集合。 # 得到字典形式結果 # 預設: a = Book.objects.all().aggregate(Avg("price")) print(a) # 執行結果{'price__avg': 60.0} # aggregate()子句的參數描述了我們想要計算的聚合值,在這個例子中,是Book模型中price欄位的平均值 # aggregate()是QuerySet的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的標識符,值是計算出來的聚合值。
# 鍵的名稱是按照欄位和聚合函數的名稱自動生成出來的。如果你想要為聚合值指定一個名稱,可以向聚合子句提供它: a = Book.objects.all().aggregate(pingjunjiage=Avg("price")) print(a) # 執行結果{'pingjunjiage': 60.0} # 如果你也想知道所有圖書價格的最大值和最小值,可以這樣查詢: a = Book.objects.aggregate(Avg('price'), Max('price'), Min('price'), Sum("price")) print(a) # 執行結果{'price__max': Decimal('80.00'), 'price__avg': 60.0, 'price__sum': Decimal('240.00'), 'price__min': Decimal('50.00')}
--------------------分組查詢:
可以通過計算查詢結果中每一個對象所關聯的對象集合,從而得出總計值(也可以是平均值或總和),即為查詢集的每一項生成聚合。
查詢alex出的書總價格
查詢各個作者出的書的總價格,這裡就涉及到分組了,分組條件是authors__name
查詢各個出版社最便宜的書價是多少
--------------------F查詢和Q查詢
僅僅靠單一的關鍵字參數查詢已經很難滿足查詢要求。此時Django為我們提供了F和Q查詢: # F 使用查詢條件的值,專門取對象中某列值的操作 # from django.db.models import F # models.Tb1.objects.update(num=F('num')+1) # Q 構建搜索條件 from django.db.models import Q # 1 Q對象(django.db.models.Q)可以對關鍵字參數進行封裝,從而更好地應用多個查詢 q1 = models.Book.objects.filter(Q(title__startswith='P')).all() print(q1) # [<Book: Python>, <Book: Perl>] # 2、可以組合使用&,|操作符,當一個操作符是用於兩個Q的對象,它產生一個新的Q對象。 Q(title__startswith='P') | Q(title__startswith='J') # 3、Q對象可以用~操作符放在前面表示否定,也可允許否定與不否定形式的組合 Q(title__startswith='P') | ~Q(pub_date__year=2005) # 4、應用範圍: # Each lookup function that takes keyword-arguments (e.g. filter(), # exclude(), get()) can also be passed one or more Q objects as # positional (not-named) arguments. If you provide multiple Q object # arguments to a lookup function, the arguments will be “AND”ed # together. For example: Book.objects.get( Q(title__startswith='P'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) ) # sql: # SELECT * from polls WHERE question LIKE 'P%' # AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06') # import datetime # e=datetime.date(2005,5,6) #2005-05-06 # 5、Q對象可以與關鍵字參數查詢一起使用,不過一定要把Q對象放在關鍵字參數查詢的前面。 # 正確: Book.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), title__startswith='P') # 錯誤: Book.objects.get( question__startswith='P', Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
QuerySet
QuerySet對象特點:
1. 可迭代的
2.可切片
#objs=models.Book.objects.all()#[obj1,obj2,ob3...] #QuerySet: 可迭代 # for obj in objs:#每一obj就是一個行對象 # print("obj:",obj) # QuerySet: 可切片 # print(objs[1]) # print(objs[1:4]) # print(objs[::-1])
QuerySet對象的高效使用:
<1>Django的queryset是惰性的 Django的queryset對應於資料庫的若幹記錄(row),通過可選的查詢來過濾。例如,下麵的代碼會得 到資料庫中名字為‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave") 上面的代碼並沒有運行任何的資料庫查詢。你可以使用person_set,給它加上一些過濾條件,或者將它傳給某個函數, 這些操作都不會發送給資料庫。這是對的,因為資料庫查詢是顯著影響web應用性能的因素之一。 <2>要真正從資料庫獲得數據,你可以遍歷queryset或者使用if queryset,總之你用到數據時就會執行sql. 為了驗證這些,需要在settings裡加入 LOGGING(驗證方式) obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) # if obj: # print("ok") <3>queryset是具有cache的 當你遍歷queryset時,所有匹配的記錄會從資料庫獲取,然後轉換成Django的model。這被稱為執行 (evaluation).這些model會保存在queryset內置的cache中,這樣如果你再次遍歷這個queryset, 你不需要重覆運行通用的查詢。 obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) ## models.Book.objects.filter(id=3).update(title="GO") ## obj_new=models.Book.objects.filter(id=3) # for i in obj: # print(i) #LOGGING只會列印一次 <4> 簡單的使用if語句進行判斷也會完全執行整個queryset並且把數據放入cache,雖然你並不需要這些 數據!為了避免這個,可以用exists()方法來檢查是否有數據: obj = Book.objects.filter(id=4) # exists()的檢查可以避免數據放入queryset的cache。 if obj.exists(): print("hello world!") <5>當queryset非常巨大時,cache會成為問題 處理成千上萬的記錄時,將它們一次裝入記憶體是很浪費的。更糟糕的是,巨大的queryset可能會鎖住系統 進程,讓你的程式瀕臨崩潰。要避免在遍曆數據的同時產生queryset cache,可以使用iterator()方法 來獲取數據,處理完數據就將其丟棄。 objs = Book.objects.all().iterator() # iterator()可以一次只從資料庫獲取少量數據,這樣可以節省記憶體 for obj in objs: print(obj.name) #BUT,再次遍歷沒有列印,因為迭代器已經在上一次遍歷(next)到最後一次了,沒得遍歷了 for obj in objs: print(obj.name) #當然,使用iterator()方法來防止生成cache,意味著遍歷同一個queryset時會重覆執行查詢。所以使 #用iterator()的時候要當心,確保你的代碼在操作一個大的queryset時沒有重覆執行查詢 總結: queryset的cache是用於減少程式對資料庫的查詢,在通常的使用下會保證只有在需要的時候才會查詢資料庫。 使用exists()和iterator()方法可以優化程式對記憶體的使用。不過,由於它們並不會生成queryset cache,可能 會造成額外的資料庫查詢。