zoukankan      html  css  js  c++  java
  • ORM 多表操作

    ORM 多表操作

    判断对应关系

    分别从一张表中取出一条记录与另外一张表比对

    • 如果双方只能对应对方一条记录,一对一
    • 有一方能对应对方多条记录,一对多
    • 双方都能对应对方多条记录, 多对多

    数据表改动

    每次删除或改动表与字段 都要做数据库迁移,数据库迁移的时候会通过改动同步到数据库

    • 删除表:

      • 注释表的类或删除类
    • 改动字段:

      • 在已有的表中添加字段,需要给字段添加一个默认值 default=xxxx
      • 如果添加的是关联字段,则不能添加default=xxxx,应为关联字段不能重复
      • 应在开发时规划好,把全部表建立好后再迁移

    关系表创建

    • 实例:我们来假定下面这些概念,字段和关系

      • 作者模型:一个作者有姓名和年龄。

      作者详细模型:

      • one-to-one:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)

      出版商模型:

      • 出版商有名称,所在城市以及email。

      书籍模型:

      • many-to-many: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many),django自动创建第三张表,存放外键书籍id,作者id

      • one-to-many: 一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。在多的一方创建关联字段

    • 注意事项:

      • 表的名称myapp_modelName,是根据 模型中的元数据自动生成的,也可以覆写为别的名称  
      • id 字段是自动添加的
        • 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
      • 这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。
      • 定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。
      • 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
      • 级联删除:publish_id = models.ForeignKey("Publish", on_delete=models.CASCADE)
        • Publish表删除一条记录,与此条记录关联的表中的记录都会被删除
        • django1.11: 默认级联删除
        • django2.2 : ForeignKey 必须加参数on_delete=models.CASCADE
    • models.py

      from django.db import models
      
      # Create your models here.
      
      
      class Author(models.Model):
          nid = models.AutoField(primary_key=True)
          name=models.CharField( max_length=32)
          age=models.IntegerField()
      
          # 与AuthorDetail建立一对一的关系
          # 在app01_author表中添加 authordetail_id字段, 小写关联表名+_id
          # AuthorDetail 记录删除,Author记录也删除, 级联删除
          authorDetail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE)
      
      class AuthorDetail(models.Model):
      
          nid = models.AutoField(primary_key=True)
          birthday=models.DateField()
          telephone=models.BigIntegerField()
          addr=models.CharField( max_length=64)
      
      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建立一对多的关系,外键字段建立在多的一方
          # app01_book表中添加publish_id
          publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
          # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
          # app01_book_authors 表结构: id book_id author_id
          # 表名拼接方法: app应用名称 + 小写表名 + 所在表属性(authors) : app01_book_authors
          authors=models.ManyToManyField(to='Author',)
      
    • models.ForginKey:

      sql语句:

      create table book(title varchar(32),......, publish_id INT, foreignkey references Publish(id))
      

      参数:django源码

      def __init__(self, to, on_delete=None, related_name=None, related_query_name=None,
                   limit_choices_to=None, parent_link=False, to_field=None,
                   db_constraint=True, **kwargs):
      
      
      • to: 关联那张表
      • to_field: 关联哪个字段,默认关联主键id

    多表记录操作

    • 添加记录

      • create方法 与 save方法 区别:

        用于创建新的对象,两者都可以。如果用于更新现有对象,只能用save

        1. save方法:
          • 先执行类实例化具体对象的操作,执行save方法后才将对象存储到数据库中
          • 不仅用于创建对象,还能用于更新对象的现有数据。在数据库层总是先执行update,找不到具体对象再insert
        sunny = Author(name='sunny', age='20')
        sunny.save()
        
        1. create方法:
          • 只能用于创建新的对象。在数据库层总是执行insert操作
      • 没有关联的表的记录的添加与单表操作一样:

        sunny_author_detail = Book.object.create(name='sunny', age='10')
        
      • 一对多,多的表中添加关联字段:

      • 一对一,与一对多添加一致

        # 书籍:多 出版社:一
        # 第一种方法,字段+_id直接赋值,推荐:直观
        python_book = Book.object.create(title='python', price='100', publish_id=1)
        
        # 第二种方法,先在publish表中查询出model对象,再赋值
        # 语法:
        # publish = pub_obj
        pub_obj = Publish.object.filter(title='python').first()  # 取得model对象
        python_book = Book.object.create(title='python', price='100', publish=pub_obj)
        # django会多出翻译的过程
        # 赋值关联字段记录,关联属性publish会被翻译成publish_id, pub_obj会取出主键id值 赋值给publish_id
        
        
      • 多对多:

        多对多,orm自动创建的第三张表,如 书籍 与 作者 book <---> author, 第三张表:app01_book_authors

        添加方式:

        • 方式一:表的关联属性.add(第一张表记录对象, 第二张表记录对象)
        • 方式二: 表的关联属性.add(第一张表记录的主键id, 第二张表记录的主键id),book.authors.add(1, 2)
        • 方式三: 表的关联属性.add(*[1, 2]), 参数打散方式, 使用最多, book.authors.add(*[1,2])
           
            # 先查询需要添加记录的表记录
            book_python = Book.objects.filter(title='python').first()
        
            author_sunny = Author.objects.filter(name='sunny').first()
            author_vickey = Author.objects.filter(name='ethan').first()
        	
            # 第三张表添加关联字段
            # 第一种方式:
            book_python.authors.add(author_sunny, author_vickey)
            # 第二种方式:
            book_python.authors.add(1, 2)
            # 第三种方式: 使用最多,因为绑定键值一般是用户指定
            book_python.authors.add(*[1, 2])
        
            '''
            book_python.authors 会找到Book与Author的关系表
            app01_book_authors:
            orm 会自动将 book_python的id 添加到 app01_book_authors 中的book_id
                  自动将 author_sunny author_vickey的id 添加到 app01_book_authors 中的 author_id
            '''
        

        解除方式:

        • 解除单个键:表的关联属性.remove(关联表的记录对象或id)

          # 解除作者:sunny 与 book:python 的绑定关系, 会在第三张表中删除此记录
          author_sunny = Author.objects.filter(name='sunny').first()
          book_python = Book.objects.filter(title='python').first()
          # book_python = Book.objects.filter(nid=1).first()
          
          book_python.authors.remove(author_sunny)
          # book_python.authors.remove(1)
          
        • 解除该记录所有绑定: 表的关联属性.clear()

          book_python = Book.objects.filter(nid=1).first()
          # 删除书籍python在第三张表中所有绑定的记录
          book_python.authors.clear()
          
        • 解除再绑定: 表的关联属性.set(id)

          book_python = Book.objects.filter(nid=1).first()
          book_python.authors.set(2)
          """
          等价于:
          book_python.authors.clear()
          book_python.authors.add(2)
          """
          
    • 查询记录:

      关联属性所在的表 发起的查询为 正向查询

      基于对象的跨表查询(基于子查询)

      先进行父查询,再对父查询的结果进行子查询

      在SQL语言中,一个SELECT-FROM-WHERE语句称为一个查询块。当获得一个查询的答案需要多个步骤的操作,首先必须创建一个查询来确定用户不知道但包含在数据库中的值,将一个查询块嵌套在另一个查询块的WHERE字句或HAVING短语的条件中查询块称为子查询或内层查询

      • 一对多:
        正向查询: 通过关联属性所在的表("多") 查 关联表("一")记录 (book_obj.publish)
        ​ 按属性(字段)查:Book对象.关联属性(字段)
        ​ Book -----------------------------------------------> Publish
        ​ <-----------------------------------------------
        反向查询: 按关联属性(字段)所在的表名小写+_set.all(): Publish对象.book_set.all()

        • 返回QuerySet对象
      • 多对多:
        ​ ​ 正向查询: 按属性(字段) book_obj.authors.all() (多对多可能包含多个记录)
        ​ ​ Book ---------------------------------------------------------------------> Author
        ​ <---------------------------------------------------------------------
        ​ ​ 反向查询: 关联属性所在的表名小写+_set.all() author_obj.book_set.all() queryset 对象

      • 一对一:
        ​ ​ 正向查询:按属性(字段) author_obj.author_detail (因为是唯一的记录 不加.all())
        ​ ​ Author --------------------------------------------------------------------> AuthorDetails
        ​ <--------------------------------------------------------------------
        ​ ​ 反向查询: 关联属性所在的表名小写 authordetails_obj.author.age (唯一记录,不加_set)

      def query(request):
          # # ############### 基于对象的跨表查询 ####################
          # 先进行父查询,在对父查询的结果进行子查询
      
      
          # 一对多:
          # 基于对象的查询, 多的表中的关联属性 即对应的表的对象
          # # 1. 查询python这本书的出版社和邮箱 ------> 正向查询
          python_book = Book.objects.filter(title='python').first()
          # python_book.publish 与这本书关联的出版社对象
          # 即 publish_obj = Publish.objects.filter(nid=python_book_id).first() 对象
          print(python_book.publish)
          print(python_book.publish.name)
          print(python_book.publish.email)
      
          # # 2. 查询苹果出版社所有书籍的名称  -------> 反向查询
          pub_obj = Publish.objects.get(name='苹果出版社')
          print(pub_obj.book_set.all())  # QuerySet对象
          book_obj_lst = pub_obj.book_set.all().values('title', 'price', 'pub_date')
          for book in book_obj_lst:
              for k, v in book.items():
                  print(f'{k}:{v}')
      
          # 多对多:
          # # 1. 查询python这本书的作者的年龄 -------> 正向查询
          python_book = Book.objects.filter(title='python').first()
          ret = python_book.authors.all()  # 与这本书关联的所有作者的QuerySet对象
          ret = python_book.authors.all().values('age')
          print(ret)
          # # 2. 查询sunny出版过的所有书籍名称  --------> 反向查询
          sunny_author = Author.objects.filter(name='sunny').first()
          ret = sunny_author.book_set.all()
          print(ret)
      
          # 一对一:
          # # 1. 查询作者名字为sunny的手机号   ----------> 正向查询
          sunny = Author.objects.get(name='sunny')
          sunny_tel = sunny.author_detail.tel
          print(sunny_tel)
          # # 2. 查询手机号为10086的作者名字  ---------> 反向查询
          tel_obj = AuthorDetails.objects.get(tel='10086')
          author_obj = tel_obj.author.name
          print(author_obj)
      
          # return HttpResponse(publish_python.name + publish_python.email, charset='gbk')
          return HttpResponse('ok')
      

    基于双下划线的查询(基于sql jion实现 即 表的拼接 )

    将多张表拼接成单张表,再进行相关的查询

    从基表出发,按关联关系进行拼表, 先拼接有关系的表

    拼表后会按"多"的表记录进行显示,如Book表的一本书有2个作者,拼表后会有2条同样书籍对象但作者不一样的记录

    • 正向查询:按子段

    • 反向查询: 按表名小写

          # ###############  关联表查询 ##################
          # filter: sql语句中where过滤的条件, values: sql语句中select要显示的字段
          
          # 一对多:
          # 1. 查询python这本书的出版社和邮箱
      
          #  以Book为基表 拼接 Publish表
          ret = Book.objects.filter(title='python').values('publish__name', 'publish__email')
          #  以Publish为基表 拼接 Book表
          ret = Publish.objects.filter(book__title="python").values('name', 'email')
      
          # 2. 查询苹果出版社所有书籍的名称
          ret = Publish.objects.filter(name='苹果出版社').values('book__title')
          ret = Book.objects.filter(publish__name='苹果出版社').values('title')
      
          # 多对多: 连接3张表, Book Author, orms自动拼接第三张表 book_authors
          # 1. 查询python这本书的作者的年龄
          ret = Book.objects.filter(title='python').values('authors__age')  # 正跨: 通过book属性authors连接第三张表
          ret = Author.objects.filter(book__title='python').values('age')  # 反跨:  通过表名小写自动连接第三张表
          # 2. 查询少商出版过的所有书籍名称
          ret = Author.objects.filter(name='少商').values('book__title')
          ret = Book.objects.filter(authors__name='少商').values('title')
      
          # 一对一:
          # # 1. 查询作者名字为少商的手机号
          ret = Author.objects.filter(name='少商').values('author_detail__tel')
          ret = AuthorDetails.objects.filter(author__name='少商').values('tel')
          # 2. 查询手机号为10086的作者名字
          ret = Author.objects.filter(author_detail__tel='10086').values('name')
          ret = AuthorDetails.objects.filter(tel='10086').values('author__name')
      
          # ############# 连续跨表查询 ####################
      
          # 查询苹果出版社出版过的所有书籍的名字以及作者的姓名
          ret = Book.objects.filter(publish__name='橘子出版社').values('title', 'authors__name')
          ret = Publish.objects.filter(name='橘子出版社').values('book__title', 'book__authors__name')
          ret = Author.objects.filter(book__publish__name='橘子出版社').values('name', 'book__title')
          ret = AuthorDetails.objects.filter(author__book__publish__name='橘子出版社').values('author__book__title', 'author__name')
      
          # 手机号以100开头的作者出版过的所有书籍名称以及出版社名称
          ret = Book.objects.filter(authors__author_detail__tel__startswith=100).values('title', 'publish__name')
          ret = Author.objects.filter(author_detail__tel__startswith=200).values('book__title', 'book__publish__name')
          ret = Publish.objects.filter(book__authors__author_detail__tel__startswith=200).values('book__title', 'name')
          #                                                      author__book__title  按关联关系进行拼接
          ret = AuthorDetails.objects.filter(tel__startswith=100).values('author__book__title',
                                                                         'author__book__publish__name')
      

    聚合与分组查询(跨表基于join)

    • 聚合函数: Count, Sum, Avg, Max, Minfrom django.db.models import Count, Sum, Avg, Max, Min**

      支持聚合函数的方法:

      • aggregate(c=Count(1)) 返回字典 {别名:值}
      • annotate(c=Count(1)) 返回QuerySet
          from django.db.models import Count, Sum, Avg, Max, Min  # 导入聚合函数
          # ############ 聚合查询 ############
          # 1. Avg:  查询所有书籍的平均价格
          # sql: select avg(price) from book
          ret = Book.objects.all().aggregate(Avg('price'))  # 返回字典, {'price__avg': Decimal('88.000000')}
          # 起别名
          ret = Book.objects.all().aggregate(priceAvg=Avg('price'))  # {'priceAvg': Decimal('88.000000')}
          # 2. Count: 查询所有书籍的个数
          ret = Book.objects.all().aggregate(c=Count('nid'))  # Count(1) 也可以
          print(ret)
      
    • 分组查询:

      支持分组函数的方法: annotate() 返回QuerySet即 对象的列表,如果是values返回包装成字典的对象列表

      语法: Book.objects.values('title').annotate(bookCount=Count(1))

      sql: select title, count(1) from book group by title

      ps: 单表按主键分组没有意义

      语法解析: values(): select与group by的字段, annotate():group by统计(分组统计)

          # ############ 分组查询 annotate############
          # ######### 单表分组查询
          # 1. 查询书籍表每一个出版社id以及对应的书籍的个数
          # values(): select & group by. annotate():统计
          # 返回QuerySet
      
          # sql: select publish_id, count(1) from book GROUP BY publish_id
          ret = Book.objects.values('publish_id').annotate(c=Count(1))
          # <QuerySet [{'publish_id': 1, 'c': 2}, {'publish_id': 2, 'c': 2}, {'publish_id': 3, 'c': 1}]>
      
          # emp员工表查询
          # 1. 查询每一个部门的名称以及对应员工的平均薪水
          ret = Emp.objects.values('dep').annotate(depAvg=Avg('salary'))  # group by dep
      
          # 2. 查询每一个省份的名称以及对应的员工人数
          ret = Emp.objects.values('pro').annotate(empCount=Count(1))
      
          # 3. 查询每一个省份的名称以及对应的员工最大年龄
          ret = Emp.objects.values('pro').annotate(maxAge=Max('age'))
      
          # ############# 跨表分组查询 ##################
          # 相关表join成一张表,再group by
          # 1. 查询每个出版社的名称以及对应的书籍的个数
          ret = Book.objects.values('publish__name').annotate(bookCount=Count(1))
          ret = Publish.objects.values('name').annotate(bookCount=Count('book__title'))
      
          # 2. 查询每个作者的名字以及出版的书籍的最高价格
          # 作者可能有重复的名称且不是一个人,所以要按主键和name进行分组
          # 如果按多个字段分组, 如果多个字段的值完全相同则放入同一个组,有一个字段不相等则放入一个新组
          ret = Author.objects.values('pk', 'name').annotate(maxPrice=Max('book__price'))
      
        # 3. 查询每个作者出版的书籍的名称和个数
          ret = Book.objects.values('title').annotate(Count('authors'))
      
      • 常用方法及其注意事项:

         # #### 重要 ###
            # 如果基表字段有重复值, 分组会将2个相同字段分为一个组 即 values按字段分组会可能会有不符合预期的逻辑错误
            # all() 即按主键分组,即每行表记录都是一个分组的依据
        
            # 如:Book表的title有一个重复的书籍名称'python学习手册',group by title 会把重复的书籍汇总到一个组
            ret = Book.objects.values('title').annotate(Count('authors'))
            # 4条记录: <QuerySet [{'title': 'python学习手册', 'authors__count': 4}, {'title': 'python标准库', 'authors__count': 3},
            #          {'title': 'JAVA编程思想', 'authors__count': 1}, {'title': 'linux私房菜', 'authors__count': 2}]>
            print(ret)
            ret = Book.objects.all().annotate(Count('authors'))
            # 5条记录: <QuerySet [<Book: python学习手册>, <Book: python标准库>, <Book: JAVA编程思想>, <Book: linux私房菜>, <Book: python学习手册>]>
            print(ret)
        
            # ################ 分组查询常用方法 ###################
            # 用all()方法返回的model对象的列表(QuerySet),在model对象自动加入了统计字段authors_count,可用对象.统计字段显示
            # 并且可在此QuerySet对象基础上用其他QuerySet可调用的方法进行级联操作
            ret = Book.objects.all().annotate(Count('authors'))
            # 简写:
            ret = Book.objects.annotate(Count('authors'))
            # 显示书籍作者大于2个的书籍名称
            ret = Book.objects.annotate(Count('authors')).filter(authors__count__gt=2).values('title')
        

    F 与 Q查询

        # ######## F 与 Q 查询
        from django.db.models import F, Q
        # ------------ F 查询:(Field) -----------
        # 运算符右边可以是字段: 可以两个字段比较      comment=F("thumbs_up")
    
        # 1. 书籍评论数等于点赞数
        ret = Book.objects.filter(comment=F("thumbs_up")).values('title', 'comment', 'thumbs_up')
        # 2. 书籍评论数大于点赞数
        ret = Book.objects.filter(comment__gt=F("thumbs_up")).values('title', 'comment', 'thumbs_up')
        # 3. 修改名称 加上版号: 如 python(第二版)
        from django.db.models.functions import Concat  # Concat 连接
        from django.db.models import Value  # Value 常量
        Book.objects.filter(title__startswith="py").update(title=Concat(F("title"), Value("第二版"), Value("中文版")))
        # 4. price提高2倍
        # Book.objects.filter(nid=2).update(price=F("price") * 2)
        Book.objects.filter(nid=2).update(price=F("price") * Value(2))
    
        # ------------ Q 查询:(Field) -----------
        # filter 参数之间都是与操作.
        # Q 查询可以进行其他逻辑查询, 并且可以嵌套使用
    
        # & : 与
        # 书籍评论数与点赞数都大于100的书籍
        ret = Book.objects.filter(Q(comment__gt=100) & Q(thumbs_up__gt=100)).values('title')
    
        # |: 或
        # 书籍评论数小于500或点赞数大于800的书籍
        ret = Book.objects.filter(Q(comment__lt=500) | Q(thumbs_up__lt=800)).values('title')
    
        # ~: 非
        # 书籍评论数不等于1000的书籍
        ret = Book.objects.filter(~Q(comment=1000)).values('title')
    
        # 查询函数可以混合使用Q 对象和关键字参数。
        # 所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。
        # 但是,如果出现Q 对象,它必须位于所有关键字参数的前面。
        ret = Book.objects.filter(~Q(comment=1000) | Q(comment__lt=100), title__contains='py').values('title')
    
  • 相关阅读:
    转载-python生成sjf
    111111111111
    【MySQL】使用硬链接的方式删除大表
    【Python】公共类-获取MySQL数据
    【Python】公共类-logger
    文件IO --- sync、fsync、fdatesync
    【Mongo】安装Mongo并配置副本集
    【MySQL】InnoDB 内存管理机制 --- Buffer Pool
    【MySQL】redo log --- 刷入磁盘过程
    Linux 系统的安装
  • 原文地址:https://www.cnblogs.com/relaxlee/p/12843100.html
Copyright © 2011-2022 走看看