一、單表操做:php
Book id title price publish email addr 1 php 100 人民出版社 111 北京 2 python 200 沙河出版社 222 沙河 3 go 100 人民出版社 111 北京 4 java 300 人民出版社 111 北京
總結:重複內容過多,浪費大量存儲空間,資源浪費。html
二、表關係之一對多:java
Book id title price publish_id 1 php 100 1 2 python 200 1 3 go 100 2 4 java 300 1 Pulish id name email addr 1 人民出版社 111 北京 2 沙河出版社 222 沙河 一個出版社能夠對應多本書,可是一本書對應不了多個出版社。
總結:一旦肯定表關係是一對多時,在多對應的表中建立關聯字段。python
三、表關係之多對多:mysql
Book id title price publish_id 1 php 100 1 2 python 200 1 3 go 100 2 4 java 300 1 Author id name age addr 1 alex 34 beijing 2 egon 55 nanjing Book2Author id book_id author_id 1 2 1 2 2 2 3 3 2
總結:一旦肯定表關係是多對多,建立第三張關係表:id 和 另外兩個表的關聯字段。git
# alex出版過的書籍名稱(子查詢) select id from Author where name='alex'; select book_id from Book2Author where author_id=1; select title from Book where id = book_id;
四、表關係之一對一sql
Author id name age ad_id(UNIQUE) 1 alex 34 1 2 egon 55 2 AuthorDetail id addr gender tel gf_name author_id(UNIQUE) 1 beijing male 110 小花 1 2 nanjing male 911 槓娘 2
總結:一旦肯定是一對一關係,在兩張表中的任意一張表中創建關聯字段+ UNIQUE。數據庫
五、表關係之關聯字段和外鍵約束django
建立關聯字段和約束不是必然關係,可是不創建約束的話,從引擎的角度來講兩個表之間沒有任何關聯,所以刪除的時候,再查找時會找不到數據。python3.x
建立關聯字段是爲了進行查詢,創建約束是爲了防止出現髒數據。
六、表關係之sql建立關聯表
GREATE TABLE publish( id INT PRIMARY KEY auto_increment, name VARCHAR (20) ); GREATE TABLE book( id INT PRIMARY KEY auto_increment, title VARCHAR (20), price DECIMAL (8,2), pub_date DATE, publish_id INT, # 關聯字段 FOREIGN KEY (publish_id) REFERENCES publish(id) # 關聯字段約束 ); GREATE TABLE authordetail ( id INT PRIMARY KEY auto_increment, tel VARCHAR (20) ); GREATE TABLE author ( id INT PRIMARY KEY auto_increment, name VARCHAR (20), age INT, authordetail_id INT UNIQUE, # 一對一約束 FOREIGN KEY (authordetail_id) REFERENCES authordetail(id) ); GREATE TABLE book2author ( # 多對多 id INT PRIMARY KEY auto_increment, book_id INT, author_id INT, FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (author_id) REFERENCES author(id) );
實例:咱們來假定下面這些概念,字段和關係
做者模型:一個做者有姓名和年齡。
做者詳細模型:把做者的詳情放到詳情表,包含生日,手機號,家庭住址等信息。做者詳情模型和做者模型之間是一對一的關係(one-to-one)
出版商模型:出版商有名稱,所在城市以及email。
書籍模型: 書籍有書名和出版日期,一本書可能會有多個做者,一個做者也能夠寫多本書,因此做者和書籍的關係就是多對多的關聯關係(many-to-many);一本書只應該由一個出版商出版,因此出版商和書籍是一對多關聯關係(one-to-many)。
from django.db import models # 出版社表 class Publish(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) city=models.CharField( max_length=32) email=models.EmailField() # 做者詳情表 class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday=models.DateField() telephone=models.BigIntegerField() addr=models.CharField( max_length=64) class Author(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) age=models.IntegerField() # 與AuthorDetail創建一對一的關係,一對一的關係創建在任意一邊均可以 # to="AuthorDetail",加了引號以後是在全局中尋找不會由於位置關係找不到AuthorDetail authorDetail=models.OneToOneField(to="AuthorDetail", to_field="nid", on_delete=models.CASCADE) """ 上面語句表明含義爲下面sql語句 authordetail_id INT UNIQUE, # 一對一約束 FOREIGN KEY (authordetail_id) REFERENCES authordetail(id) """ class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) publishDate = models.DateField() price = models.DecimalField(max_digits=5, decimal_places=2) #與publish創建 一對多關係 ,外鍵字段創建在多的一方 publish=models.ForeignKey(to="Publish", to_field="nid",on_delete=models.CASCADE) """ 上面語句表明含義爲下面的sql語句 publish_id INT, # 關聯字段 FOREIGN KEY (publish_id) REFERENCES publish(id) # 關聯字段約束 """ # 多對多 與Author表創建多對多的關係,ManyToManyField能夠建在兩個模型中的任意一個,自動建立第三張表 authors = models.ManyToManyField(to="Author") """ 上面這個語句含義爲下面的sql語句 GREATE TABLE book2author ( id INT PRIMARY KEY auto_increment, book_id INT, author_id INT, FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (author_id) REFERENCES author(id) ); """ # 這種多對多寫法能夠由 authors = models.ManyToManyField(to="Author") 替代 # class Book2Author(models.Model): # nid = models.AutoField(primary_key=True) # book = models.ForeignKey(to="Book") # author = models.ForeignKey(to="Author")
ForeignKey:一對多與多對一
ManyToManyField:多對多
OneToOneField:一對一
ForeignKey.to_field:指定當前關係與被關聯對象中的哪一個字段關聯。默認狀況下,to_field 指向被關聯對象的主鍵。
ForeignKey.on_delete:當一個model對象的ForeignKey關聯的對象被刪除時,默認狀況下此對象也會一塊兒被級聯刪除的。
CASCADE:默認值,model對象會和ForeignKey關聯對象一塊兒被刪除。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01', ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME':'orm2', # 要鏈接的數據庫,鏈接前須要建立好 'USER':'root', # 鏈接數據庫的用戶名 'PASSWORD':'1234', # 鏈接數據庫的密碼 'HOST':'127.0.0.1', # 鏈接主機,默認本級 'PORT': 3306, # 端口 默認3306 } }
在Python3.x之後,因爲Python統一了數據庫鏈接的接口,開始使用pymysql,pymysql和MySQLdb 在使用方式上是相似的:
所以,python3.x連接mysql數據庫應安裝Python,使用pip安裝方法是:
pip install PyMySQL
在django項目中配置ORM2/__init__.py文件:
import pymysql pymysql.install_as_MySQLdb()
在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', }, } }
經過兩條數據庫遷移命令便可在指定的數據庫中建立表:
$ python3 manage.py makemigrations $ python3 manage.py migrate
生成以下表:
myapp_modelName
,是根據 模型中的元數據自動生成的,也能夠覆寫爲別的名稱id
字段是自動添加的(沒有設置id時會自動添加id字段)CREATE TABLE
SQL 語句使用PostgreSQL 語法格式,要注意的是Django 會根據settings 中指定的數據庫類型來使用相應的SQL 語句。models.py
所在應用的名稱。(見前面settings設置)每一個字段有一些特有的參數,例如,CharField須要max_length參數來指定VARCHAR數據庫字段的大小。
還有一些適用於全部字段的通用參數。這些參數在文檔中有詳細定義,經常使用參數以下所示:
(1)null 若是爲True,Django 將用NULL 來在數據庫中存儲空值。 默認值是 False. (1)blank 若是爲True,該字段容許不填。默認爲False。 要注意,這與 null 不一樣。null純粹是數據庫範疇的,而 blank 是數據驗證範疇的。 若是一個字段的blank=True,表單的驗證將容許該字段是空值。若是字段的blank=False,該字段就是必填的。 (2)default 字段的默認值。能夠是一個值或者可調用對象。若是可調用 ,每有新對象被建立它都會被調用。 (3)primary_key 若是爲True,那麼這個字段就是模型的主鍵。若是你沒有指定任何一個字段的primary_key=True, Django 就會自動添加一個IntegerField字段作爲主鍵,因此除非你想覆蓋默認的主鍵行爲, 不然不必設置任何一個字段的primary_key=True。 (4)unique 若是該值設置爲 True, 這個數據字段的值在整張表中必須是惟一的 (5)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', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES) >>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'
更多詳見:字段選項(Field options)
操做前先簡單錄入一些數據:
author表:
authordetail表:
# 方式1 publish_obj=Publish(name="人民出版社",city="北京",email="renMin@163.com") publish_obj.save() # 將數據保存到數據庫 # 方式2 返回值publish_obj是添加的記錄對象 publish_obj=Publish.objects.create(name="人民出版社",city="北京",email="renMin@163.com") # 方式3 表.objects.create(**request.POST.dict())
# 方式1: # 爲book表綁定出版社:一對多綁定關係 book publish book_obj = Book.objects.create(title="西遊記", price=100, publishDate="2012-12-1", publish_id=1) print(book_obj.title) # 返回值是添加的書籍對象屬性 # 方式2: pub_obj = Publish.objects.filter(nid=1).first() book_obj = Book.objects.create(title="紅樓夢", price=100, publishDate="2012-12-1", publish=pub_obj) print(book_obj.title) # 紅樓夢 print(book_obj.price) # 100 print(book_obj.publishDate) # 2012-12-1 print(book_obj.publish) # 與這本書關聯的出版社對象:Publish object (1),設置__str__後,打印 人民出版社 # 查詢西遊記出版社對應的郵箱 book_obj = Book.objects.filter(title="西遊記").first() print(book_obj.publish.email) # 123@qq.com
book表:
核心:book_obj.publish與book_obj.publish_id是什麼?
book_obj.publish是與這本書關聯的出版社對象,book_obj.publish_id是出版社id號。
# 書籍和做者 多對多關係 # 當前生成的書籍對象 book_obj = Book.objects.create(title="金瓶*梅", price=100, publishDate="2012-12-1", publish_id=1) # 添加普通字段:書籍綁定的做者對象 egon = Author.objects.get(name="egon", age=23, authorDetail_id=2) alex = Author.objects.get(name="alex", age=33, authorDetail_id=1) # 綁定多對多關係的API接口,即向關係表book_authors中添加記錄 book_obj.authors.add(egon, alex) # 將某個特定的 model 對象添加到被關聯對象集合中。 ======= book_obj.authors.add(*[]) # 另外一種寫法是寫入author的主鍵值 # book_obj.authors.add(1,2,3) # 另外一種寫法是傳入一個列表,*是函數傳列表的時候若是等效位置參數的時候須要加一個*號 # book_obj.authors.add(*[1,2,3]) #建立並保存一個新對象,而後將這個對象加被關聯對象的集合中,而後返回這個新對象。 book_obj.authors.create()
book表:
book_author表:
book = Book.objects.filter(nid=6).first() print(book.authors.all()) # <QuerySet [<Author: alex>]> 與這本書關聯的全部做者對象的集合
答案:與這本書關聯的全部做者對象的集合。queryset數據類型,列表裏面放着的都是這本書關聯做者對象。
# 查詢ID爲6的書籍的全部做者的名字 ret = book.authors.all().values("name") print(ret) # <QuerySet [{'name': 'alex'}]>
經過book.authors.all().values就能夠取到書籍對應做者的屬性。
將某個特定的對象從被關聯對象集合中去除
# 解除多對多關係 book = Book.objects.filter(nid=6).first() book.authors.remove(2) # 將book_authors中對應的6-2這條記錄刪除 # 相似的寫法還有: # book.authors.remove(*[1,2])
book_authors表:
清空被關聯對象集合
book_obj.authors.clear()
book_obj.authors.
set
()
"關聯管理器"是在一對多或者多對多的關聯上下文中使用的管理器。它存在於下面兩種狀況:
(1)ForeignKey關係的「另外一邊」。像這樣:
from django.db import models class Reporter(models.Model): # ... pass class Article(models.Model): reporter = models.ForeignKey(Reporter)
在上面的例子中,管理器reporter.article_set擁有下面的方法。
(2)ManyToManyField關係的兩邊:
class Topping(models.Model): # ... pass class Pizza(models.Model): toppings = models.ManyToManyField(Topping)
這個例子中,topping.pizza_set 和pizza.toppings都擁有下面的方法。
把指定的模型對象添加到關聯對象集中。 例如: >>> b = Blog.objects.get(id=1) >>> e = Entry.objects.get(id=234) >>> b.entry_set.add(e) # Associates Entry e with Blog b. 在上面的例子中,對於ForeignKey關係,e.save()由關聯管理器調用,執行更新操做。然而,在多對多關係中使用add()並不會調用任何 save()方法,而是由QuerySet.bulk_create()建立關係。 延伸: # 1 *[]的使用 >>> book_obj = Book.objects.get(id=1) >>> author_list = Author.objects.filter(id__gt=2) >>> book_obj.authors.add(*author_list) # 2 直接綁定主鍵 book_obj.authors.add(*[1,3]) # 將id=1和id=3的做者對象添加到這本書的做者集合中 # 應用: 添加或者編輯時,提交做者信息時能夠用到.
建立一個新的對象,保存對象,並將它添加到關聯對象集之中。返回新建立的對象: >>> b = Blog.objects.get(id=1) >>> e = b.entry_set.create( ... headline='Hello', ... body_text='Hi', ... pub_date=datetime.date(2005, 1, 1) ... ) # No need to call e.save() at this point -- it's already been saved. 這徹底等價於(不過更加簡潔於): >>> b = Blog.objects.get(id=1) >>> e = Entry( ... blog=b, ... headline='Hello', ... body_text='Hi', ... pub_date=datetime.date(2005, 1, 1) ... ) >>> e.save(force_insert=True) 要注意咱們並不須要指定模型中用於定義關係的關鍵詞參數。在上面的例子中,咱們並無傳入blog參數給create()。Django會明白新的 Entry對象blog 應該添加到b中。
從關聯對象集中移除執行的模型對象: >>> b = Blog.objects.get(id=1) >>> e = Entry.objects.get(id=234) >>> b.entry_set.remove(e) # Disassociates Entry e from Blog b. 對於ForeignKey對象,這個方法僅在null=True時存在。
從關聯對象集中移除一切對象。 >>> b = Blog.objects.get(id=1) >>> b.entry_set.clear() 注意這樣不會刪除對象 —— 只會刪除他們之間的關聯。 就像 remove() 方法同樣,clear()只能在 null=True的ForeignKey上被調用。
def change_book(request,edit_book_id): edit_book_obj = Book.objects.filter(pk=edit_book_id).first() if request.method=="POST": title = request.POST.get("title") price = request.POST.get("price") pub_date = request.POST.get("pub_date") publish_id = request.POST.get("publish_id") authors_id_list = request.POST.getlist("authors_id_list") # checkbox,select傳多個值的時候用getlist() # 更新爲修改的新內容 Book.objects.filter(pk=edit_book_id).update(title=title,price=price,publishDate=pub_date,publish_id=publish_id) # 更新書的做者(覆蓋原記錄) # 方法一: # edit_book_obj.authors.clear() # edit_book_obj.authors.add(*authors_id_list) # 方法二:set()方法先清空再設置 edit_book_obj.authors.set(authors_id_list) return redirect("/books/") publish_list = Publish.objects.all() author_list = Author.objects.all() return render(request, "editbook.html", {"edit_book_obj": edit_book_obj,"publish_list":publish_list, "author_list":author_list})
對於全部類型的關聯字段,add()、create()、remove()和clear(),set()都會立刻更新數據庫。換句話說,在關聯的任何一端,都不須要再調用save()方法。
直接賦值:
經過賦值一個新的可迭代的對象,關聯對象集能夠被總體替換掉。
>>> new_list = [obj1, obj2, obj3] >>> e.related_set = new_list
若是外鍵關係知足null=True,關聯管理器會在添加new_list中的內容以前,首先調用clear()方法來解除關聯集中一切已存在對象的關聯。不然, new_list中的對象會在已存在的關聯的基礎上被添加。
子查詢概念:子查詢是將一個查詢語句嵌套在另外一個查詢語句中。
# 一對多查詢的正向查詢:查詢金瓶*梅這本書的出版社的名字 book_obj = Book.objects.filter(title="金瓶*梅").first() print(book_obj.publish) # 與這本書關聯出版社對象 print(book_obj.publish.name) # 等同於下列sql語句形式 # select publish_id from Book where title = "金瓶*梅"; # select name from Publish where id = (select publish_id from Book where title = "金瓶*梅");
正向查詢:按字段
# 一對多查詢的正向查詢:查詢金瓶*梅這本書的出版社的名字 book_obj = Book.objects.filter(title="金瓶*梅").first() print(book_obj.publish) # 與這本書關聯出版社對象 print(book_obj.publish.name)
反向查詢:按表名小寫_set.all()
# 一對多查詢的反向查詢:查詢人民出版社出版過的書籍名稱 publish_obj = Publish.objects.filter(name="人民出版社").first() # 出版社對象 ret = publish_obj.book_set.all() print(ret) # <QuerySet [<Book: 紅樓夢>, <Book: 西遊記>, <Book: 金瓶*梅>]>
總結:
(1)假如A、B兩個表有關係,關聯屬性在A表中。正向查詢——A去查B對象;反向查詢——B去查A對象。
(2)Book(關聯屬性:publish) ,用book去找關聯對象出版社Publish:book_obj.publish;
(3)用publish去找關聯對象書籍book:publish_obj.book_set.all() ,且數據類型是queryset。
正向查詢:按字段
# 一對一查詢的正向查詢:查詢alex的手機號 alex = Author.objects.filter(name="alex").first() print(alex.authordetail.telephone)
反向查詢:按表名小寫
# 一對一查詢的反向查詢:查詢手機號爲110的做者名字和年齡 ad = AuthorDetail.objects.filter(telephone="110").first() print(ad.author.name) print(ad.author.age)
正向查詢(按字段:authors):
# 金瓶眉全部做者的名字以及手機號 book_obj=Book.objects.filter(title="金瓶眉").first() authors=book_obj.authors.all() for author_obj in authors: print(author_obj.name,author_obj.authorDetail.telephone)
反向查詢(按表名:book_set):
# 查詢egon出過的全部書籍的名字 author_obj=Author.objects.get(name="egon") book_list=author_obj.book_set.all() #與egon做者相關的全部書籍 for book_obj in book_list: print(book_obj.title)
注意:
能夠經過在 ForeignKey() 和ManyToManyField的定義中設置 related_name 的值來覆寫 FOO_set 的名稱。例如,若是 Article model 中作一下更改:
publish = ForeignKey(Book, related_name='bookList')
那麼接下來就會如咱們看到這般:
# 查詢 人民出版社出版過的全部書籍 publish=Publish.objects.get(name="人民出版社") book_list=publish.bookList.all() # 與人民出版社關聯的全部書籍對象集合
Django 還提供了一種直觀而高效的方式在查詢(lookups)中表示關聯關係,它能自動確認 SQL JOIN 聯繫。要作跨關係查詢,就使用兩個下劃線來連接模型(model)間關聯字段的名稱,直到最終連接到你想要的model 爲止。
正向查詢按字段,反向查詢按表名小寫用來告訴ORM引擎join哪張表。
# 一對多查詢:查詢金瓶*梅這本書的出版社的名字 """ SELECT app01_publish.name FROM app01_book Inner join app01_publish ON app01_book.publish_id = app01_publish.nid WHERE app01_book.title='金瓶*梅'; """ # 方式一:正向查詢按字段 # ret = Book.objects.filter(title="金瓶*梅").values("publish__name") # 正向查詢按字段 # print(ret) # <QuerySet [{'publish__name': '人民出版社'}]> # 方式二:反向查詢按表名小寫 # SELECT app01_publish.name FROM app01_publish INNER JOIN app01_book....僅僅是順序不一樣 ret = Publish.objects.filter(book__title="金瓶*梅").values("name") # 反向查詢按表名小寫 # publish去找book所以是反向查詢,經過表名小寫的方式通知ORM引擎去join book表,而後使用book的屬性title print(ret)
總結:正向查詢按字段,反向查詢按表名小寫用來告訴ORM引擎join哪張表。
value等同於sql語句中的select,filter等同於sql語句中的where。
# 多對多查詢:查詢金瓶*梅這本書全部做者的名字 """ SELECT app01_author.name FROM app01_book INNER JOIN app01_book_authors ON app01_book.nid = app01_book_authors.book_id INNER JOIN app01_author ON app01_book_authors.author_id = app01_author.nid WHERE app01_book.title="金瓶*梅"; """ # 方式一: # 需求:經過Book表join與其關聯的Author表,屬於正向查詢:按字段Book模型中字段authors = models.ManyToManyField(to="Author"),來通知ORM引擎join book_authors 和 author ret = Book.objects.filter(title="金瓶*梅").values("authors__name") print(ret) # <QuerySet [{'authors__name': 'alex'}]> # 方式二: # 需求:經過Author表join與其關聯的Book表,屬於反向查詢:按表名小寫book通知ORM引擎join book_authors與book表 ret = Author.objects.filter(book__title="金瓶*梅").values("name") print(ret) # <QuerySet [{'name': 'alex'}]>
# 一對一查詢:查詢alex的手機號 # 方式一: # 需求:經過Author表join與其關聯的AuthorDetail表,屬於正向查詢:按Author表中字段authorDetail通知ORM引擎join Authordetail表 ret = Author.objects.filter(name="alex").values("authorDetail__telephone") print(ret) # <QuerySet [{'authorDetail__telephone': 110}]> # 方式二: # 需求:經過AuthorDetail表join與其關聯的Author表,屬於反向查詢:按表名小寫author通知ORM引擎join Author表 ret = AuthorDetail.objects.filter(author__name="alex").values("telephone") print(ret) # <QuerySet [{'telephone': 110}]>
# 練習:手機號以110開頭的做者出版過得全部書籍名稱及書籍出版社名稱 # 方式一: # 需求:經過Book表join AuthorDetail表,Book與AuthorDetail無關聯,所以必須連續跨表 # book,book_authors,author,authordetail,publish五張表join ret = Book.objects.filter(authors__authorDetail__telephone__startswith="110").values("title", "publish") print(ret) # <QuerySet [{'title': '金瓶*梅', 'publish': 1}]> # 方式二: # 需求:以做者爲基表,Author表join與其關聯的authorDetail,屬於正向查詢,找到authorDetail下telephone屬性,查看110開頭的電話的做者。 ret = Author.objects.filter(authorDetail__telephone__startwith="110").values("book__title", "book__publish__name") print(ret)
反向查詢時,若是定義了related_name ,則用related_name替換表名,例如:
publish = ForeignKey(Blog, related_name='bookList')
練習: 查詢人民出版社出版過的全部書籍的名字與價格(一對多)
# 反向查詢 再也不按表名:book,而是related_name:bookList queryResult=Publish.objects .filter(name="人民出版社") .values_list("bookList__title","bookList__price")
先了解sql中的聚合與分組概念。
# 查詢全部書籍的平均價格 from django.db.models import Avg,Max,Min,Count ret = Book.objects.all().aggregate(Avg("price")) print(ret) # {'price__avg': 100.0} 返回值是一個字典,鍵自動由字段和聚合函數拼接組成
aggregate()是QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。聚合 aggregate:返回值是一個字典,再也不是一個queryset。
鍵的名稱是聚合值的標識符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。
若是你想要爲聚合值指定一個名稱,能夠向聚合子句提供它:
# 自定義鍵: ret = Book.objects.all().aggregate(avg_price=Avg("price")) print(ret) # {'avg_price': 100.0}
若是你但願生成不止一個聚合,你能夠向aggregate()子句中添加另外一個參數。因此,若是你也想知道全部圖書價格的最大值和最小值,能夠這樣查詢:
ret = Book.objects.all().aggregate(avg_price=Avg("price"),max_price=Max("price")) print(ret) # {'avg_price': 100.0, 'max_price': Decimal('100.00')}
爲調用的QuerySet中每個對象都生成一個獨立的統計值(統計方法用聚合函數)。
前置準備建立Emp表:
class Emp(models.Model): name=models.CharField(max_length=32) age=models.IntegerField() salary=models.DecimalField(max_digits=8,decimal_places=2) dep=models.CharField(max_length=32) province=models.CharField(max_length=32)
添加數據以下:
單表分組查詢的ORM語法:單表模型.objects.values("group by的字段").annotate(聚合函數("統計字段"))
# --------------------------->分組查詢 annotate:返回值依然是queryset # 單表分組查詢 # 示例一:查詢每一個部門的名稱及員工平均薪水 # select dep,Avg(salary) from emp group by dep; ret = Emp.objects.values("dep").annotate(avg_salary=Avg('salary')) print(ret) # <QuerySet [{'dep': '教學部', 'avg_salary': 51000.0}, {'dep': '保安部', 'avg_salary': 5000.0}]> # 單表分組查詢的ORM語法:單表模型.objects.values("group by的字段").annotate(聚合函數("統計字段")) # 實例二:查詢每一個省份的名稱和員工數 ret = Emp.objects.values("province").annotate(c=Count("id")) print(ret) # <QuerySet [{'province': '山東省', 'c': 2}, {'province': '河北省', 'c': 1}]>
注意:在單表分組下,按照主鍵進行group by 是沒有任何意義的。
# annotate是按照前面顯示的字段group by。 ret = Emp.objects.all() # select * from emp; print(ret) ret = Emp.objects.values("name") # select name from emp; print(ret) # 下面表明的是對全部字段進行group by Emp.objects.all().annotate(avg_salary=Avg("salary"))
多表分組查詢示例:
Book表 id title date price publish_id 1 紅樓夢 2012-12-12 101 1 2 西遊記 2012-12-12 101 1 3 三國演繹 2012-12-12 101 1 4 金瓶*梅 2012-12-12 301 2 Publish表 id name addr email 1 人民出版社 北京 123@qq.com 2 南京出版社 南京 345@163.com 1 查詢每個出版社出版的書籍個數 Book.objects.values("publish_id").annotate(Count("id")) # 單表查詢便可完成 2 示例 查詢每個出版社的名稱以及出版的書籍個數 join sql : select * from Book inner join Publish on book.publish_id=publish.id 兩張表合併爲一張大表: id title date price publish_id publish.id publish.name publish.addr publish.email 1 紅樓夢 2012-12-12 101 1 1 人民出版社 北京 123@qq.com 2 西遊記 2012-12-12 101 1 1 人民出版社 北京 123@qq.com 3 三國演繹 2012-12-12 101 1 1 人民出版社 北京 123@qq.com 4 金瓶*梅 2012-12-12 301 2 2 南京出版社 南京 345@163.com 分組查詢sql: select publish.name,Count("title") from Book inner join Publish on book.publish_id=publish.id
group by publish.id;
在單表分組下,按照主鍵進行group by 是沒有任何意義的;可是在多表下對主鍵進行group by是有意義的。
# 查詢每個出版社的名稱以及出版的書籍個數 ret = Publish.objects.values("nid").annotate(c=Count("book__title")) # 與單表分組查詢的區別就是統計的字段跨表了 print(ret) # <QuerySet [{'nid': 1, 'c': 2}, {'nid': 2, 'c': 1}, {'nid': 3, 'c': 0}]> # 方式一:因爲nid不是符合需求,改用name分組: ret = Publish.objects.values("name").annotate(c=Count("book__title")) # 與單表分組查詢的區別就是統計的字段跨表了 print(ret) # <QuerySet [{'name': '人民出版社', 'c': 2}, {'name': '蘋果出版社', 'c': 1}, {'name': '橘子出版社', 'c': 0}]> # 方式二:用nid分組,用values去取,也能查詢到名稱信息 ret = Publish.objects.values("nid").annotate(c=Count("book__title")).values("name","c") print(ret) # <QuerySet [{'name': '人民出版社', 'c': 2}, {'name': '蘋果出版社', 'c': 1}, {'name': '橘子出版社', 'c': 0}]>
用一個新例子幫助理解:
# 查詢每個做者的名字以及出版過得書籍的最高價格 """ SELECT app01_author.name,Max(app01_book.price) FROM app01_book INNER JOIN app01_book_authors ON app01_book.nid = app01_book_authors.book_id INNER JOIN app01_author ON app01_author.nid=app01_book_authors.author_id GROUP BY app01_author.nid; """ # pk能夠指代id或nid,join book表反向查詢 ret = Author.objects.values("pk").annotate(max_price=Max("book__price")).values("name","max_price") print(ret) # <QuerySet [{'name': 'alex', 'max_price': Decimal('100.00')}, {'name': 'egon', 'max_price': None}]>
另外一個示例:
# 示例:查詢查詢每個書籍的名稱以及對應的做者個數 """ SELECT app01_book.title,COUNT(app01_author.name) FROM app01_book INNER JOIN app01_book_authors ON app01_book.nid = app01_book_authors.book_id INNER JOIN app01_author ON app01_author.nid=app01_book_authors.author_id GROUP BY app01_book.nid; """ ret=Book.objects.values("pk").annotate(c=Count("authors__name")).values("title","c") print(ret) # <QuerySet [{'title': '金瓶*梅', 'c': 1}, {'title': '紅樓夢', 'c': 0}, {'title': '西遊記', 'c': 0}]>
跨表分組查詢另外一種寫法:
# 每個後的表模型.objects.annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段") # 示例:查詢每個出版社的名稱及出版社書籍個數 ret = Publish.objects.all().annotate(c=Count("book__title")).values("name","email","c") # 省略all()的簡略寫法 ret = Publish.objects.annotate(c=Count("book__title")).values("name", "email", "c") print(ret) # <QuerySet [{'name': '人民出版社', 'email': '123@qq.com', 'c': 2}, {'name': '蘋果出版社', 'email': 'yuan@163.com', 'c': 1}, {'name': '橘子出版社', 'email': 'egon@qq.com', 'c': 0}]>
總結跨表的分組查詢的模型:
每個後的表模型.objects.values("pk").annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段") 每個後的表模型.objects.annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段")
# 練習1:統計每個出版社的最便宜的書 # 模板:每個後的表模型.objects.annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段") publishList = Publish.objects.annotate(MinPrice=Min("book__price")) for publish_obj in publishList: print(publish_obj.name,publish_obj.MinPrice) """ 人民出版社 100.00 蘋果出版社 100.00 橘子出版社 None """ # annotate的返回值是querySet,若是不想遍歷對象,能夠用上valuelist: queryResult = Publish.objects.annotate(MinPrice=Min("book__price")).values_list("name", "MinPrice") print(queryResult) # <QuerySet [('人民出版社', Decimal('100.00')), ('蘋果出版社', Decimal('100.00')), ('橘子出版社', None)]> # 練習3:統計每一本以py開頭的書籍的做者個數 # 每個後的表模型.objects.values("pk").annotate(聚合函數(關聯表__統計字段)).values("表模型的全部字段以及統計字段") ret = Book.objects.filter(title__startswith="py").values("pk").annotate(c=Count("authors__name")).values("title","c") print(ret) # <QuerySet []> # 練習4:統計不止一個做者的圖書 gt:大於 ret = Book.objects.values("pk").annotate(c=Count("authors__name")).filter(c__gt=1).values("title", "c") print(ret) # <QuerySet []> # 練習5:根據一本圖書做者數量的多少對查詢集queryset進行排序 Book.objects.annotate(num_authors=Count("authors")).order_by("num_authors") # 練習6:查詢各個做者出的書的總價格 # 按author表的全部字段group by from django.db.models import Sum queryResult = Author.objects.annotate(sum_price=Sum("book__price")).values_list("name", "sum_price") print(queryResult) # <QuerySet [('alex', Decimal('100.00')), ('egon', None)]>
Django 提供 F() 來對兩個字段的值作比較比較。F() 的實例能夠在查詢中引用字段,來比較同一個 model 實例中兩個不一樣字段的值。
修改models.py內的book類,添加評論數和已讀數:
class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) publishDate = models.DateField() price = models.DecimalField(max_digits=5, decimal_places=2) # 臨時更改表結構: # read_num = models.IntegerField() # comment_num = models.IntegerField() """ 因爲臨時更改沒有添加默認值,會提示以下報錯: Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py """ read_num = models.IntegerField(default=0) comment_num = models.IntegerField(default=0)
而後在book表內添加修改評論數和已讀數:
from django.db.models import F # 臨時更改表結構後,須要查看書籍評論次數大於已讀次數 ret = Book.objects.filter(comment_num__gt=F("read_num")) print(ret) # <QuerySet [<Book: 紅樓夢>, <Book: 金瓶*梅>]>
Django 還支持 F() 對象之間以及 F() 對象和常數之間的加減乘除和取模的操做。
# 查詢已讀數大於評論數2倍的書籍 ret = Book.objects.filter(read_num__gt=F("comment_num")*2) print(ret) # <QuerySet [<Book: 西遊記>]>
修改操做也可使用F函數,好比將每一本書的價格提升10元:
# 全部書籍的價格提高10元 Book.objects.all().update(price=F("price")+10)
filter() 等方法中的關鍵字參數查詢都是一塊兒進行「AND」 的。 若是須要執行更復雜的查詢(例如OR 語句)可使用Q對象。
from django.db.models import Q # 書籍的名稱爲紅樓夢或者書的價格爲110 Q表示或 ret = Book.objects.filter(Q(title="紅樓夢")|Q(price=110)) print(ret) # <QuerySet [<Book: 紅樓夢>, <Book: 西遊記>, <Book: 金瓶*梅>]>
Q 對象可使用& 和| 操做符組合起來。當一個操做符在兩個Q 對象上使用時,它產生一個新的Q 對象。等同於下面的語句:
WHERE title ="紅樓夢" OR price > 110
能夠組合& 和| 操做符以及使用括號進行分組來編寫任意複雜的Q 對象。
同時,Q 對象可使用~ 操做符取反,這容許組合正常的查詢和取反(NOT) 查詢:
# 書籍名稱不爲紅樓夢或者書的價格爲110 ~表示非 ret=Book.objects.filter(~Q(title="紅樓夢")|Q(price=110)) print(ret) # <QuerySet [<Book: 紅樓夢>, <Book: 西遊記>, <Book: 金瓶*梅>]>
查詢函數能夠混合使用Q對象和關鍵字參數。全部提供給查詢函數的參數(關鍵字參數或Q 對象)都將"AND」在一塊兒。可是,若是出現Q 對象,它必須位於全部關鍵字參數的前面。例如:
# 在2012年或2013年出版的名稱包含"紅" bookList=Book.objects.filter(Q(publishDate__year=2012) | Q(publishDate__year=2014),title__icontains="紅") print(bookList) # <QuerySet [<Book: 紅樓夢>]>
還有一種特別的Q對象用法,查詢條件能夠不用是字段名稱,可使用字符串來完成查詢。
def test(request): from django.db.models import Q # Q查詢普通寫法: ret = Book.objects.all().filter(Q(title="go")|Q(price=103)) print("ret", ret) # ret <QuerySet [<Book: go>]> # Q查詢特殊用法: q = Q() q.connectiion = "or" q.children.append(("title", "go")) q.children.append(("price", 103)) print("q", q) # q (AND: ('title', 'yuan'), ('price', 123)) return HttpResponse(ret, q) # q (AND: ('title', 'go'), ('price', 103))