Django ORM數據庫操作(下)
一、增加表記錄
對於表單有兩種方式:
# 方式一:實例化對象就是一條表記錄france_obj = models.Student(name="海地", course="Python", birth="2000-10-1", grade=90)
france_obj.save()
# 方式二:
models.Student.objects.create(name="海地", course="Python", birth="2000-10-1", grade=90)
二、查詢表記錄
查詢相關API:
# 查詢相關API
# 1.all():查詢所有
students = models.Student.objects.all()
print(students) # 打印結果是QuerySet集合
# 2.filter():可以實現且關系,但是或關系需要借助Q查詢實現,查不到時會報錯
print(models.Student.objects.filter(name="Frank") # 查詢名字是Frank的數據
print(models.Student.objects.filter(name="Frank", grade=90) # 查詢名字是Frank且分數是90的數據
# 3.get():如果找不到就會報錯,如果有多個值也會報錯,只能查詢有一個值的數據
print(models.Student.objects.get(name="Frank") # 查詢名字是Frank的model對象
print(models.Student.objects.get(nid=2) # 查詢Id為2的model對象
# 4.exclude():排除條件
print(models.Student.objects.exclude(name="海地") # 查詢除了名字是海地的數據
# 5.values():是QuerySet的一個方法(將對象轉換成字典形式)
print(models.Student.objects.filter(name="海地").values("nid", "course") # 查詢名字是海地的編號和課程
# 打印結果:<QuerySet[{"nid": 2, "course": "python"}, {"nid": 24, "course": "python"}]>
# 6.values_list():是QuerySet的一個方法(將對象轉換成元組形式)
print(models.Student.objects.filter(name="海地").values_list("nid", "course")
# 打印結果:<QuerySet[(2, "python"), (24, "python")]
# 7.order_by():排序
print(models.Student.objects.all().order_by("grade"))
# 8.reverse():倒序
print(models.Student.objects.all().reverse())
# 9.distinct():去重(只要結果里面有重復的)
print(models.Student.objects.filter(course="python").values("grade").distinct())
# 10.count():查看數據條數
print(models.Student.objects.filter(name="海地").count()
雙下划線值單表查詢
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 查詢id大於1且小於10的數據
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 查詢id等於11、22、33的數據
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # 查詢id不等於11、22、33的數據
models.Tb1.objects.filter(name__contains="ven") # 查詢名字包括ven的數據
models.Tb1.objects.filter(name__icontains="ven") # 查詢名字包括ven的數據,大小寫不敏感
models.Tb1.objects.filter(id__range=[1, 2]) # 查詢范圍between and
三、修改表記錄
# 方式一:
author = Author.objects.get(id=5)
author.name = "eric"
author.save()
# 方式二:
Publisher.objects.filter(id=2).update(name="America") # 不能用.get(id=2)
注意:
- 第二種方式修改不能用get的原因是:update是QuerySet對象的方法,get返回的是一個model對象,它沒有update方法,而filter返回的是QuerySet對象(filter里面的條件可能有多個條件符合,比如name="eric",可能有兩個name="eric"的數據);
- 模型的save()方法,會更新一行里的所有列,而某些情況下,我們只需要更新行里的某幾列;
此外,update()方法對於任何結果集(QuerySet)均有效,這意味着我們可以同時更新多條記錄,update()方法會返回一個整形數值,表示受影響的記錄條數;因為update返回的是一個整數,所以無法使用query屬性,對於每次創建一個對象,想顯示對應的原始SQL,需要在settings加上日志記錄部分。
四、刪除表記錄
刪除方法就是:delete(),它運行時立即刪除對象,返回被刪除對象數量和刪除對象與數量字典(返回為元組)
# 刪除數據
models.Student.objects.filter(nid="2").delete()
注意:無論在什么情況下,QuerySet中的delete()方法都只使用一條SQL語句一次性刪除所有對象,而並不是分別刪除每個對象。如果想使用在model中自定義的delete()方法,就要自行調用每個對象的delete()方法。(例如:遍歷QuerySet,在每個對象上調用delete()方法,而不是使用QuerySet中的delete()方法)
在Django刪除對象時,會模仿SQL約束ON DELETE CASCADE的行為,換句話說,刪除一個對象時也會刪除與它相關聯的外鍵對象,例如:
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
注意:delete()方法是QuerySet上的方法,但並不適用於Manager本身。這是一種保護機制,是為了避免意外地調用Entry.objects.delete()方法導致所有的記錄被去刪除,如果確認要刪除所有的對象,必須顯示地調用:
Entry.objects.all().delete()
五、模型關系及其字段
書籍模型:書籍有書名和出版日期,一本書可能會有多個作者,一個作者也可以寫多本書,所以作者和書籍的關系就是多對多的關聯關系(Many-to-Many),一本書只應該由一個出版商出版,所以出版商和書籍是一對多的關聯關系(One-to-Many);
- 創建一對一的關聯關系:OneToOne("要綁定關系的表名");
- 創建多對一的關聯關系:ForeignKey("要綁定關系的表名");
- 創建多對多的關聯關系:ManyToMany("要綁定關系的表名");
from django.db import models
class Book(models.Model):
nid = models.AutoField(primary_key=True) # 自增id(可以不寫,默認會有自增id)
title = models.CharField(max_length=32)
publish_date = models.DateField() # 出版日期
price = models.DecimalField(max_digits=5, decimal_places=2) # 一共5位數,保留2位小數
# 一個出版商有多本書,關聯字段要寫在多的一方
# 不用命名為publish_id,因為Django會自動為我們加上_id
publish = models.ForeignKey('Publish') # ForeignKey(表名):建立當前模型和遠程多對一的關聯關系
# publish是實例對象關聯的出版社對象
author_list = models.ManyToManyField("Author") # 建立多對多的關聯關系
def __str__(self): # __str__方法是用來把對象轉換成字符串的,返回什么內容就打印什么內容
return self.title
class Publish(models.Model):
# 不寫id的時候數據庫會自動創建自增id
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
class AuthorDetail(models.Model):
tel = models.IntegerField()
addr = models.CharField(max_length=32)
author = models.OneToOneField("Author") # 建立一對一的關聯關系
注意:臨時添加的字段,首先需要考慮之前的數據有沒有,可以設置個默認值。
word_number = models.IntegerField(default=0)
通過日志可以查看執行的SQL語句,在項目settings.py文件中添加如下代碼:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level': 'DEBUG',
},
}
}
注意事項:
- 數據庫表名appname_modelname,是根據模型中的元數據自動生成的,也可以復寫為別的名稱;
- id字段是自動添加的;
- 對於外鍵字段,Django會在字段名上添加"_id"來創建數據庫中的列名;
- Django會根據settings中指定的數據庫類型來使用相應的SQL語句;
- 定義好模型之后,需要告訴Django使用這些模型。通過修改settings文件中的INSTALLED_APPS設置,在其中添加models.py所在應用的名稱;
- 外鍵字段ForeignKey有一個null=True的設置(它允許外鍵接受空值NULL),可以賦給它空值None;
字段選項
每個字段(模型類的屬性)有一些特有的參數,例如:CharField需要max_length參數來指定VARCHAR數據庫字段的大小,還有一些適用於所有字段的通用參數。這些參數在官方文檔中有詳細的定義,這里我們只簡單介紹些最常用的:
# 1.null
null是針對數據庫而言,如果null=True,表示數據庫的該字段可以為空(空值將會被存儲為NULL)。Django用NULL來存儲空值,默認為False。日期型、時間型和數字型字段不接受空字符串,所以設置IntegerField、DateTimeField型字段可以為空時,需要將blank、null均設為True;如果想設置BooleanField為空時,可以選用NullBooleanField型字段;
# 2.blank
blank是針對表單的,如果blank=True,表示表單填寫時該字段可以不填,比如:admin界面下增加model一條記錄時。直觀的看到就是該字段不是粗體(blank=True,字段可以為空;blank=False,字段是必填;默認字段必填);
# 3.default
字段的默認值,可以是一個值或者可調用對象。如果是可調用對象,每當有新對象被創建時它都會被調用;
# 4.primary_key
如果為True,那么這個字段就是模型的主鍵。如果沒有指定任何一個字段的primary_key=True,Django就會自動添加一個IntegerField字段作為主鍵,所以除非我們想要覆蓋默認的主鍵行為,否則沒必要設置任何一個字段的primary_key=True;
# 5.unique
如果字段的該值設置為True,這個數據字段的值在整張表中必須是唯一的;
# 6.choices
由二元元組組成的一個可迭代對象(列表或元組),用來給字段提供選擇項。如果設置了choices,默認的表單將是一個選擇而不是標准的文本框,而且這個選擇框的選項就是choices中的選項,這是關於choices列表的例子:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每個元組中的第一個元素,是存儲在數據庫中的值;第二個元素是在管理界面或ModelChoiceField中用作顯示的內容。在一個給定的model類的實例中,想得到某個choices字段的顯示值,就調用get_FOO_display方法(這里的FOO就是choices字段的名稱)。例如:
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
("S", "Small"),
("M", "Middle"),
("L", "Large"),
)
name = models.CharField(max_length=32)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
person = Person(name="eric", shirt_seze="L")
person.save()
print(person.shirt_size)
print(persion.get_shirt_size_display())
一旦創建好數據模型之后,Django會自動生成一套數據庫抽象的API,我們可以執行關於表記錄的增刪改查操作。
六、添加表記錄
多對一添加紀錄:
# 方式一:如果是這樣直接指定publish_id字段去添加值,前提是主表里面必須有數據
# 主表:沒有被關聯的(因為book表是要依賴於publish這個表的),也就是publish表;
# 子表:關聯的表,也就是book表;
models.Book.objects.create(title="圍城", publish_date="2000-3-17", price="78.6", publish_id=1)
# 方式二:推薦使用該方式
publish_book = models.Publish.object.filter(name="人民出版社")[0]
print(publish_book)
models.Book.objects.create(title="紅樓夢", publish_date="1976-5-8", price="90", publish=publish_book)
# 方式三:save
pub_book = models.Publish.objects.get(name="人民出版社") # 只有一個的時候用get查詢,獲取得到的就是一個對象
book = models.Book(title="落花生", publish_date="1980-9-12", price="42", publish=pub_book)
book.save()
多對多添加紀錄:
書和作者是多對多的關系:一本書可以由多個作者,一個作者可以著作多本書;
步驟:首先,找到書對象;其次,再找到需要的作者對象;最后,給書對象綁定作者對象(用add方法),也就是綁定多對多的關聯關系;
# 方式一:
# 首先,創建一本書
publish = models.Publish.objects.filter("工業出版社").first()
book = models.Book.objects.create(title="璇璣", publish_date="2017-1-25", price="75.8", publish=publish)
# 其次,通過作者的名字Django默認找到id
elise = models.Author.objects.filter(name="elise")[0]
eric = models.Author.objects.filter(name="eric ")[0]
smith= models.Author.objects.filter(name="smith")[0]
# 最后,綁定多對多的關聯關系
book.author_list.add(elise, eric, smith)
# 方式二:查出所有的作者
pub_info = models.Publish.objects.filter(name"郵電出版社").first()
books = models.Book.objects.create(title="天之痕", publish_date="2016-7-6", price="80", publish=pub_info)
authors = models.Author.objects.all()
# 綁定多對多關聯關系
books.author_list.add(*authors)
解除綁定(remove):將某個特定的對象從被關聯對象集合中去除(books.authors.remove(*[]))
# 解除多對多的關聯關系
books = models.Books.objects.filter(title="天之痕").last() # 找到書對象
authors = models.Author.objects.filter(id__lt=3) # 找到符合條件的作者對象
books.author_list.remove(*authors) # 因為解除的是多條,所以需要使用*
清楚綁定(clear):清空被關聯對象集合
# 清空關系方法(clear)
books = models.Book.objects.filter(name="和平世界")
for book in books: # 把所有和平世界都清空
book.author_list.clear()
總結:
- remove:先將要清除的數據篩選出來,然后解除關聯關系;
- clear:不用查詢,直接就把數據清空;
七、基於對象的查詢(相當於SQL語句的where子查詢)
一對一查詢記錄:author和authordetail是一對一的關系
正向查詢:按字段author
反向查詢:按表名authordetail,因為是一對一的關系,就不用_set了;
# 正向查詢:手機號為911的作者的姓名
detail = models.AuthorDetail.objects.filter(tel="911").first()
print(detail.author.name)
# 反向查詢:查詢eric的手機號
eric = models.Author.objects.filter(name="eric").first()
print(eric.authordetail.tel)
一對多查詢記錄:
正向查詢:按字段publish
反向查詢:按表名book_set
# 正向查詢:查詢天之痕這本書的出版社的地址
book = models.Book.object.filter(name="天之痕")[0] # 找對象
print("*"*8, book.publish) # 查詢到的是關聯出版社的對象
print(book.publish.addr)
# 反向查詢:查詢人民出版社出版過的所有的書籍的價格和名字
publish = models.Publish.objects.filter(name="人民出版社")[0]
books = publish.book_set.all().values("price", "title")[0]
print(books, books["price"])
# 查詢人民出版社出版過的所有書籍
pub_books = models.Publish.objects.get(name="人民出版社") # get查詢到的是一個對象,不過get只能查看有一條記錄的
book_list = pub_books.book_set.all() # 與人民出版社關聯的所有數據對象集合
for book in book_list:
print(book.title)
# 這里使用for循環或values、values_list都是可以的
多對多查詢記錄
正向查詢:按字段author_list
反向查詢:按表名book_set
# 正向查詢:查詢追風箏的人這本書所有作者的姓名和年齡
book = models.Book.objects.filter(title="追風箏的人")[0]
print(book.author_list.all().values("name", "age")) # 這本書關聯的所有作者對象的集合
# 反向查詢:查詢作者是魯迅的這個人出版書的信息
luxun = models.Author.objects.filter(name="luxun")[0]
print("*"*10, luxun.book_set.all().first().title() # 與該作者關聯的所有書對象的集合
return HttpResponse("ok")
我們可以通過在ForeignKey()和ManyToManyField的定義中設置related_name的值來復寫FOO_set的名稱。例如:如果Article model中做一下更改:publish = ForeginKey(Blog, related_name="book_list"),那么接下來就會如我們看到這般:
# 查詢人民出版社出版過的所有書籍
publish = models.Publish.object.get(name="人民出版社")
books = publish.book_list.all() # 與人民出版社關聯的所有書籍對象集合
八、基於雙下划線的跨表查詢
Django還提供了一種直觀且搞笑的方式在查詢中標識關聯關系,它能自動確認SQL JOIN聯系。要做跨關系查詢,就使用兩個下划線來鏈接模型間關聯字段的名稱,直到最終鏈接到你想要的模型為止(相當於sql語句用join連接的方式,可以在settings中設置,以查看sql語句)。
多對一查詢:
practice 1.查詢人民出版社出版過的所有書籍的價格和名稱:
# 方法1
result = models.Publish.objects.filter(name="人民出版社").values("book__price", "book_title")
print(result)
# 方法2
tmp_res = models.Book.objects.filter(publish__name="人民出版社").values("price", "title")
print(tmp_res)
practice 2.查詢Linux這本書的出版社地址:filter先過濾,values顯示要求的字段
# 方法1
res_one = models.Book.objects.filter(title="Linux").values("publish__addr")
print(res_one)
# 方法2
res_two = models.Publish.objects.filter(book__title="Linux").values("addr")
多對多查詢:
practice 1.查詢eric出過的所有書的名字:
# 方法1
res = models.Author.objects.filter(name="eric").values("book__title")
print(res)
# 方法2
result = models.Book.objects.filter(author_list__name="eric").values("title")
print(result)
practice 2.查詢手機號以182開頭的作者出版過的所有書籍的名稱及出版社的名稱
# 方法1
authors = models.AuthorDetail.objects.filter(tel_startwith="182").first()
print(authors.author.book_set.all().values("title", "publish__name")
# 方法2
result = models.Book.objects.filter(author_list__author_detail__tel__startwith="182").values("title", "publish__name")
print(result)
九、聚合查詢與分組查詢
聚合查詢:aggregate(*args, **kwargs),只對一個組進行聚合
from django.db.models import Avg, Sum, Max, Min
# 查詢所有圖書的平均價格
print(models.Book.objects.all().aggregate(Avg("price")))
aggregate()是QuerySet的一個終止子句(也就是返回的不再是一個QuerySet集合),它返回的是一個字典,該字典的key是聚合值得標識符,value是計算出來的聚合值。key的名字是按照字段和聚合函數的名稱自動生成的,如果想要為聚合值指定名稱,可以向聚合子句提供它:
from django.db.models import Avg, Sum, Max, Min
# 查詢所有圖書的平均價格
print(models.Book.objects.all().aggregate(avgprice=Avg("price")))
分組查詢:annotate(),為QuerySet中每個對象都生成一個獨立的匯總值,是對分組完之后的結果進行聚合(返回QuerySet對象,可以繼續使用filter、order_by等)
practice 1.統計每本書的作者數量
# 方法1
authors = models.Book.objects.all().annotate(authorNum=Count("author_list__name)).values("authorNum")
print(authors)
# 方法2
books = models.Book.objects.all().annotate(authorNum=Count("author_list__name")
for book in books:
print(book.title, book.authorNum)
practice 2.統計每個出版社最便宜的書籍
# 方法1
print(models.Book.objects.values("publish__name").annotate(cheaper=Min("price"))) # values內的字段即group by的字段,也就是分組條件
# 方法2
print(models.Publish.objects.all().annotate(cheaper=Min("book__price")).values("name", "cheaper"))
# 方法3
publish_list = models.Publish.objects.annotate(cheaper=Min("book__price"))
for publish in publish_list:
print(publish.name, publish.cheaper)
practice 3.統計每本以py開頭書籍的作者數量:
print(models.Book.objects.filter(title__startwith="py").annotate(authNum=Count("author_list__name")).values("ahtuNum")
practice 4.統計不止一個作者的書籍:
print(models.Book.objects.annotate(num_auths=Count("author_list__name")).filter(num_auths__gt=1).values("title", "num_auths"))
practice 5.根據每本書籍數量的多少對查詢結果進行排序:
print(models.Book.objects.all().annotate(auth_nums=Count("author_list__name")).order_by("auth_nums"))
practice 6.查詢各個作者出的書籍的總價格:
# 方法1
print(models.Author.objects.all().annotate(price=Sum("book__price")).values("name", "price")
# 方法2
print(models.Book.objects.values("author_list__name").annotate(price=Sum("price")).values("author_list__name", "price"))
十、F查詢和Q查詢
F查詢:在上面所有的例子中,我們構造的過濾器都只是將字段值與某個常量做比較,如果我們要對兩個字段的值作比較,那么該怎么做呢?
Django提供F()來做這樣的比較,F()的實例可以在查詢中引用字段,來比較同一個模型實例中兩個不同字段的值:
practice 1.查看評論數大於閱讀數的書籍:
from django import F, Q
print(models.Book.objects.filter(comments__gt=F("readers"))
practice 2.操作修改,將id大於1的所有書籍價格上漲10元:
print(models.Book.objects.filter(nid__gt=1).update(price=F("price")+10)
practice 3.Django支持F()對象之間及F()對象和常數之間的加減乘除和取模的操作:
# 查詢評論數大於收藏數2倍的書籍
print(models.Book.objects.filter(comments__gt=F("collections")*2))
Q查詢:Q查詢可以組合使用"&","|"操作符,當一個操作符是用於兩個Q的對象,它產生一個新的Q對象,Q對象可以用"~"操作符放在前面標識否定,也可允許肯定與否定形式的組合。Q對象可以與關鍵字參數查詢一起使用,不過一定要把Q對象放在關鍵字參數查詢的前面。
# 因為獲取的結果是QuerySet,所以使用下標的方式獲取結果
print(models.Book.objects.filter(Q(id=3))[0])
# 查詢id=3或者標題是"Go"的書籍
print(models.Book.objects.filter(Q(id=3)|Q(title="Go"))[0])
# 查詢價格大於等於70並且標題是"J"開頭的書籍
print(models.Book.objects.filter(Q(price__gte=70)&Q(title__startwith="J")))
# 查詢標題是"J"開頭並且id不是3的書籍
print(models.Book.objects.filter(Q(title__startwith="J")&~Q(id=3)))
# Q對象可以與關鍵字參數查閱一起使用,必須把普通關鍵字查詢放到Q對象查詢的后面
print(models.Book.objects.filter(Q(price=70)|Q(title="python"), publish_date="2018-05-22"))
from django.db.models import Q
con = Q()
q1 = Q()
q1.connector = "AND"
q1.children.append(("email", "123@163.com"))
q1.children.append(("password", "abc123"))
q2 = Q()
q2.connector = "AND"
q2.children.append(("username", "abc"))
q2.children.append(("password", "xyz123"))
con.add(q1, "OR")
con.add(q2, "OR")
# 查詢email=123@163.com和password=abc123 或者 username=abc和password=xyz123的用戶信息
obj = models.UserInfo.objects.filter(con).first()
上面的例子是一個典型的復雜查詢,通過將Q對象實例化,然后增加各個條件之間的關系,而且這種寫法用在我們不知道用戶到底會傳入多少個參數的時候很方便。