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')
原文地址:https://www.cnblogs.com/relaxlee/p/12843100.html