• 模型层之多表操作


    创建模型

    假定下面这些概念,字段和关系

    作者模型:一个作者有姓名和年龄。
    
    作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
    
    出版商模型:出版商有名称,所在城市以及email。
    
    书籍模型: 
    	书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,
    	所以作者和书籍的关系就是多对多的关联关系(many-to-many); 
    	一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
    

    为了存储出版社的邮箱,地址,在 Book 表后面加字段

    这样会有大量重复的数据,浪费空间

    **一对多:**一个出版社对应多本书(关联信息建在多的一方,也就是 Book 表中)

    一旦确定表关系是一对多,在多对应的表中创建关联字段

    **多对多:**一本书有多个作者,一个作者出多本书

    一旦确定表关系是多对多,创建第三张关系表(中间表,中间表就三个字段,自己的 id,书籍 id 和作者 id)

    **一对一:**对作者详细信息的扩展

    一旦确定是一对一的关系,在两张表中的任意一张表中建立关联字段+Unique

    在 models 创建如下模型

    from django.db import models
    
    # Create your models here.
    
    class Book(models.Model):
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=32, null=True)
        price = models.DecimalField(max_digits=5, decimal_places=2)
        # 一对多的关系一旦确立,关联关系写在多的一方
        # on_delete:级联删除
        publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.CASCADE)
        # 多对多的关系需要创建第三张表(自动创建第三张表)
        authors = models.ManyToManyField(to='Author')
    
        def __str__(self):
            return self.title
    
    class Author(models.Model):
        nid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32)
        age = models.IntegerField()
        # unique=True 唯一性约束
        author_detail = models.OneToOneField(to='AuthorDetail', to_field='nid', on_delete=models.CASCADE)
        # author_detail = models.ForeignKey(to='AuthorDatail',to_field='nid',unique=True,on_delete=models.CASCADE)
    
    class AuthorDetail(models.Model):
        nid = models.AutoField(primary_key=True)
        phone = models.CharField(max_length=16, unique=True)
        addr = models.CharField(max_length=16)
    
    
    class Publish(models.Model):
        nid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32)
        city = models.CharField(max_length=32)
        # EmailField :邮箱格式,EmailField用在admin中
        email = models.EmailField()
    

    生成的表如下:

    添加表记录

    一对多

    # 方式一:
    publish_obj = models.Book.objects.get(nid=1)
    book_obj = models.Book.objects.create(name="图解HTTP", price="49", publish_date="2014-11-12", publish=publish_obj)
      
    # 方式二,推荐:
    book_obj = models.Book.objects.create(name="图解HTTP", price="49", publish_date="2014-11-12", publish_id=1)
    

    多对多

    # 当前生成的书籍对象
    book_obj = models.Book.objects.create(name="图解HTTP", price="49", publish_date="2014-11-12", publish_id=1)
    # 为书籍绑定的作者对象
    author_obj1 = models.Author.objects.filter(name="上野宣").first()
    author_obj2 = models.Author.objects.filter(name="于均良").first()
    # 绑定多对多关系,即向关系表book_authors中添加纪录
    book_obj.authors.add(author_obj1, author_obj2)
    # 或者
    book_obj.authors.add(1, 2)
    
    # 方式二
    # 当前生成的书籍对象
    book_obj = models.Book.objects.create(name="C Primer Plus", price="60", publish_date="2005-2-12", publish_id=1)
    # 绑定多对多关系
    book_obj.authors.add(*[3, 4])
    
    # 解除绑定
    book_obj = models.Book.objects.filter(nid=2).first()	# 找到book的id为2的书
    book_obj.authors.remove(2)	# 解除作者id为2的绑定关系
    book_obj.authors.clear()	# 解除所有作者的绑定关系
    
    # 解除再绑定:清空所有的绑定关系,再设置一个
    book_obj.authors.clear()
    book_obj.authors.add(1)
    # 另一种方法
    book_obj.set(1)	# 等同于上面的两步操作
    

    基于对象的跨表查询

    • 正向:关联关系在当前表中,从当前表去另一个表
    • 反向:关联关系不在当前表中,从当前表去另一个表

    一对一查询

    正向查询按字段

    # 查询上野宣作者的地址
    shangyexuan = models.Author.objects.filter(name="上野宣").first()
    print(shangyexuan.author_detail.addr)
    

    反向查询按表名小写

    # 查询地址是日本的作者名字
    address = models.AuthorDetail.objects.filter(addr="日本").first()
    # address.author是作者对象
    print(address.author.name)
    

    一对多查询

    正向查询按字段:正向查询只会查出一个

    # 查询《图解HTTP》这本书的出版社名字
    book_obj = models.Book.objects.filter(title="图解HTTP").first()
    print(book_obj.publish.name)
    

    反向查询按表名小写_set.all():返回结果是 queryset 对象

    # 查询人民邮电出版社出版的所有书
    publish_obj = models.Publish.objects.filter(name="人民邮电出版社").first()
    books = publish_obj.book_set.all()  
    for book in books:
        print(book.title)
    

    all() 拿出的是所有书的 queryset 对象,还可以进一步筛选

    # 查询人民邮电出版社出版的书名以“IP”结尾的书
    publish_obj = models.Publish.objects.filter(name="人民邮电出版社").first()
    books = publish_obj.book_set.all().filter(title__endswith="IP")
    for book in books:
        print(book.title)
    

    多对多查询

    正向查询按字段.all():正向查询一定会查出多个

    # 查询《图解HTTP》这本书的所有作者
    book_obj = models.Book.objects.filter(title="图解HTTP").first()
    authors = book_obj.authors.all()
    for author in authors:
        print(author.name)
    

    反向查询按表名小写_set.all():返回结果是 queryset 对象

    # 查询上野宣写的所有书
    author_obj = models.Author.objects.filter(name="上野宣").first()
    books = author_obj.book_set.all()
    for book in books:
        print(book.title)
    

    基于双下划线的跨表查询

    一对一查询

    # 查询上野宣作者的地址
    ret = models.Author.objects.filter(name="上野宣").values("author_detail__addr")
    print(ret)
    
    # 查询地址是日本的作者名字
    ret = models.AuthorDetail.objects.filter(addr="日本").values('author__name')
    print(ret)
    

    一对多查询

    # 查询《图解HTTP》这本书的出版社名字
    ret = models.Book.objects.filter(title="图解HTTP").values('publish__name')
    print(ret)
    
    # 查询人民邮电出版社出版的所有书的名字
    ret = models.Publish.objects.filter(name="人民邮电出版社").values('book__title')
    print(ret)
    

    多对多查询

    # 查询《图解HTTP》这本书的所有作者
    ret = models.Book.objects.filter(title="图解HTTP").values('authors__name')
    print(ret)
    
    # 查询上野宣写的所有书
    ret = models.Author.objects.filter(name="上野宣").values('book__title')
    print(ret)
    
    # 查询人民出版社出版过的所有书籍的名字以及作者的姓名
    # 方式一:
    ret = models.Book.objects.filter(publish__name="人民邮电出版社").values('title', 'authors__name')
    
    # 方式二
    ret = ret = models.Publish.objects.filter(name="人民邮电出版社").values('book__title', 'book__authors__name')
    

    不管跨了多少张表,它们的查询效率都是一样的。因为 ORM 中,无论是多少张表,都是先把这些表连接起来,再去进行查询。可以使用 print(ret.query) 查看 SQL 语句:

    补充:

    • **related_name:**反向查询时,如果定义了 related_name,则用 related_name 替换表名,例如:
    publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.CASCADE, related_name='bookList')
    
    # 查询人民邮电出版社出版过的所有书籍的名字和价格(一对多)
    # 反向查询不再按表名book,而是 related_name:bookList
    
    ret = Publish.objects.filter(name="人民邮电出版社").values('bookList__title', 'bookList__price')
    
    • **related_query_name:**反向查询时,如果定义了 related_query_name,则用 related_query_name 替换字段名
    • 不建议替换修改

    聚合查询

    aggregate(*args, **kwargs)

    # 计算所有图书的平均价格
    from django.db.models import Avg
    ret = models.Book.objects.all().aggregate(Avg('price'))
    print(ret)		# {'price__avg': 59.0}
    

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

    ret = models.Book.objects.aggregate(average_price=Avg('price'))
    print(ret)		# {'average_price': 59.0}
    

    可以向 aggregate() 子句中添加多个参数

    from django.db.models import Avg, Max, Min
    
    ret = models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
    # {'price__avg': 59.0, 'price__max': Decimal('89.00'), 'price__min': Decimal('29.00')}
    

    分组查询

    annotate(*args, **kwargs)

    # 统计每一本书作者个数
    book_list = models.Book.objects.all().annotate(author_num=Count('authors'))
    for book in book_list:
        print(book.title, str(book.author_num)+'个作者')
    # 或者   
    book_list = models.Book.objects.annotate(author_num=Count("authors")).values('title', 'author_num')
    print(book_list)
    
    # 统计每一个出版社的最便宜的书
    ret = models.Publish.objects.annotate(cheap_book=Min("book__price")).values('book__title', 'book__price')
    print(ret)
    
    # 统计每一本以图解开头的书籍的作者个数
    ret = models.Book.objects.filter(title__startswith="图解").annotate(author_num=Count("authors")).values('title', 'author_num')
    print(ret)
    
    # 统计不止一个作者的图书
    ret = models.Book.objects.annotate(author_num=Count("authors")).filter(author_num__gt=1).values('title', 'author_num')
    print(ret)
    
    # 根据一本图书作者数量的多少对查询集 QuerySet进行排序
    ret = models.Book.objects.annotate(author_num=Count("authors")).order_by('author_num').values('title', 'author_num')
    print(ret)
    

    F查询

    F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。

    首先将 Book 表中添加两条字段:

    class Book(models.Model):
        # ...
        # 阅读数
        read_num = models.IntegerField(default=0)
        # 评论数
        commit_num = models.IntegerField(default=0)
        # ...
    

    然后再进行相关查询:

    from django.db.models import F, Q
    
    # 把《图解HTTP》这本书的评论数加1
    ret = models.Book.objects.filter(title="图解HTTP").update(commit_num=F('commit_num')+1)
    
    # 查询评论数大于阅读数的书籍
    ret = models.Book.objects.filter(commit_num__gt=F('read_num')).values('title', 'commit_num', 'read_num')
    print(ret)
    

    Q查询

    filter() 等方法中的关键字参数查询都是一起进行 AND 操作的。 如果需要执行更复杂的查询(例如 OR 语句),可以使用 Q 查询。

    # 查询书名为《图解HTTP》或价格为29的书籍
    ret = models.Book.objects.filter(Q(title='图解HTTP') | Q(price=29)).values('title')
    print(ret)
    

    Q 查询可以使用 &| 操作符组合起来。当一个操作符在两个 Q 对象上使用时,它产生一个新的 Q 对象。

    上面的查询等同于 SQL 语句:

    mysql> select app01_book.title from app01_book where(app01_book.title="图解HTTP" or app01_book.price=29);
    +-----------------+
    | title           |
    +-----------------+
    | 图解HTTP        |
    | 追风筝的人       |
    +-----------------+
    2 rows in set (0.00 sec)
    

    Q 对象可以使用 ~ 操作符取反,查询函数也可以混合使用 Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或 Q 对象)都将 AND 在一起。但是,如果出现 Q 对象,它必须位于所有关键字参数的前面。例如:

    # 查询名字不是追风筝的人,并且价格大于50的书
    ret = models.Book.objects.filter(~Q(title='追风筝的人'), price__gt=50).values('title')
    print(ret)
    

    外键补充

    在实际开发中,外键通常不用

    • 约束性太强
    • 查询效率会变低
    • db_constraint=False , 这样写在 orm 创建表的时候,外键就没了

    建立外键约束,包括 unique,都是为了不写脏数据

  • 相关阅读:
    博客CSS样式 二
    产品经理
    HttpClient调用doGet、doPost、JSON传参及获得返回值
    Maven无法下载com.oracle:ojdbc.jar解决方法
    EasyExcel导入导出
    centos7 安装supervisor教程以及常见问题
    Django与Celery的安装使用
    亚马逊广告api v2版php扩展库
    Mac VMware Fusion CentOS7 安装、配置静态IP文档
    常见Web安全问题攻防解析
  • 原文地址:https://www.cnblogs.com/qiuxirufeng/p/11518186.html
Copyright © 2020-2023  润新知