Django-模型層(2)

多表操做

    口決:正向操做按字段,反向操做按表名小寫php

建立模型

  • 做者模型:姓名和年齡
  • 做者詳情模型:把做者詳性放到詳情表,包含生日,手機號,家庭住址等。做者詳情模型與做者模型爲一對一關係(one-to-one)
  • 出版商模型:出版商有名稱,所在城市及email
  • 書藉模型:書藉有書名和出版日期,書藉模型與做者模型爲多對多關係(many-to-many),出版商模型與書藉模型爲一對多關係(one-to-many)

    模型創建以下:python

from django.db import models

# Create your models here.

class Book(models.Model):
    title = models.CharField(max_length=32)
    pub_date = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to="Publish", to_field="id", 
on_delete=models.CASCADE, null=True)
    authors = models.ManyToManyField(to="Author", db_table="book2authors")

    def __str__(self):
        return self.title

    class Meta:
        db_table = "book"


class Publish(models.Model):
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.CharField(max_length=32)

    def __str__(self):
        return self.name

    class Meta:
        db_table = "publish"


class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    ad = models.OneToOneField("AuthorDetail", null=True, on_delete=models.CASCADE)

    def __str__(self):
        return self.name

    class Meta:
        db_table = "author"


class AuthorDetail(models.Model):
    birthday = models.DateField()
    telephone = models.BigIntegerField()
    addr = models.CharField(max_length=64)

    def __str__(self):
        return str(self.telephone)

    class Meta:
        db_table = "authorDetail"
View Code

    注意事項:linux

  • 對於外鍵字段,Django會在字段名上添加"_id"來建立數據庫的列名
  • 外鍵字段ForeignKey有一個null=True的設置(它容許外鍵接受空值NULL) , 你能夠賦給它空值None

一對多

    關聯字段在建立表時字段名爲字段_idgit

    先添加一的表:sql

publish = models.Publish.objects.create(name="蘋果出版社", city="深圳",email="1234@163.com")

    再添加多的表:數據庫

方式一:
book = models.Book.objects.create(title="Python", pub_date="2012-12-12", price=122, publish_id=1)

方式二:
book = models.Book.objects.create(title="Python", pub_date="2012-12-12", price=122, publish=publish)    #book.publish爲book書藉的出版社對象

一對一

    與一對多同樣,先添加一的表,再添加帶關聯字段的表django

    關聯字段在建立表時字段名爲字段_idide

多對多

  • 綁定多對多的關係,無非是在關係表建立記錄
  • 第三張表中字段名爲表名_id
# 正向(從具備關聯屬性的表添加)
# Linux這本書綁定兩個做者:Tom, Linda

# 方式一:
linux = models.Book.objects.filter(title="Linux").first()
tom = models.Author.objects.filter(name="Tom").first()
linda = models.Author.objects.filter(name="Linda").first()
linux.authors.add(tom, linda)

# 方式二:
linux = models.Book.objects.filter(title="Linux").first()
linux.authors.add(1, 2)/linux.authors.add(*[1, 2])


# 反向(從不具備關聯屬性的表添加)
# 給Ethan綁定一本書藉:PhP
ethan = models.Author.objects.filter(name="Ethan").first()
php = models.Book.objects.filter(title="PhP").first()
ethan.book_set.add(php)

    注:添加表記錄能夠經過插件(如navicat)完成函數

基於對象的跨表查詢

    實質爲子查詢,即以上一次的查詢結果做爲下一次的查詢條件測試

一對多

                        正向查詢:按字段book.publish
                        ---------------------------->
            Book對象    <---------------------------- Publish 對象
                        反向查詢:按表名小寫_set.all()

例: 正向:查詢Linux這本書藉的出版社的地址 book
= models.Book.objects.filter(title="Linux").first() print(book.publish.city) 反向:查詢蘋果出版社出版的全部書藉 publish = models.Publish.objects.filter(name="蘋果出版社").first() print(publish.book_set.all())

多對多

                        正向查詢:按字段book.authors.all()
                        --------------------------------->
            Book對象    <--------------------------------- Author 對象
                        反向查詢:按表名小寫_set.all()

例: 正向:查詢Linux書藉的全部做者 linux
= models.Book.objects.filter(title="Linux").first() print(linux.authors.all()) 反向:查詢tom出版過的全部書藉 tom = models.Author.objects.filter(name="Tom").first() print(tom.book_set.all())

一對一

                        正向查詢:按字段book.ad
                        --------------------------------->
            Author 對象 <--------------------------------- AuthorDetail 對象
                        反向查詢:按表名小寫

例: 正向:查詢Tom的地址 tom
= models.Author.objects.filter(name="Tom").first() print(tom.ad.addr) 反向:查詢地址爲南京的做者的名字 ad = models.AuthorDetail.objects.filter(addr="南京").first() print(ad.author.name)

基於雙下劃線的跨表查詢

    Django還提供了一種直觀而高效的方式在查詢(lookups)中表示關聯關係,它能自動確認SQL JOIN聯繫。要作跨關係查詢,就使用兩個下劃線來連接模型(model)間關聯字段的名稱。直到最終連接到你想要的model爲止。

    實質爲join查詢。

"""正向查詢按字段,反向查詢按表名小寫"""
# <1> 查詢Linux這本書藉的出版社的地址(一對多)
# 正向查詢
addr = models.Book.objects.filter(title="Linux").values("publish__city")

# 反向查詢
addr = models.Publish.objects.filter(book__title="Linux").values("city")


# <2> 查詢Linux書藉的全部做者名字(多對多)
# 正向查詢                
print(models.Book.objects.filter(title="Linux").values("authors__name"))
# 反向查詢
print(models.Author.objects.filter(book__title="Linux").values("name"))

# <3> 查詢Ethan的手機號(一對一) # 正向查詢 ret = models.Author.objects.filter(name="Ethan").values("ad__telephone") # 反向查詢 ret = models.AuthorDetail.objects.filter(author__name="Ethan").values("telephone")

連續跨表

# <1> 查詢人民出版社出版過的全部書藉的名字以及做者的姓名
# 正向查詢
ret = models.Book.objects.filter(publish__name="教育出版社").values("title", "authors__name")

# 反向查詢
ret = models.Publish.objects.filter(name="人民出版社").values_list("book__title", "book__authors__name")

# <2> 手機號以13開頭的做者出版過的全部書籍名稱以及出版社名稱 # 方式一: ret = models.Book.objects.filter(authors__ad__telephone__startswith="13").values("title", "publish__name")) # 方式二: ret = models.Author.objects.filter(ad__telephone__startswith="13").values("book__title", "book__publish__name")

related_name

    反向查詢時,若是定義了related_name,則用related_name替換表名,如:

# models.py

class Book(models.Model):
    publish = models.ForeignKey(to="Author", to_field="id", on_delete=models.CASCADE, null=True, related_name="booklist")

 

# 查詢人民出版社出版過的全部書籍的名字與價格(一對多)

# 反向查詢 再也不按表名:book,而是related_name:bookList
ret =models.Publish.objects.filter(name="人民出版社").values_list("bookList__title","bookList__price") 

聚合查詢與分組查詢

聚合查詢

    方法:aggregate(*args, **kwargs)

    導入聚合函數:

from django.db.models import Avg,Max,Min,Sum,Count

    調用者:QuerySet對象

    返回值:包含一些鍵值對的字典。鍵的名稱若是沒有指定,則按照字段和聚合函數的名稱自動生成,也可自行指定

# 計算全部圖書的平均價格
from django.db.models import Avg

# 自動生成鍵名稱
models.objects.aggregate(Avg("price"))     # {'price_avg': 34.55}

# 自行指定鍵名稱
models.objects.aggregate(average_price = Avg("price"))  # {'average_price': 34.55}

    能夠向aggregate()添加多個參數,即生成多個聚合

from django.db.models import Avg, Max, Min

models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))    # {'price__avg': 34.55, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

 

分組查詢

    本質:將關聯表join成一張表,再安單表的思路進行分組查詢

KEY:
1. 選連表(基於雙下劃線查詢就是連表),再Group by,最後顯示字段
2. values().annotate(),此時values()就是group by

    方法:annotate()

    調用者:QuerySet對象

    返回值:QuerySet對象

###################################--單表分組查詢--#######################################################

查詢每個部門名稱以及對應的員工數

emp:

id  name age   salary    dep
1   alex  12   2000     銷售部
2   egon  22   3000     人事部
3   wen   22   5000     人事部


sql語句:
select dep,Count(*) from emp group by dep;

ORM:
emp.objects.values("dep").annotate(c=Count("id")

###################################--多表分組查詢--###########################


多表分組查詢:

查詢每個部門名稱以及對應的員工數


emp:

id  name age   salary   dep_id
1   alex  12   2000       1
2   egon  22   3000       2
3   wen   22   5000       2


dep

id   name 
1    銷售部
2    人事部



emp-dep:

id  name age   salary   dep_id   id   name 
1   alex  12   2000       1      1    銷售部
2   egon  22   3000       2      2    人事部
3   wen   22   5000       2      2    人事部


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)


sql語句:
select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id

ORM:
dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c")
sql語句與ORM對應
# objects.values().annotate()與objects.all().annotate()區別
dep.objects.values("id").annotate(c=Count("price"))   # 獲得的是QuerySet集合,集合裏元素爲id和c鍵值對的字典

dep.objects.all().annotate(c=Count("price"))          # 獲得的是QuerySet集合,集合裏元素爲dep對象,dep對象在原有基礎上多了個c字段

查詢練習

# <1> 統計每個出版社的最便宜的書

# <2> 統計每一本書的做者個數

# <3> 統計每一本以Py開頭的書籍的做者個數

# <4> 統計不止一個做者的圖書

# <5> 根據一本圖書做者數量的多少對查詢集QuerySet進行排序

# <6> 查詢各個做者出的書的總價格
# <1> 統計每個出版社的最便宜的書
ret = models.Publish.objects.annotate(MinPrice=Min("book__price")).values_list("name", "MinPrice")

# <2> 統計每一本書的做者個數
ret = models.Book.objects.annotate(authorsNum=Count("authors__name"))

# <3> 統計每一本以Py開頭的書籍的做者個數
ret = models.Book.objects.filter(title__startswith="Py").annotate(num_authors=Count("authors"))

# <4> 統計不止一個做者的圖書
ret = models.Book.objects.annotate(num_authors=Count("authors")).filter(num_authors__gt=1)

# <5> 根據一本圖書做者數量的多少對查詢集QuerySet進行排序
ret = models.Book.objects.annotate(num_authors=Count("authors")).order_by("num_authors")

# <6> 查詢各個做者出的書的總價格
ret = models.Author.objects.annotate(SumPrice=Sum("book__price")).values_list("name", "SumPrice")
答案

F查詢與Q查詢

F查詢

    在上面的全部例子中,構造的過濾器都只是將字段值與某個常量作比較

    Django提供F()的實例能夠在查詢中引用字段,來比較同一個model實例中兩個不一樣的字段的值

    F的導入:

from django.db.models import F
from django.db.models import F

# 查詢評論數大於收藏數的書籍
ret = models.Book.objects.filter(commentNum__lt=F("keepNum"))

    Django支持F()對象之間以及F()對象和常數之間的加減乘除和取模的操做

# 查詢評論數大於收藏數2倍的書籍
ret = models.Book.objects.filter(commentNum__lt=F("keepNum")*2)

    修改操做也可使用F函數

# 將每一本書的價格提升30元
ret = models.Book.objects.all().update(price=F("price") + 30)

 

Q查詢

     filter()等方法中的關鍵字參數查詢都是一塊兒進行"and"的。若是須要執行更復雜的查詢(例如OR 語句),則使用Q對象

    Q查詢支持查詢條件的and,or, not操做,分別對應操做符&,|,~

    Q的引入:

from django.db.models import Q

    Q對象可使用&和|操做符組合起來。當一個操做符在兩個Q對象上使用時,它產生一個新的Q對象

# 查詢價格大於300或者名稱以"p"開頭的書籍
ret = models.Book.objects.filter(Q(price__gt=300)|Q(title__startswith="p"))

    能夠組合&和|操做符以及使用括號進行分組來編寫任意複雜的Q對象。同時,Q對象可使用~操做符取反,這容許組合正常的查詢和取反(NOT)查詢:

# 查詢由Ethan編寫的且不是在2017年出版的書籍名稱
ret = models.Book.objects.filter(Q(authors__name="Ethan") & ~Q(pub_date__year=2017)).values_list("title")

    查詢函數能夠混合使用Q對象和關鍵字參數。全部提供給查詢函數的參數(關鍵字參數或Q對象)都將"AND"在一塊兒。可是,若是出現Q對象,它必須位於全部關鍵字參數的前面

# 查詢由2016或2017年出版的名稱中含有"python"(不區分大小寫)的書籍
ret = models.Book.objects.filter(Q(pub_date__year=2016) | Q(pub_date__year=2017), title__icontains="python")

    當有多個操做符時,屬於同一關係外層還需套一層Q

# 查詢價格大於300或者不是2019年1月出版的書籍
ret = models.Book.objects.filter(Q(price__gt=300) | ~Q(Q(pub_date__year=2019) & Q(pub_date__month=1)))

# create, add, remove, clear, set適用於以下管理器:
"""
1. 一對多:如Publish_obj.book_set
2. 多對多:如Book_obj.authors, Author_obj.book_set
"""

remove

# 刪除Linux這本書的一個做者Tom
linux = models.Book.objects.filter(title="Linux").first()
tom = models.Author.objects.filter(name="Tom").first()
linux.authors.remove(tom)/tom.book_set.remove(linux)    # 刪除第三張表Tom與Linux記錄

clear

# 刪除Linux這本書的全部做者
linux = models.Book.objects.filter(title="Linux").first()
linux.authors.clear()

set

    至關於clear + add。set括號內參數爲list

# 刪除Linux這本書的全部做者,並添加Ethan爲做者
# 方法一:
linux = models.Book.objects.filter(title="Linux").first()
linux.authors.set([3,])

# 方法二:
linux = models.Book.objects.filter(title="Linux").first()
ethan = models.Author.objects.filter(name="Ethan").first()
linux.authors.set([ethan,])

練習題

# 一、 自行建立測試數據;

# 二、 查詢學生總人數;

# 三、 查詢「生物」課程和「物理」課程成績都及格的學生id和姓名;

# 四、 查詢每一個年級的班級數,取出班級數最多的前三個年級;

# 五、 查詢平均成績最高的學生的id和姓名以及平均成績;

# 六、 查詢每一個年級的學生人數;

# 七、 查詢每位學生的學號,姓名, 平均成績;

# 八、 查詢學生編號爲「2」的學生的姓名、該學生成績最高的課程名及分數;

# 九、 查詢姓「李」的老師的個數和所帶班級數;

# 十、查詢班級數小於5的年級id和年級名;

# 十一、查詢教過課程超過2門的老師的id和姓名;

# 十二、查詢學過編號「1」課程和編號「2」課程的同窗的學號、姓名;

# 1三、查詢所帶班級數最多的老師id和姓名;

# 1四、查詢有課程成績小於60分的同窗的學號、姓名;

# 1五、查詢男生、女生的人數,按倒序排列;

# 1六、查詢各個課程及相應的選修人數;

# 1七、查詢同時選修了物理課和生物課的學生id和姓名;

# 1八、檢索「3」課程分數小於60,按分數降序排列的同窗學號;

# 1九、查詢每門課程的平均成績,結果按平均成績升序排列,平均成績相同時,按課程號降序排列;

# 20、查詢各科成績最高和最低的分:以以下形式顯示:課程ID,最高分,最低分;
題目
# 一、 自行建立測試數據;

# 二、 查詢學生總人數;
ret = models.Student.objects.aggregate(studentNum=Count("*"))

# 三、 查詢「生物」課程和「物理」課程成績都及格的學生id和姓名;
ret = models.Score.objects.filter(Q(course__cname="物理")|Q(course__cname="生物"), score__gt=59).values("student").annotate(c=Count("*")).filter(c__gt=1).values("student__sid", "student__sname")

# 四、 查詢每一個年級的班級數,取出班級數最多的前三個年級;
ret = models.Cls.objects.values("grade_id").annotate(c=Count("cid")).order_by("-c").values("grade__gname")[:3]

# 五、 查詢平均成績最高的學生的id和姓名以及平均成績;
ret = models.Score.objects.values("student_id").annotate(avg=Avg("score")).order_by("-avg").values("student__sid", "student__sname", "avg").first()

# 六、 查詢每一個年級的學生人數;
ret = models.Student.objects.values("cls__grade").annotate(c=Count("cls__grade")).values("cls__grade__gname", "c")

# 七、 查詢每位學生的學號,姓名, 平均成績;
ret = models.Score.objects.values("student_id").annotate(avg=Avg("score")).values("student__sid", "student__sname", "avg")

# 八、 查詢學生編號爲「2」的學生的姓名、該學生成績最高的課程名及分數;
ret = models.Score.objects.filter(student_id=2).order_by("-score").values("student__sname", "course__cname", "score").first()

# 九、 查詢姓「李」的老師的個數和所帶班級數;
ret = models.Teacher.objects.filter(tname__startswith="").annotate(c=Count("tid")).values("c", "classes")

# 十、查詢班級數小於5的年級id和年級名;
ret = models.Class_Grade.objects.values("gid").annotate(c=Count("cls__grade_id")).filter(c__lt=5).values("gid", "gname")

# 十一、查詢教過課程超過2門的老師的id和姓名;
ret = models.Course.objects.values("teacher_id").annotate(c=Count("*")).filter(c__gt=1).values("teacher__tid", "teacher__tname")

# 十二、查詢學過編號「1」課程和編號「2」課程的同窗的學號、姓名;
ret = models.Score.objects.values("student_id").annotate(c=Count("*")).filter().values("student__sid", "student__sname")

# 1三、查詢所帶班級數最多的老師id和姓名;*****
ret = models.Teacher.objects.annotate(c=Count("classes__cid")).order_by("-c").values("tid", "classes__teacher__tname")[0]

# 1四、查詢有課程成績小於60分的同窗的學號、姓名;
ret = models.Score.objects.values("student").annotate(min=Min("score")).filter(score__lt=60).values("student__sid", "student__sname")

# 1五、查詢男生、女生的人數,按倒序排列;
ret = models.Student.objects.values("gender").annotate(c=Count("sid")).order_by("-c")

# 1六、查詢各個課程及相應的選修人數;
ret = models.Score.objects.values("course").annotate(c=Count("*")).values("course__cname","c")

# 1七、查詢同時選修了物理課和生物課的學生id和姓名;
ret = models.Score.objects.filter(Q(course__cname="物理")|Q(course__cname="生物")).values("student").annotate(c=Count("*")).filter(c__gt=1).values("student__sid", "student__sname")

# 1八、檢索「3」課程分數小於60,按分數降序排列的同窗學號;
ret = models.Score.objects.filter(score__lt=60, course_id="3").order_by("-score").values("student__sid")

# 1九、查詢每門課程的平均成績,結果按平均成績升序排列,平均成績相同時,按課程號降序排列;
ret = models.Score.objects.values("course").annotate(c=Avg("score")).order_by("c", "-course__cid").values("course__cname", "c")

# 20、查詢各科成績最高和最低的分:以以下形式顯示:課程ID,最高分,最低分;
ret = models.Score.objects.values("course").annotate(max=Max("score"), min=Min("score")).values("course__cid", "max", "min")
答案

表關係:

 

相關文章
相關標籤/搜索