zoukankan      html  css  js  c++  java
  • django模型层(二)多表操作

    一、 创建模型

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

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

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

      出版商模型:出版商有名称,所在城市以及email。

      书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)

    # 作者表
    class Author(models.Model):
        # nid = models.AutoField(primary_key=True) # 修改主键字段名称
        # id 默认
        name=models.CharField(max_length=32)
        # 与AuthorDetail建立一对一的关系,一对一的这个关系字段写在两个表的任意一个表里面都可以
        ad = models.OneToOneField(to="AuthorDetail",to_field="id",on_delete=models.CASCADE)
        # ad = models.OneToOneField("AuthorDetail",)  # 等同于上面注释的这个 django低版本
    
    
    
    # 作者详细信息表
    class AuthorDetail(models.Model): #不常用的放到这个表里面
    
        birthday=models.DateField()
        # telephone=models.BigIntegerField()
        telephone=models.CharField(max_length=32)
        addr=models.CharField(max_length=64)
    
    # 出版社表
    class Publish(models.Model):
    
        name = models.CharField(max_length=32)
        city = models.CharField(max_length=32)
    
    #多对多的表关系,我们学mysql的时候是怎么建立的,是不是手动创建一个第三张表,然后写上两个字段,
    # 每个字段外键关联到另外两张多对多关系的表,orm的manytomany自动帮我们创建第三张表,两种方式建立关系都可以,
    # 以后的学习我们暂时用orm自动创建的第三张表,因为手动创建的第三张表我们进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用
    #如果你想删除某张表,你只需要将这个表注销掉,然后执行那两个数据库同步指令就可以了,自动就删除了。
    
    # 书籍表
    class Book(models.Model):
    
        title = models.CharField(max_length=32)
        publishDate = models.DateField()
    
        # DecimalField -- Decimal(12,4)类型 -- 完全精度
        price = models.DecimalField(max_digits=5,decimal_places=2) # 999.99
    
        # 与Publish建立一对多的关系,外键字段建立在多的一方,字段publishs如果是外键字段,那么它自动是int类型
        # to指向表,to_field指向你关联的字段,不写这个,默认会自动关联主键字段,on_delete级联删除
        publishs = models.ForeignKey(to="Publish",to_field="id",on_delete=models.CASCADE)
        # publishs = models.ForeignKey("Publish") # 同上等同于上面注释的这个 django低版本
    
        # authors不会生成book表的字段,而是生成book表和作者表的第三张关系记录表,通过这个属性可以操作第三张表
        authors = models.ManyToManyField(to='Author',)

    1.1关于多对多表的创建方式

    方式一:自行创建第三张表

    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")

    方式二:通过ManyToManyField自动创建第三张表

    # 通过ORM自带的ManyToManyField自动创建第三张表
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(to="Book")  #自动生成的第三张表我们是没有办法添加其他字段的

    方式三:设置ManyTomanyField并指定自行创建的第三张表(称为中介模型)

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
    
    # 自己创建第三张表,并通过ManyToManyField指定关联
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
        # through_fields接受一个2元组('field1','field2'):
        # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。
    
    
    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")
        #可以扩展其他的字段了
        class Meta:
            unique_together = ("author", "book")

      注意:

          当我们需要在第三张关系表中存储额外的字段时,就要使用第三种方式,第三种方式还是可以使用多对多关联关系操作的接口(all、add、clear等等)

          当我们使用第二种方式创建多对多关联关系时,就无法使用orm提供的set、add、remove、clear方法来管理多对多的关系了。

    to
        设置要关联的表。
    
    to_field
        设置要关联的字段。
        
    on_delete
        同ForeignKey字段。
    创建一对一关系字段时的一些参数
    to
        设置要关联的表
    
    to_field
        设置要关联的表的字段
    
    related_name
        反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
    related_query_name
        反向查询操作时,使用的连接前缀,用于替换表名。
    
    on_delete
        当删除关联表中的数据时,当前表与其关联的行的行为。
    创建一对多关系字段时的一些参数
    多对多的参数:
        to
            设置要关联的表
    
        related_name
            同ForeignKey字段。
    
        related_query_name
            同ForeignKey字段。
        through
            在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
    
            但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过        
        through来指定第三张表的表名。
    
        through_fields
            设置关联的字段。
    
        db_table
            默认创建第三张表时,数据库中表的名称。  
    创建多对多字段时的一些参数
    元信息
        ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:
    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")
        class Meta:
            unique_together = ("author", "book")
    
    db_table
        ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'
    
    index_together
        联合索引。
    
    unique_together
        联合唯一索引。
    
    ordering
        指定默认按什么字段排序。
        ordering = ['pub_date',]
        只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)
    创建表时的一些元信息设置

    1.2 关于db_column和verbose_name 

        1.指定字段名: 在定义字段的时候,增加参数db_column=’real_field’; 

        2.指定表名: 在model的class中,添加Meta类,在Meta类中指定表名db_table 

        例如在某个models.py文件中,有一个类叫Info:

    class Info(models.Model):  
        ''''' 
                信息统计 
        '''  
        app_id = models.ForeignKey(App)  
        app_name = models.CharField(verbose_name='应用名',  max_length=32, db_column='app_name2')  
      
        class Meta:  
            db_table = 'info'  
            verbose_name = '信息统计'  
            verbose_name_plural = '信息统计' 

        其中db_column指定了对应的字段名,db_table指定了对应的表明; 

        如果不这样指定,字段名默认为app_name, 而表明默认为app名+类名: [app_name]_info.

        verbose_name指定在admin管理界面中显示中文;verbose_name表示单数形式的显示,verbose_name_plural表示复数形式的显示;中文的单数和复数一般不作区别。

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

    二 、添加表记录

    2.1 一对一

    添加一个名称为王照的作者

    方式一

    关系属性名称= 模型类对象
    •   ad:为定义表时关系属性名称
    • author_detail_obj:模型类对象
    author_detail_obj = models.AuthorDetail.objects.get(id=1)
    models.Author.objects.create(
               name='王照',
               ad=author_detail_obj  #1 关系属性名称= 模型类对象 
        )      

    方式二

    关系属性名称_id = 关联的模型类对象的id值
    • ad_id 生成表后显示的外键的字段名
    models.Author.objects.create(
            name='王照',
             ad_id=1 # author_detail_obj.id  # 2 关系属性名称_id = 关联的模型类对象的id值
         )

    2.2 一对多

    添加一个书籍记录

    方式一

    关系属性名称= 模型类对象
    publish_obj = models.Publish.objects.get(id=2)
    
    models.Book.objects.create(
        title='沈阳',
        publishDate='2020-11-11',
        price=20,
        publishs=publish_obj  #模型类对象
       
    
    )

    方式二

    关系属性名称_id = 关联的模型类对象的id值
    models.Book.objects.create(
        title='沈阳',
        publishDate='2020-11-11',
        price=20,
        publishs_id=2  
    
    )

     2.3  多对多

    其实就是添加多对多关系记录表的关系记录
    添加沈阳这本书 是 王照和玉波写

     方式1

    通过模型类对象添加

    o1 = models.Author.objects.get(name='王照')  # 2
    o2 = models.Author.objects.get(name='玉波')  # 3
    
    book_obj = models.Book.objects.get(title='沈阳')
    book_obj.authors.add(o1, o2)  #  2  3
                                                 
    book_obj.authors.add(*[o1, o2]) #  2  3

    方式2

    直接通过关系属性值添加

    #o1 = models.Author.objects.get(name='王照')  # 2
    #o2 = models.Author.objects.get(name='玉波')  # 3
    
    book_obj.authors.add(2, 3)
    book_obj.authors.add(*[2,3])

    二 、删除表记录

    一对一和一对多的删改和单表的删改是一样的,别忘了删除表的时候,咱们是做了级联删除的
    models.Book.objects.filter(title='沈阳小青年').delete()

    多对多

    obj = models.Book.objects.filter(title='白洁').first()
    obj.authors.remove(2,3) # 4 2和3 # 将第三张表中的这个obj对象对应的那个作者id为2和3的记录删除
    obj.authors.clear() # 清除该书籍对象对应的第三张表里面的所有记录

    三、修改表记录

    一对一和一对多的关系记录操作  和单表一样
     models.Book.objects.filter(title='沈阳').update(
            title='沈阳小青年',
            # publishs=  # 出版社模型类对象
            publishs_id=3
        )

    多对多

     obj = models.Book.objects.get(title='沈阳小青年')
      obj.authors.set(['3','4'])  #更新 -- 两步:1 删除之前的关系记录  2 添加新记录

    四、基于对象的跨表查询

    相当于mysql的子查询

    正向:关系属性写在A,那么通过A表数据去查询B表数据时,就是正向查询

    反向:反之就是反向查询

    4.1  一对一

    正向查询: 靠属性

    查询一下 王照作者的家庭住址

    author_obj = models.Author.objects.get(name='王照')
    # author_obj.ad  # 直接就找到了对应的关系记录对象
    print(author_obj.ad.addr)
    
    
    反向查询: 模型类名小写
    查询一下手机号为120的作者名称
    author_detail_obj = models.AuthorDetail.objects.get(telephone='120')
    print(author_detail_obj.author.name)

    4.2 一对多  书籍和出版社

    正向查询
    查询白洁这本书是哪个出版社出版的
    book_obj = models.Book.objects.get(title='白洁')
    print(book_obj.publishs.name)  # 红浪漫出版社
    反向查询: 模型类名小写_set  提示你:查询结果可能为多条记录
    查询33期出版社出版了哪些书
    pub_obj = models.Publish.objects.get(name='33期出版社')
    books = pub_obj.book_set.all().values('title')  # 取出反向查询的多条记录
    pub_obj.book_set.filter()  # 过滤
    print(books)
    
    

    4.2 多对多  

    多对多
    正向查询: 属性
    查询一下三国这本书是哪几个作者写的
    book_obj = models.Book.objects.get(title='三国')
    authors = book_obj.authors.all()  #  book_obj.authors objects控制器,得到的也可能是多条记录
    print(authors)
    反向查询: 模型类名小写_set
    查询一下王照这个作者写了哪些书
    author_obj = models.Author.objects.get(name='王照')
    print(author_obj.book_set.all())
    
    

    五、基于双下划线的跨表查询

    相当于mysql的inner join 连表查询

    正向:关系属性写在A,那么通过A表数据去查询B表数据时,就是正向查询

    反向:反之就是反向查询

    5.1 一对一

    正向查询: 靠属性
    查询一下 王照作者的家庭住址
    #正向连表 ad__
    ret = models.Author.objects.filter(name='王照').values('ad__addr')
    print(ret) #<QuerySet [{'ad__addr': '北京'}]>
    反向查询 :模型类名小写  
    # 反向连表 author__
    ret = models.AuthorDetail.objects.filter(author__name='王照').values('addr',) 
    print(ret) #<QuerySet [{'addr': '北京'}]>

    5.2 一对多 书籍和出版社

    查询白洁这本书是哪个出版社出版的
    正向查询
    ret = models.Book.objects.filter(title='白洁').values('publishs__name')
    print(ret)  #<QuerySet [{'publishs__name': '红浪漫出版社'}]>

    反向查询

    ret = models.Publish.objects.filter(book__title='白洁').values('name')
    print(ret) #<QuerySet [{'name': '红浪漫出版社'}]>

    5.3  多对多

    查询一下三国这本书是哪几个作者写的

    正向查询
    ret = models.Book.objects.filter(title='三国').values('authors__name')
    print(ret) #<QuerySet [{'authors__name': '王照'}, {'authors__name': '玉波'}]>

    反向查询

    ret = models.Author.objects.filter(book__title='三国').values('name')
    print(ret) #<QuerySet [{'name': '王照'}, {'name': '玉波'}]>


    六、聚合查询、分组查询、F查询和Q查询

    6.1 聚合查询

    aggregate(*args, **kwargs)
    # 计算所有图书的平均价格
    from django.db.models import Avg
    Book.objects.all().aggregate(Avg('price')) #或者给它起名 aggretate(a=Avg('price'))
     #{'price__avg': 34.35}

    aggregate()QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。(结果是字典,所以不能在后面继续使用queryset其他方法了)

    Book.objects.aggregate(average_price=Avg('price'))
    # {'average_price': 34.35}
    如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询
    from django.db.models import Avg, Max, Min
    Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))  
    #{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

    6.2 F查询

    用于对同一张表中多个字段进行操作时使用

    查询一下点赞数大于评论数的书籍
    ret = models.Book.objects.filter(dianzan__gt=F('comment')).values('title')
    print(ret)

    字段做统一操作时也能有效

    书籍价格上调10元
      models.Book.objects.all().update(
            price=F('price') + 10,  # 支持四则运算
         )

    6.3 分组查询

    相当于sql语句中的 group by

    annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)

    annotate里面必须写个聚合函数,不然没有意义,并且必须有个别名=,别名随便写,但是必须有,用哪个字段分组,values里面就写哪个字段,annotate其实就是对分组结果的统计,统计你需要什么

    查看一下每个出版社出版书的平均价格

     # 方式1
     ret = models.Book.objects.values('publishs_id').annotate(a=Avg('price'))
     #select avg('price') as a from app01_book group by publishs_id ;
    # 方式2
    
    # ret = models.Publish.objects.all().annotate()
    # 出版社模型类对象: 包含出版社表的所有字段数据和a这个分组统计结果
    #默认以id分组
    ret = models.Publish.objects.annotate(a=Avg('book__price')).values('name','a')
    # select app01_publish.name,avg(app01_book.price) as a from app01_publish inner join app01_book on app01_publish.id = app01_book.publishs_id group by app01_publish.id;
    print(ret)
    # <QuerySet [{'name': '33期出版社', 'a': 21.0}, {'name': '红浪漫出版社', 'a': 32.0}, {'name': '大头出版社', 'a': None}]>

    统计每一个出版社的最便宜的书

    publishList=Publish.objects.annotate(MinPrice=Min("book__price")) 

    #如果没有使用objects后面values或者values_list,得到的结果是queryset类型,里面是Publish的model对象,并且是对所有记 录进行的统计,统计的Minprice也成了这些model对象里面的一个属性,这种连表分组统计的写法最常用,思路也比较清晰 for publish_obj in publishList: print(publish_obj.name,publish_obj.MinPrice)

     annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:

    queryResult= Publish.objects
                .annotate(MinPrice=Min("book__price"))
                .values_list("name","MinPrice")
    print(queryResult)

    6.4 Q查询

    filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用对象
    Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
    查询一下点赞数大于等于20 或者 价格大于20的书籍
     ret = models.Book.objects.filter(~Q(dianzan__lt=20) | Q(price__gt=20))

    查询一下点赞数小于20 或者 价格大于20的书籍, 并且出版日期为2021年的

    ret = models.Book.objects.filter(Q(dianzan__lt=20) | Q(price__gt=20), publishDate__year='2021')  
    Q(dianzan__lt=20) | Q(price__gt=20)是一个条件,和逗号后面的是and的关系,如果有的条件没有用Q包裹,那么这个条件要放到被Q包裹的条件后面

    支持 Q 嵌套

    ret = models.Book.objects.filter(Q(Q(dianzan__lt=20) | Q(price__gt=20))&Q(publishDate__year='2021'))  # 支持 Q 嵌套
    print(ret)

    七 、对原生sql的操作

    7.1 查看原生sql语句

    方式1

    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'console':{
                'level':'DEBUG',
                'class':'logging.StreamHandler',
            },
        },
        'loggers': {
            'django.db.backends': {
                'handlers': ['console'],
                'propagate': True,
                'level':'DEBUG',
            },
        }
    }  

    方式2:

     ret = models.Book.objects.filter(dianzan__gt=F('comment')).values('title')
        print(ret)
    
        from django.db import connection
        print(connection.queries)

    7.2 执行原生sql语句

    方式一

    查询一下所有书籍
    ret = models.Book.objects.raw('select * from app01_book')
    # raw只能写操作本表的原生sql
    # <RawQuerySet: select * from app01_book>
    print(ret)
    for i in ret:
        print(i.title,i.price)

     方式2

     from django.db import connection
        # connection -- pymysql连接--conn
     cursor = connection.cursor()
     cursor.execute('select * from app01_book;')
     print(cursor.fetchall())
    #((3, '红楼', datetime.date(2021, 2, 4), Decimal('21.00'), 2, 11, 12), (4, '白洁', datetime.date(2021, 2, 4), Decimal('32.00'), 1, 11, 23), (5, '水浒', datetime.date(2021, 2, 4), Decimal('21.00'), 2, 80, 11))

    八、orm锁和事务

    8.1 锁

    行级锁

    models.Book.objects.select_for_update().filter(id=1)
    #手动为id=1加了锁
    # select * from app01_book where id=1 for update; 

    一般会用到事务当中,

    事务中加锁,事务不释放,锁也不释放

    8.2事务

    方式1: 视图函数中加事务

    do_stuff() 没有什么特殊含义,代表事务执行的代码
    from django.db import transaction
    
    # 视图中的所有orm或者sql语句都捆绑为了一个事务
    @transaction.atomic
    def viewfunc(request):
        # This code executes inside a transaction.
        do_stuff() 

    方式2:

    局部逻辑使用事务

    from django.db import transaction
    
    def viewfunc(request):
        # This code executes in autocommit mode (Django's default).
        do_stuff()
        
        #with语句中的所有sql加上了事务
        with transaction.atomic():   
              # models.Book.objects.select_for_update().filter(id=1)
            # This code executes inside a transaction.
            do_more_stuff()
    
        do_other_stuff()

    九、python脚本调动django环境

    import os
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_orm02.settings")
    import django
    django.setup()
    
    
    
    from app01 import models
    ret = models.Book.objects.all()
    print(ret)
  • 相关阅读:
    [HEOI2014]南园满地堆轻絮
    [HEOI2016/TJOI2016]树
    初赛知识点
    [10.4模拟赛]T2
    [ZJOI2012]灾难
    [SDOI2010]古代猪文
    [9.26模拟赛]T1
    [9.26模拟赛]T3
    [9.26模拟赛]T2
    [9.19模拟赛]最小粒子数
  • 原文地址:https://www.cnblogs.com/yj0405/p/14590241.html
Copyright © 2011-2022 走看看