Django开发:(3.1)ORM:多表操作

表关系总结:

  一对多:在多的表中建立关联字段

  多对多:创建第三张表(关联表):id 和 两个关联字段

  一对一:在两张表中的任意一张表中建立关联字段(关联字段一定要加 unique 约束)

子查询:一次查询结果作为另一次查询的查询条件

创建模型:

from django.db import models

# Create your models here.
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 = models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE) # authordetail也会被自动添加 _id
    # 对于Django2.0版本,一对多(models.ForeignKey)和一对一(models.OneToOneField)要加上 on_delete=models.CASCADE 这个属性

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 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 = models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)  # 这句代码做了两件事:1. 生成表的时候在 publish前面自动加了 _id,并在表中生成了一个字段 publish_id;2. 把 publish_id 作为外键关联到了 Publish表的 nid 字段;具体作用用SQL语句表示如下:
    """
        publish_id int,
        foreign key(publish_id) references publish(id)
    """

    # 多对多:生成第三张表来存多对多对应关系
    authors = models.ManyToManyField(to="Author")  # 这句代码的作用是创建第三张表来存多对多关系,而不是在该表中创建一个 authors_id 的字段;ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表;作用用SQL语句表示如下:
    """
    create table book_authors(
        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)
        );
    """
    # ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表

注意:

  • 表的名称myapp_modelName,是根据 模型中的元数据自动生成的,也可以覆写为别的名称
  • id 字段是自动添加的
  • 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
  • Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句
  • 修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称
  • 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。

添加表记录

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"add/",views.add)
]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from app01.models import *

def add(request):
    # 单表插入记录:(单表:没有关联字段的表)
    pub = Publish.objects.create(name="人民出版社",email="[email protected]",city="北京")

    # #######################邦定一对多关系####################

    # 为一对多有关联字段的表添加记录
    # 为book添加出版社;book是多
    # 方式一:直接为 publish_id 这个字段添加 出版社的id
    book_obj1 = Book.objects.create(title="红楼梦",price=200,publishDate="2016-8-8",publish_id=1)
    # Book中的 publish会在数据库迁移的时候添加 _id 变成 publish_id 字段
    print(book_obj1.title)

    # 方式二:为 publish 赋值,此时赋的值为 Publish的实例模型对象
    pub_obj = Publish.objects.filter(nid=1).first()  # nid为1的出版社对象
    book_obj2 = Book.objects.create(title="西游记", price=200, publishDate="2016-8-8", publish=pub_obj)  # publish等于一个 Publish对象
    # create操作时,会把 publish翻译成 publish_id,把 pub_obj的主键值取出来,并把 pub_obj的主键值赋值给 publish_id
    print(book_obj2.price)  # book_obj2的价格
    print(book_obj2.publish_id)  # book_obj2的出版社id
    print(book_obj2.publish)  # 不管是方式一还是方式二,book_obj2.publish 都表示 pub_obj 这个model对象(重点)
    # book_obj2.publish:与这本书籍关联的出版社对象;所以 book_obj2.publish 后面还能用点语法,如:
    print(book_obj2.publish.email)

    # 一对多的查询:查询“西游记”对应出版社的邮箱
    book_obj3 = Book.objects.filter(title="西游记").first()  # 西游记这本书对象
    email = book_obj3.publish.email  # book_obj3.publish:西游记这本书对应的出版社对象
    print(email)

    # #################邦定多对多关系############################
    book_obj4 = Book.objects.create(title="python全栈开发",price=100,publishDate="2017-8-8",publish_id=1)

    alex = Author.objects.get(nid=1)
    egon = Author.objects.get(nid=2)
    # 为多对多关系表添加记录的接口(添加的前提是先create一条记录)
    book_obj4.authors.add(alex,egon)  # 给python全栈开发这本书籍对象添加作者,作者为alex和egon;邦定多对多关系的API
    """
    book_obj4.authors.add(alex,egon)执行的操作:
    找到 book_obj4的主键,找到alex这个作者对象的主键,然后在 Book和authors的关系表中添加一条记录;
    找到 book_obj4的主键,找到egon这个作者对象的主键,然后在 Book和authors的关系表中添加一条记录;
    所以book_obj4.authors.add(alex,egon)会在 app01_book_authors 这张表中添加两条记录
    """
    # 另外一种写法:add中直接写对应作者的主键(id),而不是写作者的model对象
    book_obj4.authors.add(1,2)
    # 或者:
    book_obj4.authors.add(*[1,2])

    # 解除多对多关系(解除的前提是先查出来)
    book_obj5 = Book.objects.filter(nid=3).first()
    book_obj5.authors.remove(2)  # 把 book_id为book_obj4的主键、author_id为2的那条记录从 app01_book_authors 这张关系表中删除
    # book_obj5.authors.remove(1,2)
    # book_obj5.authors.remove(*[1,2])

    # 删除所有: clear()
    book_obj5.authors.clear()

    # book_obj5.authors.all() :表示与这本书关联的所有作者对象的集合(QuerySet)(重点)
    print(book_obj5.authors.all())
    # 查询所有作者的名字
    ret = book_obj5.authors.all().values("name")

    return HttpResponse("ok")

基于对象的跨表查询

还以上面的表为例: views.py

def add(request):
    # #################基于对象的跨表查询(基于子查询)############################
    # #########1. 一对多
    # 查询主键为1的书籍的出版社所在的城市
    # 正向查询;正向查询,你需要先知道关联字段在哪个表中,如这个例子中,publish在Book中,通过publish去查找Publish就是正向查询;同理,通过Publish查找Book就是反向查询
    # 正向查询号按字段(如Book中的publish),反向查询按表名(如下面的 book_set.all();book就是表名,set就是集合的意思;集合的形式:QuerySet)
    book_obj = Book.objects.filter(nid=1).first()  # 对应书籍对象
    press_city = book_obj.publish.city  # book_obj.publish:对应出版社对象
    print(press_city)
    # 查询人民出版社出版的所有书籍的名称
    publish_obj = Publish.objects.filter(name="人民出版社").first()  # 出版社对象
    book_objs = publish_obj.book_set.all()  # 该出版社对应的所有书籍对象;QuerySet;[book_obj1,book_obj2,....]
    print(book_objs)

    # 打印对应所有书籍的名称
    for obj in publish_obj.book_set.all():
        print(obj.title)


    # #######2. 一对一
    # 正向查询:查询alex的手机号
    alex = Author.objects.filter(name="alex").first()
    phoneno = alex.authordetail.telephone  # 正向查询按字段
    print(phoneno)

    # 反向查询:查询号手机号为911的作者名字
    authordetail_obj = AuthorDetail.objects.filter(telephone=911).first()
    authorname = authordetail_obj.author.name  # authordetail_obj.author为对应的作者对象
    print(authorname)


    # 查询所有住址在北京的作者的姓名
    authorDetail_list = AuthorDetail.objects.filter(addr="北京")
    for obj in authorDetail_list:
        print(obj.author.name)

    # #######3. 多对多
    # 正向查询:查询“python全栈开发”所有作者的名字
    book = Book.objects.filter(title="python全栈开发").first()
    author_book = book.authors.all()  # 表示与这本书关联的所有作者对象的集合(QuerySet)

    # 反向查询:查询alex出版过的所有书籍的名称
    author_obj = Author.objects.filter(name="alex").first()
    books_set = author_obj.book_set.all()  # 所有书籍对象
    print(books_set)

    for obj in books_set:
        print(obj.title)
  
  return HttpResponse("ok")

""" # 一对多: 
   正向按字段:publish
book ------------------> publish
   <-----------------
   反向按表名:book_set.all()

# 一对一:(反向查询直接按表名,因为本来就是一一对应的关系,不需要 _set.all())
    正向按字段:authordetail
author ------------------> authordetail
    <-----------------
     反向按表名:author  

# 多对多:
   正向按字段:authors
book ------------------> author
   <-----------------
   反向按表名:book_set.all()

"""

基于双下划线的跨表查询:(正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表)

def add(request):
    # #################基于双下划线(QuerySet)的跨表查询(基于join)############################
    # 关键点:正向查询按字段,反向查询按表名

    # 查询人民出版社出版过的所有书籍的名字与价格(一对多)
    ret1 = Book.objects.filter(publish__name="人民出版社").values("title","price")  # 正向查询:Book ----> Publish, publish是Book中的字段,publish__name是所关联的Publish的name
    print(ret1)
    ret2 = Publish.objects.filter(name="人民出版社").values("book__title","book__price")  # 反向查询:Publish ----> Book, book__title中book是表名,book__title是Book中的title字段
    print(ret2)
    """
    上述两种方式实现的效果一样,区别在于基表的不同;双下划线的查询方式能自动确认 SQL JOIN 联系
    """

    # 查询alex出版过的所有书籍的名字(多对多)(可以把双下划线理解成“的”)
    alex_book1 = Author.objects.filter(name="alex").values("book__title")  # 反向查询:Author---->Book 按表名 book__
    print(alex_book1)
    alex_book2 = Book.objects.filter(authors__name="alex").values("title")  # 正向查询:Book---->Author 按字段 authors__
    print(alex_book2)

    ##### 混合使用
    # 查询人民出版社出版过的所有书籍的名字以及作者的姓名
    mix1 = Book.objects.filter(publish__name="人民出版社").values("title","authors__name")  # 以book为基表
    print(mix1)
    mix2 = Publish.objects.filter(name="人民出版社").values("book__title","book__authors__name")  # 以 publish 为基表
    print(mix2)

    # 手机号以11开头的作者出版过的所有书籍名称以及出版社名称
    mix3 = Book.objects.filter(authors__authordetail__telephone__startswith=11).values("title","publish__name")
    print("mix3",mix3)
    mix4 = Author.objects.filter(authordetail__telephone__startswith="11").values("book__title","book__publish__name")
    print(mix4)

  return HttpResponse("ok")

聚合查询和分组查询

def add(request):
    # ###################聚合查询和分组查询##################
    # 聚合函数:aggregate
    from django.db.models import Avg
    price_avg = Book.objects.all().aggregate(Avg("price"))  # 所有书籍的平均价格;字典的形式
    print(price_avg)
    # {'price__avg': 166.666667}
    price_avg2 = Book.objects.all().aggregate(c=Avg("price"))
    print(price_avg2)
    # {'c': 166.666667}

    # 分组函数:annotate()
    # 统计每本书的作者个数
    from django.db.models import Count
    author_num = Book.objects.all().annotate(c=Count("authors__name"))  # authors__name表示author表中的name字段(正向查询);annotate的作用是给前面所有的对象添加一个独立的“注释”(相当于给前面的所有对象添加了一个新的属性)
    print(author_num)
    for obj in author_num:
        print(obj.title,obj.c)

    # 统计每个作者出版书籍的最高价格
    from django.db.models import Max
    max_price = Author.objects.all().annotate(hp=Max("book__price")).values("name","hp")  # book__price:反向查询;.values("name","hp") 链式操作
    print(max_price)
  
  return HttpResponse("ok")

aggregate(*args,**kwargs)QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它(kwargs的形式)。

而annotate()函数可以为QuerySet中的每个对象生成一个独立的摘要,输出的结果仍然是一个QuerySet对象,能够调用filter()、order_by()甚至annotate()

总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。

猜你喜欢

转载自www.cnblogs.com/neozheng/p/9160526.html